Merge branch 'fix/form' into 'release'
Drafts in API Pane Closes: #285 #223 #142 #224 #231 #176 #198 #249 #98 #248 #237 #233 #276 #178 #281 * #223, #142: Added feature to save drafts of apis which are indicated by an orange dot next to it * #285 #224: Added better routing in the api pane to save last visited api and selecting by default * #231: Fixed button disabled state * #176: Adding 2 headers and param rows by default * #198 #98: Adding a default name for api `Action{Number}` * #249: Fixed multiple scrolls in the response view * #248 #237 #233 #276 : Fixed validations * #178: Disable post body in GET * #281: Disable Code Editor context menu See merge request theappsmith/internal-tools-client!158
This commit is contained in:
commit
ce2c6af4b4
15
app/client/src/actions/apiPaneActions.ts
Normal file
15
app/client/src/actions/apiPaneActions.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
|
|
||||||
|
export const changeApi = (id: string): ReduxAction<{ id: string }> => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.API_PANE_CHANGE_API,
|
||||||
|
payload: { id },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initApiPane = (urlId?: string): ReduxAction<{ id?: string }> => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.INIT_API_PANE,
|
||||||
|
payload: { id: urlId },
|
||||||
|
};
|
||||||
|
};
|
||||||
7
app/client/src/actions/routeParamsActions.ts
Normal file
7
app/client/src/actions/routeParamsActions.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer";
|
||||||
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
|
|
||||||
|
export const updateRouteParams = (payload: RoutesParamsReducerState) => ({
|
||||||
|
type: ReduxActionTypes.UPDATE_ROUTES_PARAMS,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
@ -16,6 +16,7 @@ export const TextInput = styled(InputGroup)`
|
||||||
border-color: ${props => props.theme.colors.secondary};
|
border-color: ${props => props.theme.colors.secondary};
|
||||||
background-color: ${props => props.theme.colors.textOnDarkBG};
|
background-color: ${props => props.theme.colors.textOnDarkBG};
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.bp3-input-group .bp3-input:not(:first-child) {
|
&.bp3-input-group .bp3-input:not(:first-child) {
|
||||||
|
|
@ -54,7 +55,7 @@ const ErrorText = styled.span`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface TextInputProps {
|
export interface TextInputProps {
|
||||||
placeholderMessage?: string;
|
placeholder?: string;
|
||||||
input?: Partial<WrappedFieldInputProps>;
|
input?: Partial<WrappedFieldInputProps>;
|
||||||
meta?: WrappedFieldMetaProps;
|
meta?: WrappedFieldMetaProps;
|
||||||
icon?: IconName | MaybeElement;
|
icon?: IconName | MaybeElement;
|
||||||
|
|
@ -63,11 +64,20 @@ export interface TextInputProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BaseTextInput = (props: TextInputProps) => {
|
export const BaseTextInput = (props: TextInputProps) => {
|
||||||
const { placeholderMessage, input, meta, icon, showError, className } = props;
|
const { placeholder, input, meta, icon, showError, className } = props;
|
||||||
return (
|
return (
|
||||||
<InputContainer className={className}>
|
<InputContainer className={className}>
|
||||||
<TextInput {...input} placeholder={placeholderMessage} leftIcon={icon} />
|
<TextInput
|
||||||
{showError && <ErrorText>{meta && meta.touched && meta.error}</ErrorText>}
|
{...input}
|
||||||
|
placeholder={placeholder}
|
||||||
|
leftIcon={icon}
|
||||||
|
autoComplete={"off"}
|
||||||
|
/>
|
||||||
|
{showError && (
|
||||||
|
<ErrorText>
|
||||||
|
{meta && (meta.touched || meta.active) && meta.error}
|
||||||
|
</ErrorText>
|
||||||
|
)}
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,10 @@ const ButtonWrapper = styled(AnchorButton)<ButtonStyleProps>`
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
&&.bp3-disabled {
|
||||||
|
background-color: #d0d7dd;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export type ButtonStyleName = "primary" | "secondary" | "error";
|
export type ButtonStyleName = "primary" | "secondary" | "error";
|
||||||
|
|
|
||||||
|
|
@ -148,13 +148,14 @@ const ApiResponseView = (props: Props) => {
|
||||||
panelComponent: (
|
panelComponent: (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
theme={"LIGHT"}
|
theme={"LIGHT"}
|
||||||
height={500}
|
height={600}
|
||||||
language={"json"}
|
language={"json"}
|
||||||
input={{
|
input={{
|
||||||
value: response.body
|
value: response.body
|
||||||
? JSON.stringify(response.body, null, 2)
|
? JSON.stringify(response.body, null, 2)
|
||||||
: "",
|
: "",
|
||||||
}}
|
}}
|
||||||
|
lineNumbersMinChars={2}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { editor } from "monaco-editor";
|
||||||
|
|
||||||
const Wrapper = styled.div<{ height: number }>`
|
const Wrapper = styled.div<{ height: number }>`
|
||||||
height: ${props => props.height}px;
|
height: ${props => props.height}px;
|
||||||
overflow: auto;
|
|
||||||
color: white;
|
color: white;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -36,6 +35,8 @@ const CodeEditor = (props: Props) => {
|
||||||
lineNumbers: props.lineNumbers,
|
lineNumbers: props.lineNumbers,
|
||||||
glyphMargin: props.glyphMargin,
|
glyphMargin: props.glyphMargin,
|
||||||
folding: props.folding,
|
folding: props.folding,
|
||||||
|
contextmenu: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
// // Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
|
// // Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
|
||||||
lineDecorationsWidth: props.lineDecorationsWidth,
|
lineDecorationsWidth: props.lineDecorationsWidth,
|
||||||
lineNumbersMinChars: props.lineNumbersMinChars,
|
lineNumbersMinChars: props.lineNumbersMinChars,
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
||||||
{props.fields.map((field: any, index: number) => (
|
{props.fields.map((field: any, index: number) => (
|
||||||
<FormRowWithLabel key={index}>
|
<FormRowWithLabel key={index}>
|
||||||
{index === 0 && <FormLabel>{props.label}</FormLabel>}
|
{index === 0 && <FormLabel>{props.label}</FormLabel>}
|
||||||
<TextField name={`${field}.key`} placeholderMessage="Key" />
|
<TextField name={`${field}.key`} placeholder="Key" />
|
||||||
<TextField name={`${field}.value`} placeholderMessage="Value" />
|
<TextField name={`${field}.value`} placeholder="Value" />
|
||||||
{index === props.fields.length - 1 ? (
|
{index === props.fields.length - 1 ? (
|
||||||
<Icon
|
<Icon
|
||||||
icon="plus"
|
icon="plus"
|
||||||
|
|
@ -82,6 +82,11 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
"CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS",
|
"CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS",
|
||||||
UPDATE_WIDGET_PROPERTY_VALIDATION: "UPDATE_WIDGET_PROPERTY_VALIDATION",
|
UPDATE_WIDGET_PROPERTY_VALIDATION: "UPDATE_WIDGET_PROPERTY_VALIDATION",
|
||||||
HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE",
|
HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE",
|
||||||
|
INIT_API_PANE: "INIT_API_PANE",
|
||||||
|
API_PANE_CHANGE_API: "API_PANE_CHANGE_API",
|
||||||
|
UPDATE_API_DRAFT: "UPDATE_API_DRAFT",
|
||||||
|
DELETE_API_DRAFT: "DELETE_API_DRAFT",
|
||||||
|
UPDATE_ROUTES_PARAMS: "UPDATE_ROUTES_PARAMS",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
|
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
|
||||||
|
|
@ -118,6 +123,11 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
||||||
CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR",
|
CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ReduxFormActionTypes: { [key: string]: string } = {
|
||||||
|
VALUE_CHANGE: "@@redux-form/CHANGE",
|
||||||
|
UPDATE_FIELD_ERROR: "@@redux-form/UPDATE_SYNC_ERRORS",
|
||||||
|
};
|
||||||
|
|
||||||
export type ReduxActionErrorType = typeof ReduxActionErrorTypes[keyof typeof ReduxActionErrorTypes];
|
export type ReduxActionErrorType = typeof ReduxActionErrorTypes[keyof typeof ReduxActionErrorTypes];
|
||||||
|
|
||||||
export interface ReduxAction<T> {
|
export interface ReduxAction<T> {
|
||||||
|
|
@ -127,6 +137,10 @@ export interface ReduxAction<T> {
|
||||||
|
|
||||||
export type ReduxActionWithoutPayload = Pick<ReduxAction<undefined>, "type">;
|
export type ReduxActionWithoutPayload = Pick<ReduxAction<undefined>, "type">;
|
||||||
|
|
||||||
|
export interface ReduxActionWithMeta<T, M> extends ReduxAction<T> {
|
||||||
|
meta: M;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReduxActionErrorPayload {
|
export interface ReduxActionErrorPayload {
|
||||||
message: string;
|
message: string;
|
||||||
source?: string;
|
source?: string;
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export const API_PATH_START_WITH_SLASH_ERROR = "Path cannot start with /";
|
|
||||||
export const FIELD_REQUIRED_ERROR = "This field is required";
|
|
||||||
|
|
@ -34,7 +34,7 @@ export const PositionTypes: { [id: string]: string } = {
|
||||||
ABSOLUTE: "ABSOLUTE",
|
ABSOLUTE: "ABSOLUTE",
|
||||||
CONTAINER_DIREACTION: "CONTAINER_DIRECTION",
|
CONTAINER_DIREACTION: "CONTAINER_DIRECTION",
|
||||||
};
|
};
|
||||||
export type PositionType = (typeof PositionTypes)[keyof typeof PositionTypes];
|
export type PositionType = typeof PositionTypes[keyof typeof PositionTypes];
|
||||||
|
|
||||||
export type CSSUnit =
|
export type CSSUnit =
|
||||||
| "px"
|
| "px"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
export const ERROR_MESSAGE_SELECT_ACTION = "Please select an action";
|
export const ERROR_MESSAGE_SELECT_ACTION = "Please select an action";
|
||||||
export const ERROR_MESSAGE_SELECT_ACTION_TYPE = "Please select an action type";
|
export const ERROR_MESSAGE_SELECT_ACTION_TYPE = "Please select an action type";
|
||||||
|
|
||||||
export const ERROR_MESSAGE_CREATE_APPLICATION =
|
export const ERROR_MESSAGE_CREATE_APPLICATION =
|
||||||
"We could not create the Application";
|
"We could not create the Application";
|
||||||
|
export const API_PATH_START_WITH_SLASH_ERROR = "Path cannot start with /";
|
||||||
|
export const FIELD_REQUIRED_ERROR = "This field is required";
|
||||||
|
export const VALID_FUNCTION_NAME_ERROR =
|
||||||
|
"Action name is not a valid function name";
|
||||||
|
export const UNIQUE_NAME_ERROR = "Action name must be unique";
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
CreateApplicationFormValues,
|
CreateApplicationFormValues,
|
||||||
createApplicationFormSubmitHandler,
|
createApplicationFormSubmitHandler,
|
||||||
} from "utils/formhelpers";
|
} from "utils/formhelpers";
|
||||||
import TextField from "components/editorComponents/fields/TextField";
|
import TextField from "components/editorComponents/form/fields/TextField";
|
||||||
import { required } from "utils/validation/common";
|
import { required } from "utils/validation/common";
|
||||||
import { FormGroup } from "@blueprintjs/core";
|
import { FormGroup } from "@blueprintjs/core";
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ export const CreateApplicationForm = (
|
||||||
<FormGroup intent={error ? "danger" : "none"} helperText={error}>
|
<FormGroup intent={error ? "danger" : "none"} helperText={error}>
|
||||||
<TextField
|
<TextField
|
||||||
name="applicationName"
|
name="applicationName"
|
||||||
placeholderMessage="Name"
|
placeholder="Name"
|
||||||
validate={required}
|
validate={required}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,16 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { reduxForm, InjectedFormProps, FormSubmitHandler } from "redux-form";
|
import { reduxForm, InjectedFormProps, FormSubmitHandler } from "redux-form";
|
||||||
import {
|
import { HTTP_METHOD_OPTIONS } from "constants/ApiEditorConstants";
|
||||||
FORM_INITIAL_VALUES,
|
|
||||||
HTTP_METHOD_OPTIONS,
|
|
||||||
} from "constants/ApiEditorConstants";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import FormLabel from "components/editorComponents/FormLabel";
|
import FormLabel from "components/editorComponents/FormLabel";
|
||||||
import FormRow from "components/editorComponents/FormRow";
|
import FormRow from "components/editorComponents/FormRow";
|
||||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||||
import { RestAction } from "api/ActionAPI";
|
import { RestAction } from "api/ActionAPI";
|
||||||
import TextField from "components/editorComponents/fields/TextField";
|
import TextField from "components/editorComponents/form/fields/TextField";
|
||||||
import DropdownField from "components/editorComponents/fields/DropdownField";
|
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
||||||
import DatasourcesField from "components/editorComponents/fields/DatasourcesField";
|
import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField";
|
||||||
import KeyValueFieldArray from "components/editorComponents/fields/KeyValueFieldArray";
|
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||||
import JSONEditorField from "components/editorComponents/fields/JSONEditorField";
|
import JSONEditorField from "components/editorComponents/form/fields/JSONEditorField";
|
||||||
import { required } from "utils/validation/common";
|
|
||||||
import { apiPathValidation } from "utils/validation/ApiForm";
|
|
||||||
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
||||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||||
|
|
||||||
|
|
@ -74,6 +69,8 @@ const JSONEditorFieldWrapper = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface APIFormProps {
|
interface APIFormProps {
|
||||||
|
allowSave: boolean;
|
||||||
|
allowPostBody: boolean;
|
||||||
onSubmit: FormSubmitHandler<RestAction>;
|
onSubmit: FormSubmitHandler<RestAction>;
|
||||||
onSaveClick: () => void;
|
onSaveClick: () => void;
|
||||||
onRunClick: () => void;
|
onRunClick: () => void;
|
||||||
|
|
@ -87,6 +84,8 @@ type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
||||||
|
|
||||||
const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
|
allowSave,
|
||||||
|
allowPostBody,
|
||||||
onSaveClick,
|
onSaveClick,
|
||||||
onDeleteClick,
|
onDeleteClick,
|
||||||
onRunClick,
|
onRunClick,
|
||||||
|
|
@ -99,12 +98,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<MainConfiguration>
|
<MainConfiguration>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
<TextField
|
<TextField name="name" placeholder="API Name *" showError />
|
||||||
name="name"
|
|
||||||
placeholderMessage="API Name *"
|
|
||||||
validate={required}
|
|
||||||
showError
|
|
||||||
/>
|
|
||||||
<ActionButtons>
|
<ActionButtons>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
text="Delete"
|
text="Delete"
|
||||||
|
|
@ -124,6 +118,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
filled
|
filled
|
||||||
onClick={onSaveClick}
|
onClick={onSaveClick}
|
||||||
loading={isSaving}
|
loading={isSaving}
|
||||||
|
disabled={!allowSave}
|
||||||
/>
|
/>
|
||||||
</ActionButtons>
|
</ActionButtons>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
@ -135,9 +130,8 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<DatasourcesField name="datasource.id" />
|
<DatasourcesField name="datasource.id" />
|
||||||
<TextField
|
<TextField
|
||||||
placeholderMessage="API Path"
|
placeholder="API Path"
|
||||||
name="actionConfiguration.path"
|
name="actionConfiguration.path"
|
||||||
validate={[apiPathValidation]}
|
|
||||||
icon="slash"
|
icon="slash"
|
||||||
showError
|
showError
|
||||||
/>
|
/>
|
||||||
|
|
@ -153,10 +147,14 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
name="actionConfiguration.queryParameters"
|
name="actionConfiguration.queryParameters"
|
||||||
label="Params"
|
label="Params"
|
||||||
/>
|
/>
|
||||||
|
{allowPostBody && (
|
||||||
|
<React.Fragment>
|
||||||
<FormLabel>{"Post Body"}</FormLabel>
|
<FormLabel>{"Post Body"}</FormLabel>
|
||||||
<JSONEditorFieldWrapper>
|
<JSONEditorFieldWrapper>
|
||||||
<JSONEditorField name="actionConfiguration.body" />
|
<JSONEditorField name="actionConfiguration.body" />
|
||||||
</JSONEditorFieldWrapper>
|
</JSONEditorFieldWrapper>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
</RequestParamsWrapper>
|
</RequestParamsWrapper>
|
||||||
<ApiResponseView />
|
<ApiResponseView />
|
||||||
</SecondaryWrapper>
|
</SecondaryWrapper>
|
||||||
|
|
@ -167,5 +165,4 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
export default reduxForm<RestAction, APIFormProps>({
|
export default reduxForm<RestAction, APIFormProps>({
|
||||||
form: API_EDITOR_FORM_NAME,
|
form: API_EDITOR_FORM_NAME,
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
initialValues: FORM_INITIAL_VALUES,
|
|
||||||
})(ApiEditorForm);
|
})(ApiEditorForm);
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { submit, initialize, getFormValues, destroy } from "redux-form";
|
import { submit, getFormValues } from "redux-form";
|
||||||
import ApiEditorForm from "./APIEditor/ApiEditorForm";
|
import ApiEditorForm from "./Form";
|
||||||
import {
|
import {
|
||||||
createActionRequest,
|
createActionRequest,
|
||||||
runApiAction,
|
runApiAction,
|
||||||
|
|
@ -11,17 +11,17 @@ import {
|
||||||
import { RestAction } from "api/ActionAPI";
|
import { RestAction } from "api/ActionAPI";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
import { API_EDITOR_URL } from "constants/routes";
|
|
||||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||||
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { FORM_INITIAL_VALUES } from "constants/ApiEditorConstants";
|
import { HTTP_METHODS } from "constants/ApiEditorConstants";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
actions: ActionDataState;
|
actions: ActionDataState;
|
||||||
apiPane: ApiPaneReduxState;
|
apiPane: ApiPaneReduxState;
|
||||||
formData: any;
|
formData: RestAction;
|
||||||
}
|
}
|
||||||
interface ReduxActionProps {
|
interface ReduxActionProps {
|
||||||
submitForm: (name: string) => void;
|
submitForm: (name: string) => void;
|
||||||
|
|
@ -29,8 +29,6 @@ interface ReduxActionProps {
|
||||||
runAction: () => void;
|
runAction: () => void;
|
||||||
deleteAction: (id: string) => void;
|
deleteAction: (id: string) => void;
|
||||||
updateAction: (data: RestAction) => void;
|
updateAction: (data: RestAction) => void;
|
||||||
initialize: (formName: string, data?: Partial<RestAction>) => void;
|
|
||||||
destroy: (formName: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = ReduxActionProps &
|
type Props = ReduxActionProps &
|
||||||
|
|
@ -46,42 +44,6 @@ const EmptyStateContainer = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class ApiEditor extends React.Component<Props> {
|
class ApiEditor extends React.Component<Props> {
|
||||||
componentDidMount(): void {
|
|
||||||
const currentApiId = this.props.match.params.apiId;
|
|
||||||
const currentApplicationId = this.props.match.params.applicationId;
|
|
||||||
const currentPageId = this.props.match.params.pageId;
|
|
||||||
|
|
||||||
if (!currentApiId) return;
|
|
||||||
if (!this.props.actions.data.length) {
|
|
||||||
this.props.history.push(
|
|
||||||
API_EDITOR_URL(currentApplicationId, currentPageId),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = this.props.actions.data.filter(
|
|
||||||
action => action.id === currentApiId,
|
|
||||||
)[0];
|
|
||||||
this.props.initialize(API_EDITOR_FORM_NAME, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<Props>): void {
|
|
||||||
const currentId = this.props.match.params.apiId;
|
|
||||||
if (currentId && currentId !== prevProps.match.params.apiId) {
|
|
||||||
const data = this.props.actions.data.filter(
|
|
||||||
action => action.id === currentId,
|
|
||||||
)[0];
|
|
||||||
this.props.destroy(API_EDITOR_FORM_NAME);
|
|
||||||
let initialData = data;
|
|
||||||
if (!initialData.actionConfiguration) {
|
|
||||||
initialData = {
|
|
||||||
...data,
|
|
||||||
...FORM_INITIAL_VALUES,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.props.initialize(API_EDITOR_FORM_NAME, initialData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = (values: RestAction) => {
|
handleSubmit = (values: RestAction) => {
|
||||||
const { formData } = this.props;
|
const { formData } = this.props;
|
||||||
if (formData.id) {
|
if (formData.id) {
|
||||||
|
|
@ -103,15 +65,19 @@ class ApiEditor extends React.Component<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
apiPane: { isSaving, isRunning, isDeleting },
|
apiPane: { isSaving, isRunning, isDeleting, drafts },
|
||||||
match: {
|
match: {
|
||||||
params: { apiId },
|
params: { apiId },
|
||||||
},
|
},
|
||||||
|
formData,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const httpMethod = _.get(formData, "actionConfiguration.httpMethod");
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{apiId ? (
|
{apiId ? (
|
||||||
<ApiEditorForm
|
<ApiEditorForm
|
||||||
|
allowSave={apiId in drafts}
|
||||||
|
allowPostBody={httpMethod && httpMethod !== HTTP_METHODS[0]}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
isRunning={isRunning}
|
isRunning={isRunning}
|
||||||
isDeleting={isDeleting}
|
isDeleting={isDeleting}
|
||||||
|
|
@ -133,7 +99,7 @@ class ApiEditor extends React.Component<Props> {
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||||
actions: state.entities.actions,
|
actions: state.entities.actions,
|
||||||
apiPane: state.ui.apiPane,
|
apiPane: state.ui.apiPane,
|
||||||
formData: getFormValues(API_EDITOR_FORM_NAME)(state),
|
formData: getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
||||||
|
|
@ -142,9 +108,6 @@ const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
||||||
runAction: () => dispatch(runApiAction()),
|
runAction: () => dispatch(runApiAction()),
|
||||||
deleteAction: (id: string) => dispatch(deleteAction({ id })),
|
deleteAction: (id: string) => dispatch(deleteAction({ id })),
|
||||||
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
||||||
initialize: (formName: string, data?: Partial<RestAction>) =>
|
|
||||||
dispatch(initialize(formName, data)),
|
|
||||||
destroy: (formName: string) => dispatch(destroy(formName)),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ApiEditor);
|
export default connect(mapStateToProps, mapDispatchToProps)(ApiEditor);
|
||||||
|
|
@ -4,11 +4,7 @@ import { RouteComponentProps } from "react-router";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||||
import {
|
import { API_EDITOR_URL, APIEditorRouteParams } from "constants/routes";
|
||||||
API_EDITOR_ID_URL,
|
|
||||||
API_EDITOR_URL,
|
|
||||||
APIEditorRouteParams,
|
|
||||||
} from "constants/routes";
|
|
||||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||||
import { FormIcons } from "icons/FormIcons";
|
import { FormIcons } from "icons/FormIcons";
|
||||||
import { Spinner } from "@blueprintjs/core";
|
import { Spinner } from "@blueprintjs/core";
|
||||||
|
|
@ -16,15 +12,13 @@ import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
||||||
import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
|
import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
|
||||||
import { TICK } from "@blueprintjs/icons/lib/esm/generated/iconNames";
|
import { TICK } from "@blueprintjs/icons/lib/esm/generated/iconNames";
|
||||||
import { createActionRequest } from "actions/actionActions";
|
import { createActionRequest } from "actions/actionActions";
|
||||||
|
import { changeApi, initApiPane } from "actions/apiPaneActions";
|
||||||
import { RestAction } from "api/ActionAPI";
|
import { RestAction } from "api/ActionAPI";
|
||||||
|
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
|
||||||
const LoadingContainer = styled.div`
|
const LoadingContainer = styled(CenteredWrapper)`
|
||||||
height: 50%;
|
height: 50%;
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ApiSidebarWrapper = styled.div`
|
const ApiSidebarWrapper = styled.div`
|
||||||
|
|
@ -98,6 +92,14 @@ const ActionName = styled.span`
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const DraftIconIndicator = styled.span<{ isHidden: boolean }>`
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f2994a;
|
||||||
|
opacity: ${({ isHidden }) => (isHidden ? 0 : 1)};
|
||||||
|
`;
|
||||||
|
|
||||||
const CreateNewButton = styled(BaseButton)`
|
const CreateNewButton = styled(BaseButton)`
|
||||||
&& {
|
&& {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -135,6 +137,8 @@ interface ReduxStateProps {
|
||||||
|
|
||||||
interface ReduxDispatchProps {
|
interface ReduxDispatchProps {
|
||||||
createAction: (name: string) => void;
|
createAction: (name: string) => void;
|
||||||
|
onApiChange: (id: string) => void;
|
||||||
|
initApiPane: (urlId?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = ReduxStateProps &
|
type Props = ReduxStateProps &
|
||||||
|
|
@ -164,7 +168,12 @@ class ApiSidebar extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.props.initApiPane(this.props.match.params.apiId);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<Props>): void {
|
componentDidUpdate(prevProps: Readonly<Props>): void {
|
||||||
|
// If url has changed, hide the create input
|
||||||
if (!prevProps.match.params.apiId && this.props.match.params.apiId) {
|
if (!prevProps.match.params.apiId && this.props.match.params.apiId) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isCreating: false,
|
isCreating: false,
|
||||||
|
|
@ -174,13 +183,13 @@ class ApiSidebar extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreateNew = () => {
|
handleCreateNew = () => {
|
||||||
const { history } = this.props;
|
const { history, actions } = this.props;
|
||||||
const { pageId, applicationId } = this.props.match.params;
|
const { pageId, applicationId } = this.props.match.params;
|
||||||
|
|
||||||
history.push(API_EDITOR_URL(applicationId, pageId));
|
history.push(API_EDITOR_URL(applicationId, pageId));
|
||||||
this.setState({
|
this.setState({
|
||||||
isCreating: true,
|
isCreating: true,
|
||||||
name: "",
|
name: `action${actions.data.length}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -208,11 +217,13 @@ class ApiSidebar extends React.Component<Props, State> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleApiChange = (actionId: string) => {
|
||||||
|
this.props.onApiChange(actionId);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { applicationId, pageId } = this.props.match.params;
|
|
||||||
const {
|
const {
|
||||||
apiPane,
|
apiPane: { isFetching, isSaving, drafts },
|
||||||
history,
|
|
||||||
match,
|
match,
|
||||||
actions: { data },
|
actions: { data },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
@ -222,7 +233,7 @@ class ApiSidebar extends React.Component<Props, State> {
|
||||||
const actions: RestAction[] = search ? fuse.search(search) : data;
|
const actions: RestAction[] = search ? fuse.search(search) : data;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{apiPane.isFetching ? (
|
{isFetching ? (
|
||||||
<LoadingContainer>
|
<LoadingContainer>
|
||||||
<Spinner size={30} />
|
<Spinner size={30} />
|
||||||
</LoadingContainer>
|
</LoadingContainer>
|
||||||
|
|
@ -235,16 +246,12 @@ class ApiSidebar extends React.Component<Props, State> {
|
||||||
value: search,
|
value: search,
|
||||||
onChange: this.handleSearchChange,
|
onChange: this.handleSearchChange,
|
||||||
}}
|
}}
|
||||||
placeholderMessage="Search"
|
placeholder="Search"
|
||||||
/>
|
/>
|
||||||
{actions.map(action => (
|
{actions.map(action => (
|
||||||
<ApiItem
|
<ApiItem
|
||||||
key={action.id}
|
key={action.id}
|
||||||
onClick={() =>
|
onClick={() => this.handleApiChange(action.id)}
|
||||||
history.push(
|
|
||||||
API_EDITOR_ID_URL(applicationId, pageId, action.id),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
isSelected={activeActionId === action.id}
|
isSelected={activeActionId === action.id}
|
||||||
>
|
>
|
||||||
{action.actionConfiguration ? (
|
{action.actionConfiguration ? (
|
||||||
|
|
@ -255,13 +262,14 @@ class ApiSidebar extends React.Component<Props, State> {
|
||||||
<HTTPMethod />
|
<HTTPMethod />
|
||||||
)}
|
)}
|
||||||
<ActionName>{action.name}</ActionName>
|
<ActionName>{action.name}</ActionName>
|
||||||
|
<DraftIconIndicator isHidden={!(action.id in drafts)} />
|
||||||
</ApiItem>
|
</ApiItem>
|
||||||
))}
|
))}
|
||||||
</ApiItemsWrapper>
|
</ApiItemsWrapper>
|
||||||
{isCreating ? (
|
{isCreating ? (
|
||||||
<CreateApiWrapper>
|
<CreateApiWrapper>
|
||||||
<BaseTextInput
|
<BaseTextInput
|
||||||
placeholderMessage="API name"
|
placeholder="API name"
|
||||||
input={{
|
input={{
|
||||||
value: name,
|
value: name,
|
||||||
onChange: this.handleNameChange,
|
onChange: this.handleNameChange,
|
||||||
|
|
@ -273,12 +281,12 @@ class ApiSidebar extends React.Component<Props, State> {
|
||||||
text=""
|
text=""
|
||||||
onClick={this.saveAction}
|
onClick={this.saveAction}
|
||||||
filled
|
filled
|
||||||
loading={apiPane.isSaving}
|
loading={isSaving}
|
||||||
/>
|
/>
|
||||||
</CreateApiWrapper>
|
</CreateApiWrapper>
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{!apiPane.isFetching && (
|
{!isFetching && (
|
||||||
<CreateNewButton
|
<CreateNewButton
|
||||||
text="Create new API"
|
text="Create new API"
|
||||||
icon={FormIcons.ADD_NEW_ICON()}
|
icon={FormIcons.ADD_NEW_ICON()}
|
||||||
|
|
@ -301,6 +309,8 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Function): ReduxDispatchProps => ({
|
const mapDispatchToProps = (dispatch: Function): ReduxDispatchProps => ({
|
||||||
createAction: (name: string) => dispatch(createActionRequest({ name })),
|
createAction: (name: string) => dispatch(createActionRequest({ name })),
|
||||||
|
onApiChange: (actionId: string) => dispatch(changeApi(actionId)),
|
||||||
|
initApiPane: (urlId?: string) => dispatch(initApiPane(urlId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ApiSidebar);
|
export default connect(mapStateToProps, mapDispatchToProps)(ApiSidebar);
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import {
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { Dialog, Classes, AnchorButton } from "@blueprintjs/core";
|
import { Dialog, Classes, AnchorButton } from "@blueprintjs/core";
|
||||||
import { initEditor } from "actions/initActions";
|
import { initEditor } from "actions/initActions";
|
||||||
|
import { updateRouteParams } from "actions/routeParamsActions";
|
||||||
|
import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer";
|
||||||
|
|
||||||
type EditorProps = {
|
type EditorProps = {
|
||||||
currentPageName?: string;
|
currentPageName?: string;
|
||||||
|
|
@ -39,6 +41,7 @@ type EditorProps = {
|
||||||
previewPage: Function;
|
previewPage: Function;
|
||||||
initEditor: Function;
|
initEditor: Function;
|
||||||
createPage: Function;
|
createPage: Function;
|
||||||
|
updateRouteParams: (params: RoutesParamsReducerState) => void;
|
||||||
pages: PageListPayload;
|
pages: PageListPayload;
|
||||||
switchPage: (pageId: string) => void;
|
switchPage: (pageId: string) => void;
|
||||||
isPublishing: boolean;
|
isPublishing: boolean;
|
||||||
|
|
@ -60,30 +63,7 @@ class Editor extends Component<EditorProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate(previously: EditorProps) {
|
componentDidUpdate(previously: EditorProps) {
|
||||||
// const currently = this.props;
|
this.props.updateRouteParams(this.props.match.params);
|
||||||
// if (currently.publishedTime !== previously.publishedTime) {
|
|
||||||
// this.setState({
|
|
||||||
// isDialogOpen: true,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if (
|
|
||||||
// currently.currentPageId &&
|
|
||||||
// previously.currentPageId !== currently.currentPageId &&
|
|
||||||
// currently.currentApplicationId
|
|
||||||
// ) {
|
|
||||||
// this.props.history.replace(
|
|
||||||
// BUILDER_PAGE_URL(
|
|
||||||
// currently.currentApplicationId,
|
|
||||||
// currently.currentPageId,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// if (
|
|
||||||
// previously.match.params.pageId !== currently.match.params.pageId &&
|
|
||||||
// currently.currentPageId !== currently.match.params.pageId
|
|
||||||
// ) {
|
|
||||||
// this.props.switchPage(currently.match.params.pageId);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDialogClose = () => {
|
handleDialogClose = () => {
|
||||||
|
|
@ -207,6 +187,8 @@ const mapDispatchToProps = (dispatch: any) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
updateRouteParams: (params: RoutesParamsReducerState) =>
|
||||||
|
dispatch(updateRouteParams(params)),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
withRouter,
|
withRouter,
|
||||||
RouteComponentProps,
|
RouteComponentProps,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import ApiEditor from "./ApiEditor";
|
import ApiEditor from "./APIEditor";
|
||||||
import {
|
import {
|
||||||
API_EDITOR_ID_URL,
|
API_EDITOR_ID_URL,
|
||||||
API_EDITOR_URL,
|
API_EDITOR_URL,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { createReducer } from "utils/AppsmithUtils";
|
||||||
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
|
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
|
||||||
import { ActionResponse } from "api/ActionAPI";
|
import { ActionResponse } from "api/ActionAPI";
|
||||||
import { ActionDataState } from "./actionsReducer";
|
import { ActionDataState } from "./actionsReducer";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const initialState: APIDataState = {};
|
const initialState: APIDataState = {};
|
||||||
|
|
||||||
|
|
@ -16,6 +17,10 @@ const apiDataReducer = createReducer(initialState, {
|
||||||
state: ActionDataState,
|
state: ActionDataState,
|
||||||
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
||||||
) => ({ ...state, ...action.payload }),
|
) => ({ ...state, ...action.payload }),
|
||||||
|
[ReduxActionTypes.DELETE_ACTION_SUCCESS]: (
|
||||||
|
state: ActionDataState,
|
||||||
|
action: ReduxAction<{ id: string }>,
|
||||||
|
) => _.omit(state, action.payload.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiDataReducer;
|
export default apiDataReducer;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { ApplicationsReduxState } from "./uiReducers/applicationsReducer";
|
||||||
import { BindingsDataState } from "./entityReducers/bindingsReducer";
|
import { BindingsDataState } from "./entityReducers/bindingsReducer";
|
||||||
import { PageListReduxState } from "./entityReducers/pageListReducer";
|
import { PageListReduxState } from "./entityReducers/pageListReducer";
|
||||||
import { ApiPaneReduxState } from "./uiReducers/apiPaneReducer";
|
import { ApiPaneReduxState } from "./uiReducers/apiPaneReducer";
|
||||||
|
import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer";
|
||||||
|
|
||||||
const appReducer = combineReducers({
|
const appReducer = combineReducers({
|
||||||
entities: entityReducer,
|
entities: entityReducer,
|
||||||
|
|
@ -36,6 +37,7 @@ export interface AppState {
|
||||||
appView: AppViewReduxState;
|
appView: AppViewReduxState;
|
||||||
applications: ApplicationsReduxState;
|
applications: ApplicationsReduxState;
|
||||||
apiPane: ApiPaneReduxState;
|
apiPane: ApiPaneReduxState;
|
||||||
|
routesParams: RoutesParamsReducerState;
|
||||||
};
|
};
|
||||||
entities: {
|
entities: {
|
||||||
canvasWidgets: CanvasWidgetsReduxState;
|
canvasWidgets: CanvasWidgetsReduxState;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,14 @@ import { createReducer } from "utils/AppsmithUtils";
|
||||||
import {
|
import {
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
|
ReduxAction,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
|
import { RestAction } from "api/ActionAPI";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const initialState: ApiPaneReduxState = {
|
const initialState: ApiPaneReduxState = {
|
||||||
|
lastUsed: "",
|
||||||
|
drafts: {},
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isRunning: false,
|
isRunning: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
|
@ -12,6 +17,8 @@ const initialState: ApiPaneReduxState = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiPaneReduxState {
|
export interface ApiPaneReduxState {
|
||||||
|
lastUsed: string;
|
||||||
|
drafts: Record<string, RestAction>;
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
isRunning: boolean;
|
isRunning: boolean;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
|
|
@ -79,6 +86,30 @@ const apiPaneReducer = createReducer(initialState, {
|
||||||
...state,
|
...state,
|
||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
}),
|
}),
|
||||||
|
[ReduxActionTypes.UPDATE_API_DRAFT]: (
|
||||||
|
state: ApiPaneReduxState,
|
||||||
|
action: ReduxAction<{ id: string; draft: Partial<RestAction> }>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
drafts: {
|
||||||
|
...state.drafts,
|
||||||
|
[action.payload.id]: action.payload.draft,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[ReduxActionTypes.DELETE_API_DRAFT]: (
|
||||||
|
state: ApiPaneReduxState,
|
||||||
|
action: ReduxAction<{ id: string }>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
drafts: _.omit(state.drafts, action.payload.id),
|
||||||
|
}),
|
||||||
|
[ReduxActionTypes.API_PANE_CHANGE_API]: (
|
||||||
|
state: ApiPaneReduxState,
|
||||||
|
action: ReduxAction<{ id: string }>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
lastUsed: action.payload.id,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiPaneReducer;
|
export default apiPaneReducer;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import appViewReducer from "./appViewReducer";
|
||||||
import applicationsReducer from "./applicationsReducer";
|
import applicationsReducer from "./applicationsReducer";
|
||||||
import { widgetSidebarReducer } from "./widgetSidebarReducer";
|
import { widgetSidebarReducer } from "./widgetSidebarReducer";
|
||||||
import apiPaneReducer from "./apiPaneReducer";
|
import apiPaneReducer from "./apiPaneReducer";
|
||||||
|
import routesParamsReducer from "reducers/uiReducers/routesParamsReducer";
|
||||||
|
|
||||||
const uiReducer = combineReducers({
|
const uiReducer = combineReducers({
|
||||||
widgetSidebar: widgetSidebarReducer,
|
widgetSidebar: widgetSidebarReducer,
|
||||||
|
|
@ -15,5 +16,6 @@ const uiReducer = combineReducers({
|
||||||
appView: appViewReducer,
|
appView: appViewReducer,
|
||||||
applications: applicationsReducer,
|
applications: applicationsReducer,
|
||||||
apiPane: apiPaneReducer,
|
apiPane: apiPaneReducer,
|
||||||
|
routesParams: routesParamsReducer,
|
||||||
});
|
});
|
||||||
export default uiReducer;
|
export default uiReducer;
|
||||||
|
|
|
||||||
23
app/client/src/reducers/uiReducers/routesParamsReducer.ts
Normal file
23
app/client/src/reducers/uiReducers/routesParamsReducer.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { createReducer } from "utils/AppsmithUtils";
|
||||||
|
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
|
||||||
|
|
||||||
|
const initialState: RoutesParamsReducerState = {
|
||||||
|
applicationId: "",
|
||||||
|
pageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const routesParamsReducer = createReducer(initialState, {
|
||||||
|
[ReduxActionTypes.UPDATE_ROUTES_PARAMS]: (
|
||||||
|
state: RoutesParamsReducerState,
|
||||||
|
action: ReduxAction<RoutesParamsReducerState>,
|
||||||
|
) => {
|
||||||
|
return { ...action.payload };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface RoutesParamsReducerState {
|
||||||
|
applicationId: string;
|
||||||
|
pageId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default routesParamsReducer;
|
||||||
|
|
@ -31,13 +31,11 @@ import {
|
||||||
deleteActionSuccess,
|
deleteActionSuccess,
|
||||||
updateActionSuccess,
|
updateActionSuccess,
|
||||||
} from "actions/actionActions";
|
} from "actions/actionActions";
|
||||||
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "constants/routes";
|
|
||||||
import {
|
import {
|
||||||
extractDynamicBoundValue,
|
extractDynamicBoundValue,
|
||||||
getDynamicBindings,
|
getDynamicBindings,
|
||||||
isDynamicValue,
|
isDynamicValue,
|
||||||
} from "utils/DynamicBindingUtils";
|
} from "utils/DynamicBindingUtils";
|
||||||
import history from "utils/history";
|
|
||||||
import { validateResponse } from "./ErrorSagas";
|
import { validateResponse } from "./ErrorSagas";
|
||||||
import { getDataTree } from "selectors/entitiesSelector";
|
import { getDataTree } from "selectors/entitiesSelector";
|
||||||
import {
|
import {
|
||||||
|
|
@ -47,7 +45,7 @@ import {
|
||||||
import { getFormData } from "selectors/formSelectors";
|
import { getFormData } from "selectors/formSelectors";
|
||||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||||
|
|
||||||
const getAction = (
|
export const getAction = (
|
||||||
state: AppState,
|
state: AppState,
|
||||||
actionId: string,
|
actionId: string,
|
||||||
): RestAction | undefined => {
|
): RestAction | undefined => {
|
||||||
|
|
@ -225,7 +223,6 @@ export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
yield put(createActionSuccess(response.data));
|
yield put(createActionSuccess(response.data));
|
||||||
history.push(API_EDITOR_ID_URL(response.data.id));
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put({
|
yield put({
|
||||||
|
|
@ -289,7 +286,6 @@ export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
yield put(deleteActionSuccess({ id }));
|
yield put(deleteActionSuccess({ id }));
|
||||||
history.push(API_EDITOR_URL);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put({
|
yield put({
|
||||||
|
|
|
||||||
181
app/client/src/sagas/ApiPaneSagas.ts
Normal file
181
app/client/src/sagas/ApiPaneSagas.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
/**
|
||||||
|
* Handles the Api pane ui state. It looks into the routing based on actions too
|
||||||
|
* */
|
||||||
|
import _ from "lodash";
|
||||||
|
import { all, select, put, takeEvery, take, call } from "redux-saga/effects";
|
||||||
|
import {
|
||||||
|
ReduxAction,
|
||||||
|
ReduxActionTypes,
|
||||||
|
ReduxActionWithMeta,
|
||||||
|
ReduxFormActionTypes,
|
||||||
|
} from "constants/ReduxActionConstants";
|
||||||
|
import { getFormData } from "selectors/formSelectors";
|
||||||
|
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||||
|
import history from "utils/history";
|
||||||
|
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "constants/routes";
|
||||||
|
import { destroy, initialize } from "redux-form";
|
||||||
|
import { getAction } from "./ActionSagas";
|
||||||
|
import { AppState } from "reducers";
|
||||||
|
import { RestAction } from "api/ActionAPI";
|
||||||
|
import { FORM_INITIAL_VALUES } from "constants/ApiEditorConstants";
|
||||||
|
import { changeApi } from "actions/apiPaneActions";
|
||||||
|
import {
|
||||||
|
API_PATH_START_WITH_SLASH_ERROR,
|
||||||
|
FIELD_REQUIRED_ERROR,
|
||||||
|
UNIQUE_NAME_ERROR,
|
||||||
|
VALID_FUNCTION_NAME_ERROR,
|
||||||
|
} from "constants/messages";
|
||||||
|
|
||||||
|
const getApiDraft = (state: AppState, id: string) => {
|
||||||
|
const drafts = state.ui.apiPane.drafts;
|
||||||
|
if (id in drafts) return drafts[id];
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActions = (state: AppState) => state.entities.actions.data;
|
||||||
|
|
||||||
|
const getLastUsedAction = (state: AppState) => state.ui.apiPane.lastUsed;
|
||||||
|
|
||||||
|
const getRouterParams = (state: AppState) => state.ui.routesParams;
|
||||||
|
|
||||||
|
function* initApiPaneSaga(actionPayload: ReduxAction<{ id?: string }>) {
|
||||||
|
let actions = yield select(getActions);
|
||||||
|
while (!actions.length) {
|
||||||
|
yield take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS);
|
||||||
|
actions = yield select(getActions);
|
||||||
|
}
|
||||||
|
const urlId = actionPayload.payload.id;
|
||||||
|
const lastUsedId = yield select(getLastUsedAction);
|
||||||
|
let id = "";
|
||||||
|
if (urlId) {
|
||||||
|
id = urlId;
|
||||||
|
} else if (lastUsedId) {
|
||||||
|
id = lastUsedId;
|
||||||
|
}
|
||||||
|
yield put(changeApi(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
|
const { id } = actionPayload.payload;
|
||||||
|
const { applicationId, pageId } = yield select(getRouterParams);
|
||||||
|
const action = yield select(getAction, id);
|
||||||
|
if (!action) {
|
||||||
|
history.push(API_EDITOR_URL(applicationId, pageId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const draft = yield select(getApiDraft, id);
|
||||||
|
yield put(destroy(API_EDITOR_FORM_NAME));
|
||||||
|
const data = _.isEmpty(draft) ? action : draft;
|
||||||
|
yield put(initialize(API_EDITOR_FORM_NAME, data));
|
||||||
|
history.push(API_EDITOR_ID_URL(applicationId, pageId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function* updateDraftsSaga() {
|
||||||
|
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||||
|
if (!values.id) return;
|
||||||
|
const action = yield select(getAction, values.id);
|
||||||
|
if (_.isEqual(values, action)) {
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.DELETE_API_DRAFT,
|
||||||
|
payload: { id: values.id },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.UPDATE_API_DRAFT,
|
||||||
|
payload: { id: values.id, draft: values },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* validateInputSaga(
|
||||||
|
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
|
||||||
|
) {
|
||||||
|
const errors = {};
|
||||||
|
const {
|
||||||
|
payload,
|
||||||
|
meta: { field },
|
||||||
|
} = actionPayload;
|
||||||
|
const actions: RestAction[] = yield select(getActions);
|
||||||
|
const sameNames = actions.filter(
|
||||||
|
(action: RestAction) => action.name === payload && action.id,
|
||||||
|
);
|
||||||
|
if (field === "name") {
|
||||||
|
if (!_.trim(payload)) {
|
||||||
|
_.set(errors, field, FIELD_REQUIRED_ERROR);
|
||||||
|
} else if (payload.indexOf(" ") !== -1) {
|
||||||
|
_.set(errors, field, VALID_FUNCTION_NAME_ERROR);
|
||||||
|
} else if (sameNames.length > 0) {
|
||||||
|
// TODO Check this
|
||||||
|
_.set(errors, field, UNIQUE_NAME_ERROR);
|
||||||
|
} else {
|
||||||
|
_.unset(errors, field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (field === "actionConfiguration.path") {
|
||||||
|
if (payload && payload.startsWith("/")) {
|
||||||
|
_.set(errors, field, API_PATH_START_WITH_SLASH_ERROR);
|
||||||
|
} else {
|
||||||
|
_.unset(errors, field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield put({
|
||||||
|
type: ReduxFormActionTypes.UPDATE_FIELD_ERROR,
|
||||||
|
meta: {
|
||||||
|
form: API_EDITOR_FORM_NAME,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
syncErrors: errors,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function* formValueChangeSaga(
|
||||||
|
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
|
||||||
|
) {
|
||||||
|
const { form } = actionPayload.meta;
|
||||||
|
if (form !== API_EDITOR_FORM_NAME) return;
|
||||||
|
yield all([call(validateInputSaga, actionPayload), call(updateDraftsSaga)]);
|
||||||
|
}
|
||||||
|
function* handleActionCreatedSaga(actionPayload: ReduxAction<RestAction>) {
|
||||||
|
const { id } = actionPayload.payload;
|
||||||
|
const action = yield select(getAction, id);
|
||||||
|
const data = {
|
||||||
|
...action,
|
||||||
|
...FORM_INITIAL_VALUES,
|
||||||
|
};
|
||||||
|
yield put(initialize(API_EDITOR_FORM_NAME, data));
|
||||||
|
const { applicationId, pageId } = yield select(getRouterParams);
|
||||||
|
history.push(API_EDITOR_ID_URL(applicationId, pageId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function* handleActionUpdatedSaga(
|
||||||
|
actionPayload: ReduxAction<{ data: RestAction }>,
|
||||||
|
) {
|
||||||
|
const { id } = actionPayload.payload.data;
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.DELETE_API_DRAFT,
|
||||||
|
payload: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function* handleActionDeletedSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
|
const { id } = actionPayload.payload;
|
||||||
|
const { applicationId, pageId } = yield select(getRouterParams);
|
||||||
|
history.push(API_EDITOR_URL(applicationId, pageId));
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.DELETE_API_DRAFT,
|
||||||
|
payload: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function* root() {
|
||||||
|
yield all([
|
||||||
|
takeEvery(ReduxActionTypes.INIT_API_PANE, initApiPaneSaga),
|
||||||
|
takeEvery(ReduxActionTypes.API_PANE_CHANGE_API, changeApiSaga),
|
||||||
|
// Intercepting the redux-form change actionType
|
||||||
|
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
||||||
|
takeEvery(ReduxActionTypes.CREATE_ACTION_SUCCESS, handleActionCreatedSaga),
|
||||||
|
takeEvery(ReduxActionTypes.UPDATE_ACTION_SUCCESS, handleActionUpdatedSaga),
|
||||||
|
takeEvery(ReduxActionTypes.DELETE_ACTION_SUCCESS, handleActionDeletedSaga),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import bindingsSagas from "./BindingsSagas";
|
||||||
import watchActionWidgetMapSagas, {
|
import watchActionWidgetMapSagas, {
|
||||||
watchPropertyAndBindingUpdate,
|
watchPropertyAndBindingUpdate,
|
||||||
} from "./ActionWidgetMapSagas";
|
} from "./ActionWidgetMapSagas";
|
||||||
|
import apiPaneSagas from "./ApiPaneSagas";
|
||||||
|
|
||||||
export function* rootSaga() {
|
export function* rootSaga() {
|
||||||
yield all([
|
yield all([
|
||||||
|
|
@ -27,5 +28,6 @@ export function* rootSaga() {
|
||||||
spawn(bindingsSagas),
|
spawn(bindingsSagas),
|
||||||
spawn(watchActionWidgetMapSagas),
|
spawn(watchActionWidgetMapSagas),
|
||||||
spawn(watchPropertyAndBindingUpdate),
|
spawn(watchPropertyAndBindingUpdate),
|
||||||
|
spawn(apiPaneSagas),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { API_PATH_START_WITH_SLASH_ERROR } from "constants/ValidationsMessages";
|
|
||||||
|
|
||||||
export const apiPathValidation = (value: string) => {
|
|
||||||
if (value && value.startsWith("/")) return API_PATH_START_WITH_SLASH_ERROR;
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { FIELD_REQUIRED_ERROR } from "constants/ValidationsMessages";
|
import { FIELD_REQUIRED_ERROR } from "constants/messages";
|
||||||
|
|
||||||
export const required = (value: any) => {
|
export const required = (value: any) => {
|
||||||
if (value === undefined || value === null || value === "") {
|
if (value === undefined || value === null || value === "") {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user