Merge branch 'fix/api-pane-fixes' into 'release'
Fixes for the API Editor See merge request theappsmith/internal-tools-client!72
This commit is contained in:
commit
4d8478efd9
|
|
@ -28,6 +28,7 @@
|
||||||
"eslint": "^6.4.0",
|
"eslint": "^6.4.0",
|
||||||
"flow-bin": "^0.91.0",
|
"flow-bin": "^0.91.0",
|
||||||
"fontfaceobserver": "^2.1.0",
|
"fontfaceobserver": "^2.1.0",
|
||||||
|
"history": "^4.10.1",
|
||||||
"husky": "^3.0.5",
|
"husky": "^3.0.5",
|
||||||
"jsonpath-plus": "^1.0.0",
|
"jsonpath-plus": "^1.0.0",
|
||||||
"lint-staged": "^9.2.5",
|
"lint-staged": "^9.2.5",
|
||||||
|
|
@ -53,7 +54,6 @@
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"react-scripts": "^3.1.1",
|
"react-scripts": "^3.1.1",
|
||||||
"react-select": "^3.0.8",
|
"react-select": "^3.0.8",
|
||||||
"react-tabs": "^3.0.0",
|
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-form": "^8.2.6",
|
"redux-form": "^8.2.6",
|
||||||
"redux-saga": "^1.0.0",
|
"redux-saga": "^1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,14 @@ import { RestAction } from "../api/ActionAPI";
|
||||||
|
|
||||||
export const createAction = (payload: RestAction) => {
|
export const createAction = (payload: RestAction) => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.CREATE_ACTION,
|
type: ReduxActionTypes.CREATE_ACTION_INIT,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createActionSuccess = (payload: RestAction) => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.CREATE_ACTION_SUCCESS,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -14,13 +21,6 @@ export const fetchActions = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const selectAction = (payload: { id: string }) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.SELECT_ACTION,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchApiConfig = (payload: { id: string }) => {
|
export const fetchApiConfig = (payload: { id: string }) => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.FETCH_ACTION,
|
type: ReduxActionTypes.FETCH_ACTION,
|
||||||
|
|
@ -30,21 +30,35 @@ export const fetchApiConfig = (payload: { id: string }) => {
|
||||||
|
|
||||||
export const runAction = (payload: { id: string }) => {
|
export const runAction = (payload: { id: string }) => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.RUN_ACTION,
|
type: ReduxActionTypes.RUN_ACTION_INIT,
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteAction = (payload: { id: string }) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.DELETE_ACTION,
|
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateAction = (payload: { data: RestAction }) => {
|
export const updateAction = (payload: { data: RestAction }) => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.UPDATE_ACTION,
|
type: ReduxActionTypes.UPDATE_ACTION_INIT,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateActionSuccess = (payload: { data: RestAction }) => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.UPDATE_ACTION_SUCCESS,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteAction = (payload: { id: string }) => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.DELETE_ACTION_INIT,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteActionSuccess = (payload: { id: string }) => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.DELETE_ACTION_SUCCESS,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -55,4 +69,7 @@ export default {
|
||||||
fetchApiConfig,
|
fetchApiConfig,
|
||||||
runAction,
|
runAction,
|
||||||
deleteAction,
|
deleteAction,
|
||||||
|
deleteActionSuccess,
|
||||||
|
updateAction,
|
||||||
|
updateActionSuccess,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
3
app/client/src/assets/icons/form/add-new.svg
Normal file
3
app/client/src/assets/icons/form/add-new.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.40782 0L0.393555 2.90999V16H8.47325V14.609H1.79346V3.89572H4.42798V1.39105H10.3326V5.5642H11.7325V0H3.40782ZM9.71082 6.78307V10.1043H6.38865V12.6788H9.71082V16H12.2844V12.6788H15.6066V10.1043H12.2844V6.78307H9.71082Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 348 B |
|
|
@ -38,7 +38,6 @@ class CreatableDropdown extends React.Component<DropdownProps> {
|
||||||
onCreateOption={onCreateOption}
|
onCreateOption={onCreateOption}
|
||||||
{...input}
|
{...input}
|
||||||
onChange={value => input.onChange(value)}
|
onChange={value => input.onChange(value)}
|
||||||
onBlur={() => input.onBlur(input.value)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ type DropdownProps = {
|
||||||
label: string;
|
label: string;
|
||||||
}>;
|
}>;
|
||||||
input: WrappedFieldInputProps;
|
input: WrappedFieldInputProps;
|
||||||
|
placeholder: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectStyles = {
|
const selectStyles = {
|
||||||
control: (styles: any) => ({
|
control: (styles: any) => ({
|
||||||
...styles,
|
...styles,
|
||||||
width: 100,
|
width: 120,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -21,12 +22,11 @@ export const BaseDropdown = (props: DropdownProps) => {
|
||||||
const { input, options } = props;
|
const { input, options } = props;
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
defaultValue={options[0]}
|
placeholder={props.placeholder}
|
||||||
options={options}
|
options={options}
|
||||||
styles={selectStyles}
|
styles={selectStyles}
|
||||||
{...input}
|
{...input}
|
||||||
onChange={value => input.onChange(value)}
|
onChange={value => input.onChange(value)}
|
||||||
onBlur={() => input.onBlur(input.value)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
|
import { Tab, Tabs } from "@blueprintjs/core";
|
||||||
import "react-tabs/style/react-tabs.scss";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
const TabsWrapper = styled(Tabs)`
|
const TabsWrapper = styled.div`
|
||||||
ul {
|
padding: 0 5px;
|
||||||
border-bottom-color: #d0d7dd;
|
.bp3-tab-indicator {
|
||||||
li {
|
background-color: ${props => props.theme.colors.primary};
|
||||||
&.react-tabs__tab--selected {
|
}
|
||||||
border-color: #d0d7dd;
|
.bp3-tab {
|
||||||
left: -1px;
|
&[aria-selected="true"] {
|
||||||
border-radius: 0;
|
color: ${props => props.theme.colors.primary};
|
||||||
border-top: 5px solid ${props => props.theme.colors.primary};
|
}
|
||||||
}
|
:hover {
|
||||||
|
color: ${props => props.theme.colors.primary};
|
||||||
|
}
|
||||||
|
:focus {
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
@ -21,21 +24,23 @@ type TabbedViewComponentType = {
|
||||||
tabs: Array<{
|
tabs: Array<{
|
||||||
key: string;
|
key: string;
|
||||||
title: string;
|
title: string;
|
||||||
panelComponent: () => React.ReactNode;
|
panelComponent: JSX.Element;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BaseTabbedView = (props: TabbedViewComponentType) => {
|
export const BaseTabbedView = (props: TabbedViewComponentType) => {
|
||||||
return (
|
return (
|
||||||
<TabsWrapper>
|
<TabsWrapper>
|
||||||
<TabList>
|
<Tabs>
|
||||||
{props.tabs.map(tab => (
|
{props.tabs.map(tab => (
|
||||||
<Tab key={tab.key}>{tab.title}</Tab>
|
<Tab
|
||||||
|
key={tab.key}
|
||||||
|
id={tab.key}
|
||||||
|
title={tab.title}
|
||||||
|
panel={tab.panelComponent}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</TabList>
|
</Tabs>
|
||||||
{props.tabs.map(tab => (
|
|
||||||
<TabPanel key={tab.key}>{tab.panelComponent()}</TabPanel>
|
|
||||||
))}
|
|
||||||
</TabsWrapper>
|
</TabsWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const InputStyles = css`
|
||||||
border: 1px solid ${props => props.theme.colors.inputInactiveBorders};
|
border: 1px solid ${props => props.theme.colors.inputInactiveBorders};
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background-color: ${props => props.theme.colors.inputInactiveBG};
|
background-color: ${props => props.theme.colors.textOnDarkBG};
|
||||||
&:focus {
|
&:focus {
|
||||||
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};
|
||||||
|
|
|
||||||
106
app/client/src/components/editor/ApiResponseView.tsx
Normal file
106
app/client/src/components/editor/ApiResponseView.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { withRouter, RouteComponentProps } from "react-router";
|
||||||
|
import FormRow from "./FormRow";
|
||||||
|
import { BaseText } from "../canvas/TextViewComponent";
|
||||||
|
import { BaseTabbedView } from "../canvas/TabbedView";
|
||||||
|
import JSONViewer from "./JSONViewer";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { AppState } from "../../reducers";
|
||||||
|
import { ActionApiResponse } from "../../reducers/entityReducers/actionsReducer";
|
||||||
|
|
||||||
|
const ResponseWrapper = styled.div`
|
||||||
|
flex: 4;
|
||||||
|
`;
|
||||||
|
const ResponseMetaInfo = styled.div`
|
||||||
|
display: flex;
|
||||||
|
${BaseText} {
|
||||||
|
color: #768896;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface ReduxStateProps {
|
||||||
|
responses: {
|
||||||
|
[id: string]: ActionApiResponse;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableWrapper = styled.div`
|
||||||
|
&&& {
|
||||||
|
table {
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
td {
|
||||||
|
font-size: 12px;
|
||||||
|
width: 50%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ResponseHeadersView = (props: {
|
||||||
|
data: { [name: string]: Array<string> };
|
||||||
|
}) => {
|
||||||
|
if (!props.data) return <div />;
|
||||||
|
return (
|
||||||
|
<TableWrapper>
|
||||||
|
<table className="bp3-html-table bp3-html-table-striped bp3-html-table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Key</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.keys(props.data).map(k => (
|
||||||
|
<tr key={k}>
|
||||||
|
<td>{k}</td>
|
||||||
|
<td>{props.data[k].join(", ")}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</TableWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = ReduxStateProps & RouteComponentProps<{ id: string }>;
|
||||||
|
|
||||||
|
const ApiResponseView = (props: Props) => {
|
||||||
|
const response = props.responses[props.match.params.id] || {};
|
||||||
|
return (
|
||||||
|
<ResponseWrapper>
|
||||||
|
<FormRow>
|
||||||
|
<BaseText styleName="secondary">{response.statusCode}</BaseText>
|
||||||
|
<ResponseMetaInfo>
|
||||||
|
<BaseText styleName="secondary">300ms</BaseText>
|
||||||
|
<BaseText styleName="secondary">203 kb</BaseText>
|
||||||
|
</ResponseMetaInfo>
|
||||||
|
</FormRow>
|
||||||
|
<BaseTabbedView
|
||||||
|
tabs={[
|
||||||
|
{
|
||||||
|
key: "body",
|
||||||
|
title: "Response Body",
|
||||||
|
panelComponent: <JSONViewer data={response.body} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "headers",
|
||||||
|
title: "Response Headers",
|
||||||
|
panelComponent: <ResponseHeadersView data={response.headers} />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</ResponseWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||||
|
responses: state.entities.actions.responses,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(withRouter(ApiResponseView));
|
||||||
|
|
@ -19,11 +19,12 @@ const JSONEditor = (props: any) => {
|
||||||
highlightActiveLine={true}
|
highlightActiveLine={true}
|
||||||
width="100%"
|
width="100%"
|
||||||
setOptions={{
|
setOptions={{
|
||||||
enableBasicAutocompletion: true,
|
enableBasicAutocompletion: false,
|
||||||
enableLiveAutocompletion: false,
|
enableLiveAutocompletion: false,
|
||||||
enableSnippets: false,
|
enableSnippets: false,
|
||||||
showLineNumbers: true,
|
showLineNumbers: true,
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
|
useWorker: false,
|
||||||
}}
|
}}
|
||||||
name={input.name}
|
name={input.name}
|
||||||
onBlur={aceOnBlur(input.onBlur)}
|
onBlur={aceOnBlur(input.onBlur)}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@ import styled from "styled-components";
|
||||||
const JSONViewWrapper = styled.div`
|
const JSONViewWrapper = styled.div`
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
& > div {
|
||||||
|
font-size: ${props => props.theme.fontSizes[2]}px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const JSONViewer = (props: { data: JSON }) => {
|
const JSONViewer = (props: { data: JSON }) => {
|
||||||
if (!props.data) return null;
|
if (!props.data) return <div />;
|
||||||
return (
|
return (
|
||||||
<JSONViewWrapper>
|
<JSONViewWrapper>
|
||||||
<ReactJson
|
<ReactJson
|
||||||
|
|
@ -17,9 +20,6 @@ const JSONViewer = (props: { data: JSON }) => {
|
||||||
displayDataTypes={false}
|
displayDataTypes={false}
|
||||||
indentWidth={2}
|
indentWidth={2}
|
||||||
enableClipboard={false}
|
enableClipboard={false}
|
||||||
style={{
|
|
||||||
fontSize: "10px",
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</JSONViewWrapper>
|
</JSONViewWrapper>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import WidgetSidebar from "../../pages/Editor/WidgetSidebar";
|
||||||
import ApiSidebar from "../../pages/Editor/ApiSidebar";
|
import ApiSidebar from "../../pages/Editor/ApiSidebar";
|
||||||
|
|
||||||
const SidebarWrapper = styled.div`
|
const SidebarWrapper = styled.div`
|
||||||
flex: 7;
|
flex: 9;
|
||||||
background-color: ${props => props.theme.colors.paneBG};
|
background-color: ${props => props.theme.colors.paneBG};
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
color: ${props => props.theme.colors.textOnDarkBG};
|
color: ${props => props.theme.colors.textOnDarkBG};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ interface DropdownFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
}>;
|
}>;
|
||||||
|
placeholder: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownField = (props: DropdownFieldProps) => {
|
const DropdownField = (props: DropdownFieldProps) => {
|
||||||
|
|
@ -17,6 +18,7 @@ const DropdownField = (props: DropdownFieldProps) => {
|
||||||
name={props.name}
|
name={props.name}
|
||||||
component={BaseDropdown}
|
component={BaseDropdown}
|
||||||
options={props.options}
|
options={props.options}
|
||||||
|
placeholder={props.placeholder}
|
||||||
format={(value: string) => _.find(props.options, { value })}
|
format={(value: string) => _.find(props.options, { value })}
|
||||||
normalize={(option: { value: string }) => option.value}
|
normalize={(option: { value: string }) => option.value}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ const ResourcesField = (
|
||||||
component={CreatableDropdown}
|
component={CreatableDropdown}
|
||||||
isLoading={props.resources.loading}
|
isLoading={props.resources.loading}
|
||||||
options={options}
|
options={options}
|
||||||
|
placeholder="Resource"
|
||||||
onCreateOption={props.createResource}
|
onCreateOption={props.createResource}
|
||||||
format={(value: string) => _.find(options, { value })}
|
format={(value: string) => _.find(options, { value })}
|
||||||
normalize={(option: { value: string }) => option.value}
|
normalize={(option: { value: string }) => option.value}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import {
|
||||||
HTTP_METHOD_OPTIONS,
|
HTTP_METHOD_OPTIONS,
|
||||||
} from "../../constants/ApiEditorConstants";
|
} from "../../constants/ApiEditorConstants";
|
||||||
import FormLabel from "../editor/FormLabel";
|
import FormLabel from "../editor/FormLabel";
|
||||||
import { BaseText } from "../canvas/TextViewComponent";
|
|
||||||
import { BaseTabbedView } from "../canvas/TabbedView";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import FormContainer from "../editor/FormContainer";
|
import FormContainer from "../editor/FormContainer";
|
||||||
import { BaseButton } from "../canvas/Button";
|
import { BaseButton } from "../canvas/Button";
|
||||||
|
|
@ -16,7 +14,7 @@ import KeyValueFieldArray from "../fields/KeyValueFieldArray";
|
||||||
import JSONEditorField from "../fields/JSONEditorField";
|
import JSONEditorField from "../fields/JSONEditorField";
|
||||||
import DropdownField from "../fields/DropdownField";
|
import DropdownField from "../fields/DropdownField";
|
||||||
import { RestAction } from "../../api/ActionAPI";
|
import { RestAction } from "../../api/ActionAPI";
|
||||||
import JSONViewer from "../../components/editor/JSONViewer";
|
import ApiResponseView from "../editor/ApiResponseView";
|
||||||
import { API_EDITOR_FORM_NAME } from "../../constants/forms";
|
import { API_EDITOR_FORM_NAME } from "../../constants/forms";
|
||||||
import ResourcesField from "../fields/ResourcesField";
|
import ResourcesField from "../fields/ResourcesField";
|
||||||
|
|
||||||
|
|
@ -54,10 +52,11 @@ const ForwardSlash = styled.div`
|
||||||
const RequestParamsWrapper = styled.div`
|
const RequestParamsWrapper = styled.div`
|
||||||
flex: 5;
|
flex: 5;
|
||||||
border-right: 1px solid #d0d7dd;
|
border-right: 1px solid #d0d7dd;
|
||||||
|
overflow-y: scroll;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ResponseWrapper = styled.div`
|
const ActionButtons = styled.div`
|
||||||
flex: 4;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ActionButton = styled(BaseButton)`
|
const ActionButton = styled(BaseButton)`
|
||||||
|
|
@ -65,14 +64,6 @@ const ActionButton = styled(BaseButton)`
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ResponseMetaInfo = styled.div`
|
|
||||||
display: flex;
|
|
||||||
${BaseText} {
|
|
||||||
color: #768896;
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const JSONEditorFieldWrapper = styled.div`
|
const JSONEditorFieldWrapper = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border: 1px solid #d0d7dd;
|
border: 1px solid #d0d7dd;
|
||||||
|
|
@ -84,33 +75,40 @@ interface APIFormProps {
|
||||||
onSaveClick: () => void;
|
onSaveClick: () => void;
|
||||||
onRunClick: () => void;
|
onRunClick: () => void;
|
||||||
onDeleteClick: () => void;
|
onDeleteClick: () => void;
|
||||||
response: any;
|
isEdit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
||||||
|
|
||||||
class ApiEditorForm extends React.Component<Props> {
|
class ApiEditorForm extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { onSaveClick, onDeleteClick, onRunClick } = this.props;
|
const { onSaveClick, onDeleteClick, onRunClick, isEdit } = this.props;
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
<TextField name="name" placeholderMessage="API Name" />
|
<TextField name="name" placeholderMessage="API Name" />
|
||||||
<ActionButton
|
<ActionButtons>
|
||||||
text="Delete"
|
<ActionButton
|
||||||
styleName="error"
|
text="Delete"
|
||||||
onClick={onDeleteClick}
|
styleName="error"
|
||||||
/>
|
onClick={onDeleteClick}
|
||||||
<ActionButton text="Run" styleName="secondary" onClick={onRunClick} />
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
text="Save"
|
text="Run"
|
||||||
styleName="primary"
|
styleName="secondary"
|
||||||
filled
|
onClick={onRunClick}
|
||||||
onClick={onSaveClick}
|
/>
|
||||||
/>
|
<ActionButton
|
||||||
|
text={isEdit ? "Update" : "Save"}
|
||||||
|
styleName="primary"
|
||||||
|
filled
|
||||||
|
onClick={onSaveClick}
|
||||||
|
/>
|
||||||
|
</ActionButtons>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
<DropdownField
|
<DropdownField
|
||||||
|
placeholder="Method"
|
||||||
name="actionConfiguration.httpMethod"
|
name="actionConfiguration.httpMethod"
|
||||||
options={HTTP_METHOD_OPTIONS}
|
options={HTTP_METHOD_OPTIONS}
|
||||||
/>
|
/>
|
||||||
|
|
@ -138,35 +136,7 @@ class ApiEditorForm extends React.Component<Props> {
|
||||||
</JSONEditorFieldWrapper>
|
</JSONEditorFieldWrapper>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</RequestParamsWrapper>
|
</RequestParamsWrapper>
|
||||||
<ResponseWrapper>
|
<ApiResponseView />
|
||||||
<FormRow>
|
|
||||||
<BaseText styleName="secondary">
|
|
||||||
{this.props.response.statusCode}
|
|
||||||
</BaseText>
|
|
||||||
<ResponseMetaInfo>
|
|
||||||
<BaseText styleName="secondary">300ms</BaseText>
|
|
||||||
<BaseText styleName="secondary">203 kb</BaseText>
|
|
||||||
</ResponseMetaInfo>
|
|
||||||
</FormRow>
|
|
||||||
<BaseTabbedView
|
|
||||||
tabs={[
|
|
||||||
{
|
|
||||||
key: "body",
|
|
||||||
title: "Response Body",
|
|
||||||
panelComponent: () => (
|
|
||||||
<JSONViewer data={this.props.response.body} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "headers",
|
|
||||||
title: "Response Headers",
|
|
||||||
panelComponent: () => (
|
|
||||||
<JSONViewer data={this.props.response.headers} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</ResponseWrapper>
|
|
||||||
</SecondaryWrapper>
|
</SecondaryWrapper>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,26 @@ export const HTTP_METHOD_OPTIONS = HTTP_METHODS.map(method => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const FORM_INITIAL_VALUES = {
|
export const FORM_INITIAL_VALUES = {
|
||||||
resourceId: "5d808014795dc6000482bc83",
|
|
||||||
actionConfiguration: {
|
actionConfiguration: {
|
||||||
headers: [
|
headers: [
|
||||||
{
|
{
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
queryParameters: [
|
queryParameters: [
|
||||||
{
|
{
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ export const theme: Theme = {
|
||||||
color: Colors.GEYSER_LIGHT,
|
color: Colors.GEYSER_LIGHT,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sidebarWidth: "300px",
|
sidebarWidth: "350px",
|
||||||
headerHeight: "50px",
|
headerHeight: "50px",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,17 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
FETCH_PROPERTY_PANE_CONFIGS_SUCCESS: "FETCH_PROPERTY_PANE_CONFIGS_SUCCESS",
|
FETCH_PROPERTY_PANE_CONFIGS_SUCCESS: "FETCH_PROPERTY_PANE_CONFIGS_SUCCESS",
|
||||||
FETCH_CONFIGS_INIT: "FETCH_CONFIGS_INIT",
|
FETCH_CONFIGS_INIT: "FETCH_CONFIGS_INIT",
|
||||||
ADD_WIDGET_REF: "ADD_WIDGET_REF",
|
ADD_WIDGET_REF: "ADD_WIDGET_REF",
|
||||||
CREATE_ACTION: "CREATE_ACTION",
|
CREATE_ACTION_INIT: "CREATE_ACTION_INIT",
|
||||||
|
CREATE_ACTION_SUCCESS: "CREATE_ACTION_SUCCESS",
|
||||||
FETCH_ACTIONS_INIT: "FETCH_ACTIONS_INIT",
|
FETCH_ACTIONS_INIT: "FETCH_ACTIONS_INIT",
|
||||||
FETCH_ACTIONS_SUCCESS: "FETCH_ACTIONS_SUCCESS",
|
FETCH_ACTIONS_SUCCESS: "FETCH_ACTIONS_SUCCESS",
|
||||||
FETCH_ACTION: "FETCH_ACTION",
|
FETCH_ACTION: "FETCH_ACTION",
|
||||||
RUN_ACTION: "RUN_ACTION",
|
RUN_ACTION_INIT: "RUN_ACTION_INIT",
|
||||||
RUN_ACTION_SUCCESS: "RUN_ACTION_SUCCESS",
|
RUN_ACTION_SUCCESS: "RUN_ACTION_SUCCESS",
|
||||||
|
UPDATE_ACTION_INIT: "UPDATE_ACTION_INIT",
|
||||||
|
UPDATE_ACTION_SUCCESS: "UPDATE_ACTION_SUCCESS",
|
||||||
|
DELETE_ACTION_INIT: "DELETE_ACTION_INIT",
|
||||||
|
DELETE_ACTION_SUCCESS: "DELETE_ACTION_SUCCESS",
|
||||||
UPDATE_ACTION: "UPDATE_ACTION",
|
UPDATE_ACTION: "UPDATE_ACTION",
|
||||||
DELETE_ACTION: "DELETE_ACTION",
|
DELETE_ACTION: "DELETE_ACTION",
|
||||||
FETCH_RESOURCES_INIT: "FETCH_RESOURCES_INIT",
|
FETCH_RESOURCES_INIT: "FETCH_RESOURCES_INIT",
|
||||||
|
|
@ -71,6 +76,8 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
||||||
PROPERTY_PANE_ERROR: "PROPERTY_PANE_ERROR",
|
PROPERTY_PANE_ERROR: "PROPERTY_PANE_ERROR",
|
||||||
FETCH_ACTIONS_ERROR: "FETCH_ACTIONS_ERROR",
|
FETCH_ACTIONS_ERROR: "FETCH_ACTIONS_ERROR",
|
||||||
UPDATE_WIDGET_PROPERTY_ERROR: "UPDATE_WIDGET_PROPERTY_ERROR",
|
UPDATE_WIDGET_PROPERTY_ERROR: "UPDATE_WIDGET_PROPERTY_ERROR",
|
||||||
|
UPDATE_ACTION_ERROR: "UPDATE_ACTION_ERROR",
|
||||||
|
DELETE_ACTION_ERROR: "DELETE_ACTION_ERROR",
|
||||||
FETCH_RESOURCES_ERROR: "FETCH_RESOURCES_ERROR",
|
FETCH_RESOURCES_ERROR: "FETCH_RESOURCES_ERROR",
|
||||||
CREATE_RESOURCE_ERROR: "CREATE_RESOURCE_ERROR",
|
CREATE_RESOURCE_ERROR: "CREATE_RESOURCE_ERROR",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Icon } from "@blueprintjs/core";
|
||||||
import { IconNames } from "@blueprintjs/icons";
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
import { IconProps, IconWrapper } from "../constants/IconConstants";
|
import { IconProps, IconWrapper } from "../constants/IconConstants";
|
||||||
import { ReactComponent as DeleteIcon } from "../assets/icons/form/trash.svg";
|
import { ReactComponent as DeleteIcon } from "../assets/icons/form/trash.svg";
|
||||||
|
import { ReactComponent as AddNewIcon } from "../assets/icons/form/add-new.svg";
|
||||||
|
|
||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
|
|
||||||
|
|
@ -14,6 +15,11 @@ export const FormIcons: {
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
),
|
),
|
||||||
|
ADD_NEW_ICON: (props: IconProps) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<AddNewIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
),
|
||||||
PLUS_ICON: (props: IconProps) => (
|
PLUS_ICON: (props: IconProps) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<Icon icon={IconNames.PLUS} color={props.color} iconSize={props.height} />
|
<Icon icon={IconNames.PLUS} color={props.color} iconSize={props.height} />
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import Editor from "./pages/Editor";
|
||||||
import PageNotFound from "./pages/common/PageNotFound";
|
import PageNotFound from "./pages/common/PageNotFound";
|
||||||
import LoginPage from "./pages/common/LoginPage";
|
import LoginPage from "./pages/common/LoginPage";
|
||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from "./serviceWorker";
|
||||||
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
import { Router, Route, Switch } from "react-router-dom";
|
||||||
import { createStore, applyMiddleware } from "redux";
|
import { createStore, applyMiddleware } from "redux";
|
||||||
|
import history from "./utils/history";
|
||||||
import appReducer from "./reducers";
|
import appReducer from "./reducers";
|
||||||
import { ThemeProvider, theme } from "./constants/DefaultTheme";
|
import { ThemeProvider, theme } from "./constants/DefaultTheme";
|
||||||
import createSagaMiddleware from "redux-saga";
|
import createSagaMiddleware from "redux-saga";
|
||||||
|
|
@ -32,14 +33,14 @@ ReactDOM.render(
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<BrowserRouter>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path={BASE_URL} component={App} />
|
<Route exact path={BASE_URL} component={App} />
|
||||||
<ProtectedRoute path={BUILDER_URL} component={Editor} />
|
<ProtectedRoute path={BUILDER_URL} component={Editor} />
|
||||||
<Route exact path={LOGIN_URL} component={LoginPage} />
|
<Route exact path={LOGIN_URL} component={LoginPage} />
|
||||||
<Route component={PageNotFound} />
|
<Route component={PageNotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</BrowserRouter>
|
</Router>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
</DndProvider>,
|
</DndProvider>,
|
||||||
|
|
|
||||||
15
app/client/src/normalizers/ApiFormNormalizer.ts
Normal file
15
app/client/src/normalizers/ApiFormNormalizer.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { RestAction } from "../api/ActionAPI";
|
||||||
|
|
||||||
|
export const normalizeApiFormData = (formData: any): RestAction => {
|
||||||
|
return {
|
||||||
|
...formData,
|
||||||
|
actionConfiguration: {
|
||||||
|
...formData.actionConfiguration,
|
||||||
|
body: formData.actionConfiguration.body
|
||||||
|
? typeof formData.actionConfiguration.body === "string"
|
||||||
|
? JSON.parse(formData.actionConfiguration.body)
|
||||||
|
: formData.actionConfiguration.body
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -15,10 +15,11 @@ import { API_EDITOR_URL } from "../../constants/routes";
|
||||||
import { API_EDITOR_FORM_NAME } from "../../constants/forms";
|
import { API_EDITOR_FORM_NAME } from "../../constants/forms";
|
||||||
import { ResourceDataState } from "../../reducers/entityReducers/resourcesReducer";
|
import { ResourceDataState } from "../../reducers/entityReducers/resourcesReducer";
|
||||||
import { fetchResources } from "../../actions/resourcesActions";
|
import { fetchResources } from "../../actions/resourcesActions";
|
||||||
|
import { FORM_INITIAL_VALUES } from "../../constants/ApiEditorConstants";
|
||||||
|
import { normalizeApiFormData } from "../../normalizers/ApiFormNormalizer";
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
actions: RestAction[];
|
actions: RestAction[];
|
||||||
response: any;
|
|
||||||
formData: any;
|
formData: any;
|
||||||
resources: ResourceDataState;
|
resources: ResourceDataState;
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +56,9 @@ class ApiEditor extends React.Component<Props> {
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<Props>): void {
|
componentDidUpdate(prevProps: Readonly<Props>): void {
|
||||||
const currentId = this.props.match.params.id;
|
const currentId = this.props.match.params.id;
|
||||||
|
if (!currentId && prevProps.match.params.id) {
|
||||||
|
this.props.initialize(API_EDITOR_FORM_NAME, FORM_INITIAL_VALUES);
|
||||||
|
}
|
||||||
if (currentId && currentId !== prevProps.match.params.id) {
|
if (currentId && currentId !== prevProps.match.params.id) {
|
||||||
const data = this.props.actions.filter(
|
const data = this.props.actions.filter(
|
||||||
action => action.id === currentId,
|
action => action.id === currentId,
|
||||||
|
|
@ -65,17 +69,7 @@ class ApiEditor extends React.Component<Props> {
|
||||||
|
|
||||||
handleSubmit = (values: RestAction) => {
|
handleSubmit = (values: RestAction) => {
|
||||||
const { formData } = this.props;
|
const { formData } = this.props;
|
||||||
const data: RestAction = {
|
const data = normalizeApiFormData(formData);
|
||||||
...formData,
|
|
||||||
actionConfiguration: {
|
|
||||||
...formData.actionConfiguration,
|
|
||||||
body: formData.actionConfiguration.body
|
|
||||||
? typeof formData.actionConfiguration.body === "string"
|
|
||||||
? JSON.parse(formData.actionConfiguration.body)
|
|
||||||
: formData.actionConfiguration.body
|
|
||||||
: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
this.props.updateAction(data);
|
this.props.updateAction(data);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -100,7 +94,7 @@ class ApiEditor extends React.Component<Props> {
|
||||||
onSaveClick={this.handleSaveClick}
|
onSaveClick={this.handleSaveClick}
|
||||||
onDeleteClick={this.handleDeleteClick}
|
onDeleteClick={this.handleDeleteClick}
|
||||||
onRunClick={this.handleRunClick}
|
onRunClick={this.handleRunClick}
|
||||||
response={this.props.response}
|
isEdit={!!this.props.match.params.id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +102,6 @@ class ApiEditor extends React.Component<Props> {
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||||
actions: state.entities.actions.data,
|
actions: state.entities.actions.data,
|
||||||
response: state.entities.actions.response,
|
|
||||||
formData: getFormValues(API_EDITOR_FORM_NAME)(state),
|
formData: getFormValues(API_EDITOR_FORM_NAME)(state),
|
||||||
resources: state.entities.resources,
|
resources: state.entities.resources,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,29 @@ import styled from "styled-components";
|
||||||
import { AppState } from "../../reducers";
|
import { AppState } from "../../reducers";
|
||||||
import { fetchActions } from "../../actions/actionActions";
|
import { fetchActions } from "../../actions/actionActions";
|
||||||
import { ActionDataState } from "../../reducers/entityReducers/actionsReducer";
|
import { ActionDataState } from "../../reducers/entityReducers/actionsReducer";
|
||||||
import { API_EDITOR_ID_URL } from "../../constants/routes";
|
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "../../constants/routes";
|
||||||
|
import { BaseButton } from "../../components/canvas/Button";
|
||||||
|
import { FormIcons } from "../../icons/FormIcons";
|
||||||
|
|
||||||
|
const ApiSidebarWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
const ApiItemsWrapper = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
const ApiItem = styled.div<{ isSelected: boolean }>`
|
const ApiItem = styled.div<{ isSelected: boolean }>`
|
||||||
|
height: 32px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px 10px;
|
padding: 5px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 2px;
|
||||||
background-color: ${props =>
|
background-color: ${props =>
|
||||||
props.isSelected ? props.theme.colors.paneCard : props.theme.colors.paneBG}
|
props.isSelected ? props.theme.colors.paneCard : props.theme.colors.paneBG}
|
||||||
:hover {
|
:hover {
|
||||||
|
|
@ -23,6 +37,7 @@ const ApiItem = styled.div<{ isSelected: boolean }>`
|
||||||
|
|
||||||
const HTTPMethod = styled.span<{ method: string | undefined }>`
|
const HTTPMethod = styled.span<{ method: string | undefined }>`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
font-size: 12px;
|
||||||
color: ${props => {
|
color: ${props => {
|
||||||
switch (props.method) {
|
switch (props.method) {
|
||||||
case "GET":
|
case "GET":
|
||||||
|
|
@ -41,6 +56,29 @@ const HTTPMethod = styled.span<{ method: string | undefined }>`
|
||||||
|
|
||||||
const ActionName = styled.span`
|
const ActionName = styled.span`
|
||||||
flex: 3;
|
flex: 3;
|
||||||
|
padding: 0 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CreateNewButton = styled(BaseButton)`
|
||||||
|
&& {
|
||||||
|
border: none;
|
||||||
|
color: ${props => props.theme.colors.textOnDarkBG};
|
||||||
|
height: 32px;
|
||||||
|
&:hover {
|
||||||
|
color: ${props => props.theme.colors.paneBG};
|
||||||
|
svg {
|
||||||
|
path {
|
||||||
|
fill: ${props => props.theme.colors.paneBG};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
|
|
@ -58,28 +96,42 @@ type Props = ReduxStateProps &
|
||||||
|
|
||||||
class ApiSidebar extends React.Component<Props> {
|
class ApiSidebar extends React.Component<Props> {
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
this.props.fetchActions();
|
if (!this.props.actions.data.length) {
|
||||||
|
this.props.fetchActions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCreateNew = () => {
|
||||||
|
const { history } = this.props;
|
||||||
|
history.push(API_EDITOR_URL);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { actions, history, match } = this.props;
|
const { actions, history, match } = this.props;
|
||||||
const activeActionId = match.params.id;
|
const activeActionId = match.params.id;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<ApiSidebarWrapper>
|
||||||
{actions.loading && "Loading..."}
|
<ApiItemsWrapper>
|
||||||
{actions.data.map(action => (
|
{actions.data.map(action => (
|
||||||
<ApiItem
|
<ApiItem
|
||||||
key={action.id}
|
key={action.id}
|
||||||
onClick={() => history.push(API_EDITOR_ID_URL(action.id))}
|
onClick={() => history.push(API_EDITOR_ID_URL(action.id))}
|
||||||
isSelected={activeActionId === action.id}
|
isSelected={activeActionId === action.id}
|
||||||
>
|
className={actions.loading ? "bp3-skeleton" : ""}
|
||||||
<HTTPMethod method={action.actionConfiguration.httpMethod}>
|
>
|
||||||
{action.actionConfiguration.httpMethod}
|
<HTTPMethod method={action.actionConfiguration.httpMethod}>
|
||||||
</HTTPMethod>
|
{action.actionConfiguration.httpMethod}
|
||||||
<ActionName>{action.name}</ActionName>
|
</HTTPMethod>
|
||||||
</ApiItem>
|
<ActionName>{action.name}</ActionName>
|
||||||
))}
|
</ApiItem>
|
||||||
</React.Fragment>
|
))}
|
||||||
|
</ApiItemsWrapper>
|
||||||
|
<CreateNewButton
|
||||||
|
text="Create new API"
|
||||||
|
icon={FormIcons.ADD_NEW_ICON()}
|
||||||
|
onClick={this.handleCreateNew}
|
||||||
|
/>
|
||||||
|
</ApiSidebarWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,25 @@ import { RestAction } from "../../api/ActionAPI";
|
||||||
const initialState: ActionDataState = {
|
const initialState: ActionDataState = {
|
||||||
list: {},
|
list: {},
|
||||||
data: [],
|
data: [],
|
||||||
response: {
|
responses: {},
|
||||||
body: null,
|
|
||||||
headers: null,
|
|
||||||
statusCode: "",
|
|
||||||
},
|
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ActionApiResponse {
|
||||||
|
body: JSON;
|
||||||
|
headers: any;
|
||||||
|
statusCode: string;
|
||||||
|
timeTaken: number;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ActionDataState {
|
export interface ActionDataState {
|
||||||
list: {
|
list: {
|
||||||
[name: string]: PageAction;
|
[name: string]: PageAction;
|
||||||
};
|
};
|
||||||
data: RestAction[];
|
data: RestAction[];
|
||||||
response: {
|
responses: {
|
||||||
body: any;
|
[id: string]: ActionApiResponse;
|
||||||
headers: any;
|
|
||||||
statusCode: string;
|
|
||||||
};
|
};
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -42,24 +44,51 @@ const actionsReducer = createReducer(initialState, {
|
||||||
});
|
});
|
||||||
return { ...state, list: { ...actionMap } };
|
return { ...state, list: { ...actionMap } };
|
||||||
},
|
},
|
||||||
[ReduxActionTypes.FETCH_ACTIONS_INIT]: (state: ActionDataState) => {
|
[ReduxActionTypes.FETCH_ACTIONS_INIT]: (state: ActionDataState) => ({
|
||||||
return { ...state, loading: true };
|
...state,
|
||||||
},
|
loading: true,
|
||||||
|
}),
|
||||||
[ReduxActionTypes.FETCH_ACTIONS_SUCCESS]: (
|
[ReduxActionTypes.FETCH_ACTIONS_SUCCESS]: (
|
||||||
state: ActionDataState,
|
state: ActionDataState,
|
||||||
action: ReduxAction<RestAction[]>,
|
action: ReduxAction<RestAction[]>,
|
||||||
) => {
|
) => ({
|
||||||
return { ...state, data: action.payload, loading: false };
|
...state,
|
||||||
},
|
data: action.payload,
|
||||||
[ReduxActionErrorTypes.FETCH_ACTIONS_ERROR]: (state: ActionDataState) => {
|
loading: false,
|
||||||
return { ...state, data: [], loading: false };
|
}),
|
||||||
},
|
[ReduxActionErrorTypes.FETCH_ACTIONS_ERROR]: (state: ActionDataState) => ({
|
||||||
|
...state,
|
||||||
|
data: [],
|
||||||
|
loading: false,
|
||||||
|
}),
|
||||||
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
||||||
state: ActionDataState,
|
state: ActionDataState,
|
||||||
action: ReduxAction<any>,
|
action: ReduxAction<{ [id: string]: ActionApiResponse }>,
|
||||||
) => {
|
) => ({ ...state, responses: { ...state.responses, ...action.payload } }),
|
||||||
return { ...state, response: action.payload };
|
[ReduxActionTypes.CREATE_ACTION_SUCCESS]: (
|
||||||
},
|
state: ActionDataState,
|
||||||
|
action: ReduxAction<RestAction>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
data: state.data.concat([action.payload]),
|
||||||
|
}),
|
||||||
|
[ReduxActionTypes.UPDATE_ACTION_SUCCESS]: (
|
||||||
|
state: ActionDataState,
|
||||||
|
action: ReduxAction<{ data: RestAction }>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
data: state.data.map(d => {
|
||||||
|
if (d.id === action.payload.data.id) return action.payload.data;
|
||||||
|
return d;
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[ReduxActionTypes.DELETE_ACTION_SUCCESS]: (
|
||||||
|
state: ActionDataState,
|
||||||
|
action: ReduxAction<{ id: string }>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
data: state.data.filter(d => d.id !== action.payload.id),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default actionsReducer;
|
export default actionsReducer;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,14 @@ import {
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
} from "../constants/ReduxActionConstants";
|
} from "../constants/ReduxActionConstants";
|
||||||
import { Intent } from "@blueprintjs/core";
|
import { Intent } from "@blueprintjs/core";
|
||||||
import { all, call, select, put, takeEvery } from "redux-saga/effects";
|
import {
|
||||||
|
all,
|
||||||
|
call,
|
||||||
|
select,
|
||||||
|
put,
|
||||||
|
takeEvery,
|
||||||
|
takeLatest,
|
||||||
|
} from "redux-saga/effects";
|
||||||
import { initialize } from "redux-form";
|
import { initialize } from "redux-form";
|
||||||
import { ActionPayload, PageAction } from "../constants/ActionConstants";
|
import { ActionPayload, PageAction } from "../constants/ActionConstants";
|
||||||
import ActionAPI, {
|
import ActionAPI, {
|
||||||
|
|
@ -18,8 +25,14 @@ import _ from "lodash";
|
||||||
import { mapToPropList } from "../utils/AppsmithUtils";
|
import { mapToPropList } from "../utils/AppsmithUtils";
|
||||||
import AppToaster from "../components/editor/ToastComponent";
|
import AppToaster from "../components/editor/ToastComponent";
|
||||||
import { GenericApiResponse } from "../api/ApiResponses";
|
import { GenericApiResponse } from "../api/ApiResponses";
|
||||||
import { fetchActions } from "../actions/actionActions";
|
|
||||||
import { API_EDITOR_FORM_NAME } from "../constants/forms";
|
import { API_EDITOR_FORM_NAME } from "../constants/forms";
|
||||||
|
import {
|
||||||
|
createActionSuccess,
|
||||||
|
deleteActionSuccess,
|
||||||
|
updateActionSuccess,
|
||||||
|
} from "../actions/actionActions";
|
||||||
|
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "../constants/routes";
|
||||||
|
import history from "../utils/history";
|
||||||
|
|
||||||
const getDataTree = (state: AppState) => {
|
const getDataTree = (state: AppState) => {
|
||||||
return state.entities;
|
return state.entities;
|
||||||
|
|
@ -82,7 +95,8 @@ export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
|
||||||
message: `${actionPayload.payload.name} Action created`,
|
message: `${actionPayload.payload.name} Action created`,
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
yield put(fetchActions());
|
yield put(createActionSuccess(response.data));
|
||||||
|
history.push(API_EDITOR_ID_URL(response.data.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,7 +130,7 @@ export function* runActionSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
const response: any = yield ActionAPI.executeAction({ actionId: id });
|
const response: any = yield ActionAPI.executeAction({ actionId: id });
|
||||||
yield put({
|
yield put({
|
||||||
type: ReduxActionTypes.RUN_ACTION_SUCCESS,
|
type: ReduxActionTypes.RUN_ACTION_SUCCESS,
|
||||||
payload: response,
|
payload: { [id]: response },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +149,7 @@ export function* updateActionSaga(
|
||||||
message: `${actionPayload.payload.data.name} Action updated`,
|
message: `${actionPayload.payload.data.name} Action updated`,
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
yield put(fetchActions());
|
yield put(updateActionSuccess({ data: response.data }));
|
||||||
} else {
|
} else {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: "Error occurred when updating action",
|
message: "Error occurred when updating action",
|
||||||
|
|
@ -154,7 +168,8 @@ export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
message: `${response.data.name} Action deleted`,
|
message: `${response.data.name} Action deleted`,
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
yield put(fetchActions());
|
yield put(deleteActionSuccess({ id }));
|
||||||
|
history.push(API_EDITOR_URL);
|
||||||
} else {
|
} else {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: "Error occurred when deleting action",
|
message: "Error occurred when deleting action",
|
||||||
|
|
@ -166,11 +181,11 @@ export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
export function* watchActionSagas() {
|
export function* watchActionSagas() {
|
||||||
yield all([
|
yield all([
|
||||||
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
|
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
|
||||||
takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeActionSaga),
|
takeLatest(ReduxActionTypes.EXECUTE_ACTION, executeActionSaga),
|
||||||
takeEvery(ReduxActionTypes.CREATE_ACTION, createActionSaga),
|
takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
|
||||||
takeEvery(ReduxActionTypes.FETCH_ACTION, fetchActionSaga),
|
takeEvery(ReduxActionTypes.FETCH_ACTION, fetchActionSaga),
|
||||||
takeEvery(ReduxActionTypes.RUN_ACTION, runActionSaga),
|
takeLatest(ReduxActionTypes.RUN_ACTION_INIT, runActionSaga),
|
||||||
takeEvery(ReduxActionTypes.UPDATE_ACTION, updateActionSaga),
|
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
|
||||||
takeEvery(ReduxActionTypes.DELETE_ACTION, deleteActionSaga),
|
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import ResourcesApi, {
|
||||||
CreateResourceConfig,
|
CreateResourceConfig,
|
||||||
Resource,
|
Resource,
|
||||||
} from "../api/ResourcesApi";
|
} from "../api/ResourcesApi";
|
||||||
|
import { API_EDITOR_FORM_NAME } from "../constants/forms";
|
||||||
|
|
||||||
function* fetchResourcesSaga() {
|
function* fetchResourcesSaga() {
|
||||||
const response: GenericApiResponse<
|
const response: GenericApiResponse<
|
||||||
|
|
@ -37,7 +38,7 @@ function* createResourceSaga(actionPayload: ReduxAction<CreateResourceConfig>) {
|
||||||
type: ReduxActionTypes.CREATE_RESOURCE_SUCCESS,
|
type: ReduxActionTypes.CREATE_RESOURCE_SUCCESS,
|
||||||
payload: response.data,
|
payload: response.data,
|
||||||
});
|
});
|
||||||
yield put(change("ApiEditorForm", "resourceId", response.data.id));
|
yield put(change(API_EDITOR_FORM_NAME, "resourceId", response.data.id));
|
||||||
} else {
|
} else {
|
||||||
yield put({
|
yield put({
|
||||||
type: ReduxActionTypes.CREATE_RESOURCE_ERROR,
|
type: ReduxActionTypes.CREATE_RESOURCE_ERROR,
|
||||||
|
|
|
||||||
2
app/client/src/utils/history.ts
Normal file
2
app/client/src/utils/history.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
const createHistory = require("history").createBrowserHistory;
|
||||||
|
export default createHistory();
|
||||||
|
|
@ -5587,7 +5587,7 @@ hex-color-regex@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||||
|
|
||||||
history@^4.9.0:
|
history@^4.10.1, history@^4.9.0:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||||
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user