chore: Updating generate page interaction to show it in a modal following the IDE 2.0 interaction pattern (#37414)

## Description

Updating generate page interaction to show it in a modal following the
IDE 2.0 interaction pattern

Fixes [#32952](https://github.com/appsmithorg/appsmith/issues/32952)

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/11900113834>
> Commit: 3903c44fe5a6c7db0d22d9cf982c28a1380f4546
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11900113834&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 18 Nov 2024 21:26:44 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
  - Introduced a modal for generating pages, enhancing user interaction.
- Added new action types and constants for managing page generation
processes.
  - Updated UI messages for clarity in the page generation context.
- Improved handling of datasource selection and page generation in
various components.

- **Bug Fixes**
- Improved error handling in various components to prevent silent
failures.

- **Refactor**
- Streamlined routing logic by removing deprecated paths and functions.
- Transitioned from direct navigation to modal-based interactions for
page generation.
  - Enhanced control flow and error handling within components.

- **Chores**
- Updated import paths for better organization of action-related
functions within the Redux architecture.

- **Tests**
- Enhanced test cases for CRUD operations, ensuring better validation
and error handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ankita Kinger 2024-11-19 12:05:10 +05:30 committed by GitHub
parent afd2fcc1fa
commit 72eb2cd4cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 503 additions and 503 deletions

View File

@ -47,6 +47,7 @@ describe(
dataSources._dropdownOption,
"worldCountryInfo",
);
agHelper.GetNClick(dataSources._generatePageBtn);
GenerateCRUDNValidateDeployPage("ABW", "Aruba", "North America", "Code");
@ -93,6 +94,7 @@ describe(
assertHelper.AssertNetworkStatus("@getDatasourceStructure"); //Making sure table dropdown is populated
agHelper.GetNClick(dataSources._selectTableDropdown, 0, true);
agHelper.GetNClickByContains(dataSources._dropdownOption, "customers");
agHelper.GetNClick(dataSources._generatePageBtn);
GenerateCRUDNValidateDeployPage(
"103",
@ -110,6 +112,7 @@ describe(
it("3. Generate CRUD page from datasource present in ACTIVE section", function () {
EditorNavigation.SelectEntityByName(dsName, EntityType.Datasource);
dataSources.SelectTableFromPreviewSchemaList("employees");
agHelper.GetNClick(dataSources._datasourceCardGeneratePageBtn);
GenerateCRUDNValidateDeployPage(
"1002",
@ -311,9 +314,6 @@ describe(
col3Text: string,
jsonFromHeader: string,
) {
agHelper.GetNClick(
`${dataSources._generatePageBtn}, ${dataSources._datasourceCardGeneratePageBtn}`,
);
assertHelper.AssertNetworkStatus("@replaceLayoutWithCRUDPage", 201);
agHelper.AssertContains("Successfully generated a page");
//assertHelper.AssertNetworkStatus("@getActions", 200);//Since failing sometimes

View File

@ -399,9 +399,7 @@ describe(
col3Text: string,
jsonFromHeader: string,
) {
agHelper.GetNClick(
`${dataSources._generatePageBtn}, ${dataSources._datasourceCardGeneratePageBtn}`,
);
agHelper.GetNClick(dataSources._datasourceCardGeneratePageBtn);
assertHelper.AssertNetworkStatus("@replaceLayoutWithCRUDPage", 201);
agHelper.AssertContains("Successfully generated a page");
//assertHelper.AssertNetworkStatus("@getActions", 200);//Since failing sometimes

View File

@ -38,6 +38,7 @@ describe(
assertHelper.AssertNetworkStatus("@getDatasourceStructure"); //Making sure table dropdown is populated
agHelper.GetNClick(dataSources._selectTableDropdown, 0, true);
agHelper.GetNClickByContains(dataSources._dropdownOption, "film");
agHelper.GetNClick(dataSources._generatePageBtn);
GenerateCRUDNValidateDeployPage(
"ACADEMY DINOSAUR",
@ -86,6 +87,7 @@ describe(
assertHelper.AssertNetworkStatus("@getDatasourceStructure"); //Making sure table dropdown is populated
agHelper.GetNClick(dataSources._selectTableDropdown, 0, true);
agHelper.GetNClickByContains(dataSources._dropdownOption, "suppliers");
agHelper.GetNClick(dataSources._generatePageBtn);
GenerateCRUDNValidateDeployPage(
"Exotic Liquids",
@ -104,6 +106,7 @@ describe(
it("3. Generate CRUD page from datasource present in ACTIVE section", function () {
EditorNavigation.SelectEntityByName(dsName, EntityType.Datasource);
dataSources.SelectTableFromPreviewSchemaList("public.orders");
agHelper.GetNClick(dataSources._datasourceCardGeneratePageBtn);
GenerateCRUDNValidateDeployPage(
"VINET",
@ -135,9 +138,6 @@ describe(
col3Text: string,
jsonFromHeader: string,
) {
agHelper.GetNClick(
`${dataSources._generatePageBtn}, ${dataSources._datasourceCardGeneratePageBtn}`,
);
assertHelper.AssertNetworkStatus("@replaceLayoutWithCRUDPage", 201);
agHelper.AssertContains("Successfully generated a page");
//assertHelper.AssertNetworkStatus("@getActions", 200);//Since failing sometimes

View File

@ -52,7 +52,7 @@ describe(
200,
);
agHelper.AssertContains("Generate from data");
agHelper.AssertContains("Generate a page based on your data");
agHelper.GetNClick(generatePage.selectTableDropdown);
agHelper.GetNClickByContains(
generatePage.dropdownOption,

View File

@ -1,8 +1,6 @@
import {
ADD_PATH,
ADMIN_SETTINGS_PATH,
GEN_TEMPLATE_FORM_ROUTE,
GEN_TEMPLATE_URL,
getViewerCustomPath,
getViewerPath,
TEMPLATES_PATH,
@ -122,12 +120,6 @@ export const saasEditorApiIdURL = (
}`,
});
export const generateTemplateFormURL = (props: URLBuilderParams): string =>
urlBuilder.build({
...props,
suffix: `${GEN_TEMPLATE_URL}${GEN_TEMPLATE_FORM_ROUTE}`,
});
export const onboardingCheckListUrl = (props: URLBuilderParams): string =>
urlBuilder.build({
...props,

View File

@ -1070,6 +1070,17 @@ const CurlImportActionErrorTypes = {
SUBMIT_CURL_FORM_ERROR: "SUBMIT_CURL_FORM_ERROR",
};
const GeneratePageActionTypes = {
SET_GENERATE_PAGE_MODAL_OPEN: "SET_GENERATE_PAGE_MODAL_OPEN",
SET_GENERATE_PAGE_MODAL_CLOSE: "SET_GENERATE_PAGE_MODAL_CLOSE",
SUBMIT_GENERATE_PAGE_FORM_INIT: "SUBMIT_GENERATE_PAGE_FORM_INIT",
SUBMIT_GENERATE_PAGE_FORM_SUCCESS: "SUBMIT_GENERATE_PAGE_FORM_SUCCESS",
};
const GeneratePageActionErrorTypes = {
SUBMIT_GENERATE_PAGE_FORM_ERROR: "SUBMIT_GENERATE_PAGE_FORM_ERROR",
};
const BatchUpdateActionTypes = {
BATCHED_UPDATE: "BATCHED_UPDATE",
EXECUTE_BATCH: "EXECUTE_BATCH",
@ -1276,12 +1287,13 @@ export const ReduxActionTypes = {
...AppSettingsActionTypes,
...BatchUpdateActionTypes,
...BuildingBlocksActionTypes,
...DatasourceEditorActionTypes,
...CurlImportActionTypes,
...DatasourceEditorActionTypes,
...ErrorManagementActionTypes,
...ExplorerActionTypes,
...EvaluationActionTypes,
...FeatureFlagActionTypes,
...GeneratePageActionTypes,
...GitActionTypes,
...HelpActionTypes,
...IDEActionTypes,
@ -1324,6 +1336,7 @@ export const ReduxActionErrorTypes = {
...DatasourceEditorActionErrorTypes,
...EvaluationActionErrorTypes,
...FeatureFlagActionErrorTypes,
...GeneratePageActionErrorTypes,
...GitActionErrorTypes,
...IDEActionErrorTypes,
...ImportExportActionErrorTypes,

View File

@ -752,7 +752,10 @@ export const BUILD_FROM_SCRATCH_ACTION_TITLE = () => "Build with drag & drop";
export const GENERATE_PAGE_ACTION_TITLE = () => "Generate page with data";
export const GENERATE_PAGE_FORM_TITLE = () => "Generate from data";
export const GENERATE_PAGE_FORM_TITLE = () =>
"Generate a page based on your data";
export const GENERATE_PAGE_FORM_SUB_TITLE = () =>
"Use your datasource's schema to generate a simple CRUD page.";
export const GEN_CRUD_SUCCESS_MESSAGE = () =>
"Hurray! Your application is ready for use.";

View File

@ -70,10 +70,6 @@ export const APP_LIBRARIES_EDITOR_PATH = `/libraries`;
export const APP_PACKAGES_EDITOR_PATH = `/packages`;
export const APP_SETTINGS_EDITOR_PATH = `/settings`;
export const SAAS_GSHEET_EDITOR_ID_PATH = `/saas/google-sheets-plugin/datasources/:datasourceId`;
export const GEN_TEMPLATE_URL = "generate-page";
export const GENERATE_TEMPLATE_PATH = `/${GEN_TEMPLATE_URL}`;
export const GEN_TEMPLATE_FORM_ROUTE = "/form";
export const GENERATE_TEMPLATE_FORM_PATH = `${GENERATE_TEMPLATE_PATH}${GEN_TEMPLATE_FORM_ROUTE}`;
export const BUILDER_CHECKLIST_PATH = `/checklist`;
export const ADMIN_SETTINGS_PATH = "/settings";
export const ADMIN_SETTINGS_CATEGORY_DEFAULT_PATH = "/settings/general";
@ -124,10 +120,6 @@ export const matchViewerForkPath = (pathName: string) =>
match(`${VIEWER_PATH}${VIEWER_FORK_PATH}`)(pathName) ||
match(`${VIEWER_CUSTOM_PATH}${VIEWER_FORK_PATH}`)(pathName) ||
match(`${VIEWER_PATH_DEPRECATED}${VIEWER_FORK_PATH}`)(pathName);
export const matchGeneratePagePath = (pathName: string) =>
match(`${BUILDER_PATH}${GENERATE_TEMPLATE_FORM_PATH}`)(pathName) ||
match(`${BUILDER_CUSTOM_PATH}${GENERATE_TEMPLATE_FORM_PATH}`)(pathName) ||
match(`${BUILDER_PATH_DEPRECATED}${GENERATE_TEMPLATE_FORM_PATH}`)(pathName);
export const matchAppLibrariesPath = (pathName: string) =>
match(`${BUILDER_PATH}${APP_LIBRARIES_EDITOR_PATH}`)(pathName);

View File

@ -1,4 +1,3 @@
import { generateTemplateFormURL } from "ee/RouteBuilder";
import {
GENERATE_NEW_PAGE_BUTTON_TEXT,
createMessage,
@ -18,7 +17,7 @@ import type { ApiDatasourceForm } from "entities/Datasource/RestAPIForm";
import NewActionButton from "pages/Editor/DataSourceEditor/NewActionButton";
import { useShowPageGenerationOnHeader } from "pages/Editor/DataSourceEditor/hooks";
import React from "react";
import { useSelector } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import {
getCurrentApplicationId,
getCurrentBasePageId,
@ -26,10 +25,10 @@ import {
} from "selectors/editorSelectors";
import { getIsAnvilEnabledInCurrentApplication } from "layoutSystems/anvil/integrations/selectors";
import { isEnabledForPreviewData } from "utils/editorContextUtils";
import history from "utils/history";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { EditorNames } from "./";
import { getCurrentApplication } from "ee/selectors/applicationSelectors";
import { openGeneratePageModal } from "pages/Editor/GeneratePage/store/generatePageActions";
export interface HeaderActionProps {
datasource: Datasource | ApiDatasourceForm | undefined;
@ -47,7 +46,7 @@ export const useHeaderActions = (
showReconnectButton = false,
}: HeaderActionProps,
) => {
const basePageId = useSelector(getCurrentBasePageId);
const dispatch = useDispatch();
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const releaseDragDropBuildingBlocks = useFeatureFlag(
FEATURE_FLAG.release_drag_drop_building_blocks_enabled,
@ -97,13 +96,10 @@ export const useHeaderActions = (
}
AnalyticsUtil.logEvent("DATASOURCE_CARD_GEN_CRUD_PAGE_ACTION");
history.push(
generateTemplateFormURL({
basePageId,
params: {
datasourceId: (datasource as Datasource).id,
new_page: true,
},
dispatch(
openGeneratePageModal({
datasourceId: (datasource as Datasource).id,
new_page: true,
}),
);
};

View File

@ -12,7 +12,6 @@ import {
BUILDER_PATH_DEPRECATED,
DATA_SOURCES_EDITOR_ID_PATH,
DATA_SOURCES_EDITOR_LIST_PATH,
GENERATE_TEMPLATE_FORM_PATH,
INTEGRATION_EDITOR_PATH,
JS_COLLECTION_EDITOR_PATH,
JS_COLLECTION_ID_PATH,
@ -33,7 +32,6 @@ import {
import DatasourceForm from "pages/Editor/SaaSEditor/DatasourceForm";
import DataSourceEditor from "pages/Editor/DataSourceEditor";
import DatasourceBlankState from "pages/Editor/DataSourceEditor/DatasourceBlankState";
import GeneratePage from "pages/Editor/GeneratePage";
import type { RouteProps } from "react-router";
import { useSelector } from "react-redux";
import { combinedPreviewModeSelector } from "selectors/editorSelectors";
@ -139,12 +137,6 @@ function useRoutes(path: string): RouteReturnType[] {
exact: true,
path: `${path}${SAAS_EDITOR_DATASOURCE_ID_PATH}`,
},
{
key: "GeneratePage",
component: isPreviewMode ? WidgetsEditor : GeneratePage,
exact: true,
path: `${path}${GENERATE_TEMPLATE_FORM_PATH}`,
},
];
}

View File

@ -28,7 +28,7 @@ import {
createNewAPIBasedOnParentEntity,
createNewJSCollectionBasedOnParentEntity,
} from "ee/actions/helpers";
import { openCurlImportModal } from "pages/Editor/CurlImport/helpers";
import { openCurlImportModal } from "pages/Editor/CurlImport/store/curlImportActions";
export type SelectEvent =
| React.MouseEvent

View File

@ -6,7 +6,10 @@ import {
} from "selectors/curlImportSelectors";
import { submit } from "redux-form";
import { CURL_IMPORT_FORM } from "ee/constants/forms";
import { closeCurlImportModal, openCurlImportModal } from "./helpers";
import {
closeCurlImportModal,
openCurlImportModal,
} from "./store/curlImportActions";
import CurlLogo from "assets/images/Curl-logo.svg";
import { createMessage, IMPORT_BTN_LABEL } from "ee/constants/messages";
import {

View File

@ -1,6 +1,5 @@
import { submitCurlImportForm } from "../../../actions/importActions";
import type { ActionParentEntityTypeInterface } from "ee/entities/Engine/actionHelpers";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
export interface CurlImportFormValues {
curl: string;
@ -17,15 +16,3 @@ export const curlImportSubmitHandler = (
) => {
dispatch(submitCurlImportForm(values));
};
export const openCurlImportModal = () => {
return {
type: ReduxActionTypes.SET_CURL_MODAL_OPEN,
};
};
export const closeCurlImportModal = () => {
return {
type: ReduxActionTypes.SET_CURL_MODAL_CLOSE,
};
};

View File

@ -0,0 +1,21 @@
import type { ActionParentEntityTypeInterface } from "ee/entities/Engine/actionHelpers";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
export interface CurlImportFormValues {
curl: string;
contextId: string;
name: string;
contextType: ActionParentEntityTypeInterface;
}
export const openCurlImportModal = () => {
return {
type: ReduxActionTypes.SET_CURL_MODAL_OPEN,
};
};
export const closeCurlImportModal = () => {
return {
type: ReduxActionTypes.SET_CURL_MODAL_CLOSE,
};
};

View File

@ -52,7 +52,6 @@ const FieldWrapper = styled.div`
export const ViewModeWrapper = styled.div`
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--ads-v2-color-border);
padding: var(--ads-v2-spaces-7) 0;
gap: var(--ads-v2-spaces-4);
overflow: auto;

View File

@ -2,8 +2,6 @@ import React, { useMemo, useState } from "react";
import { AddButtonWrapper, EntityClassNames } from "../Entity";
import EntityAddButton from "../Entity/AddButton";
import styled from "styled-components";
import history from "utils/history";
import { generateTemplateFormURL } from "ee/RouteBuilder";
import { useParams } from "react-router";
import { useDispatch } from "react-redux";
import type { ExplorerURLParams } from "ee/pages/Editor/Explorer/helpers";
@ -32,6 +30,7 @@ import {
LayoutSystemFeatures,
useLayoutSystemFeatures,
} from "layoutSystems/common/useLayoutSystemFeatures";
import { openGeneratePageModal } from "pages/Editor/GeneratePage/store/generatePageActions";
const Wrapper = styled.div`
.title {
@ -85,7 +84,7 @@ function AddPageContextMenu({
items.push({
title: createMessage(GENERATE_PAGE_ACTION_TITLE),
icon: "database-2-line",
onClick: () => history.push(generateTemplateFormURL({ basePageId })),
onClick: () => dispatch(openGeneratePageModal()),
"data-testid": "generate-page",
key: "GENERATE_PAGE",
});

View File

@ -11,23 +11,25 @@ import {
} from "ee/selectors/entitiesSelector";
import type { Datasource } from "entities/Datasource";
import { fetchDatasourceStructure } from "actions/datasourceActions";
import {
fetchDatasourceStructure,
setDatasourceViewModeFlag,
} from "actions/datasourceActions";
import { generateTemplateToUpdatePage } from "actions/pageActions";
import { useLocation } from "react-router";
import { INTEGRATION_TABS } from "constants/routes";
import history from "utils/history";
import { getQueryParams } from "utils/URLUtils";
import { getIsGeneratingTemplatePage } from "selectors/pageListSelectors";
import {
getGeneratePageModalParams,
getIsGeneratingTemplatePage,
} from "selectors/pageListSelectors";
import DataSourceOption, {
CONNECT_NEW_DATASOURCE_OPTION_ID,
DatasourceImage,
} from "../DataSourceOption";
import { getQueryStringfromObject } from "ee/entities/URLRedirect/URLAssembly";
import type { DropdownOption } from "@appsmith/ads-old";
import { Button, Icon, Text, Select, Option, Tooltip } from "@appsmith/ads";
import GoogleSheetForm from "./GoogleSheetForm";
import {
GENERATE_PAGE_FORM_TITLE,
createMessage,
GEN_CRUD_DATASOURCE_DROPDOWN_LABEL,
} from "ee/constants/messages";
@ -70,6 +72,7 @@ import equal from "fast-deep-equal";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import { getHasCreateDatasourcePermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
import { closeGeneratePageModal } from "../../store/generatePageActions";
// ---------- Styles ----------
@ -77,24 +80,11 @@ const TooltipWrapper = styled.div`
margin-left: 6px;
`;
const Wrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-end;
border: none;
`;
const FormWrapper = styled.div`
display: flex;
flex-direction: column;
`;
const DescWrapper = styled.div`
flex: 1;
display: flex;
flex-direction: column;
`;
const Row = styled.p`
display: flex;
flex-direction: row;
@ -218,7 +208,7 @@ const DatasourceOptionSelectedView = (props: any) => {
function GeneratePageForm() {
const dispatch = useDispatch();
const querySearch = useLocation().search;
const params = useSelector(getGeneratePageModalParams);
const basePageId = useSelector(getCurrentBasePageId);
const pageId = useSelector(getCurrentPageId);
@ -539,10 +529,9 @@ function GeneratePageForm() {
]);
useEffect(() => {
if (querySearch) {
const queryParams = getQueryParams();
const datasourceId = queryParams.datasourceId;
const generateNewPage = queryParams.new_page;
if (params?.datasourceId || params?.new_page) {
const datasourceId = params.datasourceId;
const generateNewPage = params.new_page;
if (datasourceId) {
if (generateNewPage || numberOfEntities > 0) {
@ -552,15 +541,9 @@ function GeneratePageForm() {
}
setDatasourceIdToBeSelected(datasourceId);
delete queryParams.datasourceId;
delete queryParams.new_page;
const redirectURL =
window.location.pathname + getQueryStringfromObject(queryParams);
history.replace(redirectURL);
}
}
}, [numberOfEntities, querySearch, setDatasourceIdToBeSelected]);
}, [numberOfEntities, params, setDatasourceIdToBeSelected]);
const routeToCreateNewDatasource = () => {
AnalyticsUtil.logEvent("GEN_CRUD_PAGE_CREATE_NEW_DATASOURCE");
@ -577,6 +560,7 @@ function GeneratePageForm() {
AnalyticsUtil.logEvent("NAVIGATE_TO_CREATE_NEW_DATASOURCE_PAGE", {
entryPoint,
});
dispatch(closeGeneratePageModal());
};
const generatePageAction = (data: GeneratePagePayload) => {
@ -602,6 +586,7 @@ function GeneratePageForm() {
AnalyticsUtil.logEvent("GEN_CRUD_PAGE_FORM_SUBMIT");
dispatch(generateTemplateToUpdatePage(payload));
dispatch(closeGeneratePageModal());
};
const handleFormSubmit = () => {
@ -625,6 +610,8 @@ function GeneratePageForm() {
});
history.push(redirectURL);
dispatch(setDatasourceViewModeFlag(false));
dispatch(closeGeneratePageModal());
};
// if the datasource has basic information to connect to db it is considered as a valid structure hence isValid true.
@ -682,250 +669,247 @@ function GeneratePageForm() {
!selectedTable.value || !showSubmitButton || isSelectedTableEmpty;
return (
<div>
<Wrapper>
<DescWrapper>
<Text kind="heading-m">{GENERATE_PAGE_FORM_TITLE()}</Text>
</DescWrapper>
</Wrapper>
<FormWrapper>
<FormWrapper>
<SelectWrapper width={DROPDOWN_DIMENSION.WIDTH}>
<Label>{createMessage(GEN_CRUD_DATASOURCE_DROPDOWN_LABEL)}</Label>
<Select
data-testid="t--datasource-dropdown"
getPopupContainer={(triggerNode) => triggerNode.parentNode.parentNode}
onChange={(value) => {
if (value === CONNECT_NEW_DATASOURCE_OPTION_ID) {
routeToCreateNewDatasource();
} else {
onSelectDataSource(
value,
dataSourceOptions.find((ds) => ds.value === value),
);
}
}}
style={{ width: DROPDOWN_DIMENSION.WIDTH }}
value={
selectedDatasource?.label !== DEFAULT_DROPDOWN_OPTION?.label
? {
key: selectedDatasource?.value,
label: (
<DatasourceOptionSelectedView
iconType={GeneratePageSelectedViewIconEnum.PLUGIN_ICON}
option={selectedDatasource}
pluginImages={pluginImages}
/>
),
}
: selectedDatasource
}
// TODO: This needs to be fixed. Removed for cypress tests to pass
virtual={false}
>
{dataSourceOptions.map((option) => {
const isConnectNewDataSourceBtn =
CONNECT_NEW_DATASOURCE_OPTION_ID ===
(option as DropdownOption).id;
const isSupportedForTemplate = (option as DropdownOption)?.data
?.isSupportedForTemplate;
const isNotSupportedDatasource =
!isSupportedForTemplate && !isConnectNewDataSourceBtn;
return (
<Option
disabled={isNotSupportedDatasource}
key={option.value}
value={option.value}
>
<DataSourceOption
dataTestid="t--datasource-dropdown-option"
extraProps={{ routeToCreateNewDatasource }}
key={(option as DropdownOption).id}
option={option}
optionWidth={DROPDOWN_DIMENSION.WIDTH}
/>
</Option>
);
})}
</Select>
</SelectWrapper>
{selectedDatasource.value ? (
<SelectWrapper width={DROPDOWN_DIMENSION.WIDTH}>
<Label>{createMessage(GEN_CRUD_DATASOURCE_DROPDOWN_LABEL)}</Label>
<Label>
Select {pluginField.TABLE} from&nbsp;
<Bold>{selectedDatasource.label}</Bold>
</Label>
<Select
data-testid="t--datasource-dropdown"
onChange={(value) => {
if (value === CONNECT_NEW_DATASOURCE_OPTION_ID) {
routeToCreateNewDatasource();
} else {
onSelectDataSource(
value,
dataSourceOptions.find((ds) => ds.value === value),
);
}
}}
style={{ width: DROPDOWN_DIMENSION.WIDTH }}
data-testid="t--table-dropdown"
getPopupContainer={(triggerNode) =>
triggerNode.parentNode.parentNode
}
isDisabled={!!tableDropdownErrorMsg}
isLoading={fetchingDatasourceConfigs}
isValid={!tableDropdownErrorMsg}
onChange={(value) =>
onSelectTable(
value,
datasourceTableOptions.find(
(table) => table.value === value,
) as DatasourceTableDropdownOption,
)
}
value={
selectedDatasource?.label !== DEFAULT_DROPDOWN_OPTION?.label
selectedTable?.label !== DEFAULT_DROPDOWN_OPTION?.label
? {
key: selectedDatasource?.value,
key: selectedTable?.value,
label: (
<DatasourceOptionSelectedView
iconType={GeneratePageSelectedViewIconEnum.PLUGIN_ICON}
option={selectedDatasource}
pluginImages={pluginImages}
iconType={GeneratePageSelectedViewIconEnum.ADS_ICON}
option={selectedTable}
/>
),
}
: selectedDatasource
: selectedTable
}
// TODO: This needs to be fixed. Removed for cypress tests to pass
virtual={false}
>
{dataSourceOptions.map((option) => {
const isConnectNewDataSourceBtn =
CONNECT_NEW_DATASOURCE_OPTION_ID ===
(option as DropdownOption).id;
const isSupportedForTemplate = (option as DropdownOption)?.data
?.isSupportedForTemplate;
const isNotSupportedDatasource =
!isSupportedForTemplate && !isConnectNewDataSourceBtn;
{datasourceTableOptions.map((table) => {
return (
<Option
disabled={isNotSupportedDatasource}
key={option.value}
value={option.value}
>
<DataSourceOption
dataTestid="t--datasource-dropdown-option"
extraProps={{ routeToCreateNewDatasource }}
key={(option as DropdownOption).id}
option={option}
optionWidth={DROPDOWN_DIMENSION.WIDTH}
/>
<Option key={table.value} value={table.value}>
<OptionWrapper>
<StyledIconWrapper>
<Icon
color={table?.iconColor}
name={table.icon as string}
size={table.iconSize}
/>
</StyledIconWrapper>
<Text renderAs="p">{table.label}</Text>
</OptionWrapper>
</Option>
);
})}
</Select>
{tableDropdownErrorMsg && (
<ErrorMsg className="ads-dropdown-errorMsg">
{tableDropdownErrorMsg}
</ErrorMsg>
)}
</SelectWrapper>
{selectedDatasource.value ? (
<SelectWrapper width={DROPDOWN_DIMENSION.WIDTH}>
<Label>
Select {pluginField.TABLE} from&nbsp;
<Bold>{selectedDatasource.label}</Bold>
</Label>
<Select
data-testid="t--table-dropdown"
isDisabled={!!tableDropdownErrorMsg}
isLoading={fetchingDatasourceConfigs}
isValid={!tableDropdownErrorMsg}
onChange={(value) =>
onSelectTable(
value,
datasourceTableOptions.find(
(table) => table.value === value,
) as DatasourceTableDropdownOption,
)
}
value={
selectedTable?.label !== DEFAULT_DROPDOWN_OPTION?.label
? {
key: selectedTable?.value,
label: (
<DatasourceOptionSelectedView
iconType={GeneratePageSelectedViewIconEnum.ADS_ICON}
option={selectedTable}
/>
),
}
: selectedTable
}
// TODO: This needs to be fixed. Removed for cypress tests to pass
virtual={false}
>
{datasourceTableOptions.map((table) => {
return (
<Option key={table.value} value={table.value}>
<OptionWrapper>
<StyledIconWrapper>
<Icon
color={table?.iconColor}
name={table.icon as string}
size={table.iconSize}
/>
</StyledIconWrapper>
<Text renderAs="p">{table.label}</Text>
</OptionWrapper>
</Option>
);
})}
</Select>
{tableDropdownErrorMsg && (
<ErrorMsg className="ads-dropdown-errorMsg">
{tableDropdownErrorMsg}
</ErrorMsg>
)}
</SelectWrapper>
) : null}
{showEditDatasourceBtn && (
<div>
<Button kind="primary" onClick={goToEditDatasource} size="md">
Edit datasource
</Button>
) : null}
{showEditDatasourceBtn && (
<div>
<Button kind="primary" onClick={goToEditDatasource} size="md">
Edit datasource
</Button>
</div>
)}
{!isGoogleSheetPlugin ? (
<>
{showSearchableColumn && (
<SelectWrapper width={DROPDOWN_DIMENSION.WIDTH}>
<Row>
Select a searchable {pluginField.COLUMN} from the selected&nbsp;
{pluginField.TABLE}
<TooltipWrapper>
<Tooltip content="Only string values are allowed for searchable column">
<Icon name="question-line" size="md" />
</Tooltip>
</TooltipWrapper>
</Row>
<Select
data-testid="t--table-dropdown"
getPopupContainer={(triggerNode) =>
triggerNode.parentNode.parentNode
}
isDisabled={selectedTableColumnOptions.length === 0}
onChange={(value) =>
onSelectColumn(
value,
selectedTableColumnOptions.find(
(column) => column.value === value,
),
)
}
value={
selectedColumn?.label !== DEFAULT_DROPDOWN_OPTION?.label
? {
key: selectedColumn?.value,
label: (
<DatasourceOptionSelectedView
iconType={GeneratePageSelectedViewIconEnum.ADS_ICON}
option={selectedColumn}
/>
),
}
: selectedColumn
}
virtual={false}
>
{selectedTableColumnOptions.map((column) => {
return (
<Option key={column.value} value={column.value}>
<OptionWrapper>
<StyledIconWrapper>
<Icon
color={column?.iconColor}
name={column.icon as string}
size={column.iconSize}
/>
</StyledIconWrapper>
<Text renderAs="p">{column.label}</Text>
<Text
className="datasource-sub-text"
color="var(--ads-v2-color-fg-muted)"
renderAs="span"
>
{column.subText}
</Text>
</OptionWrapper>
</Option>
);
})}
</Select>
<HelperMsg>
{selectedTableColumnOptions.length === 0
? `* Optional (No searchable ${pluginField.COLUMN} to select)`
: "* Optional"}
</HelperMsg>
</SelectWrapper>
)}
<div className="mt-4">
<GeneratePageSubmitBtn
disabled={submitButtonDisable}
isLoading={!!isGeneratingTemplatePage}
onSubmit={handleFormSubmit}
showSubmitButton={!!showSubmitButton}
/>
</div>
)}
{!isGoogleSheetPlugin ? (
<>
{showSearchableColumn && (
<SelectWrapper width={DROPDOWN_DIMENSION.WIDTH}>
<Row>
Select a searchable {pluginField.COLUMN} from the
selected&nbsp;
{pluginField.TABLE}
<TooltipWrapper>
<Tooltip content="Only string values are allowed for searchable column">
<Icon name="question-line" size="md" />
</Tooltip>
</TooltipWrapper>
</Row>
<Select
data-testid="t--table-dropdown"
isDisabled={selectedTableColumnOptions.length === 0}
onChange={(value) =>
onSelectColumn(
value,
selectedTableColumnOptions.find(
(column) => column.value === value,
),
)
}
value={
selectedColumn?.label !== DEFAULT_DROPDOWN_OPTION?.label
? {
key: selectedColumn?.value,
label: (
<DatasourceOptionSelectedView
iconType={
GeneratePageSelectedViewIconEnum.ADS_ICON
}
option={selectedColumn}
/>
),
}
: selectedColumn
}
virtual={false}
>
{selectedTableColumnOptions.map((column) => {
return (
<Option key={column.value} value={column.value}>
<OptionWrapper>
<StyledIconWrapper>
<Icon
color={column?.iconColor}
name={column.icon as string}
size={column.iconSize}
/>
</StyledIconWrapper>
<Text renderAs="p">{column.label}</Text>
<Text
className="datasource-sub-text"
color="var(--ads-v2-color-fg-muted)"
renderAs="span"
>
{column.subText}
</Text>
</OptionWrapper>
</Option>
);
})}
</Select>
<HelperMsg>
{selectedTableColumnOptions.length === 0
? `* Optional (No searchable ${pluginField.COLUMN} to select)`
: "* Optional"}
</HelperMsg>
</SelectWrapper>
)}
<div className="mt-4">
<GeneratePageSubmitBtn
disabled={submitButtonDisable}
isLoading={!!isGeneratingTemplatePage}
onSubmit={handleFormSubmit}
showSubmitButton={!!showSubmitButton}
/>
</div>
</>
) : (
<GoogleSheetForm
generatePageAction={generatePageAction}
googleSheetPluginId={selectedDatasourcePluginId}
renderSubmitButton={({
disabled,
isLoading,
onSubmit,
}: {
onSubmit: () => void;
disabled: boolean;
isLoading: boolean;
}) => (
<GeneratePageSubmitBtn
disabled={disabled}
isLoading={!!isGeneratingTemplatePage || isLoading}
onSubmit={onSubmit}
showSubmitButton={!!showSubmitButton}
/>
)}
selectedDatasource={selectedDatasource}
selectedSpreadsheet={selectedTable}
sheetColumnsHeaderProps={sheetColumnsHeaderProps}
sheetsListProps={sheetsListProps}
spreadSheetsProps={spreadSheetsProps}
/>
)}
</FormWrapper>
</div>
</>
) : (
<GoogleSheetForm
generatePageAction={generatePageAction}
googleSheetPluginId={selectedDatasourcePluginId}
renderSubmitButton={({
disabled,
isLoading,
onSubmit,
}: {
onSubmit: () => void;
disabled: boolean;
isLoading: boolean;
}) => (
<GeneratePageSubmitBtn
disabled={disabled}
isLoading={!!isGeneratingTemplatePage || isLoading}
onSubmit={onSubmit}
showSubmitButton={!!showSubmitButton}
/>
)}
selectedDatasource={selectedDatasource}
selectedSpreadsheet={selectedTable}
sheetColumnsHeaderProps={sheetColumnsHeaderProps}
sheetsListProps={sheetsListProps}
spreadSheetsProps={spreadSheetsProps}
/>
)}
</FormWrapper>
);
}

View File

@ -301,6 +301,9 @@ function GoogleSheetForm(props: Props) {
<Select
data-testid="t--sheetName-dropdown"
getPopupContainer={(triggerNode) =>
triggerNode.parentNode.parentNode
}
isLoading={isFetchingSheetsList}
onChange={(value) =>
onSelectSheetOption(

View File

@ -1,19 +0,0 @@
import React from "react";
import styled from "styled-components";
import GeneratePageForm from "./GeneratePageForm/GeneratePageForm";
const Container = styled.div`
display: flex;
padding: var(--ads-v2-spaces-7) 0;
`;
function PageContent() {
return (
<Container>
<GeneratePageForm />
</Container>
);
}
export default PageContent;

View File

@ -1,54 +1,52 @@
import React from "react";
import styled from "styled-components";
import PageContent from "./components/PageContent";
import { Text } from "@appsmith/ads";
import { BackButton } from "components/utils/helperComponents";
import React, { useCallback } from "react";
import {
Modal,
ModalBody,
ModalContent,
ModalHeader,
Text,
} from "@appsmith/ads";
import {
createMessage,
GENERATE_PAGE_FORM_TITLE,
GENERATE_PAGE_FORM_SUB_TITLE,
} from "ee/constants/messages";
import GeneratePageForm from "./components/GeneratePageForm/GeneratePageForm";
import { useSelector, useDispatch } from "react-redux";
import { getIsGeneratePageModalOpen } from "selectors/pageListSelectors";
import {
closeGeneratePageModal,
openGeneratePageModal,
} from "./store/generatePageActions";
const Container = styled.div`
display: flex;
flex-direction: column;
overflow-y: auto;
height: 100%;
padding: var(--ads-v2-spaces-7);
`;
function GeneratePageModal() {
const dispatch = useDispatch();
const isOpen = useSelector(getIsGeneratePageModalOpen);
const HeadingContainer = styled.div`
display: flex;
padding-top: var(--ads-v2-spaces-4);
`;
const Header = styled.div`
width: 100%;
> a {
margin: 0;
}
`;
function GeneratePage() {
const isGenerateFormPage = window.location.pathname.includes("/form");
const heading = isGenerateFormPage ? "Quick page wizard" : "New page";
const handleModalOpenChange = useCallback(
(modalState: boolean) => {
if (modalState) {
dispatch(openGeneratePageModal());
} else {
dispatch(closeGeneratePageModal());
}
},
[dispatch],
);
return (
<Container>
{isGenerateFormPage ? (
<Header>
<BackButton />
</Header>
) : null}
<HeadingContainer>
<Text kind="heading-l">{heading}</Text>
</HeadingContainer>
{isGenerateFormPage ? (
<Text renderAs="p">
Auto create a simple CRUD interface on top of your data
</Text>
) : null}
<PageContent />
</Container>
<Modal onOpenChange={handleModalOpenChange} open={isOpen}>
<ModalContent style={{ width: "444px" }}>
<ModalHeader>{createMessage(GENERATE_PAGE_FORM_TITLE)}</ModalHeader>
<ModalBody>
<Text renderAs="p">
{createMessage(GENERATE_PAGE_FORM_SUB_TITLE)}
</Text>
<GeneratePageForm />
</ModalBody>
</ModalContent>
</Modal>
);
}
export default GeneratePage;
export default GeneratePageModal;

View File

@ -0,0 +1,15 @@
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import type { GeneratePageModalParams } from "reducers/entityReducers/pageListReducer";
export const openGeneratePageModal = (payload?: GeneratePageModalParams) => {
return {
type: ReduxActionTypes.SET_GENERATE_PAGE_MODAL_OPEN,
payload,
};
};
export const closeGeneratePageModal = () => {
return {
type: ReduxActionTypes.SET_GENERATE_PAGE_MODAL_CLOSE,
};
};

View File

@ -21,7 +21,6 @@ import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import NewActionButton from "../DataSourceEditor/NewActionButton";
import {
datasourcesEditorIdURL,
generateTemplateFormURL,
saasEditorDatasourceIdURL,
} from "ee/RouteBuilder";
import {
@ -57,6 +56,7 @@ import {
} from "ee/utils/BusinessFeatures/permissionPageHelpers";
import { useEditorType } from "ee/hooks";
import { getIsAnvilEnabledInCurrentApplication } from "layoutSystems/anvil/integrations/selectors";
import { openGeneratePageModal } from "../GeneratePage/store/generatePageActions";
const Wrapper = styled.div`
padding: 15px;
@ -266,13 +266,10 @@ function DatasourceCard(props: DatasourceCardProps) {
}
AnalyticsUtil.logEvent("DATASOURCE_CARD_GEN_CRUD_PAGE_ACTION");
history.push(
generateTemplateFormURL({
basePageId,
params: {
datasourceId: datasource.id,
new_page: true,
},
dispatch(
openGeneratePageModal({
datasourceId: datasource.id,
new_page: true,
}),
);
};

View File

@ -51,6 +51,7 @@ import { PartialImportModal } from "components/editorComponents/PartialImportExp
import type { Page } from "entities/Page";
import { AppCURLImportModal } from "ee/pages/Editor/CurlImport";
import { IDE_HEADER_HEIGHT } from "IDE";
import GeneratePageModal from "./GeneratePage";
interface EditorProps {
currentApplicationId?: string;
@ -210,6 +211,7 @@ class Editor extends Component<Props> {
<PartialExportModal />
<PartialImportModal />
<AppCURLImportModal />
<GeneratePageModal />
</GlobalHotKeys>
</div>
<RequestConfirmationModal />

View File

@ -1,20 +1,9 @@
import { Flex } from "@appsmith/ads";
import {
ADD_PAGE_FROM_TEMPLATE_MODAL,
createMessage,
} from "ee/constants/messages";
import React from "react";
import styled from "styled-components";
const BackText = styled.div<{ width?: number; hidden?: boolean }>`
${(props) => props.hidden && "visibility: hidden;"}
`;
const HeaderWrapper = styled.div`
display: flex;
align-items: center;
.back-button {
margin-right: 8px;
}
`;
interface TemplateModalHeaderProps {
className?: string;
@ -22,9 +11,9 @@ interface TemplateModalHeaderProps {
function TemplateModalHeader(props: TemplateModalHeaderProps) {
return (
<HeaderWrapper className={props.className}>
<BackText>{createMessage(ADD_PAGE_FROM_TEMPLATE_MODAL.title)}</BackText>
</HeaderWrapper>
<Flex alignItems="center" className={props.className}>
{createMessage(ADD_PAGE_FROM_TEMPLATE_MODAL.title)}
</Flex>
);
}

View File

@ -20,6 +20,10 @@ import type { Page } from "entities/Page";
const initialState: PageListReduxState = {
pages: [],
isGeneratingTemplatePage: false,
generatePage: {
modalOpen: false,
params: {},
},
baseApplicationId: "",
applicationId: "",
currentBasePageId: "",
@ -235,6 +239,27 @@ export const pageListReducer = createReducer(initialState, {
},
};
},
[ReduxActionTypes.SET_GENERATE_PAGE_MODAL_OPEN]: (
state: PageListReduxState,
action: ReduxAction<GeneratePageModalParams>,
) => {
return {
...state,
generatePage: {
modalOpen: true,
params: action.payload || {},
},
};
},
[ReduxActionTypes.SET_GENERATE_PAGE_MODAL_CLOSE]: (
state: PageListReduxState,
) => ({
...state,
generatePage: {
...state.generatePage,
modalOpen: false,
},
}),
[ReduxActionTypes.GENERATE_TEMPLATE_PAGE_INIT]: (
state: PageListReduxState,
) => {
@ -299,6 +324,11 @@ export interface AppLayoutConfig {
type: SupportedLayouts;
}
export interface GeneratePageModalParams {
datasourceId?: string;
new_page?: boolean;
}
export interface PageListReduxState {
pages: Page[];
baseApplicationId: string;
@ -309,6 +339,10 @@ export interface PageListReduxState {
defaultPageId: string;
appLayout?: AppLayoutConfig;
isGeneratingTemplatePage?: boolean;
generatePage?: {
modalOpen: boolean;
params?: GeneratePageModalParams;
};
loading: Record<string, boolean>;
}

View File

@ -151,7 +151,6 @@ import {
import {
apiEditorIdURL,
datasourcesEditorIdURL,
generateTemplateFormURL,
integrationEditorURL,
saasEditorDatasourceIdURL,
} from "ee/RouteBuilder";
@ -189,6 +188,7 @@ import { executeGoogleApi } from "./loadGoogleApi";
import type { ActionParentEntityTypeInterface } from "ee/entities/Engine/actionHelpers";
import { getCurrentModuleId } from "ee/selectors/modulesSelector";
import type { ApplicationPayload } from "entities/Application";
import { openGeneratePageModalWithSelectedDS } from "../utils/GeneratePageUtils";
function* fetchDatasourcesSaga(
action: ReduxAction<
@ -339,42 +339,36 @@ export function* addMockDbToDatasources(actionPayload: addMockDb) {
const isGeneratePageInitiator =
getIsGeneratePageInitiator(isGeneratePageMode);
if (isGeneratePageInitiator) {
history.push(
generateTemplateFormURL({
basePageId,
params: {
datasourceId: response.data.id,
},
}),
);
} else {
if (skipRedirection) {
return;
}
let url = "";
const plugin: Plugin = yield select(getPlugin, response.data.pluginId);
if (plugin && plugin.type === PluginType.SAAS) {
url = saasEditorDatasourceIdURL({
basePageId,
pluginPackageName: plugin.packageName,
datasourceId: response.data.id,
params: {
viewMode: true,
},
});
} else {
url = datasourcesEditorIdURL({
basePageId,
datasourceId: response.data.id,
params: omit(getQueryParams(), "viewMode"),
});
}
history.push(url);
if (skipRedirection) {
return;
}
let url = "";
const plugin: Plugin = yield select(getPlugin, response.data.pluginId);
if (plugin && plugin.type === PluginType.SAAS) {
url = saasEditorDatasourceIdURL({
basePageId,
pluginPackageName: plugin.packageName,
datasourceId: response.data.id,
params: {
viewMode: true,
},
});
} else {
url = datasourcesEditorIdURL({
basePageId,
datasourceId: response.data.id,
params: omit(getQueryParams(), "viewMode"),
});
}
history.push(url);
yield call(openGeneratePageModalWithSelectedDS, {
shouldOpenModalWIthSelectedDS: Boolean(isGeneratePageInitiator),
datasourceId: response.data.id,
});
}
} catch (error) {
yield put({
@ -1519,7 +1513,6 @@ function* updateDatasourceSuccessSaga(action: UpdateDatasourceSuccessAction) {
const actionRouteInfo = get(state, "ui.datasourcePane.actionRouteInfo");
const generateCRUDSupportedPlugin: GenerateCRUDEnabledPluginMap =
yield select(getGenerateCRUDEnabledPluginMap);
const basePageId: string = yield select(getCurrentBasePageId);
const updatedDatasource = action.payload;
const { queryParams = {} } = action;
@ -1529,19 +1522,6 @@ function* updateDatasourceSuccessSaga(action: UpdateDatasourceSuccessAction) {
);
if (
isGeneratePageInitiator &&
updatedDatasource.pluginId &&
generateCRUDSupportedPlugin[updatedDatasource.pluginId]
) {
history.push(
generateTemplateFormURL({
basePageId,
params: {
datasourceId: updatedDatasource.id,
},
}),
);
} else if (
actionRouteInfo &&
updatedDatasource.id === actionRouteInfo.datasourceId &&
action.redirect
@ -1554,6 +1534,15 @@ function* updateDatasourceSuccessSaga(action: UpdateDatasourceSuccessAction) {
);
}
yield call(openGeneratePageModalWithSelectedDS, {
shouldOpenModalWIthSelectedDS: Boolean(
isGeneratePageInitiator &&
updatedDatasource.pluginId &&
generateCRUDSupportedPlugin[updatedDatasource.pluginId],
),
datasourceId: updatedDatasource.id,
});
yield put({
type: ReduxActionTypes.STORE_AS_DATASOURCE_COMPLETE,
});

View File

@ -57,7 +57,6 @@ import type { EventLocation } from "ee/utils/analyticsUtilTypes";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import {
datasourcesEditorIdURL,
generateTemplateFormURL,
integrationEditorURL,
queryEditorIdURL,
} from "ee/RouteBuilder";
@ -85,6 +84,7 @@ import {
import { TEMP_DATASOURCE_ID } from "constants/Datasource";
import { doesPluginRequireDatasource } from "ee/entities/Engine/actionHelpers";
import { convertToBasePageIdSelector } from "selectors/pageListSelectors";
import { openGeneratePageModalWithSelectedDS } from "../utils/GeneratePageUtils";
// Called whenever the query being edited is changed via the URL or query pane
function* changeQuerySaga(actionPayload: ReduxAction<ChangeQueryPayload>) {
@ -464,25 +464,7 @@ function* handleDatasourceCreatedSaga(
const generateCRUDSupportedPlugin: GenerateCRUDEnabledPluginMap =
yield select(getGenerateCRUDEnabledPluginMap);
// isGeneratePageInitiator ensures that datasource is being created from generate page with data
// then we check if the current plugin is supported for generate page with data functionality
// and finally isDBCreated ensures that datasource is not in temporary state and
// user has explicitly saved the datasource, before redirecting back to generate page
if (
isGeneratePageInitiator &&
updatedDatasource.pluginId &&
generateCRUDSupportedPlugin[updatedDatasource.pluginId] &&
isDBCreated
) {
history.push(
generateTemplateFormURL({
basePageId,
params: {
datasourceId: updatedDatasource.id,
},
}),
);
} else if (
!currentApplicationIdForCreateNewApp ||
(!!currentApplicationIdForCreateNewApp && payload.id !== TEMP_DATASOURCE_ID)
) {
@ -498,6 +480,20 @@ function* handleDatasourceCreatedSaga(
}),
);
}
// isGeneratePageInitiator ensures that datasource is being created from generate page with data
// then we check if the current plugin is supported for generate page with data functionality
// and finally isDBCreated ensures that datasource is not in temporary state and
// user has explicitly saved the datasource, before redirecting back to generate page
yield call(openGeneratePageModalWithSelectedDS, {
shouldOpenModalWIthSelectedDS: Boolean(
isGeneratePageInitiator &&
updatedDatasource.pluginId &&
generateCRUDSupportedPlugin[updatedDatasource.pluginId] &&
isDBCreated,
),
datasourceId: updatedDatasource.id,
});
}
function* handleNameChangeSaga(

View File

@ -1,4 +1,4 @@
import { all, put, select, takeEvery } from "redux-saga/effects";
import { all, call, put, select, takeEvery } from "redux-saga/effects";
import type { ApplicationPayload } from "entities/Application";
import type { ReduxAction } from "ee/constants/ReduxActionConstants";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
@ -10,11 +10,7 @@ import {
import type { Action } from "entities/Action";
import { PluginType } from "entities/Action";
import type { GenerateCRUDEnabledPluginMap, Plugin } from "api/PluginApi";
import {
generateTemplateFormURL,
saasEditorApiIdURL,
saasEditorDatasourceIdURL,
} from "ee/RouteBuilder";
import { saasEditorApiIdURL, saasEditorDatasourceIdURL } from "ee/RouteBuilder";
import { getCurrentBasePageId } from "selectors/editorSelectors";
import type { CreateDatasourceSuccessAction } from "actions/datasourceActions";
import { getQueryParams } from "utils/URLUtils";
@ -28,6 +24,7 @@ import {
} from "ee/selectors/applicationSelectors";
import { TEMP_DATASOURCE_ID } from "constants/Datasource";
import { convertToBasePageIdSelector } from "selectors/pageListSelectors";
import { openGeneratePageModalWithSelectedDS } from "../utils/GeneratePageUtils";
function* handleDatasourceCreatedSaga(
actionPayload: CreateDatasourceSuccessAction,
@ -63,25 +60,7 @@ function* handleDatasourceCreatedSaga(
const generateCRUDSupportedPlugin: GenerateCRUDEnabledPluginMap =
yield select(getGenerateCRUDEnabledPluginMap);
// isGeneratePageInitiator ensures that datasource is being created from generate page with data
// then we check if the current plugin is supported for generate page with data functionality
// and finally isDBCreated ensures that datasource is not in temporary state and
// user has explicitly saved the datasource, before redirecting back to generate page
if (
isGeneratePageInitiator &&
updatedDatasource.pluginId &&
generateCRUDSupportedPlugin[updatedDatasource.pluginId] &&
isDBCreated
) {
history.push(
generateTemplateFormURL({
basePageId,
params: {
datasourceId: updatedDatasource.id,
},
}),
);
} else if (
!currentApplicationIdForCreateNewApp ||
(!!currentApplicationIdForCreateNewApp && payload.id !== TEMP_DATASOURCE_ID)
) {
@ -98,6 +77,20 @@ function* handleDatasourceCreatedSaga(
}),
);
}
// isGeneratePageInitiator ensures that datasource is being created from generate page with data
// then we check if the current plugin is supported for generate page with data functionality
// and finally isDBCreated ensures that datasource is not in temporary state and
// user has explicitly saved the datasource, before redirecting back to generate page
yield call(openGeneratePageModalWithSelectedDS, {
shouldOpenModalWIthSelectedDS: Boolean(
isGeneratePageInitiator &&
updatedDatasource.pluginId &&
generateCRUDSupportedPlugin[updatedDatasource.pluginId] &&
isDBCreated,
),
datasourceId: updatedDatasource.id,
});
}
function* handleActionCreatedSaga(actionPayload: ReduxAction<Action>) {

View File

@ -16,6 +16,12 @@ export const getIsGeneratingTemplatePage = createSelector(
(pageList: PageListReduxState) => pageList.isGeneratingTemplatePage,
);
export const getIsGeneratePageModalOpen = (state: AppState) =>
state.entities.pageList.generatePage?.modalOpen;
export const getGeneratePageModalParams = (state: AppState) =>
state.entities.pageList.generatePage?.params;
export const convertToPageIdSelector = (state: AppState, basePageId: string) =>
state.entities.pageList.pages?.find((page) => page.basePageId === basePageId)
?.pageId;

View File

@ -0,0 +1,18 @@
import { openGeneratePageModal } from "pages/Editor/GeneratePage/store/generatePageActions";
import { put } from "redux-saga/effects";
export function* openGeneratePageModalWithSelectedDS({
datasourceId,
shouldOpenModalWIthSelectedDS,
}: {
shouldOpenModalWIthSelectedDS: boolean;
datasourceId: string;
}) {
if (shouldOpenModalWIthSelectedDS) {
yield put(
openGeneratePageModal({
datasourceId,
}),
);
}
}