diff --git a/app/client/package.json b/app/client/package.json index 1f5ebbc78a..f183c7a20c 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -74,6 +74,9 @@ "react-dnd-touch-backend": "^9.4.0", "react-dom": "^16.7.0", "react-helmet": "^5.2.1", + "react-infinite-scroller": "^1.2.4", + "react-json-view": "^1.19.1", + "react-paginating": "^1.4.0", "react-redux": "^7.1.3", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", diff --git a/app/client/src/actions/collectionAction.ts b/app/client/src/actions/collectionAction.ts new file mode 100644 index 0000000000..9b2bbcd62e --- /dev/null +++ b/app/client/src/actions/collectionAction.ts @@ -0,0 +1,7 @@ +import { ReduxActionTypes } from "constants/ReduxActionConstants"; + +export const fetchImportedCollections = () => { + return { + type: ReduxActionTypes.FETCH_IMPORTED_COLLECTIONS_INIT, + }; +}; diff --git a/app/client/src/actions/importActions.ts b/app/client/src/actions/importActions.ts new file mode 100644 index 0000000000..63e800e663 --- /dev/null +++ b/app/client/src/actions/importActions.ts @@ -0,0 +1,9 @@ +import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { curlImportFormValues } from "pages/Editor/APIEditor/helpers"; + +export const submitCurlImportForm = (payload: curlImportFormValues) => { + return { + type: ReduxActionTypes.SUBMIT_CURL_FORM_INIT, + payload, + }; +}; diff --git a/app/client/src/actions/providerActions.ts b/app/client/src/actions/providerActions.ts new file mode 100644 index 0000000000..2ac702309a --- /dev/null +++ b/app/client/src/actions/providerActions.ts @@ -0,0 +1,46 @@ +import { ReduxActionTypes } from "constants/ReduxActionConstants"; + +import { + AddApiToPageRequest, + FetchProviderWithCategoryRequest, +} from "api/ProvidersApi"; + +export const fetchProviders = () => { + return { + type: ReduxActionTypes.FETCH_PROVIDERS_INIT, + }; +}; + +export const fetchProviderCategories = () => { + return { + type: ReduxActionTypes.FETCH_PROVIDERS_CATEGORIES_INIT, + }; +}; + +export const fetchProviderTemplates = () => { + return { + type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT, + }; +}; + +export const addApiToPage = (payload: AddApiToPageRequest) => { + return { + type: ReduxActionTypes.ADD_API_TO_PAGE_INIT, + payload, + }; +}; + +export const fetchProvidersWithCategory = ( + payload: FetchProviderWithCategoryRequest, +) => { + return { + type: ReduxActionTypes.FETCH_PROVIDERS_WITH_CATEGORY_INIT, + payload, + }; +}; + +export const clearProviders = () => { + return { + type: ReduxActionTypes.CLEAR_PROVIDERS, + }; +}; diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index bbbba58866..8498f24397 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -33,6 +33,15 @@ export interface Property { value: string; } +export interface BodyFormData { + editable: boolean; + mandatory: boolean; + description: string; + key: string; + value?: string; + type: string; +} + export interface APIConfigRequest { headers: Property[]; httpMethod: string; @@ -40,6 +49,7 @@ export interface APIConfigRequest { body: JSON | string | Record | null; queryParameters: Property[]; paginationType: PaginationType; + bodyFormData: BodyFormData[]; } export interface QueryConfig { @@ -60,6 +70,31 @@ export interface RestAction { actionConfiguration: Partial; jsonPathKeys: string[]; cacheResponse?: string; + pluginId: string; +} + +export interface RapidApiAction { + id: string; + name: string; + datasource: Pick | Omit; + pluginType: string; + pageId: string; + actionConfiguration: Partial; + jsonPathKeys: string[]; + cacheResponse?: string; + templateId: string; + proverId: string; + provider: ProviderInfo; + pluginId: string; + documentation: { text: string }; +} + +export interface ProviderInfo { + name: string; + imageUrl: string; + url: string; + description: string; + credentialSteps: string; } export type PaginationField = "PREV" | "NEXT"; diff --git a/app/client/src/api/CollectionApi.ts b/app/client/src/api/CollectionApi.ts new file mode 100644 index 0000000000..426d867fb7 --- /dev/null +++ b/app/client/src/api/CollectionApi.ts @@ -0,0 +1,12 @@ +import { AxiosPromise } from "axios"; +import Api from "./Api"; +import { ImportedCollections } from "constants/collectionsConstants"; + +class ImportedCollectionsApi extends Api { + static importedCollectionsURL = "v1/import/templateCollections"; + static fetchImportedCollections(): AxiosPromise { + return Api.get(ImportedCollectionsApi.importedCollectionsURL); + } +} + +export default ImportedCollectionsApi; diff --git a/app/client/src/api/ImportApi.ts b/app/client/src/api/ImportApi.ts new file mode 100644 index 0000000000..25f890a4bc --- /dev/null +++ b/app/client/src/api/ImportApi.ts @@ -0,0 +1,25 @@ +import { AxiosPromise } from "axios"; +import Api from "./Api"; +import { ApiResponse } from "./ApiResponses"; + +export interface CurlImportRequest { + type: string; + pageId: string; + name: string; + curl: string; +} + +class CurlImportApi extends Api { + static curlImportURL = `v1/import`; + + static curlImport(request: CurlImportRequest): AxiosPromise { + const { pageId, name, curl } = request; + return Api.post(CurlImportApi.curlImportURL, curl, { + type: "CURL", + pageId, + name, + }); + } +} + +export default CurlImportApi; diff --git a/app/client/src/api/PluginApi.ts b/app/client/src/api/PluginApi.ts index ebb5dbb751..96df6a1061 100644 --- a/app/client/src/api/PluginApi.ts +++ b/app/client/src/api/PluginApi.ts @@ -6,6 +6,8 @@ export interface Plugin { id: string; name: string; type: "API" | "DB"; + packageName: string; + uiComponent: "ApiEditorForm" | "RapidApiEditorForm" | "DbEditorForm"; } class PluginsApi extends Api { diff --git a/app/client/src/api/ProvidersApi.ts b/app/client/src/api/ProvidersApi.ts new file mode 100644 index 0000000000..0006c2da45 --- /dev/null +++ b/app/client/src/api/ProvidersApi.ts @@ -0,0 +1,78 @@ +import { AxiosPromise } from "axios"; +import Api from "./Api"; +import { ApiResponse } from "./ApiResponses"; +import { Providers, ProviderTemplates } from "constants/providerConstants"; + +export interface FetchProvidersResponse extends ApiResponse { + data: Providers; +} + +export interface FetchProviderCategoriesResponse extends ApiResponse { + data: string[]; +} + +export interface FetchProviderTemplateResponse extends ApiResponse { + data: ProviderTemplates[]; +} + +export interface FetchProviderTemplatesRequest { + providerId: string; +} + +export interface FetchProviderWithCategoryRequest { + category: string; + page: number; +} + +export interface AddApiToPageRequest { + name: string; + pageId: string; + marketplaceElement: any; +} + +export class ProvidersApi extends Api { + static providersURL = "v1/providers"; + static providerCategoriesURL = "v1/providers/categories"; + + static providerTemplateURL = (providerId: string) => { + return `v1/marketplace/templates?providerId=${providerId}`; + }; + + static providersWithCategoryURL = (category: string, page: number) => { + return `v1/marketplace/providers?category=${category}&page=${page}&size=50`; + }; + + static addApiToPageURL = `v1/items/addToPage`; + + static fetchProviders(): AxiosPromise { + return Api.get(ProvidersApi.providersURL); + } + + static fetchProviderTemplates( + request: FetchProviderTemplatesRequest, + ): AxiosPromise { + const { providerId } = request; + return Api.get(ProvidersApi.providerTemplateURL(providerId)); + } + + static addApiToPage(request: AddApiToPageRequest): AxiosPromise { + return Api.post(ProvidersApi.addApiToPageURL, request); + } + + static fetchProvidersCategories(): AxiosPromise< + FetchProviderCategoriesResponse + > { + return Api.get(ProvidersApi.providerCategoriesURL); + } + + static fetchProvidersWithCategory( + request: FetchProviderWithCategoryRequest, + ): AxiosPromise { + const { page } = request; + return Api.get( + ProvidersApi.providersWithCategoryURL(request.category, page), + ); + } +} + +export default ProvidersApi; diff --git a/app/client/src/assets/icons/form/info-outline.svg b/app/client/src/assets/icons/form/info-outline.svg new file mode 100644 index 0000000000..0cfe7d23e0 --- /dev/null +++ b/app/client/src/assets/icons/form/info-outline.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/client/src/assets/images/Curl-logo.svg b/app/client/src/assets/images/Curl-logo.svg new file mode 100644 index 0000000000..e574f5f631 --- /dev/null +++ b/app/client/src/assets/images/Curl-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/client/src/assets/images/Curl.png b/app/client/src/assets/images/Curl.png new file mode 100644 index 0000000000..474feeef84 Binary files /dev/null and b/app/client/src/assets/images/Curl.png differ diff --git a/app/client/src/assets/images/Postman-logo.svg b/app/client/src/assets/images/Postman-logo.svg new file mode 100644 index 0000000000..9fb1dc9a81 --- /dev/null +++ b/app/client/src/assets/images/Postman-logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/client/src/assets/images/Postman.png b/app/client/src/assets/images/Postman.png new file mode 100644 index 0000000000..cfd823454d Binary files /dev/null and b/app/client/src/assets/images/Postman.png differ diff --git a/app/client/src/assets/images/no_image.png b/app/client/src/assets/images/no_image.png new file mode 100644 index 0000000000..cb274965d4 Binary files /dev/null and b/app/client/src/assets/images/no_image.png differ diff --git a/app/client/src/components/designSystems/appsmith/Dropdown.tsx b/app/client/src/components/designSystems/appsmith/Dropdown.tsx index 5be9a2bb54..cc48be9a33 100644 --- a/app/client/src/components/designSystems/appsmith/Dropdown.tsx +++ b/app/client/src/components/designSystems/appsmith/Dropdown.tsx @@ -12,6 +12,8 @@ type DropdownProps = { input: WrappedFieldInputProps; placeholder: string; width?: number; + isSearchable?: boolean; + isDisabled?: boolean; }; const selectStyles = { @@ -56,6 +58,8 @@ export const BaseDropdown = (props: DropdownProps) => { {...input} width={props.width} onChange={value => input.onChange(value)} + isSearchable={props.isSearchable} + isDisabled={props.isDisabled} /> ); }; diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index 66f260a025..f9eb323769 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -13,6 +13,7 @@ import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer"; import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen"; import CodeEditor from "components/editorComponents/CodeEditor"; import { getActionResponses } from "selectors/entitiesSelector"; +import { Colors } from "constants/Colors"; const ResponseWrapper = styled.div` position: relative; @@ -30,7 +31,7 @@ const ResponseMetaInfo = styled.div` const StatusCodeText = styled(BaseText)<{ code: string }>` color: ${props => - props.code.match(/2\d\d/) ? props.theme.colors.primary : "red"}; + props.code.match(/2\d\d/) ? props.theme.colors.primary : Colors.RED}; `; const TableWrapper = styled.div` diff --git a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx index 5b0a833027..9c5778cc7b 100644 --- a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx +++ b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx @@ -13,6 +13,7 @@ import "codemirror/addon/display/autorefresh"; import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors"; import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants"; import ErrorTooltip from "components/editorComponents/ErrorTooltip"; +import HelperTooltip from "components/editorComponents/HelperTooltip"; import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form"; import _ from "lodash"; import { parseDynamicString } from "utils/DynamicBindingUtils"; @@ -27,6 +28,7 @@ const getBorderStyle = ( hasError: boolean; singleLine: boolean; isFocused: boolean; + disabled?: boolean; }, ) => { if (props.hasError) return props.theme.colors.error; @@ -79,6 +81,7 @@ const Wrapper = styled.div<{ hasError: boolean; singleLine: boolean; isFocused: boolean; + disabled?: boolean; }>` ${props => props.singleLine && props.isFocused @@ -91,7 +94,8 @@ const Wrapper = styled.div<{ ` : `z-index: 0; position: relative`} background-color: ${props => - props.editorTheme === THEMES.DARK ? "#272822" : "#fff"} + props.editorTheme === THEMES.DARK ? "#272822" : "#fff"}; + background-color: ${props => props.disabled && "#eef2f5"}; border: 1px solid; border-color: ${getBorderStyle}; border-radius: 4px; @@ -115,6 +119,13 @@ const Wrapper = styled.div<{ border-radius: 4px; height: auto; } + ${props => + props.disabled && + ` + .CodeMirror-cursor { + display: none !important; + } + `} .CodeMirror pre.CodeMirror-placeholder { color: #a3b3bf; } @@ -129,6 +140,25 @@ const Wrapper = styled.div<{ } `} } + && { + .CodeMirror-lines { + background-color: ${props => props.disabled && "#eef2f5"}; + cursor: ${props => (props.disabled ? "not-allowed" : "text")} + } + } + .bp3-popover-target { + padding-right: 10px; + padding-top: 5px; + } + .leftImageStyles { + width: 20px; + height: 20px; + margin: 5px; + } + .linkStyles { + margin: 5px; + margin-right: 11px; + } `; const IconContainer = styled.div` @@ -149,6 +179,9 @@ const IconContainer = styled.div` } } } + .bp3-popover-target { + padding-right: 10px; + } `; const THEMES = { @@ -165,12 +198,17 @@ interface ReduxStateProps { export type DynamicAutocompleteInputProps = { placeholder?: string; leftIcon?: Function; + rightIcon?: Function; + description?: string; height?: number; theme?: THEME; meta?: Partial; showLineNumbers?: boolean; allowTabIndent?: boolean; singleLine: boolean; + disabled?: boolean; + leftImage?: string; + link?: string; }; type Props = ReduxStateProps & @@ -199,7 +237,10 @@ class DynamicAutocompleteInput extends Component { if (this.textArea.current) { const options: EditorConfiguration = {}; if (this.props.theme === "DARK") options.theme = "monokai"; - if (!this.props.input.onChange) options.readOnly = true; + if (!this.props.input.onChange || this.props.disabled) { + options.readOnly = true; + options.scrollbarStyle = "null"; + } if (this.props.showLineNumbers) options.lineNumbers = true; const extraKeys: Record = { "Ctrl-Space": "autocomplete", @@ -350,7 +391,7 @@ class DynamicAutocompleteInput extends Component { }; render() { - const { input, meta, theme, singleLine } = this.props; + const { input, meta, theme, singleLine, disabled } = this.props; const hasError = !!(meta && meta.error); let showError = false; if (this.editor) { @@ -364,17 +405,45 @@ class DynamicAutocompleteInput extends Component { hasError={hasError} singleLine={singleLine} isFocused={this.state.isFocused} + disabled={disabled} > {this.props.leftIcon && } + + {this.props.leftImage && ( + img + )} +