Merge branch 'release' into feature/telemetry

This commit is contained in:
Arpit Mohan 2020-11-04 18:02:25 +05:30
commit 946b3ba25f
23 changed files with 451 additions and 244 deletions

View File

@ -1,41 +1,41 @@
const queryLocators = require("../../../locators/QueryEditor.json");
const queryEditor = require("../../../locators/QueryEditor.json");
// const queryLocators = require("../../../locators/QueryEditor.json");
// const queryEditor = require("../../../locators/QueryEditor.json");
let datasourceName;
// let datasourceName;
describe("Add widget", function() {
beforeEach(() => {
cy.createPostgresDatasource();
cy.get("@createDatasource").then(httpResponse => {
datasourceName = httpResponse.response.body.data.name;
});
});
// describe("Add widget", function() {
// beforeEach(() => {
// cy.createPostgresDatasource();
// cy.get("@createDatasource").then(httpResponse => {
// datasourceName = httpResponse.response.body.data.name;
// });
// });
it("Add widget", () => {
cy.NavigateToQueryEditor();
cy.contains(".t--datasource-name", datasourceName)
.find(queryLocators.createQuery)
.click();
// it("Add widget", () => {
// cy.NavigateToQueryEditor();
// cy.contains(".t--datasource-name", datasourceName)
// .find(queryLocators.createQuery)
// .click();
cy.get(queryLocators.templateMenu).click();
cy.get(".CodeMirror textarea")
.first()
.focus()
.type("select * from configs");
cy.wait(500);
cy.get(queryEditor.runQuery).click();
cy.wait("@postExecute").should(
"have.nested.property",
"response.body.responseMeta.status",
200,
);
cy.get(".t--add-widget").click();
cy.SearchEntityandOpen("Table1");
cy.isSelectRow(1);
cy.readTabledataPublish("1", "0").then(tabData => {
const tabValue = tabData;
expect(tabValue).to.be.equal("5");
cy.log("the value is " + tabValue);
});
});
});
// cy.get(queryLocators.templateMenu).click();
// cy.get(".CodeMirror textarea")
// .first()
// .focus()
// .type("select * from configs");
// cy.wait(500);
// cy.get(queryEditor.runQuery).click();
// cy.wait("@postExecute").should(
// "have.nested.property",
// "response.body.responseMeta.status",
// 200,
// );
// cy.get(".t--add-widget").click();
// cy.SearchEntityandOpen("Table1");
// cy.isSelectRow(1);
// cy.readTabledataPublish("1", "0").then(tabData => {
// const tabValue = tabData;
// expect(tabValue).to.be.equal("5");
// cy.log("the value is " + tabValue);
// });
// });
// });

View File

@ -1,5 +1,4 @@
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { EditorModes } from "../components/editorComponents/CodeEditor/EditorConfig";
import { APP_MODE } from "../reducers/entityReducers/appReducer";
import { UpdateApplicationPayload } from "api/ApplicationApi";

View File

@ -2,6 +2,7 @@ import {
ReduxActionTypes,
ReduxAction,
InitializeEditorPayload,
ReduxActionErrorTypes,
} from "constants/ReduxActionConstants";
export const initEditor = (
@ -14,3 +15,17 @@ export const initEditor = (
pageId,
},
});
export const initEditorError = (): ReduxAction<{ show: false }> => ({
type: ReduxActionErrorTypes.INITIALIZE_EDITOR_ERROR,
payload: {
show: false,
},
});
export const initViewerError = (): ReduxAction<{ show: false }> => ({
type: ReduxActionErrorTypes.INITIALIZE_PAGE_VIEWER_ERROR,
payload: {
show: false,
},
});

View File

@ -4,13 +4,10 @@ import {
API_REQUEST_HEADERS,
} from "constants/ApiConstants";
import { ActionApiResponse } from "./ActionAPI";
import {
AUTH_LOGIN_URL,
PAGE_NOT_FOUND_URL,
SERVER_ERROR_URL,
} from "constants/routes";
import { AUTH_LOGIN_URL, PAGE_NOT_FOUND_URL } from "constants/routes";
import history from "utils/history";
import { convertObjectToQueryParams } from "utils/AppsmithUtils";
import { SERVER_API_TIMEOUT_ERROR } from "../constants/messages";
//TODO(abhinav): Refactor this to make more composable.
export const apiRequestConfig = {
@ -22,8 +19,10 @@ export const apiRequestConfig = {
const axiosInstance: AxiosInstance = axios.create();
export const axiosConnectionAbortedCode = "ECONNABORTED";
const executeActionRegex = /actions\/execute/;
const currentUserRegex = /\/me$/;
const timeoutErrorRegex = /timeout of (\d+)ms exceeded/;
axiosInstance.interceptors.request.use((config: any) => {
return { ...config, timer: performance.now() };
});
@ -50,20 +49,25 @@ axiosInstance.interceptors.response.use(
return response.data;
},
function(error: any) {
// Return if the call was cancelled via cancel token
if (axios.isCancel(error)) {
return;
}
if (error.code === "ECONNABORTED") {
if (error.config && error.config.url.match(currentUserRegex)) {
history.replace({ pathname: SERVER_ERROR_URL });
}
return Promise.reject({
message: "Please check your internet connection",
});
}
// Return modified response if action execution failed
if (error.config && error.config.url.match(executeActionRegex)) {
return makeExecuteActionResponse(error.response);
}
// Return error if any timeout happened in other api calls
if (
error.code === axiosConnectionAbortedCode &&
error.message &&
error.message.match(timeoutErrorRegex)
) {
return Promise.reject({
...error,
message: SERVER_API_TIMEOUT_ERROR,
});
}
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -292,8 +292,10 @@ class CodeEditor extends Component<Props, State> {
("dataTreePath" in this.props && !!this.props.dataTreePath));
const showBindingPrompt =
(!this.props.input.value?.includes("{{") || !this.props.input.value) &&
showEvaluatedValue;
showEvaluatedValue &&
(!_.isString(this.props.input.value) ||
!this.props.input.value?.includes("{{") ||
!this.props.input.value);
return (
<DynamicAutocompleteInputWrapper

View File

@ -8,3 +8,20 @@ const APP_STORE_NAMESPACE = "APPSMITH_LOCAL_STORE";
export const getAppStoreName = (appId: string) =>
`${APP_STORE_NAMESPACE}-${appId}`;
export const getAppStore = (appId: string) => {
const appStoreName = getAppStoreName(appId);
let storeString = "{}";
// Check if localStorage exists
if (localStorage) {
const appStore = localStorage.getItem(appStoreName);
if (appStore) storeString = appStore;
}
let store;
try {
store = JSON.parse(storeString);
} catch (e) {
store = {};
}
return store;
};

View File

@ -296,6 +296,7 @@ export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTy
export const ReduxActionErrorTypes: { [key: string]: string } = {
INITIALIZE_EDITOR_ERROR: "INITIALIZE_EDITOR_ERROR",
INITIALIZE_PAGE_VIEWER_ERROR: "INITIALIZE_PAGE_VIEWER_ERROR",
API_ERROR: "API_ERROR",
WIDGET_DELETE_ERROR: "WIDGET_DELETE_ERROR",
UPDATE_APPLICATION_ERROR: "UPDATE_APPLICATION_ERROR",

View File

@ -168,3 +168,6 @@ export const GOOGLE_RECAPTCHA_KEY_ERROR =
"Google Re-Captcha Token Generation failed! Please check the Re-captcha Site Key.";
export const GOOGLE_RECAPTCHA_DOMAIN_ERROR =
"Google Re-Captcha Token Generation failed! Please check the allowed domains.";
export const SERVER_API_TIMEOUT_ERROR =
"Appsmith server is taking too long to respond. Please try again after some time";

View File

@ -10,7 +10,10 @@ import {
getApplicationViewerPageURL,
} from "constants/routes";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { getIsInitialized } from "selectors/appViewSelectors";
import {
getIsInitialized,
getIsInitializeError,
} from "selectors/appViewSelectors";
import { executeAction } from "actions/widgetActions";
import { ExecuteActionPayload } from "constants/ActionConstants";
import { updateWidgetPropertyRequest } from "actions/controlActions";
@ -23,6 +26,8 @@ import {
} from "actions/metaActions";
import { editorInitializer } from "utils/EditorUtils";
import * as Sentry from "@sentry/react";
import log from "loglevel";
import ServerTimeout from "../common/ServerTimeout";
const SentryRoute = Sentry.withSentryRouting(Route);
const AppViewerBody = styled.section`
@ -36,6 +41,7 @@ const AppViewerBody = styled.section`
export type AppViewerProps = {
initializeAppViewer: (applicationId: string, pageId?: string) => void;
isInitialized: boolean;
isInitializeError: boolean;
executeAction: (actionPayload: ExecuteActionPayload) => void;
updateWidgetProperty: (
widgetId: string,
@ -62,7 +68,7 @@ class AppViewer extends Component<
this.setState({ registered: true });
});
const { applicationId, pageId } = this.props.match.params;
console.log({ applicationId, pageId });
log.debug({ applicationId, pageId });
if (applicationId) {
this.props.initializeAppViewer(applicationId, pageId);
}
@ -73,7 +79,10 @@ class AppViewer extends Component<
};
public render() {
const { isInitialized } = this.props;
const { isInitialized, isInitializeError } = this.props;
if (isInitializeError) {
return <ServerTimeout />;
}
return (
<EditorContext.Provider
value={{
@ -100,6 +109,7 @@ class AppViewer extends Component<
const mapStateToProps = (state: AppState) => ({
isInitialized: getIsInitialized(state),
isInitializeError: getIsInitializeError(state),
});
const mapDispatchToProps = (dispatch: any) => ({

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import { Helmet } from "react-helmet";
import { connect } from "react-redux";
import { withRouter, RouteComponentProps } from "react-router-dom";
import { RouteComponentProps, withRouter } from "react-router-dom";
import {
BuilderRouteParams,
getApplicationViewerPageURL,
@ -13,15 +13,16 @@ import TouchBackend from "react-dnd-touch-backend";
import {
getCurrentApplicationId,
getCurrentPageId,
getPublishingError,
getIsEditorLoading,
getIsEditorInitialized,
getIsEditorInitializeError,
getIsEditorLoading,
getIsPublishingApplication,
getPublishingError,
} from "selectors/editorSelectors";
import {
Dialog,
Classes,
AnchorButton,
Classes,
Dialog,
Hotkey,
Hotkeys,
Spinner,
@ -40,11 +41,12 @@ import ConfirmRunModal from "pages/Editor/ConfirmRunModal";
import * as Sentry from "@sentry/react";
import {
copyWidget,
pasteWidget,
deleteSelectedWidget,
cutWidget,
deleteSelectedWidget,
pasteWidget,
} from "actions/widgetActions";
import { isMac } from "utils/helpers";
import ServerTimeout from "../common/ServerTimeout";
type EditorProps = {
currentApplicationId?: string;
@ -53,6 +55,7 @@ type EditorProps = {
isPublishing: boolean;
isEditorLoading: boolean;
isEditorInitialized: boolean;
isEditorInitializeError: boolean;
errorPublishing: boolean;
copySelectedWidget: () => void;
pasteCopiedWidget: () => void;
@ -189,6 +192,8 @@ class Editor extends Component<Props> {
nextProps.isPublishing !== this.props.isPublishing ||
nextProps.isEditorLoading !== this.props.isEditorLoading ||
nextProps.errorPublishing !== this.props.errorPublishing ||
nextProps.isEditorInitializeError !==
this.props.isEditorInitializeError ||
nextState.isDialogOpen !== this.state.isDialogOpen ||
nextState.registered !== this.state.registered
);
@ -200,6 +205,9 @@ class Editor extends Component<Props> {
});
};
public render() {
if (this.props.isEditorInitializeError) {
return <ServerTimeout />;
}
if (!this.props.isEditorInitialized || !this.state.registered) {
return (
<CenteredWrapper style={{ height: "calc(100vh - 48px)" }}>
@ -260,6 +268,7 @@ const mapStateToProps = (state: AppState) => ({
isPublishing: getIsPublishingApplication(state),
isEditorLoading: getIsEditorLoading(state),
isEditorInitialized: getIsEditorInitialized(state),
isEditorInitializeError: getIsEditorInitializeError(state),
user: getCurrentUser(state),
});

View File

@ -1,5 +1,5 @@
import React from "react";
import { RouterProps } from "react-router";
import { RouteComponentProps, withRouter } from "react-router";
import styled from "styled-components";
import Button from "components/editorComponents/Button";
import PageUnavailableImage from "assets/images/404-image.png";
@ -20,7 +20,7 @@ const Wrapper = styled.div`
}
`;
class PageNotFound extends React.PureComponent<RouterProps> {
class PageNotFound extends React.PureComponent<RouteComponentProps> {
public render() {
return (
<>
@ -54,4 +54,4 @@ class PageNotFound extends React.PureComponent<RouterProps> {
}
}
export default PageNotFound;
export default withRouter(PageNotFound);

View File

@ -0,0 +1,56 @@
import React from "react";
import styled from "styled-components";
import AppTimeoutImage from "assets/images/timeout-image.png";
const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
height: calc(100vh - ${props => props.theme.headerHeight});
.bold-text {
font-weight: ${props => props.theme.fontWeights[3]};
font-size: 24px;
}
.page-unavailable-img {
width: 35%;
}
.button-position {
margin: auto;
}
`;
const RetryButton = styled.button`
background-color: #f3672a;
color: white;
height: 40px;
width: 300px;
border: none;
cursor: pointer;
font-weight: 600;
font-size: 17px;
`;
const ServerTimeout = () => {
return (
<Wrapper>
<img
src={AppTimeoutImage}
alt="Page Unavailable"
className="page-unavailable-img"
/>
<div>
<p className="bold-text">
Appsmith server is taking too long to respond
</p>
<p>Please retry after some time</p>
<RetryButton onClick={() => window.location.reload()}>
{"Retry"}
</RetryButton>
</div>
</Wrapper>
);
};
export default ServerTimeout;

View File

@ -5,12 +5,14 @@ import {
ReduxAction,
ReduxActionTypes,
PageListPayload,
ReduxActionErrorTypes,
} from "constants/ReduxActionConstants";
import { FetchPublishedPageSuccessPayload } from "actions/pageActions";
const initialState: AppViewReduxState = {
isFetchingPage: false,
initialized: false,
initializeError: false,
pages: [],
pageWidgetId: "0",
};
@ -24,6 +26,11 @@ const appViewReducer = createReducer(initialState, {
) => {
return { ...state, initialized: true };
},
[ReduxActionErrorTypes.INITIALIZE_PAGE_VIEWER_ERROR]: (
state: AppViewReduxState,
) => {
return { ...state, initializeError: true };
},
[ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT]: (state: AppViewReduxState) => {
return { ...state, dsl: undefined, isFetchingPage: true };
},
@ -45,6 +52,7 @@ const appViewReducer = createReducer(initialState, {
export interface AppViewReduxState {
initialized: boolean;
initializeError: boolean;
dsl?: ContainerWidgetProps<WidgetProps>;
isFetchingPage: boolean;
currentLayoutId?: string;

View File

@ -5,12 +5,11 @@ import {
ReduxActionTypes,
ReduxActionErrorTypes,
} from "constants/ReduxActionConstants";
import { WidgetProps } from "widgets/BaseWidget";
import { ContainerWidgetProps } from "widgets/ContainerWidget";
import moment from "moment";
const initialState: EditorReduxState = {
initialized: false,
initializationError: false,
loadingStates: {
publishing: false,
publishingError: false,
@ -68,11 +67,8 @@ const editorReducer = createReducer(initialState, {
) => {
return {
...state,
loadingStates: {
...state.loadingStates,
loading: false,
loadingError: true,
},
initializationError: true,
initialized: false,
};
},
[ReduxActionTypes.PUBLISH_APPLICATION_INIT]: (state: EditorReduxState) => {
@ -178,7 +174,7 @@ const editorReducer = createReducer(initialState, {
export interface EditorReduxState {
initialized: boolean;
dsl?: ContainerWidgetProps<WidgetProps>;
initializationError: boolean;
pageWidgetId?: string;
currentLayoutId?: string;
currentPageName?: string;

View File

@ -11,6 +11,7 @@ import { put, takeLatest, call } from "redux-saga/effects";
import { ERROR_401, ERROR_500, ERROR_0 } from "constants/messages";
import { ToastType } from "react-toastify";
import log from "loglevel";
import { axiosConnectionAbortedCode } from "../api/Api";
export function* callAPI(apiCall: any, requestPayload: any) {
try {
@ -60,7 +61,7 @@ export function getResponseErrorMessage(response: ApiResponse) {
: undefined;
}
type ErrorPayloadType = { code?: number; message?: string };
type ErrorPayloadType = { code?: number | string; message?: string };
let ActionErrorDisplayMap: {
[key: string]: (error: ErrorPayloadType) => string;
} = {};
@ -92,6 +93,7 @@ export function* errorSaga(
type,
payload: { error, show = true },
} = errorAction;
const message =
error && error.message ? error.message : ActionErrorDisplayMap[type](error);

View File

@ -1,7 +1,14 @@
import { all, call, put, select, take, takeLatest } from "redux-saga/effects";
import {
all,
call,
put,
select,
take,
takeLatest,
race,
} from "redux-saga/effects";
import {
InitializeEditorPayload,
Page,
ReduxAction,
ReduxActionErrorTypes,
ReduxActionTypes,
@ -21,126 +28,86 @@ import { fetchActions, fetchActionsForView } from "actions/actionActions";
import { fetchApplication } from "actions/applicationActions";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { getCurrentApplication } from "selectors/applicationSelectors";
import { AppState } from "reducers";
import PageApi, { FetchPageResponse } from "api/PageApi";
import { validateResponse } from "./ErrorSagas";
import { extractCurrentDSL } from "utils/WidgetPropsUtils";
import { APP_MODE } from "reducers/entityReducers/appReducer";
import { getAppStoreName } from "constants/AppConstants";
import { getAppStore } from "constants/AppConstants";
import { getDefaultPageId } from "./selectors";
const getAppStore = (appId: string) => {
const appStoreName = getAppStoreName(appId);
const storeString = localStorage.getItem(appStoreName) || "{}";
let store;
try {
store = JSON.parse(storeString);
} catch (e) {
store = {};
}
return store;
};
import { populatePageDSLsSaga } from "./PageSagas";
import { initEditorError, initViewerError } from "../actions/initActions";
function* initializeEditorSaga(
initializeEditorAction: ReduxAction<InitializeEditorPayload>,
) {
const { applicationId, pageId } = initializeEditorAction.payload;
// Step 1: Set App Mode. Start getting all the data needed
yield put(setAppMode(APP_MODE.EDIT));
yield put({ type: ReduxActionTypes.START_EVALUATION });
yield all([
put(fetchPageList(applicationId, APP_MODE.EDIT)),
put(fetchEditorConfigs()),
put(fetchActions(applicationId)),
put(fetchPage(pageId)),
put(fetchApplication(applicationId, APP_MODE.EDIT)),
]);
// Step 2: Wait for all data to be in the state
yield all([
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
take(ReduxActionTypes.FETCH_PAGE_SUCCESS),
take(ReduxActionTypes.SWITCH_CURRENT_PAGE_ID),
take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS),
]);
// Step 3: Call all the APIs which needs Organization Id from PageList API response.
yield all([put(fetchPlugins()), put(fetchDatasources())]);
// Step 4: Wait for all data to be in the state
yield all([
take(ReduxActionTypes.FETCH_PLUGINS_SUCCESS),
take(ReduxActionTypes.FETCH_DATASOURCES_SUCCESS),
]);
// Step 5: Set app store
yield put(updateAppStore(getAppStore(applicationId)));
const currentApplication = yield select(getCurrentApplication);
const appName = currentApplication ? currentApplication.name : "";
const appId = currentApplication ? currentApplication.id : "";
AnalyticsUtil.logEvent("EDITOR_OPEN", {
appId: appId,
appName: appName,
});
// Step 6: Notify UI that the editor is ready to go
yield put({
type: ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS,
});
yield call(populatePageDSLsSaga);
}
function* fetchPageDSLSaga(pageId: string) {
try {
const fetchPageResponse: FetchPageResponse = yield call(PageApi.fetchPage, {
id: pageId,
yield put(setAppMode(APP_MODE.EDIT));
yield put({ type: ReduxActionTypes.START_EVALUATION });
yield all([
put(fetchPageList(applicationId, APP_MODE.EDIT)),
put(fetchEditorConfigs()),
put(fetchActions(applicationId)),
put(fetchPage(pageId)),
put(fetchApplication(applicationId, APP_MODE.EDIT)),
]);
const resultOfPrimaryCalls = yield race({
success: all([
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
take(ReduxActionTypes.FETCH_PAGE_SUCCESS),
take(ReduxActionTypes.FETCH_APPLICATION_SUCCESS),
take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS),
]),
failure: take([
ReduxActionErrorTypes.FETCH_PAGE_LIST_ERROR,
ReduxActionErrorTypes.FETCH_PAGE_ERROR,
ReduxActionErrorTypes.FETCH_APPLICATION_ERROR,
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
]),
});
const isValidResponse = yield validateResponse(fetchPageResponse);
if (isValidResponse) {
return {
pageId: pageId,
dsl: extractCurrentDSL(fetchPageResponse),
};
if (resultOfPrimaryCalls.failure) {
yield put(initEditorError());
return;
}
} catch (error) {
yield put({
type: ReduxActionTypes.FETCH_PAGE_DSL_ERROR,
payload: {
pageId: pageId,
error,
show: false,
},
});
}
}
export function* populatePageDSLsSaga() {
try {
yield put({
type: ReduxActionTypes.POPULATE_PAGEDSLS_INIT,
yield all([put(fetchPlugins()), put(fetchDatasources())]);
const resultOfSecondaryCalls = yield race({
success: all([
take(ReduxActionTypes.FETCH_PLUGINS_SUCCESS),
take(ReduxActionTypes.FETCH_DATASOURCES_SUCCESS),
]),
failure: take([
ReduxActionErrorTypes.FETCH_PLUGINS_ERROR,
ReduxActionErrorTypes.FETCH_DATASOURCES_ERROR,
]),
});
const pageIds: string[] = yield select((state: AppState) =>
state.entities.pageList.pages.map((page: Page) => page.pageId),
);
const pageDSLs = yield all(
pageIds.map((pageId: string) => {
return call(fetchPageDSLSaga, pageId);
}),
);
yield put({
type: ReduxActionTypes.FETCH_PAGE_DSLS_SUCCESS,
payload: pageDSLs,
if (resultOfSecondaryCalls.failure) {
yield put(initEditorError());
return;
}
yield put(updateAppStore(getAppStore(applicationId)));
const currentApplication = yield select(getCurrentApplication);
const appName = currentApplication ? currentApplication.name : "";
const appId = currentApplication ? currentApplication.id : "";
AnalyticsUtil.logEvent("EDITOR_OPEN", {
appId: appId,
appName: appName,
});
} catch (error) {
yield put({
type: ReduxActionErrorTypes.POPULATE_PAGEDSLS_ERROR,
payload: {
error,
},
type: ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS,
});
} catch (e) {
yield put(initEditorError());
return;
}
yield call(populatePageDSLsSaga);
}
export function* initializeAppViewerSaga(
@ -156,11 +123,23 @@ export function* initializeAppViewerSaga(
put(fetchApplication(applicationId, APP_MODE.PUBLISHED)),
]);
yield all([
take(ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS),
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
take(ReduxActionTypes.FETCH_APPLICATION_SUCCESS),
]);
const resultOfPrimaryCalls = yield race({
success: all([
take(ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS),
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
take(ReduxActionTypes.FETCH_APPLICATION_SUCCESS),
]),
failure: take([
ReduxActionErrorTypes.FETCH_ACTIONS_VIEW_MODE_ERROR,
ReduxActionErrorTypes.FETCH_PAGE_LIST_ERROR,
ReduxActionErrorTypes.FETCH_APPLICATION_ERROR,
]),
});
if (resultOfPrimaryCalls.failure) {
yield put(initViewerError());
return;
}
yield put(updateAppStore(getAppStore(applicationId)));
const defaultPageId = yield select(getDefaultPageId);
@ -168,7 +147,15 @@ export function* initializeAppViewerSaga(
if (toLoadPageId) {
yield put(fetchPublishedPage(toLoadPageId, true));
yield take(ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS);
const resultOfFetchPage = yield race({
success: take(ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS),
failure: take(ReduxActionErrorTypes.FETCH_PUBLISHED_PAGE_ERROR),
});
if (resultOfFetchPage.failure) {
yield put(initViewerError());
return;
}
yield put(setAppMode(APP_MODE.PUBLISHED));
yield put(updateAppStore(getAppStore(applicationId)));

View File

@ -1,6 +1,7 @@
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
import { AppState } from "reducers";
import {
Page,
PageListPayload,
ReduxAction,
ReduxActionErrorTypes,
@ -43,7 +44,6 @@ import {
select,
takeLatest,
takeLeading,
take,
} from "redux-saga/effects";
import history from "utils/history";
import { BUILDER_PAGE_URL } from "constants/routes";
@ -117,6 +117,16 @@ export function* fetchPageListSaga(
PerformanceTracker.stopAsyncTracking(
PerformanceTransactionName.FETCH_PAGE_LIST_API,
);
} else {
PerformanceTracker.stopAsyncTracking(
PerformanceTransactionName.FETCH_PAGE_LIST_API,
);
yield put({
type: ReduxActionErrorTypes.FETCH_PAGE_LIST_ERROR,
payload: {
error: response.responseMeta.error,
},
});
}
} catch (error) {
PerformanceTracker.stopAsyncTracking(
@ -608,6 +618,57 @@ export function* setDataUrl() {
yield put(setUrlData(urlData));
}
function* fetchPageDSLSaga(pageId: string) {
try {
const fetchPageResponse: FetchPageResponse = yield call(PageApi.fetchPage, {
id: pageId,
});
const isValidResponse = yield validateResponse(fetchPageResponse);
if (isValidResponse) {
return {
pageId: pageId,
dsl: extractCurrentDSL(fetchPageResponse),
};
}
} catch (error) {
yield put({
type: ReduxActionTypes.FETCH_PAGE_DSL_ERROR,
payload: {
pageId: pageId,
error,
show: false,
},
});
}
}
export function* populatePageDSLsSaga() {
try {
yield put({
type: ReduxActionTypes.POPULATE_PAGEDSLS_INIT,
});
const pageIds: string[] = yield select((state: AppState) =>
state.entities.pageList.pages.map((page: Page) => page.pageId),
);
const pageDSLs = yield all(
pageIds.map((pageId: string) => {
return call(fetchPageDSLSaga, pageId);
}),
);
yield put({
type: ReduxActionTypes.FETCH_PAGE_DSLS_SUCCESS,
payload: pageDSLs,
});
} catch (error) {
yield put({
type: ReduxActionErrorTypes.POPULATE_PAGEDSLS_ERROR,
payload: {
error,
},
});
}
}
export default function* pageSagas() {
yield all([
takeLatest(ReduxActionTypes.FETCH_PAGE_INIT, fetchPageSaga),

View File

@ -32,6 +32,11 @@ export const getIsInitialized = createSelector(
(view: AppViewReduxState) => view.initialized,
);
export const getIsInitializeError = createSelector(
getAppViewState,
(view: AppViewReduxState) => view.initializeError,
);
export const getCurrentDSLPageId = createSelector(
getPageListState,
(pageList: PageListReduxState) => pageList.currentPageId,

View File

@ -20,14 +20,12 @@ import { getDataTree } from "selectors/dataTreeSelectors";
import _ from "lodash";
import { ContainerWidgetProps } from "widgets/ContainerWidget";
import { DataTreeWidget, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { getActions, getWidgetsMeta } from "sagas/selectors";
import { getActions } from "sagas/selectors";
import * as log from "loglevel";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
import { getCanvasWidgets } from "./entitiesSelector";
import { MetaState } from "../reducers/entityReducers/metaReducer";
import { WidgetTypes } from "../constants/WidgetConstants";
const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig;
@ -45,6 +43,9 @@ const getWidgets = (state: AppState): CanvasWidgetsReduxState =>
export const getIsEditorInitialized = (state: AppState) =>
state.ui.editor.initialized;
export const getIsEditorInitializeError = (state: AppState): boolean =>
state.ui.editor.initializationError;
export const getIsEditorLoading = (state: AppState) =>
state.ui.editor.loadingStates.loading;

View File

@ -4,74 +4,93 @@ import lombok.Getter;
import java.text.MessageFormat;
enum AppsmithErrorAction {
DEFAULT,
LOG_EXTERNALLY
}
@Getter
public enum AppsmithError {
INVALID_PARAMETER(400, 4000, "Please enter a valid parameter {0}."),
PLUGIN_NOT_INSTALLED(400, 4001, "Plugin {0} not installed"),
PLUGIN_ID_NOT_GIVEN(400, 4002, "Missing plugin id. Please enter one."),
DATASOURCE_NOT_GIVEN(400, 4003, "Missing datasource. Add/enter/connect a datasource to create a valid action."),
PAGE_ID_NOT_GIVEN(400, 4004, "Missing page id. Please enter one."),
PAGE_DOESNT_BELONG_TO_USER_ORGANIZATION(400, 4006, "Page {0} does not belong to the current user {1} organization"),
UNSUPPORTED_OPERATION(400, 4007, "Unsupported operation"),
USER_DOESNT_BELONG_ANY_ORGANIZATION(400, 4009, "User {0} does not belong to any organization"),
USER_DOESNT_BELONG_TO_ORGANIZATION(400, 4010, "User {0} does not belong to an organization with id {1}"),
NO_CONFIGURATION_FOUND_IN_DATASOURCE(400, 4011, "No datasource configuration found. Please configure it and try again."),
INVALID_ACTION(400, 4012, "Action {0} with id {1} is invalid: {2}"),
INVALID_DATASOURCE(400, 4013, "Datasource is invalid. Please edit to make it valid. Details: {0}"),
INVALID_ACTION_NAME(400, 4014, "Action name is invalid. Please input syntactically correct name"),
INVALID_DATASOURCE_CONFIGURATION(400, 4015, "Datasource configuration is invalid"),
NO_CONFIGURATION_FOUND_IN_ACTION(400, 4016, "No action configuration found. Please configure it and try again."),
NAME_CLASH_NOT_ALLOWED_IN_REFACTOR(400, 4017, "The new name {1} already exists in the current page. Choose another name."),
PAGE_DOESNT_BELONG_TO_APPLICATION(400, 4018, "Page {0} does not belong to the application {1}"),
NO_DSL_FOUND_IN_PAGE(400, 4020, "The page {0} doesn't have a DSL. This is an unexpected state"),
USER_ALREADY_EXISTS_IN_ORGANIZATION(400, 4021, "The user {0} has already been added to the organization with role {1}"),
UNAUTHORIZED_DOMAIN(401, 4019, "Invalid email domain provided. Please sign in with a valid work email ID"),
USER_NOT_SIGNED_IN(401, 4020, "User is not logged in. Please sign in with the registered email ID or sign up" ),
INVALID_PASSWORD_RESET(400, 4020, "Unable to reset the password. Please initiate a request via 'forgot password' button to reset your password"),
LOGIN_INTERNAL_ERROR(401, 4021, "Internal error while trying to login"),
JSON_PROCESSING_ERROR(400, 4022, "Json processing error with error {0}"),
INVALID_CREDENTIALS(200, 4023, "Invalid credentials provided. Did you input the credentials correctly?"),
DUPLICATE_KEY(409, 4024, "Duplicate key error"),
USER_ALREADY_EXISTS_SIGNUP(409, 4025, "There is already an account registered with this email {0}. Please sign in."),
UNAUTHORIZED_ACCESS(403, 4025, "Unauthorized access"),
ACTION_IS_NOT_AUTHORIZED(403, 4026, "Sorry. You do not have permissions to perform this action"),
INVALID_DATASOURCE_NAME(400, 4026, "Invalid datasource name. Check again."),
NO_RESOURCE_FOUND(404, 4027, "Unable to find {0} with id {1}"),
USER_NOT_FOUND(404, 4027, "Unable to find user with email {0}"),
ACL_NO_RESOURCE_FOUND(404, 4028, "Unable to find {0} with id {1}. Either the asset doesn't exist or you don't have required permissions"),
GENERIC_BAD_REQUEST(400, 4028, "Bad Request: {0}"),
VALIDATION_FAILURE(400, 4028, "Validation Failure(s): {0}"),
INVALID_CURL_COMMAND(400, 4029, "Invalid cURL command, couldn't import."),
INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request"),
REPOSITORY_SAVE_FAILED(500, 5001, "Failed to save the repository. Try again."),
PLUGIN_INSTALLATION_FAILED_DOWNLOAD_ERROR(500, 5002, "Plugin installation failed due to an error while downloading it. Check the jar location & try again."),
PLUGIN_RUN_FAILED(500, 5003, "Plugin execution failed with error {0}"),
PLUGIN_EXECUTION_TIMEOUT(504, 5040, "Plugin Execution exceeded the maximum allowed time. Please increase the timeout in your action settings or check your backend action endpoint"),
PLUGIN_LOAD_FORM_JSON_FAIL(500, 5004, "Unable to load datasource form configuration. Details: {0}."),
PLUGIN_LOAD_TEMPLATES_FAIL(500, 5005, "Unable to load datasource templates. Details: {0}."),
MARKETPLACE_TIMEOUT(504, 5041, "Marketplace is responding too slowly. Please try again later"),
DATASOURCE_HAS_ACTIONS(409, 4030, "Cannot delete datasource since it has {0} action(s) using it."),
ORGANIZATION_ID_NOT_GIVEN(400, 4031, "Missing organization id. Please enter one."),
INVALID_CURL_METHOD(400, 4032, "Invalid method in cURL command: {0}."),
OAUTH_NOT_AVAILABLE(500, 5006, "Login with {0} is not supported."),
MARKETPLACE_NOT_CONFIGURED(500, 5007, "Marketplace is not configured."),
INVALID_PARAMETER(400, 4000, "Please enter a valid parameter {0}.", AppsmithErrorAction.DEFAULT),
PLUGIN_NOT_INSTALLED(400, 4001, "Plugin {0} not installed", AppsmithErrorAction.DEFAULT),
PLUGIN_ID_NOT_GIVEN(400, 4002, "Missing plugin id. Please enter one.", AppsmithErrorAction.DEFAULT),
DATASOURCE_NOT_GIVEN(400, 4003, "Missing datasource. Add/enter/connect a datasource to create a valid action.", AppsmithErrorAction.DEFAULT),
PAGE_ID_NOT_GIVEN(400, 4004, "Missing page id. Please enter one.", AppsmithErrorAction.DEFAULT),
PAGE_DOESNT_BELONG_TO_USER_ORGANIZATION(400, 4006, "Page {0} does not belong to the current user {1} " +
"organization", AppsmithErrorAction.LOG_EXTERNALLY),
UNSUPPORTED_OPERATION(400, 4007, "Unsupported operation", AppsmithErrorAction.DEFAULT),
USER_DOESNT_BELONG_ANY_ORGANIZATION(400, 4009, "User {0} does not belong to any organization",
AppsmithErrorAction.LOG_EXTERNALLY),
USER_DOESNT_BELONG_TO_ORGANIZATION(400, 4010, "User {0} does not belong to an organization with id {1}",
AppsmithErrorAction.LOG_EXTERNALLY),
NO_CONFIGURATION_FOUND_IN_DATASOURCE(400, 4011, "No datasource configuration found. Please configure it and try again.", AppsmithErrorAction.DEFAULT),
INVALID_ACTION(400, 4012, "Action {0} with id {1} is invalid: {2}", AppsmithErrorAction.DEFAULT),
INVALID_DATASOURCE(400, 4013, "Datasource is invalid. Please edit to make it valid. Details: {0}", AppsmithErrorAction.DEFAULT),
INVALID_ACTION_NAME(400, 4014, "Action name is invalid. Please input syntactically correct name", AppsmithErrorAction.DEFAULT),
INVALID_DATASOURCE_CONFIGURATION(400, 4015, "Datasource configuration is invalid", AppsmithErrorAction.DEFAULT),
NO_CONFIGURATION_FOUND_IN_ACTION(400, 4016, "No action configuration found. Please configure it and try again.", AppsmithErrorAction.DEFAULT),
NAME_CLASH_NOT_ALLOWED_IN_REFACTOR(400, 4017, "The new name {1} already exists in the current page. Choose another name.", AppsmithErrorAction.DEFAULT),
PAGE_DOESNT_BELONG_TO_APPLICATION(400, 4018, "Page {0} does not belong to the application {1}",
AppsmithErrorAction.LOG_EXTERNALLY),
NO_DSL_FOUND_IN_PAGE(400, 4020, "The page {0} doesn't have a DSL. This is an unexpected state", AppsmithErrorAction.DEFAULT),
USER_ALREADY_EXISTS_IN_ORGANIZATION(400, 4021, "The user {0} has already been added to the organization with role {1}", AppsmithErrorAction.DEFAULT),
UNAUTHORIZED_DOMAIN(401, 4019, "Invalid email domain provided. Please sign in with a valid work email ID", AppsmithErrorAction.DEFAULT),
USER_NOT_SIGNED_IN(401, 4020, "User is not logged in. Please sign in with the registered email ID or sign up",
AppsmithErrorAction.DEFAULT),
INVALID_PASSWORD_RESET(400, 4020, "Unable to reset the password. Please initiate a request via 'forgot password' " +
"button to reset your password", AppsmithErrorAction.DEFAULT),
LOGIN_INTERNAL_ERROR(401, 4021, "Internal error while trying to login", AppsmithErrorAction.LOG_EXTERNALLY),
JSON_PROCESSING_ERROR(400, 4022, "Json processing error with error {0}", AppsmithErrorAction.LOG_EXTERNALLY),
INVALID_CREDENTIALS(200, 4023, "Invalid credentials provided. Did you input the credentials correctly?", AppsmithErrorAction.DEFAULT),
DUPLICATE_KEY(409, 4024, "Duplicate key error", AppsmithErrorAction.DEFAULT),
USER_ALREADY_EXISTS_SIGNUP(409, 4025, "There is already an account registered with this email {0}. Please sign in.", AppsmithErrorAction.DEFAULT),
UNAUTHORIZED_ACCESS(403, 4025, "Unauthorized access", AppsmithErrorAction.DEFAULT),
ACTION_IS_NOT_AUTHORIZED(403, 4026, "Sorry. You do not have permissions to perform this action", AppsmithErrorAction.DEFAULT),
INVALID_DATASOURCE_NAME(400, 4026, "Invalid datasource name. Check again.", AppsmithErrorAction.DEFAULT),
NO_RESOURCE_FOUND(404, 4027, "Unable to find {0} with id {1}", AppsmithErrorAction.DEFAULT),
USER_NOT_FOUND(404, 4027, "Unable to find user with email {0}", AppsmithErrorAction.DEFAULT),
ACL_NO_RESOURCE_FOUND(404, 4028, "Unable to find {0} with id {1}. Either the asset doesn't exist or you don't have required permissions", AppsmithErrorAction.DEFAULT),
GENERIC_BAD_REQUEST(400, 4028, "Bad Request: {0}", AppsmithErrorAction.DEFAULT),
VALIDATION_FAILURE(400, 4028, "Validation Failure(s): {0}", AppsmithErrorAction.DEFAULT),
INVALID_CURL_COMMAND(400, 4029, "Invalid cURL command, couldn't import.", AppsmithErrorAction.DEFAULT),
INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request", AppsmithErrorAction.LOG_EXTERNALLY),
REPOSITORY_SAVE_FAILED(500, 5001, "Failed to save the repository. Try again.", AppsmithErrorAction.DEFAULT),
PLUGIN_INSTALLATION_FAILED_DOWNLOAD_ERROR(500, 5002, "Plugin installation failed due to an error while " +
"downloading it. Check the jar location & try again.", AppsmithErrorAction.LOG_EXTERNALLY),
PLUGIN_RUN_FAILED(500, 5003, "Plugin execution failed with error {0}", AppsmithErrorAction.DEFAULT),
PLUGIN_EXECUTION_TIMEOUT(504, 5040, "Plugin Execution exceeded the maximum allowed time. Please increase the timeout in your action settings or check your backend action endpoint", AppsmithErrorAction.DEFAULT),
PLUGIN_LOAD_FORM_JSON_FAIL(500, 5004, "Unable to load datasource form configuration. Details: {0}.",
AppsmithErrorAction.LOG_EXTERNALLY),
PLUGIN_LOAD_TEMPLATES_FAIL(500, 5005, "Unable to load datasource templates. Details: {0}.",
AppsmithErrorAction.LOG_EXTERNALLY),
MARKETPLACE_TIMEOUT(504, 5041, "Marketplace is responding too slowly. Please try again later", AppsmithErrorAction.DEFAULT),
DATASOURCE_HAS_ACTIONS(409, 4030, "Cannot delete datasource since it has {0} action(s) using it.", AppsmithErrorAction.DEFAULT),
ORGANIZATION_ID_NOT_GIVEN(400, 4031, "Missing organization id. Please enter one.", AppsmithErrorAction.DEFAULT),
INVALID_CURL_METHOD(400, 4032, "Invalid method in cURL command: {0}.", AppsmithErrorAction.DEFAULT),
OAUTH_NOT_AVAILABLE(500, 5006, "Login with {0} is not supported.", AppsmithErrorAction.LOG_EXTERNALLY),
MARKETPLACE_NOT_CONFIGURED(500, 5007, "Marketplace is not configured.", AppsmithErrorAction.DEFAULT),
;
private final Integer httpErrorCode;
private final Integer appErrorCode;
private final String message;
private final AppsmithErrorAction errorAction;
AppsmithError(Integer httpErrorCode, Integer appErrorCode, String message, Object... args) {
AppsmithError(Integer httpErrorCode, Integer appErrorCode, String message, AppsmithErrorAction errorAction, Object... args) {
this.httpErrorCode = httpErrorCode;
this.appErrorCode = appErrorCode;
MessageFormat fmt = new MessageFormat(message);
this.message = fmt.format(args);
this.errorAction = errorAction;
}
public String getMessage(Object... args) {
return new MessageFormat(this.message).format(args);
}
public AppsmithErrorAction getErrorAction() {
return this.errorAction;
}
}

View File

@ -29,4 +29,8 @@ public class AppsmithException extends Exception {
return this.error == null ? -1 : this.error.getAppErrorCode();
}
public AppsmithErrorAction getErrorAction() {
return this.error.getErrorAction();
}
}

View File

@ -37,8 +37,16 @@ public class GlobalExceptionHandler {
private void doLog(Throwable error) {
log.error("", error);
if (rollbar != null) {
rollbar.log(error);
if (error instanceof AppsmithException) {
if (rollbar != null && ((AppsmithException)error).getErrorAction() == AppsmithErrorAction.LOG_EXTERNALLY) {
rollbar.log(error);
}
}
else {
if(rollbar != null) {
rollbar.log(error);
}
}
}