feat: Show Crud Info Modal data from backend (#6882)

CRUD Info modal which pops up on successful CRUD generation, will now have dynamic data for each CRUD template.

Modal success `message` and `Image` to explain the working of the CRUD template is fetched from the backend.

Co-authored-by: Abhijeet <ABHI.NAGARNAIK@GMAIL.COM>
This commit is contained in:
Rishabh Rathod 2021-08-26 12:53:39 +05:30 committed by GitHub
parent 058055331a
commit dc86c9b82b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 291 additions and 124 deletions

View File

@ -1,6 +1,14 @@
import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxActionTypes } from "constants/ReduxActionConstants";
export const setCrudInfoModalOpen = (payload: boolean) => { export type SetCrudInfoModalOpenPayload = {
open: boolean;
generateCRUDSuccessInfo?: {
successImageUrl: string;
successMessage: string;
};
};
export const setCrudInfoModalData = (payload: SetCrudInfoModalOpenPayload) => {
return { return {
type: ReduxActionTypes.SET_CRUD_INFO_MODAL_OPEN, type: ReduxActionTypes.SET_CRUD_INFO_MODAL_OPEN,
payload, payload,

View File

@ -319,25 +319,20 @@ export interface ReduxActionWithExtraParams<T> extends ReduxAction<T> {
extraParams: Record<any, any>; extraParams: Record<any, any>;
} }
export const generateTemplateSuccess = ({ export type GenerateCRUDSuccess = {
isNewPage, page: {
layoutId, layouts: Array<any>;
pageId, id: string;
pageName, name: string;
}: { isDefault?: boolean;
layoutId: string; };
pageId: string;
pageName: string;
isNewPage: boolean; isNewPage: boolean;
}) => { };
export const generateTemplateSuccess = (payload: GenerateCRUDSuccess) => {
return { return {
type: ReduxActionTypes.GENERATE_TEMPLATE_PAGE_SUCCESS, type: ReduxActionTypes.GENERATE_TEMPLATE_PAGE_SUCCESS,
payload: { payload,
layoutId,
pageId,
pageName,
isNewPage,
},
}; };
}; };

View File

@ -395,11 +395,13 @@ export const GENERATE_PAGE_ACTION_SUBTITLE = () =>
export const GENERATE_PAGE_FORM_TITLE = () => "Generate from Data"; export const GENERATE_PAGE_FORM_TITLE = () => "Generate from Data";
export const GEN_CRUD_INFO_DIALOG_HEADING = () => export const GEN_CRUD_SUCCESS_MESSAGE = () =>
"Hurray! Your application is ready to use."; "Hurray! Your application is ready to use.";
export const GEN_CRUD_SUCCESS_DESC = () =>
"Search through your data in the table and update it using the form";
export const GEN_CRUD_INFO_DIALOG_TITLE = () => "How it works?"; export const GEN_CRUD_INFO_DIALOG_TITLE = () => "How it works?";
export const GEN_CRUD_INFO_DIALOG_SUBTITLE = () => export const GEN_CRUD_INFO_DIALOG_SUBTITLE = () =>
"Search through your data in the table and update it using the form."; "CRUD page is generated from selected datasource. You can use the Form to modify the data. Since all your data is already connected you can add more queries and modify the bindings";
// Actions Right pane // Actions Right pane
export const SEE_CONNECTED_ENTITIES = () => "See all connected entities"; export const SEE_CONNECTED_ENTITIES = () => "See all connected entities";

View File

@ -1,47 +1,43 @@
import React from "react"; import React, { useState, useEffect } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { connect, useDispatch } from "react-redux"; import { connect, useDispatch } from "react-redux";
import { AppState } from "reducers"; import { AppState } from "reducers";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import Button, { Category, Size } from "components/ads/Button"; import Button, { Category, Size } from "components/ads/Button";
import Text, { TextType } from "components/ads/Text"; import Text, { TextType } from "components/ads/Text";
import { getCrudInfoModalOpen } from "selectors/crudInfoModalSelectors"; import { getCrudInfoModalData } from "selectors/crudInfoModalSelectors";
import { setCrudInfoModalOpen } from "actions/crudInfoModalActions"; import { setCrudInfoModalData } from "actions/crudInfoModalActions";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import { S3_BUCKET_URL } from "constants/ThirdPartyConstants"; import { S3_BUCKET_URL } from "constants/ThirdPartyConstants";
import Dialog from "components/ads/DialogComponent"; import Dialog from "components/ads/DialogComponent";
import { GEN_CRUD_INFO_DIALOG_HEADING } from "../../../../constants/messages"; import { GenerateCRUDSuccessInfoData } from "../../../../reducers/uiReducers/crudInfoModalReducer";
import { import {
GEN_CRUD_INFO_DIALOG_SUBTITLE, GEN_CRUD_INFO_DIALOG_SUBTITLE,
GEN_CRUD_INFO_DIALOG_TITLE, GEN_CRUD_SUCCESS_MESSAGE,
GEN_CRUD_SUCCESS_DESC,
createMessage,
} from "constants/messages"; } from "constants/messages";
import { getTypographyByKey } from "constants/DefaultTheme";
type Props = { type Props = {
crudInfoModalOpen: boolean; crudInfoModalOpen: boolean;
generateCRUDSuccessInfo: GenerateCRUDSuccessInfoData | null;
}; };
const HeaderContents = styled.div` const getSuccessGIF = () => `${S3_BUCKET_URL}/crud/check_mark_verified.gif`;
padding: ${(props) => props.theme.spaces[9]}px;
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: ${(props) => props.theme.spaces[7]}px;
background-color: ${Colors.FOAM};
`;
const Heading = styled.div` const Heading = styled.div`
color: ${(props) => props.theme.colors.modal.headerText}; color: ${Colors.CODE_GRAY};
display: flex; display: flex;
justify-content: center; justify-content: center;
font-size: 20px; font-size: 20px;
line-height: 24px; line-height: 24px;
color: ${(props) => props.theme.colors.success.dark};
`; `;
const ActionButtonWrapper = styled.div` const ActionButtonWrapper = styled.div`
display: flex; display: flex;
justify-content: flex-end; justify-content: center;
margin: 30px 0px 0px; margin: 30px 0px 0px;
`; `;
@ -63,17 +59,22 @@ const Content = styled.div`
flex-direction: column; flex-direction: column;
`; `;
const Desc = styled.p`
${(props) => getTypographyByKey(props, "p1")}
color: ${Colors.DOVE_GRAY2};
margin-top: 8px;
`;
const Wrapper = styled.div` const Wrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
max-height: 700px;
.info-title { min-height: 500px;
font-weight: bold;
}
.info-subtitle { .info-subtitle {
padding-top: 5px; padding-top: 5px;
text-align: center;
} }
`; `;
@ -89,44 +90,41 @@ const ImageWrapper = styled.div`
justify-content: center; justify-content: center;
`; `;
function Header() { const SuccessContentWrapper = styled.div`
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
`;
const SuccessImage = styled.img`
margin: ${(props) => props.theme.spaces[6]}px;
`;
const STEP = {
SHOW_SUCCESS_GIF: "show_success_gif",
SHOW_INFO: "show_info",
};
function InfoContent({
onClose,
successMessage,
}: {
onClose: () => void;
successMessage: string;
}) {
return ( return (
<HeaderContents> <>
<Heading> {GEN_CRUD_INFO_DIALOG_HEADING()}</Heading>
</HeaderContents>
);
}
const getInfoImage = (): string =>
`${S3_BUCKET_URL}/crud/working-flow-chart.png`;
function GenCRUDSuccessModal(props: Props) {
const { crudInfoModalOpen } = props;
const dispatch = useDispatch();
const onClose = () => {
AnalyticsUtil.logEvent("CLOSE_GEN_PAGE_INFO_MODAL");
dispatch(setCrudInfoModalOpen(false));
};
return (
<Dialog
canEscapeKeyClose
canOutsideClickClose
isOpen={crudInfoModalOpen}
setModalClose={onClose}
>
<Wrapper>
<Header />
<Content> <Content>
<Text className="info-title" type={TextType.H4}> <Text
{GEN_CRUD_INFO_DIALOG_TITLE()} className="info-subtitle"
</Text> dangerouslySetInnerHTML={{
__html: successMessage,
<Text className="info-subtitle" type={TextType.P1}> }}
{GEN_CRUD_INFO_DIALOG_SUBTITLE()} type={TextType.P1}
</Text> />
<ImageWrapper> <ImageWrapper>
<InfoImage alt="CRUD Info" src={getInfoImage()} /> <InfoImage alt="CRUD Info" src={getInfoImage()} />
</ImageWrapper> </ImageWrapper>
@ -142,13 +140,59 @@ function GenCRUDSuccessModal(props: Props) {
text="GOT IT" text="GOT IT"
/> />
</ActionButtonWrapper> </ActionButtonWrapper>
</>
);
}
const getInfoImage = (): string =>
`${S3_BUCKET_URL}/crud/working-flow-chart.png`;
function GenCRUDSuccessModal(props: Props) {
const { crudInfoModalOpen, generateCRUDSuccessInfo } = props;
const dispatch = useDispatch();
const [step, setStep] = useState(STEP.SHOW_SUCCESS_GIF);
const onClose = () => {
AnalyticsUtil.logEvent("CLOSE_GEN_PAGE_INFO_MODAL");
dispatch(setCrudInfoModalData({ open: false }));
};
const successMessage =
(generateCRUDSuccessInfo && generateCRUDSuccessInfo.successMessage) ||
createMessage(GEN_CRUD_INFO_DIALOG_SUBTITLE);
useEffect(() => {
setTimeout(() => {
setStep(STEP.SHOW_INFO);
}, 2000);
}, [setStep]);
return (
<Dialog
canEscapeKeyClose
canOutsideClickClose
isOpen={crudInfoModalOpen}
setModalClose={onClose}
>
<Wrapper>
{step === STEP.SHOW_SUCCESS_GIF ? (
<SuccessContentWrapper>
<SuccessImage alt="Success" src={getSuccessGIF()} width="50px" />
<Heading> {createMessage(GEN_CRUD_SUCCESS_MESSAGE)}</Heading>
<Desc>{createMessage(GEN_CRUD_SUCCESS_DESC)}</Desc>
</SuccessContentWrapper>
) : null}
{step === STEP.SHOW_INFO ? (
<InfoContent onClose={onClose} successMessage={successMessage} />
) : null}
</Wrapper> </Wrapper>
</Dialog> </Dialog>
); );
} }
const mapStateToProps = (state: AppState) => ({ const mapStateToProps = (state: AppState) => ({
crudInfoModalOpen: getCrudInfoModalOpen(state), crudInfoModalOpen: getCrudInfoModalData(state).crudInfoModalOpen,
generateCRUDSuccessInfo: getCrudInfoModalData(state).generateCRUDSuccessInfo,
}); });
export default connect(mapStateToProps)(GenCRUDSuccessModal); export default connect(mapStateToProps)(GenCRUDSuccessModal);

View File

@ -7,6 +7,7 @@ import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { createReducer } from "utils/AppsmithUtils"; import { createReducer } from "utils/AppsmithUtils";
import { GenerateCRUDSuccess } from "actions/pageActions";
const initialState: PageListReduxState = { const initialState: PageListReduxState = {
pages: [], pages: [],
@ -114,27 +115,24 @@ export const pageListReducer = createReducer(initialState, {
}, },
[ReduxActionTypes.GENERATE_TEMPLATE_PAGE_SUCCESS]: ( [ReduxActionTypes.GENERATE_TEMPLATE_PAGE_SUCCESS]: (
state: PageListReduxState, state: PageListReduxState,
action: ReduxAction<{ action: ReduxAction<GenerateCRUDSuccess>,
pageName: string;
pageId: string;
layoutId: string;
isDefault: boolean;
isNewPage: boolean;
}>,
) => { ) => {
const _state = state; const _state = state;
if (action.payload.isNewPage) { if (action.payload.isNewPage) {
_state.pages = state.pages.map((page) => ({ ...page, latest: false })); _state.pages = state.pages.map((page) => ({ ...page, latest: false }));
const newPage = { const newPage = {
pageName: action.payload.pageName, pageName: action.payload.page.name,
pageId: action.payload.pageId, pageId: action.payload.page.id,
layoutId: action.payload.layoutId, layoutId: action.payload.page.layouts[0].id,
isDefault: action.payload.isDefault, isDefault: !!action.payload.page.isDefault,
}; };
_state.pages.push({ ...newPage, latest: true }); _state.pages.push({ ...newPage, latest: true });
} }
return { ..._state, isGeneratingTemplatePage: false }; return {
..._state,
isGeneratingTemplatePage: false,
};
}, },
[ReduxActionErrorTypes.GENERATE_TEMPLATE_PAGE_ERROR]: ( [ReduxActionErrorTypes.GENERATE_TEMPLATE_PAGE_ERROR]: (
state: PageListReduxState, state: PageListReduxState,

View File

@ -1,21 +1,33 @@
import { createReducer } from "utils/AppsmithUtils"; import { createReducer } from "utils/AppsmithUtils";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { SetCrudInfoModalOpenPayload } from "actions/crudInfoModalActions";
const initialState: CrudInfoModalReduxState = { const initialState: CrudInfoModalReduxState = {
crudInfoModalOpen: false, crudInfoModalOpen: false,
generateCRUDSuccessInfo: null,
}; };
const crudInfoModalReducer = createReducer(initialState, { const crudInfoModalReducer = createReducer(initialState, {
[ReduxActionTypes.SET_CRUD_INFO_MODAL_OPEN]: ( [ReduxActionTypes.SET_CRUD_INFO_MODAL_OPEN]: (
state: CrudInfoModalReduxState, state: CrudInfoModalReduxState,
action: ReduxAction<boolean>, action: ReduxAction<SetCrudInfoModalOpenPayload>,
) => { ) => {
return { ...state, crudInfoModalOpen: action.payload }; return {
...state,
crudInfoModalOpen: action.payload.open,
generateCRUDSuccessInfo: action.payload.generateCRUDSuccessInfo,
};
}, },
}); });
export type GenerateCRUDSuccessInfoData = {
successImageUrl: string;
successMessage: string;
};
export interface CrudInfoModalReduxState { export interface CrudInfoModalReduxState {
crudInfoModalOpen: boolean; crudInfoModalOpen: boolean;
generateCRUDSuccessInfo: GenerateCRUDSuccessInfoData | null;
} }
export default crudInfoModalReducer; export default crudInfoModalReducer;

View File

@ -67,7 +67,7 @@ import {
import { getDataTree } from "selectors/dataTreeSelectors"; import { getDataTree } from "selectors/dataTreeSelectors";
import { IncorrectBindingError, validateResponse } from "./ErrorSagas"; import { IncorrectBindingError, validateResponse } from "./ErrorSagas";
import { executePageLoadActions } from "actions/widgetActions"; import { executePageLoadActions } from "actions/widgetActions";
import { ApiResponse } from "api/ApiResponses"; import { ApiResponse, GenericApiResponse } from "api/ApiResponses";
import { import {
getCurrentApplicationId, getCurrentApplicationId,
getCurrentLayoutId, getCurrentLayoutId,
@ -101,7 +101,7 @@ import {
generateTemplateSuccess, generateTemplateSuccess,
} from "../actions/pageActions"; } from "../actions/pageActions";
import { getAppMode } from "selectors/applicationSelectors"; import { getAppMode } from "selectors/applicationSelectors";
import { setCrudInfoModalOpen } from "actions/crudInfoModalActions"; import { setCrudInfoModalData } from "actions/crudInfoModalActions";
import { selectMultipleWidgetsAction } from "actions/widgetSelectionActions"; import { selectMultipleWidgetsAction } from "actions/widgetSelectionActions";
const getWidgetName = (state: AppState, widgetId: string) => const getWidgetName = (state: AppState, widgetId: string) =>
@ -898,26 +898,29 @@ export function* generateTemplatePageSaga(
try { try {
const request: GenerateTemplatePageRequest = action.payload; const request: GenerateTemplatePageRequest = action.payload;
// if pageId is available in request, it will just update that page else it will generate new page. // if pageId is available in request, it will just update that page else it will generate new page.
const response: ApiResponse = yield call( const response: GenericApiResponse<{
PageApi.generateTemplatePage, page: any;
request, successImageUrl: string;
); successMessage: string;
}> = yield call(PageApi.generateTemplatePage, request);
const isValidResponse: boolean = yield validateResponse(response); const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) { if (isValidResponse) {
const pageId = response.data.id; const pageId = response.data.page.id;
const applicationId = const applicationId =
response.data.applicationId || request.applicationId; response.data.page.applicationId || request.applicationId;
yield handleFetchedPage({ yield handleFetchedPage({
fetchPageResponse: response, fetchPageResponse: {
data: response.data.page,
responseMeta: response.responseMeta,
},
pageId, pageId,
}); });
// TODO : Add this to onSuccess (Redux Action) // TODO : Add this to onSuccess (Redux Action)
yield put( yield put(
generateTemplateSuccess({ generateTemplateSuccess({
pageId: response.data.id, page: response.data.page,
pageName: response.data.name,
layoutId: response.data.layouts[0].id,
isNewPage: !request.pageId, // if pageId if not defined, that means a new page is generated. isNewPage: !request.pageId, // if pageId if not defined, that means a new page is generated.
}), }),
); );
@ -931,7 +934,15 @@ export function* generateTemplatePageSaga(
variant: Variant.success, variant: Variant.success,
}); });
yield put(setCrudInfoModalOpen(true)); yield put(
setCrudInfoModalData({
open: true,
generateCRUDSuccessInfo: {
successImageUrl: response.data.successImageUrl,
successMessage: response.data.successMessage,
},
}),
);
} }
} catch (error) { } catch (error) {
yield put(generateTemplateError()); yield put(generateTemplateError());

View File

@ -1,4 +1,24 @@
import { AppState } from "reducers"; import { AppState } from "reducers";
import { createSelector } from "reselect";
import {
CrudInfoModalReduxState,
GenerateCRUDSuccessInfoData,
} from "reducers/uiReducers/crudInfoModalReducer";
export const getCrudInfoModalOpen = (state: AppState): boolean => export type CrudInfoModalData = {
state.ui.crudInfoModal.crudInfoModalOpen; crudInfoModalOpen: boolean;
generateCRUDSuccessInfo: GenerateCRUDSuccessInfoData | null;
};
const getCrudInfoModalState = (state: AppState): CrudInfoModalReduxState =>
state.ui.crudInfoModal;
export const getCrudInfoModalData = createSelector(
getCrudInfoModalState,
(crudInfoModal) => {
return {
crudInfoModalOpen: crudInfoModal.crudInfoModalOpen,
generateCRUDSuccessInfo: crudInfoModal.generateCRUDSuccessInfo,
};
},
);

View File

@ -0,0 +1,7 @@
package com.appsmith.server.constants;
public class Resources {
public static final String GENERATE_CRUD_PAGE_SUCCESS_URL_TABULAR = "https://assets.appsmith.com/crud/working-flow-chart.png";
}

View File

@ -3,6 +3,7 @@ package com.appsmith.server.controllers;
import com.appsmith.server.constants.Url; import com.appsmith.server.constants.Url;
import com.appsmith.server.dtos.ApplicationPagesDTO; import com.appsmith.server.dtos.ApplicationPagesDTO;
import com.appsmith.server.dtos.CRUDPageResourceDTO; import com.appsmith.server.dtos.CRUDPageResourceDTO;
import com.appsmith.server.dtos.CRUDPageResponseDTO;
import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.dtos.ResponseDTO;
import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.ApplicationPageService;
@ -57,7 +58,7 @@ public class PageController {
@PostMapping("/crud-page") @PostMapping("/crud-page")
@ResponseStatus(HttpStatus.CREATED) @ResponseStatus(HttpStatus.CREATED)
public Mono<ResponseDTO<PageDTO>> createCRUDPage(@RequestBody @NonNull CRUDPageResourceDTO resource) { public Mono<ResponseDTO<CRUDPageResponseDTO>> createCRUDPage(@RequestBody @NonNull CRUDPageResourceDTO resource) {
log.debug("Going to create crud-page"); log.debug("Going to create crud-page");
return createDBTablePageSolution.createPageFromDBTable(null, resource) return createDBTablePageSolution.createPageFromDBTable(null, resource)
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null)); .map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
@ -65,7 +66,7 @@ public class PageController {
@PutMapping("/crud-page/{pageId}") @PutMapping("/crud-page/{pageId}")
@ResponseStatus(HttpStatus.OK) @ResponseStatus(HttpStatus.OK)
public Mono<ResponseDTO<PageDTO>> createCRUDPage(@PathVariable String pageId, public Mono<ResponseDTO<CRUDPageResponseDTO>> createCRUDPage(@PathVariable String pageId,
@NonNull @RequestBody CRUDPageResourceDTO resource) { @NonNull @RequestBody CRUDPageResourceDTO resource) {
log.debug("Going to update resource {}", pageId); log.debug("Going to update resource {}", pageId);
return createDBTablePageSolution.createPageFromDBTable(pageId, resource) return createDBTablePageSolution.createPageFromDBTable(pageId, resource)

View File

@ -0,0 +1,24 @@
package com.appsmith.server.dtos;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* This class will hold the fields that will be consumed by the client after the successful CRUD page generation
*/
@NoArgsConstructor
@Getter
@Setter
public class CRUDPageResponseDTO {
PageDTO page;
// This field will give some guidelines how to interact with the widgets on the canvas created by CreateDBTablePageSolution
// e.g. We have generated the table from Datasource. You can use the Form> to modify it. Since all your data is
// already connected you can add more queries and modify the bindings
String successMessage;
// This field will be used to display the image on how to use the template application
String successImageUrl;
}

View File

@ -11,6 +11,7 @@ import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.constants.AnalyticsEvents; import com.appsmith.server.constants.AnalyticsEvents;
import com.appsmith.server.constants.Entity; import com.appsmith.server.constants.Entity;
import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.Resources;
import com.appsmith.server.domains.ApplicationJson; import com.appsmith.server.domains.ApplicationJson;
import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.Layout;
@ -19,6 +20,7 @@ import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.Plugin;
import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionDTO;
import com.appsmith.server.dtos.CRUDPageResourceDTO; import com.appsmith.server.dtos.CRUDPageResourceDTO;
import com.appsmith.server.dtos.CRUDPageResponseDTO;
import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
@ -119,7 +121,7 @@ public class CreateDBTablePageSolution {
* @param pageResourceDTO * @param pageResourceDTO
* @return generated pageDTO from the template resource * @return generated pageDTO from the template resource
*/ */
public Mono<PageDTO> createPageFromDBTable(String pageId, CRUDPageResourceDTO pageResourceDTO) { public Mono<CRUDPageResponseDTO> createPageFromDBTable(String pageId, CRUDPageResourceDTO pageResourceDTO) {
/* /*
1. Fetch page from the application 1. Fetch page from the application
@ -349,7 +351,14 @@ public class CreateDBTablePageSolution {
|| StringUtils.equals(actionDTO.getName(), LIST_QUERY) || StringUtils.equals(actionDTO.getName(), LIST_QUERY)
? layoutActionService.setExecuteOnLoad(actionDTO.getId(), true) : Mono.just(actionDTO)) ? layoutActionService.setExecuteOnLoad(actionDTO.getId(), true) : Mono.just(actionDTO))
.then(applicationPageService.getPage(savedPageId, false) .then(applicationPageService.getPage(savedPageId, false)
.flatMap(pageDTO -> sendGenerateCRUDPageAnalyticsEvent(pageDTO, datasource, plugin.getName())) .flatMap(pageDTO -> {
CRUDPageResponseDTO crudPage = new CRUDPageResponseDTO();
crudPage.setPage(pageDTO);
crudPage.setSuccessMessage(createSuccessMessage(plugin));
// Update the S3 image once received
crudPage.setSuccessImageUrl(Resources.GENERATE_CRUD_PAGE_SUCCESS_URL_TABULAR);
return sendGenerateCRUDPageAnalyticsEvent(crudPage, datasource, plugin.getName());
})
); );
}); });
} }
@ -881,7 +890,21 @@ public class CreateDBTablePageSolution {
return actionConfiguration; return actionConfiguration;
} }
private Mono<PageDTO> sendGenerateCRUDPageAnalyticsEvent(PageDTO page, Datasource datasource, String pluginName) { private String createSuccessMessage(Plugin plugin) {
String displayWidget = Entity.S3_PLUGIN_PACKAGE_NAME.equals(plugin.getPackageName()) ? "LIST" : "TABLE";
String updateWidget = Entity.S3_PLUGIN_PACKAGE_NAME.equals(plugin.getPackageName()) ? "FILEPICKER" : "FORM";
// Field used to send success message after the successful page creation
String successMessage = "We have generated the <b>" + displayWidget + "</b> from the <b>" + plugin.getName()
+ " Datasource</b>. You can use the <b>" + updateWidget + "</b> to modify it. Since all your " +
"data is already connected you can add more queries and modify the bindings";
return successMessage;
}
private Mono<CRUDPageResponseDTO> sendGenerateCRUDPageAnalyticsEvent(CRUDPageResponseDTO crudPage, Datasource datasource, String pluginName) {
PageDTO page = crudPage.getPage();
return sessionUserService.getCurrentUser() return sessionUserService.getCurrentUser()
.map(currentUser -> { .map(currentUser -> {
try { try {
@ -897,7 +920,7 @@ public class CreateDBTablePageSolution {
} catch (Exception e) { } catch (Exception e) {
log.warn("Error sending generate CRUD DB table page data point", e); log.warn("Error sending generate CRUD DB table page data point", e);
} }
return page; return crudPage;
}); });
} }

View File

@ -16,6 +16,7 @@ import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.Plugin;
import com.appsmith.server.dtos.CRUDPageResourceDTO; import com.appsmith.server.dtos.CRUDPageResourceDTO;
import com.appsmith.server.dtos.CRUDPageResponseDTO;
import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
@ -182,7 +183,7 @@ public class CreateDBTablePageSolutionTests {
@WithUserDetails(value = "api_user") @WithUserDetails(value = "api_user")
public void createPageWithInvalidApplicationIdTest() { public void createPageWithInvalidApplicationIdTest() {
Mono<PageDTO> resultMono = solution.createPageFromDBTable(testApp.getPages().get(0).getId(), resource); Mono<CRUDPageResponseDTO> resultMono = solution.createPageFromDBTable(testApp.getPages().get(0).getId(), resource);
StepVerifier StepVerifier
.create(resultMono) .create(resultMono)
@ -202,7 +203,7 @@ public class CreateDBTablePageSolutionTests {
invalidDatasource.setDatasourceConfiguration(new DatasourceConfiguration()); invalidDatasource.setDatasourceConfiguration(new DatasourceConfiguration());
resource.setDatasourceId(invalidDatasource.getId()); resource.setDatasourceId(invalidDatasource.getId());
Mono<PageDTO> resultMono = datasourceService.create(invalidDatasource) Mono<CRUDPageResponseDTO> resultMono = datasourceService.create(invalidDatasource)
.flatMap(datasource -> { .flatMap(datasource -> {
resource.setApplicationId(testApp.getId()); resource.setApplicationId(testApp.getId());
resource.setDatasourceId(datasource.getId()); resource.setDatasourceId(datasource.getId());
@ -220,7 +221,7 @@ public class CreateDBTablePageSolutionTests {
@Test @Test
@WithUserDetails(value = "api_user") @WithUserDetails(value = "api_user")
public void createPageWithInvalidRequestBodyTest() { public void createPageWithInvalidRequestBodyTest() {
Mono<PageDTO> resultMono = solution.createPageFromDBTable(testApp.getPages().get(0).getId(), new CRUDPageResourceDTO()); Mono<CRUDPageResponseDTO> resultMono = solution.createPageFromDBTable(testApp.getPages().get(0).getId(), new CRUDPageResourceDTO());
StepVerifier StepVerifier
.create(resultMono) .create(resultMono)
@ -234,11 +235,12 @@ public class CreateDBTablePageSolutionTests {
public void createPageWithNullPageId() { public void createPageWithNullPageId() {
resource.setApplicationId(testApp.getId()); resource.setApplicationId(testApp.getId());
Mono<PageDTO> resultMono = solution.createPageFromDBTable(null, resource); Mono<CRUDPageResponseDTO> resultMono = solution.createPageFromDBTable(null, resource);
StepVerifier StepVerifier
.create(resultMono) .create(resultMono)
.assertNext(page -> { .assertNext(crudPage -> {
PageDTO page = crudPage.getPage();
Layout layout = page.getLayouts().get(0); Layout layout = page.getLayouts().get(0);
assertThat(page.getName()).isEqualTo("SampleTable"); assertThat(page.getName()).isEqualTo("SampleTable");
assertThat(page.getLayouts()).isNotEmpty(); assertThat(page.getLayouts()).isNotEmpty();
@ -249,6 +251,8 @@ public class CreateDBTablePageSolutionTests {
assertThat(layout.getActionsUsedInDynamicBindings()).isNotEmpty(); assertThat(layout.getActionsUsedInDynamicBindings()).isNotEmpty();
assertThat(layout.getDsl().get("children").toString().replaceAll(specialCharactersRegex, "")) assertThat(layout.getDsl().get("children").toString().replaceAll(specialCharactersRegex, ""))
.containsIgnoringCase(dropdownOptions.replaceAll(specialCharactersRegex, "")); .containsIgnoringCase(dropdownOptions.replaceAll(specialCharactersRegex, ""));
assertThat(crudPage.getSuccessMessage()).isNotNull();
assertThat(crudPage.getSuccessImageUrl()).isNotNull();
}) })
.verifyComplete(); .verifyComplete();
} }
@ -263,7 +267,8 @@ public class CreateDBTablePageSolutionTests {
newPage.setName("crud-admin-page"); newPage.setName("crud-admin-page");
Mono<PageDTO> resultMono = applicationPageService.createPage(newPage) Mono<PageDTO> resultMono = applicationPageService.createPage(newPage)
.flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource)); .flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource))
.map(crudPageResponseDTO -> crudPageResponseDTO.getPage());
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId())))
@ -303,9 +308,11 @@ public class CreateDBTablePageSolutionTests {
PageDTO newPage = new PageDTO(); PageDTO newPage = new PageDTO();
newPage.setApplicationId(testApp.getId()); newPage.setApplicationId(testApp.getId());
newPage.setName("crud-admin-page-mysql"); newPage.setName("crud-admin-page-mysql");
StringBuilder pluginName = new StringBuilder();
Mono<Datasource> datasourceMono = pluginRepository.findByName("Mysql") Mono<Datasource> datasourceMono = pluginRepository.findByName("Mysql")
.flatMap(plugin -> { .flatMap(plugin -> {
pluginName.append(plugin.getName());
Datasource datasource = new Datasource(); Datasource datasource = new Datasource();
datasource.setPluginId(plugin.getId()); datasource.setPluginId(plugin.getId());
datasource.setOrganizationId(testOrg.getId()); datasource.setOrganizationId(testOrg.getId());
@ -315,7 +322,7 @@ public class CreateDBTablePageSolutionTests {
return datasourceService.create(datasource); return datasourceService.create(datasource);
}); });
Mono<PageDTO> resultMono = datasourceMono Mono<CRUDPageResponseDTO> resultMono = datasourceMono
.flatMap(datasource1 -> { .flatMap(datasource1 -> {
resource.setDatasourceId(datasource1.getId()); resource.setDatasourceId(datasource1.getId());
return applicationPageService.createPage(newPage); return applicationPageService.createPage(newPage);
@ -323,9 +330,10 @@ public class CreateDBTablePageSolutionTests {
.flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource)); .flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource));
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(crudPageResponseDTO -> getActions(crudPageResponseDTO.getPage().getId())))
.assertNext(tuple -> { .assertNext(tuple -> {
PageDTO page = tuple.getT1(); CRUDPageResponseDTO crudPageResponseDTO = tuple.getT1();
PageDTO page = crudPageResponseDTO.getPage();
List<NewAction> actions = tuple.getT2(); List<NewAction> actions = tuple.getT2();
Layout layout = page.getLayouts().get(0); Layout layout = page.getLayouts().get(0);
assertThat(page.getName()).isEqualTo(newPage.getName()); assertThat(page.getName()).isEqualTo(newPage.getName());
@ -351,6 +359,9 @@ public class CreateDBTablePageSolutionTests {
assertThat(action.getUnpublishedAction().getExecuteOnLoad()).isFalse(); assertThat(action.getUnpublishedAction().getExecuteOnLoad()).isFalse();
} }
} }
assertThat(crudPageResponseDTO.getSuccessMessage()).containsIgnoringCase(pluginName);
assertThat(crudPageResponseDTO.getSuccessMessage()).containsIgnoringCase("TABLE");
assertThat(crudPageResponseDTO.getSuccessImageUrl()).isNotNull();
}) })
.verifyComplete(); .verifyComplete();
} }
@ -380,7 +391,8 @@ public class CreateDBTablePageSolutionTests {
resource.setDatasourceId(datasource1.getId()); resource.setDatasourceId(datasource1.getId());
return applicationPageService.createPage(newPage); return applicationPageService.createPage(newPage);
}) })
.flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource)); .flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource))
.map(crudPageResponseDTO -> crudPageResponseDTO.getPage());
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId())))
@ -433,7 +445,8 @@ public class CreateDBTablePageSolutionTests {
.flatMap(datasource1 -> { .flatMap(datasource1 -> {
resource.setDatasourceId(datasource1.getId()); resource.setDatasourceId(datasource1.getId());
return solution.createPageFromDBTable(null, resource); return solution.createPageFromDBTable(null, resource);
}); })
.map(crudPageResponseDTO -> crudPageResponseDTO.getPage());
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId())))
@ -486,7 +499,8 @@ public class CreateDBTablePageSolutionTests {
.flatMap(datasource1 -> { .flatMap(datasource1 -> {
resource.setDatasourceId(datasource1.getId()); resource.setDatasourceId(datasource1.getId());
return solution.createPageFromDBTable(null, resource); return solution.createPageFromDBTable(null, resource);
}); })
.map(crudPageResponseDTO -> crudPageResponseDTO.getPage());
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId())))
@ -522,6 +536,7 @@ public class CreateDBTablePageSolutionTests {
public void createPageWithNullPageIdForS3() { public void createPageWithNullPageIdForS3() {
resource.setApplicationId(testApp.getId()); resource.setApplicationId(testApp.getId());
StringBuilder pluginName = new StringBuilder();
Mono<Datasource> datasourceMono = pluginRepository.findByName("S3") Mono<Datasource> datasourceMono = pluginRepository.findByName("S3")
.flatMap(plugin -> { .flatMap(plugin -> {
@ -530,19 +545,21 @@ public class CreateDBTablePageSolutionTests {
datasource.setOrganizationId(testOrg.getId()); datasource.setOrganizationId(testOrg.getId());
datasource.setName("S3-CRUD-Page-Table-DS"); datasource.setName("S3-CRUD-Page-Table-DS");
datasource.setDatasourceConfiguration(datasourceConfiguration); datasource.setDatasourceConfiguration(datasourceConfiguration);
pluginName.append(plugin.getName());
return datasourceService.create(datasource); return datasourceService.create(datasource);
}); });
Mono<PageDTO> resultMono = datasourceMono Mono<CRUDPageResponseDTO> resultMono = datasourceMono
.flatMap(datasource1 -> { .flatMap(datasource1 -> {
resource.setDatasourceId(datasource1.getId()); resource.setDatasourceId(datasource1.getId());
return solution.createPageFromDBTable(null, resource); return solution.createPageFromDBTable(null, resource);
}); });
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(crudPageResponseDTO -> getActions(crudPageResponseDTO.getPage().getId())))
.assertNext(tuple -> { .assertNext(tuple -> {
PageDTO page = tuple.getT1(); CRUDPageResponseDTO crudPage = tuple.getT1();
PageDTO page = crudPage.getPage();
List<NewAction> actions = tuple.getT2(); List<NewAction> actions = tuple.getT2();
Layout layout = page.getLayouts().get(0); Layout layout = page.getLayouts().get(0);
assertThat(page.getName()).isEqualTo("SampleTable"); assertThat(page.getName()).isEqualTo("SampleTable");
@ -561,6 +578,9 @@ public class CreateDBTablePageSolutionTests {
assertThat(actionConfiguration.getPluginSpecifiedTemplates().get(1).getValue().toString()) assertThat(actionConfiguration.getPluginSpecifiedTemplates().get(1).getValue().toString())
.isEqualTo(resource.getTableName()); .isEqualTo(resource.getTableName());
} }
assertThat(crudPage.getSuccessMessage()).containsIgnoringCase(pluginName);
assertThat(crudPage.getSuccessMessage()).containsIgnoringCase("LIST");
}) })
.verifyComplete(); .verifyComplete();
} }
@ -596,7 +616,8 @@ public class CreateDBTablePageSolutionTests {
resource.setDatasourceId(datasource1.getId()); resource.setDatasourceId(datasource1.getId());
return applicationPageService.createPage(newPage); return applicationPageService.createPage(newPage);
}) })
.flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource)); .flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource))
.map(crudPageResponseDTO -> crudPageResponseDTO.getPage());
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId())))
@ -656,7 +677,8 @@ public class CreateDBTablePageSolutionTests {
resource.setDatasourceId(datasource1.getId()); resource.setDatasourceId(datasource1.getId());
return applicationPageService.createPage(newPage); return applicationPageService.createPage(newPage);
}) })
.flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource)); .flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource))
.map(crudPageResponseDTO -> crudPageResponseDTO.getPage());
StepVerifier StepVerifier
.create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId()))) .create(resultMono.zipWhen(pageDTO -> getActions(pageDTO.getId())))