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};
|
||||
background-color: ${props => props.theme.colors.textOnDarkBG};
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
&.bp3-input-group .bp3-input:not(:first-child) {
|
||||
|
|
@ -54,7 +55,7 @@ const ErrorText = styled.span`
|
|||
`;
|
||||
|
||||
export interface TextInputProps {
|
||||
placeholderMessage?: string;
|
||||
placeholder?: string;
|
||||
input?: Partial<WrappedFieldInputProps>;
|
||||
meta?: WrappedFieldMetaProps;
|
||||
icon?: IconName | MaybeElement;
|
||||
|
|
@ -63,11 +64,20 @@ export interface TextInputProps {
|
|||
}
|
||||
|
||||
export const BaseTextInput = (props: TextInputProps) => {
|
||||
const { placeholderMessage, input, meta, icon, showError, className } = props;
|
||||
const { placeholder, input, meta, icon, showError, className } = props;
|
||||
return (
|
||||
<InputContainer className={className}>
|
||||
<TextInput {...input} placeholder={placeholderMessage} leftIcon={icon} />
|
||||
{showError && <ErrorText>{meta && meta.touched && meta.error}</ErrorText>}
|
||||
<TextInput
|
||||
{...input}
|
||||
placeholder={placeholder}
|
||||
leftIcon={icon}
|
||||
autoComplete={"off"}
|
||||
/>
|
||||
{showError && (
|
||||
<ErrorText>
|
||||
{meta && (meta.touched || meta.active) && meta.error}
|
||||
</ErrorText>
|
||||
)}
|
||||
</InputContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ const ButtonWrapper = styled(AnchorButton)<ButtonStyleProps>`
|
|||
}
|
||||
}};
|
||||
}
|
||||
&&.bp3-disabled {
|
||||
background-color: #d0d7dd;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type ButtonStyleName = "primary" | "secondary" | "error";
|
||||
|
|
|
|||
|
|
@ -148,13 +148,14 @@ const ApiResponseView = (props: Props) => {
|
|||
panelComponent: (
|
||||
<CodeEditor
|
||||
theme={"LIGHT"}
|
||||
height={500}
|
||||
height={600}
|
||||
language={"json"}
|
||||
input={{
|
||||
value: response.body
|
||||
? JSON.stringify(response.body, null, 2)
|
||||
: "",
|
||||
}}
|
||||
lineNumbersMinChars={2}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { editor } from "monaco-editor";
|
|||
|
||||
const Wrapper = styled.div<{ height: number }>`
|
||||
height: ${props => props.height}px;
|
||||
overflow: auto;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
|
|
@ -36,6 +35,8 @@ const CodeEditor = (props: Props) => {
|
|||
lineNumbers: props.lineNumbers,
|
||||
glyphMargin: props.glyphMargin,
|
||||
folding: props.folding,
|
||||
contextmenu: false,
|
||||
scrollBeyondLastLine: false,
|
||||
// // Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
|
||||
lineDecorationsWidth: props.lineDecorationsWidth,
|
||||
lineNumbersMinChars: props.lineNumbersMinChars,
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
|||
{props.fields.map((field: any, index: number) => (
|
||||
<FormRowWithLabel key={index}>
|
||||
{index === 0 && <FormLabel>{props.label}</FormLabel>}
|
||||
<TextField name={`${field}.key`} placeholderMessage="Key" />
|
||||
<TextField name={`${field}.value`} placeholderMessage="Value" />
|
||||
<TextField name={`${field}.key`} placeholder="Key" />
|
||||
<TextField name={`${field}.value`} placeholder="Value" />
|
||||
{index === props.fields.length - 1 ? (
|
||||
<Icon
|
||||
icon="plus"
|
||||
|
|
@ -82,6 +82,11 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
"CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS",
|
||||
UPDATE_WIDGET_PROPERTY_VALIDATION: "UPDATE_WIDGET_PROPERTY_VALIDATION",
|
||||
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];
|
||||
|
|
@ -118,6 +123,11 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
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 interface ReduxAction<T> {
|
||||
|
|
@ -127,6 +137,10 @@ export interface ReduxAction<T> {
|
|||
|
||||
export type ReduxActionWithoutPayload = Pick<ReduxAction<undefined>, "type">;
|
||||
|
||||
export interface ReduxActionWithMeta<T, M> extends ReduxAction<T> {
|
||||
meta: M;
|
||||
}
|
||||
|
||||
export interface ReduxActionErrorPayload {
|
||||
message: 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",
|
||||
CONTAINER_DIREACTION: "CONTAINER_DIRECTION",
|
||||
};
|
||||
export type PositionType = (typeof PositionTypes)[keyof typeof PositionTypes];
|
||||
export type PositionType = typeof PositionTypes[keyof typeof PositionTypes];
|
||||
|
||||
export type CSSUnit =
|
||||
| "px"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
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_CREATE_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,
|
||||
createApplicationFormSubmitHandler,
|
||||
} 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 { FormGroup } from "@blueprintjs/core";
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ export const CreateApplicationForm = (
|
|||
<FormGroup intent={error ? "danger" : "none"} helperText={error}>
|
||||
<TextField
|
||||
name="applicationName"
|
||||
placeholderMessage="Name"
|
||||
placeholder="Name"
|
||||
validate={required}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
import React from "react";
|
||||
import { reduxForm, InjectedFormProps, FormSubmitHandler } from "redux-form";
|
||||
import {
|
||||
FORM_INITIAL_VALUES,
|
||||
HTTP_METHOD_OPTIONS,
|
||||
} from "constants/ApiEditorConstants";
|
||||
import { HTTP_METHOD_OPTIONS } from "constants/ApiEditorConstants";
|
||||
import styled from "styled-components";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import FormRow from "components/editorComponents/FormRow";
|
||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
import TextField from "components/editorComponents/fields/TextField";
|
||||
import DropdownField from "components/editorComponents/fields/DropdownField";
|
||||
import DatasourcesField from "components/editorComponents/fields/DatasourcesField";
|
||||
import KeyValueFieldArray from "components/editorComponents/fields/KeyValueFieldArray";
|
||||
import JSONEditorField from "components/editorComponents/fields/JSONEditorField";
|
||||
import { required } from "utils/validation/common";
|
||||
import { apiPathValidation } from "utils/validation/ApiForm";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
||||
import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField";
|
||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||
import JSONEditorField from "components/editorComponents/form/fields/JSONEditorField";
|
||||
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
|
||||
|
|
@ -74,6 +69,8 @@ const JSONEditorFieldWrapper = styled.div`
|
|||
`;
|
||||
|
||||
interface APIFormProps {
|
||||
allowSave: boolean;
|
||||
allowPostBody: boolean;
|
||||
onSubmit: FormSubmitHandler<RestAction>;
|
||||
onSaveClick: () => void;
|
||||
onRunClick: () => void;
|
||||
|
|
@ -87,6 +84,8 @@ type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
|||
|
||||
const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||
const {
|
||||
allowSave,
|
||||
allowPostBody,
|
||||
onSaveClick,
|
||||
onDeleteClick,
|
||||
onRunClick,
|
||||
|
|
@ -99,12 +98,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
<Form onSubmit={handleSubmit}>
|
||||
<MainConfiguration>
|
||||
<FormRow>
|
||||
<TextField
|
||||
name="name"
|
||||
placeholderMessage="API Name *"
|
||||
validate={required}
|
||||
showError
|
||||
/>
|
||||
<TextField name="name" placeholder="API Name *" showError />
|
||||
<ActionButtons>
|
||||
<ActionButton
|
||||
text="Delete"
|
||||
|
|
@ -124,6 +118,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
filled
|
||||
onClick={onSaveClick}
|
||||
loading={isSaving}
|
||||
disabled={!allowSave}
|
||||
/>
|
||||
</ActionButtons>
|
||||
</FormRow>
|
||||
|
|
@ -135,9 +130,8 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
/>
|
||||
<DatasourcesField name="datasource.id" />
|
||||
<TextField
|
||||
placeholderMessage="API Path"
|
||||
placeholder="API Path"
|
||||
name="actionConfiguration.path"
|
||||
validate={[apiPathValidation]}
|
||||
icon="slash"
|
||||
showError
|
||||
/>
|
||||
|
|
@ -153,10 +147,14 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
name="actionConfiguration.queryParameters"
|
||||
label="Params"
|
||||
/>
|
||||
<FormLabel>{"Post Body"}</FormLabel>
|
||||
<JSONEditorFieldWrapper>
|
||||
<JSONEditorField name="actionConfiguration.body" />
|
||||
</JSONEditorFieldWrapper>
|
||||
{allowPostBody && (
|
||||
<React.Fragment>
|
||||
<FormLabel>{"Post Body"}</FormLabel>
|
||||
<JSONEditorFieldWrapper>
|
||||
<JSONEditorField name="actionConfiguration.body" />
|
||||
</JSONEditorFieldWrapper>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</RequestParamsWrapper>
|
||||
<ApiResponseView />
|
||||
</SecondaryWrapper>
|
||||
|
|
@ -167,5 +165,4 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
export default reduxForm<RestAction, APIFormProps>({
|
||||
form: API_EDITOR_FORM_NAME,
|
||||
enableReinitialize: true,
|
||||
initialValues: FORM_INITIAL_VALUES,
|
||||
})(ApiEditorForm);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { submit, initialize, getFormValues, destroy } from "redux-form";
|
||||
import ApiEditorForm from "./APIEditor/ApiEditorForm";
|
||||
import { submit, getFormValues } from "redux-form";
|
||||
import ApiEditorForm from "./Form";
|
||||
import {
|
||||
createActionRequest,
|
||||
runApiAction,
|
||||
|
|
@ -11,17 +11,17 @@ import {
|
|||
import { RestAction } from "api/ActionAPI";
|
||||
import { AppState } from "reducers";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { API_EDITOR_URL } from "constants/routes";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
||||
import styled from "styled-components";
|
||||
import { FORM_INITIAL_VALUES } from "constants/ApiEditorConstants";
|
||||
import { HTTP_METHODS } from "constants/ApiEditorConstants";
|
||||
import _ from "lodash";
|
||||
|
||||
interface ReduxStateProps {
|
||||
actions: ActionDataState;
|
||||
apiPane: ApiPaneReduxState;
|
||||
formData: any;
|
||||
formData: RestAction;
|
||||
}
|
||||
interface ReduxActionProps {
|
||||
submitForm: (name: string) => void;
|
||||
|
|
@ -29,8 +29,6 @@ interface ReduxActionProps {
|
|||
runAction: () => void;
|
||||
deleteAction: (id: string) => void;
|
||||
updateAction: (data: RestAction) => void;
|
||||
initialize: (formName: string, data?: Partial<RestAction>) => void;
|
||||
destroy: (formName: string) => void;
|
||||
}
|
||||
|
||||
type Props = ReduxActionProps &
|
||||
|
|
@ -46,42 +44,6 @@ const EmptyStateContainer = styled.div`
|
|||
`;
|
||||
|
||||
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) => {
|
||||
const { formData } = this.props;
|
||||
if (formData.id) {
|
||||
|
|
@ -103,15 +65,19 @@ class ApiEditor extends React.Component<Props> {
|
|||
|
||||
render() {
|
||||
const {
|
||||
apiPane: { isSaving, isRunning, isDeleting },
|
||||
apiPane: { isSaving, isRunning, isDeleting, drafts },
|
||||
match: {
|
||||
params: { apiId },
|
||||
},
|
||||
formData,
|
||||
} = this.props;
|
||||
const httpMethod = _.get(formData, "actionConfiguration.httpMethod");
|
||||
return (
|
||||
<React.Fragment>
|
||||
{apiId ? (
|
||||
<ApiEditorForm
|
||||
allowSave={apiId in drafts}
|
||||
allowPostBody={httpMethod && httpMethod !== HTTP_METHODS[0]}
|
||||
isSaving={isSaving}
|
||||
isRunning={isRunning}
|
||||
isDeleting={isDeleting}
|
||||
|
|
@ -133,7 +99,7 @@ class ApiEditor extends React.Component<Props> {
|
|||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||
actions: state.entities.actions,
|
||||
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 => ({
|
||||
|
|
@ -142,9 +108,6 @@ const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
|||
runAction: () => dispatch(runApiAction()),
|
||||
deleteAction: (id: string) => dispatch(deleteAction({ id })),
|
||||
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);
|
||||
|
|
@ -4,11 +4,7 @@ import { RouteComponentProps } from "react-router";
|
|||
import styled from "styled-components";
|
||||
import { AppState } from "reducers";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import {
|
||||
API_EDITOR_ID_URL,
|
||||
API_EDITOR_URL,
|
||||
APIEditorRouteParams,
|
||||
} from "constants/routes";
|
||||
import { API_EDITOR_URL, APIEditorRouteParams } from "constants/routes";
|
||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import { Spinner } from "@blueprintjs/core";
|
||||
|
|
@ -16,15 +12,13 @@ import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
|||
import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
|
||||
import { TICK } from "@blueprintjs/icons/lib/esm/generated/iconNames";
|
||||
import { createActionRequest } from "actions/actionActions";
|
||||
import { changeApi, initApiPane } from "actions/apiPaneActions";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
const LoadingContainer = styled(CenteredWrapper)`
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const ApiSidebarWrapper = styled.div`
|
||||
|
|
@ -98,6 +92,14 @@ const ActionName = styled.span`
|
|||
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)`
|
||||
&& {
|
||||
border: none;
|
||||
|
|
@ -135,6 +137,8 @@ interface ReduxStateProps {
|
|||
|
||||
interface ReduxDispatchProps {
|
||||
createAction: (name: string) => void;
|
||||
onApiChange: (id: string) => void;
|
||||
initApiPane: (urlId?: string) => void;
|
||||
}
|
||||
|
||||
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 {
|
||||
// If url has changed, hide the create input
|
||||
if (!prevProps.match.params.apiId && this.props.match.params.apiId) {
|
||||
this.setState({
|
||||
isCreating: false,
|
||||
|
|
@ -174,13 +183,13 @@ class ApiSidebar extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
handleCreateNew = () => {
|
||||
const { history } = this.props;
|
||||
const { history, actions } = this.props;
|
||||
const { pageId, applicationId } = this.props.match.params;
|
||||
|
||||
history.push(API_EDITOR_URL(applicationId, pageId));
|
||||
this.setState({
|
||||
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() {
|
||||
const { applicationId, pageId } = this.props.match.params;
|
||||
const {
|
||||
apiPane,
|
||||
history,
|
||||
apiPane: { isFetching, isSaving, drafts },
|
||||
match,
|
||||
actions: { data },
|
||||
} = this.props;
|
||||
|
|
@ -222,7 +233,7 @@ class ApiSidebar extends React.Component<Props, State> {
|
|||
const actions: RestAction[] = search ? fuse.search(search) : data;
|
||||
return (
|
||||
<React.Fragment>
|
||||
{apiPane.isFetching ? (
|
||||
{isFetching ? (
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
|
|
@ -235,16 +246,12 @@ class ApiSidebar extends React.Component<Props, State> {
|
|||
value: search,
|
||||
onChange: this.handleSearchChange,
|
||||
}}
|
||||
placeholderMessage="Search"
|
||||
placeholder="Search"
|
||||
/>
|
||||
{actions.map(action => (
|
||||
<ApiItem
|
||||
key={action.id}
|
||||
onClick={() =>
|
||||
history.push(
|
||||
API_EDITOR_ID_URL(applicationId, pageId, action.id),
|
||||
)
|
||||
}
|
||||
onClick={() => this.handleApiChange(action.id)}
|
||||
isSelected={activeActionId === action.id}
|
||||
>
|
||||
{action.actionConfiguration ? (
|
||||
|
|
@ -255,13 +262,14 @@ class ApiSidebar extends React.Component<Props, State> {
|
|||
<HTTPMethod />
|
||||
)}
|
||||
<ActionName>{action.name}</ActionName>
|
||||
<DraftIconIndicator isHidden={!(action.id in drafts)} />
|
||||
</ApiItem>
|
||||
))}
|
||||
</ApiItemsWrapper>
|
||||
{isCreating ? (
|
||||
<CreateApiWrapper>
|
||||
<BaseTextInput
|
||||
placeholderMessage="API name"
|
||||
placeholder="API name"
|
||||
input={{
|
||||
value: name,
|
||||
onChange: this.handleNameChange,
|
||||
|
|
@ -273,12 +281,12 @@ class ApiSidebar extends React.Component<Props, State> {
|
|||
text=""
|
||||
onClick={this.saveAction}
|
||||
filled
|
||||
loading={apiPane.isSaving}
|
||||
loading={isSaving}
|
||||
/>
|
||||
</CreateApiWrapper>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{!apiPane.isFetching && (
|
||||
{!isFetching && (
|
||||
<CreateNewButton
|
||||
text="Create new API"
|
||||
icon={FormIcons.ADD_NEW_ICON()}
|
||||
|
|
@ -301,6 +309,8 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
|||
|
||||
const mapDispatchToProps = (dispatch: Function): ReduxDispatchProps => ({
|
||||
createAction: (name: string) => dispatch(createActionRequest({ name })),
|
||||
onApiChange: (actionId: string) => dispatch(changeApi(actionId)),
|
||||
initApiPane: (urlId?: string) => dispatch(initApiPane(urlId)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ApiSidebar);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import {
|
|||
} from "constants/ReduxActionConstants";
|
||||
import { Dialog, Classes, AnchorButton } from "@blueprintjs/core";
|
||||
import { initEditor } from "actions/initActions";
|
||||
import { updateRouteParams } from "actions/routeParamsActions";
|
||||
import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer";
|
||||
|
||||
type EditorProps = {
|
||||
currentPageName?: string;
|
||||
|
|
@ -39,6 +41,7 @@ type EditorProps = {
|
|||
previewPage: Function;
|
||||
initEditor: Function;
|
||||
createPage: Function;
|
||||
updateRouteParams: (params: RoutesParamsReducerState) => void;
|
||||
pages: PageListPayload;
|
||||
switchPage: (pageId: string) => void;
|
||||
isPublishing: boolean;
|
||||
|
|
@ -60,30 +63,7 @@ class Editor extends Component<EditorProps> {
|
|||
}
|
||||
}
|
||||
componentDidUpdate(previously: EditorProps) {
|
||||
// const currently = this.props;
|
||||
// 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);
|
||||
// }
|
||||
this.props.updateRouteParams(this.props.match.params);
|
||||
}
|
||||
|
||||
handleDialogClose = () => {
|
||||
|
|
@ -207,6 +187,8 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||
},
|
||||
});
|
||||
},
|
||||
updateRouteParams: (params: RoutesParamsReducerState) =>
|
||||
dispatch(updateRouteParams(params)),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
withRouter,
|
||||
RouteComponentProps,
|
||||
} from "react-router-dom";
|
||||
import ApiEditor from "./ApiEditor";
|
||||
import ApiEditor from "./APIEditor";
|
||||
import {
|
||||
API_EDITOR_ID_URL,
|
||||
API_EDITOR_URL,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { createReducer } from "utils/AppsmithUtils";
|
|||
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
|
||||
import { ActionResponse } from "api/ActionAPI";
|
||||
import { ActionDataState } from "./actionsReducer";
|
||||
import _ from "lodash";
|
||||
|
||||
const initialState: APIDataState = {};
|
||||
|
||||
|
|
@ -16,6 +17,10 @@ const apiDataReducer = createReducer(initialState, {
|
|||
state: ActionDataState,
|
||||
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
||||
) => ({ ...state, ...action.payload }),
|
||||
[ReduxActionTypes.DELETE_ACTION_SUCCESS]: (
|
||||
state: ActionDataState,
|
||||
action: ReduxAction<{ id: string }>,
|
||||
) => _.omit(state, action.payload.id),
|
||||
});
|
||||
|
||||
export default apiDataReducer;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { ApplicationsReduxState } from "./uiReducers/applicationsReducer";
|
|||
import { BindingsDataState } from "./entityReducers/bindingsReducer";
|
||||
import { PageListReduxState } from "./entityReducers/pageListReducer";
|
||||
import { ApiPaneReduxState } from "./uiReducers/apiPaneReducer";
|
||||
import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer";
|
||||
|
||||
const appReducer = combineReducers({
|
||||
entities: entityReducer,
|
||||
|
|
@ -36,6 +37,7 @@ export interface AppState {
|
|||
appView: AppViewReduxState;
|
||||
applications: ApplicationsReduxState;
|
||||
apiPane: ApiPaneReduxState;
|
||||
routesParams: RoutesParamsReducerState;
|
||||
};
|
||||
entities: {
|
||||
canvasWidgets: CanvasWidgetsReduxState;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@ import { createReducer } from "utils/AppsmithUtils";
|
|||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxAction,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
import _ from "lodash";
|
||||
|
||||
const initialState: ApiPaneReduxState = {
|
||||
lastUsed: "",
|
||||
drafts: {},
|
||||
isFetching: false,
|
||||
isRunning: false,
|
||||
isSaving: false,
|
||||
|
|
@ -12,6 +17,8 @@ const initialState: ApiPaneReduxState = {
|
|||
};
|
||||
|
||||
export interface ApiPaneReduxState {
|
||||
lastUsed: string;
|
||||
drafts: Record<string, RestAction>;
|
||||
isFetching: boolean;
|
||||
isRunning: boolean;
|
||||
isSaving: boolean;
|
||||
|
|
@ -79,6 +86,30 @@ const apiPaneReducer = createReducer(initialState, {
|
|||
...state,
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import appViewReducer from "./appViewReducer";
|
|||
import applicationsReducer from "./applicationsReducer";
|
||||
import { widgetSidebarReducer } from "./widgetSidebarReducer";
|
||||
import apiPaneReducer from "./apiPaneReducer";
|
||||
import routesParamsReducer from "reducers/uiReducers/routesParamsReducer";
|
||||
|
||||
const uiReducer = combineReducers({
|
||||
widgetSidebar: widgetSidebarReducer,
|
||||
|
|
@ -15,5 +16,6 @@ const uiReducer = combineReducers({
|
|||
appView: appViewReducer,
|
||||
applications: applicationsReducer,
|
||||
apiPane: apiPaneReducer,
|
||||
routesParams: routesParamsReducer,
|
||||
});
|
||||
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,
|
||||
updateActionSuccess,
|
||||
} from "actions/actionActions";
|
||||
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "constants/routes";
|
||||
import {
|
||||
extractDynamicBoundValue,
|
||||
getDynamicBindings,
|
||||
isDynamicValue,
|
||||
} from "utils/DynamicBindingUtils";
|
||||
import history from "utils/history";
|
||||
import { validateResponse } from "./ErrorSagas";
|
||||
import { getDataTree } from "selectors/entitiesSelector";
|
||||
import {
|
||||
|
|
@ -47,7 +45,7 @@ import {
|
|||
import { getFormData } from "selectors/formSelectors";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
|
||||
const getAction = (
|
||||
export const getAction = (
|
||||
state: AppState,
|
||||
actionId: string,
|
||||
): RestAction | undefined => {
|
||||
|
|
@ -225,7 +223,6 @@ export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
|
|||
intent: Intent.SUCCESS,
|
||||
});
|
||||
yield put(createActionSuccess(response.data));
|
||||
history.push(API_EDITOR_ID_URL(response.data.id));
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
|
|
@ -289,7 +286,6 @@ export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
intent: Intent.SUCCESS,
|
||||
});
|
||||
yield put(deleteActionSuccess({ id }));
|
||||
history.push(API_EDITOR_URL);
|
||||
}
|
||||
} catch (error) {
|
||||
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, {
|
||||
watchPropertyAndBindingUpdate,
|
||||
} from "./ActionWidgetMapSagas";
|
||||
import apiPaneSagas from "./ApiPaneSagas";
|
||||
|
||||
export function* rootSaga() {
|
||||
yield all([
|
||||
|
|
@ -27,5 +28,6 @@ export function* rootSaga() {
|
|||
spawn(bindingsSagas),
|
||||
spawn(watchActionWidgetMapSagas),
|
||||
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) => {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user