fix: ui fixes and type defination fixes
- avoid using any or undefined types in the code - fix ui issues for api home screen - update naming convensions - remove unwanted code - use color variables
This commit is contained in:
parent
526b9671d8
commit
af3b5d212f
|
|
@ -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",
|
||||
|
|
|
|||
7
app/client/src/actions/collectionAction.ts
Normal file
7
app/client/src/actions/collectionAction.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
|
||||
export const fetchImportedCollections = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_IMPORTED_COLLECTIONS_INIT,
|
||||
};
|
||||
};
|
||||
9
app/client/src/actions/importActions.ts
Normal file
9
app/client/src/actions/importActions.ts
Normal file
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
46
app/client/src/actions/providerActions.ts
Normal file
46
app/client/src/actions/providerActions.ts
Normal file
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
@ -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<string, any> | null;
|
||||
queryParameters: Property[];
|
||||
paginationType: PaginationType;
|
||||
bodyFormData: BodyFormData[];
|
||||
}
|
||||
|
||||
export interface QueryConfig {
|
||||
|
|
@ -60,6 +70,31 @@ export interface RestAction {
|
|||
actionConfiguration: Partial<APIConfigRequest>;
|
||||
jsonPathKeys: string[];
|
||||
cacheResponse?: string;
|
||||
pluginId: string;
|
||||
}
|
||||
|
||||
export interface RapidApiAction {
|
||||
id: string;
|
||||
name: string;
|
||||
datasource: Pick<Datasource, "id"> | Omit<Datasource, "id">;
|
||||
pluginType: string;
|
||||
pageId: string;
|
||||
actionConfiguration: Partial<APIConfigRequest>;
|
||||
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";
|
||||
|
|
|
|||
12
app/client/src/api/CollectionApi.ts
Normal file
12
app/client/src/api/CollectionApi.ts
Normal file
|
|
@ -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<ImportedCollections> {
|
||||
return Api.get(ImportedCollectionsApi.importedCollectionsURL);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportedCollectionsApi;
|
||||
25
app/client/src/api/ImportApi.ts
Normal file
25
app/client/src/api/ImportApi.ts
Normal file
|
|
@ -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<ApiResponse> {
|
||||
const { pageId, name, curl } = request;
|
||||
return Api.post(CurlImportApi.curlImportURL, curl, {
|
||||
type: "CURL",
|
||||
pageId,
|
||||
name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default CurlImportApi;
|
||||
|
|
@ -6,6 +6,8 @@ export interface Plugin {
|
|||
id: string;
|
||||
name: string;
|
||||
type: "API" | "DB";
|
||||
packageName: string;
|
||||
uiComponent: "ApiEditorForm" | "RapidApiEditorForm" | "DbEditorForm";
|
||||
}
|
||||
|
||||
class PluginsApi extends Api {
|
||||
|
|
|
|||
78
app/client/src/api/ProvidersApi.ts
Normal file
78
app/client/src/api/ProvidersApi.ts
Normal file
|
|
@ -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<FetchProvidersResponse> {
|
||||
return Api.get(ProvidersApi.providersURL);
|
||||
}
|
||||
|
||||
static fetchProviderTemplates(
|
||||
request: FetchProviderTemplatesRequest,
|
||||
): AxiosPromise<FetchProviderTemplateResponse> {
|
||||
const { providerId } = request;
|
||||
return Api.get(ProvidersApi.providerTemplateURL(providerId));
|
||||
}
|
||||
|
||||
static addApiToPage(request: AddApiToPageRequest): AxiosPromise<ApiResponse> {
|
||||
return Api.post(ProvidersApi.addApiToPageURL, request);
|
||||
}
|
||||
|
||||
static fetchProvidersCategories(): AxiosPromise<
|
||||
FetchProviderCategoriesResponse
|
||||
> {
|
||||
return Api.get(ProvidersApi.providerCategoriesURL);
|
||||
}
|
||||
|
||||
static fetchProvidersWithCategory(
|
||||
request: FetchProviderWithCategoryRequest,
|
||||
): AxiosPromise<FetchProvidersResponse> {
|
||||
const { page } = request;
|
||||
return Api.get(
|
||||
ProvidersApi.providersWithCategoryURL(request.category, page),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ProvidersApi;
|
||||
13
app/client/src/assets/icons/form/info-outline.svg
Normal file
13
app/client/src/assets/icons/form/info-outline.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon/Outline/info">
|
||||
<path id="Mask" fill-rule="evenodd" clip-rule="evenodd" d="M11 8.31554C11 7.75751 11.448 7.30461 12 7.30461C12.552 7.30461 13 7.75751 13 8.31554C13 8.87357 12.552 9.32646 12 9.32646C11.448 9.32646 11 8.87357 11 8.31554ZM11 11.3483C11 10.7903 11.448 10.3374 12 10.3374C12.552 10.3374 13 10.7903 13 11.3483V16.4029C13 16.9609 12.552 17.4138 12 17.4138C11.448 17.4138 11 16.9609 11 16.4029V11.3483ZM12 20.4466C7.589 20.4466 4 16.8184 4 12.3592C4 7.90005 7.589 4.27185 12 4.27185C16.411 4.27185 20 7.90005 20 12.3592C20 16.8184 16.411 20.4466 12 20.4466ZM12 2.25C6.477 2.25 2 6.7759 2 12.3592C2 17.9426 6.477 22.4685 12 22.4685C17.522 22.4685 22 17.9426 22 12.3592C22 6.7759 17.522 2.25 12 2.25Z" fill="#A3B3BF"/>
|
||||
<mask id="mask25" mask-type="alpha" maskUnits="userSpaceOnUse" x="2" y="2" width="20" height="21">
|
||||
<path id="Mask_2" fill-rule="evenodd" clip-rule="evenodd" d="M11 8.31554C11 7.75751 11.448 7.30461 12 7.30461C12.552 7.30461 13 7.75751 13 8.31554C13 8.87357 12.552 9.32646 12 9.32646C11.448 9.32646 11 8.87357 11 8.31554ZM11 11.3483C11 10.7903 11.448 10.3374 12 10.3374C12.552 10.3374 13 10.7903 13 11.3483V16.4029C13 16.9609 12.552 17.4138 12 17.4138C11.448 17.4138 11 16.9609 11 16.4029V11.3483ZM12 20.4466C7.589 20.4466 4 16.8184 4 12.3592C4 7.90005 7.589 4.27185 12 4.27185C16.411 4.27185 20 7.90005 20 12.3592C20 16.8184 16.411 20.4466 12 20.4466ZM12 2.25C6.477 2.25 2 6.7759 2 12.3592C2 17.9426 6.477 22.4685 12 22.4685C17.522 22.4685 22 17.9426 22 12.3592C22 6.7759 17.522 2.25 12 2.25Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask25)">
|
||||
<g id="🎨 Color">
|
||||
<rect id="Base" y="0.228027" width="24" height="24.2621" fill="#A3B3BF"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
10
app/client/src/assets/images/Curl-logo.svg
Normal file
10
app/client/src/assets/images/Curl-logo.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="53" height="52" viewBox="0 0 53 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M48.8083 14.9576C48.4317 14.9576 48.127 14.6581 48.127 14.2891C48.127 13.9196 48.4317 13.6207 48.8083 13.6207C49.1843 13.6207 49.4896 13.9196 49.4896 14.2891C49.4896 14.6581 49.1843 14.9576 48.8083 14.9576ZM42.0422 26.3782C41.6656 26.3782 41.3609 26.0787 41.3609 25.7098C41.3609 25.3403 41.6656 25.0413 42.0422 25.0413C42.4182 25.0413 42.7229 25.3403 42.7229 25.7098C42.7229 26.0787 42.4182 26.3782 42.0422 26.3782ZM48.8083 13.0654C48.1195 13.0654 47.5611 13.6133 47.5611 14.2891C47.5611 14.4333 47.5981 14.5675 47.6448 14.696L41.7938 24.535C41.2284 24.6513 40.7949 25.1203 40.7949 25.7098C40.7949 26.3856 41.3534 26.9335 42.0422 26.9335C42.7304 26.9335 43.2888 26.3856 43.2888 25.7098C43.2888 25.574 43.2518 25.4498 43.2105 25.3277L49.091 15.4565C49.6392 15.3276 50.0555 14.8665 50.0555 14.2891C50.0555 13.6133 49.4971 13.0654 48.8083 13.0654Z" fill="#0C544C"/>
|
||||
<path d="M43.7068 14.9576C43.3302 14.9576 43.0255 14.6581 43.0255 14.2891C43.0255 13.9196 43.3302 13.6207 43.7068 13.6207C44.0828 13.6207 44.3875 13.9196 44.3875 14.2891C44.3875 14.6581 44.0828 14.9576 43.7068 14.9576ZM36.9401 26.3782C36.5641 26.3782 36.2588 26.0787 36.2588 25.7098C36.2588 25.3403 36.5641 25.0413 36.9401 25.0413C37.3167 25.0413 37.6214 25.3403 37.6214 25.7098C37.6214 26.0787 37.3167 26.3782 36.9401 26.3782ZM43.7068 13.0654C43.0175 13.0654 42.4596 13.6133 42.4596 14.2891C42.4596 14.4333 42.4966 14.5675 42.5432 14.696L36.6923 24.535C36.1263 24.6513 35.6929 25.1203 35.6929 25.7098C35.6929 26.3856 36.2513 26.9335 36.9401 26.9335C37.6289 26.9335 38.1873 26.3856 38.1873 25.7098C38.1873 25.574 38.1503 25.4498 38.109 25.3277L43.9895 15.4565C44.5377 15.3276 44.954 14.8665 44.954 14.2891C44.954 13.6133 44.395 13.0654 43.7068 13.0654Z" fill="#073551"/>
|
||||
<path d="M34.3881 17.0034C34.7641 17.0034 35.0694 17.3029 35.0694 17.6718C35.0694 18.0408 34.7641 18.3402 34.3881 18.3402C34.0115 18.3402 33.7068 18.0408 33.7068 17.6718C33.7068 17.3029 34.0115 17.0034 34.3881 17.0034ZM34.3881 18.8955C35.0769 18.8955 35.6353 18.3476 35.6353 17.6718C35.6353 17.5366 35.5978 17.4118 35.5565 17.2897C35.3912 16.8039 34.9385 16.4481 34.3881 16.4481C34.3001 16.4481 34.2229 16.4802 34.1397 16.4976C33.5743 16.6134 33.1409 17.0823 33.1409 17.6718C33.1409 18.3476 33.6993 18.8955 34.3881 18.8955Z" fill="#073551"/>
|
||||
<path d="M33.7068 21.9475C33.7068 21.578 34.012 21.279 34.3881 21.279C34.7641 21.279 35.0694 21.578 35.0694 21.9475C35.0694 22.3164 34.7641 22.6154 34.3881 22.6154C34.012 22.6154 33.7068 22.3164 33.7068 21.9475ZM35.6353 21.9475C35.6353 21.8117 35.5978 21.6874 35.5565 21.5653C35.3912 21.0795 34.939 20.7238 34.3881 20.7238C34.3001 20.7238 34.2229 20.7559 34.1397 20.7727C33.5743 20.889 33.1409 21.358 33.1409 21.9475C33.1409 22.6227 33.6993 23.1711 34.3881 23.1711C35.0769 23.1711 35.6353 22.6227 35.6353 21.9475Z" fill="#073551"/>
|
||||
<path d="M2.94458 23.3204C2.94458 24.2762 3.12697 24.7456 3.88657 25.3604L5.05816 26.3183C5.8006 26.9162 6.1804 26.933 7.10523 26.933H9.61578C10.1436 26.933 10.7058 26.8193 10.7058 26.1562C10.7058 25.4741 10.0793 25.3772 9.61578 25.3772H6.82628C6.51085 25.3772 6.19757 25.2151 5.91647 24.9878L4.95946 24.1941C4.71055 24.0004 4.5303 23.8383 4.5303 23.5309V21.6025C4.5303 21.2951 4.71055 21.133 4.95946 20.9372L5.91647 20.1435C6.19757 19.9182 6.51085 19.7561 6.82628 19.7561H9.61578C10.0793 19.7561 10.7058 19.6572 10.7058 18.9772C10.7058 18.314 10.1436 18.2003 9.61578 18.2003H7.10523C6.1804 18.2003 5.8006 18.2172 5.05816 18.8151L3.88657 19.7709C3.12697 20.3877 2.94458 20.8572 2.94458 21.813V23.3204Z" fill="#073551"/>
|
||||
<path d="M19.2927 19.2698C19.2927 18.7351 19.1769 18.2003 18.5009 18.2003C17.8229 18.2003 17.707 18.7351 17.707 19.2698V23.7898L15.3788 25.232C15.2308 25.3288 15.0656 25.3772 14.8832 25.3772H14.3553C13.9755 25.3772 13.5635 25.2804 13.4305 25.053C13.2974 24.8277 13.2824 24.4046 13.2653 24.0488L13.1 19.2698C13.0829 18.7666 13.0013 18.2003 12.3233 18.2003C11.598 18.2003 11.5143 18.8003 11.5315 19.3666L11.6967 24.2593C11.7289 25.1014 11.7139 25.7667 12.4241 26.3667C12.952 26.8025 13.4477 26.933 13.894 26.933H15.0656C15.4947 26.933 15.9239 26.7393 16.4367 26.4151L17.7242 25.5878V25.8467C17.7242 26.3814 17.84 26.933 18.516 26.933C19.3592 26.933 19.2927 26.1393 19.2927 25.7835V19.2698Z" fill="#073551"/>
|
||||
<path d="M20.1194 25.8635C20.1194 26.3983 20.2353 26.933 20.9112 26.933C21.5892 26.933 21.7051 26.3983 21.7051 25.8635V21.9267L23.9174 20.1288C24.1985 19.9014 24.4131 19.7561 24.7092 19.7561H25.1727C25.5525 19.7561 25.9323 19.7561 26.1297 20.0319C26.2949 20.2572 26.2949 20.453 26.2949 20.7119C26.2949 21.213 26.5267 21.6677 27.1039 21.6677C27.7819 21.6677 27.8806 21.1161 27.8806 20.5814C27.8806 19.8698 27.6982 19.3351 27.1704 18.8151C26.7412 18.394 26.2305 18.2003 25.619 18.2003H24.3465C23.8509 18.2003 23.3895 18.5077 22.9432 18.8635L21.7051 19.8698V19.2361C21.7051 18.7182 21.5399 18.2003 20.9112 18.2003C20.1859 18.2003 20.1194 18.7835 20.1194 19.3498V25.8635Z" fill="#073551"/>
|
||||
<path d="M30.7868 16.0781C30.7868 15.1539 30.5229 14.9918 29.6152 14.9918H27.948C27.418 14.9918 26.8579 15.1055 26.8579 15.7687C26.8579 16.4508 27.4845 16.5476 27.948 16.5476H29.2032V25.3772H27.948C27.418 25.3772 26.8579 25.4909 26.8579 26.1561C26.8579 26.8361 27.4845 26.933 27.948 26.933H31.9756C32.4391 26.933 33.0656 26.8361 33.0656 26.1561C33.0656 25.4909 32.5056 25.3772 31.9756 25.3772H30.7868V16.0781Z" fill="#073551"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
BIN
app/client/src/assets/images/Curl.png
Normal file
BIN
app/client/src/assets/images/Curl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
27
app/client/src/assets/images/Postman-logo.svg
Normal file
27
app/client/src/assets/images/Postman-logo.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<svg width="53" height="52" viewBox="0 0 53 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M11.7954 42.0293L3.52979 42.3105L7.76694 38.0839L11.7958 42.0296L11.7954 42.0293Z" fill="#F15A24"/>
|
||||
<path d="M49.2895 24.0654C47.7539 35.8129 36.8016 44.1127 24.8302 42.6043C12.8566 41.0974 4.39721 30.3521 5.93296 18.6055C7.46961 6.85816 18.4195 -1.4425 30.394 0.0660409C42.3669 1.57246 50.8283 12.3188 49.2897 24.0651" fill="#F15A24"/>
|
||||
<path d="M33.1847 12.2389C34.6209 13.7369 33.2034 17.2786 33.2034 17.2786C33.2034 17.2786 31.8025 22.2858 23.781 28.7792L22.2837 27.3372L32.4663 17.1264L32.6631 16.8658C32.934 16.4326 33.1583 12.2098 33.1847 12.2389Z" fill="#999999"/>
|
||||
<path d="M33.1847 12.2389C34.6209 13.7369 33.2034 17.2786 33.2034 17.2786C33.2034 17.2786 31.8025 22.2858 23.781 28.7792L22.2837 27.3372L32.4663 17.1264L32.6631 16.8658C32.934 16.4326 33.1583 12.2098 33.1847 12.2389V12.2389Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M20.0847 25.2145L33.1636 12.2021C33.8274 12.9755 33.9686 13.8087 33.776 14.996C33.6462 15.8183 32.1987 17.4616 32.1987 17.4616L22.2839 27.3374" fill="#666666"/>
|
||||
<path d="M20.0847 25.2145L33.1636 12.2021C33.8274 12.9755 33.9686 13.8087 33.776 14.996C33.6462 15.8183 32.1987 17.4616 32.1987 17.4616L22.2839 27.3374" stroke="white" stroke-width="0.150125" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M33.1842 12.2478L20.0838 25.2146L20.1102 25.1894L20.1053 25.1913L16.855 22.049C23.6328 13.7771 28.0152 12.2349 28.0152 12.2349C28.0152 12.2349 30.8681 11.1422 32.9034 11.9901C33.0598 12.0723 33.1277 12.1196 33.1838 12.2481" fill="#FF931E"/>
|
||||
<path d="M33.1842 12.2478L20.0838 25.2146L20.1102 25.1894L20.1053 25.1913L16.855 22.049C23.6328 13.7771 28.0152 12.2349 28.0152 12.2349C28.0152 12.2349 30.8681 11.1422 32.9034 11.9901C33.0598 12.0723 33.1277 12.1196 33.1838 12.2481L33.1842 12.2478V12.2478Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M23.7798 28.7788C20.6544 31.3042 16.6545 35.2715 11.2176 38.0385L12.4269 43.1108L7.30127 38.1575L20.083 25.2144" fill="#999999"/>
|
||||
<path d="M23.7798 28.7788C20.6544 31.3042 16.6545 35.2715 11.2176 38.0385L12.4269 43.1108L7.30127 38.1575L20.083 25.2144" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.9429 22.0915L20.1181 25.1947L12.7827 26.7537L16.8727 22.0356L16.9433 22.0917L16.9429 22.0915Z" fill="#F15A24"/>
|
||||
<path d="M16.9429 22.0915L20.1181 25.1947L12.7827 26.7537L16.8727 22.0356L16.9433 22.0917L16.9429 22.0915Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M31.935 8.48492C31.6235 11.2433 33.6476 13.7263 36.4588 14.0339C39.2679 14.3402 41.801 12.3527 42.1145 9.59542C42.4269 6.83812 40.3997 4.3524 37.5896 4.0459C34.778 3.74116 32.2468 5.72603 31.935 8.48492Z" fill="#999999"/>
|
||||
<path d="M31.935 8.48492C31.6235 11.2433 33.6476 13.7263 36.4588 14.0339C39.2679 14.3402 41.801 12.3527 42.1145 9.59542C42.4269 6.83812 40.3997 4.3524 37.5896 4.0459C34.778 3.74116 32.2468 5.72603 31.935 8.48492V8.48492Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M41.3785 11.8166C42.3232 10.4624 42.2743 8.43642 42.0847 7.83629C41.8519 7.10124 41.3314 5.90609 40.4233 5.28479L35.6216 9.62417C35.6216 9.62417 39.3118 13.5144 39.4079 13.5272C39.4478 13.5318 40.517 13.0514 41.3785 11.8166Z" fill="#666666"/>
|
||||
<path d="M41.3785 11.8166C42.3232 10.4624 42.2743 8.43642 42.0847 7.83629C41.8519 7.10124 41.3314 5.90609 40.4233 5.28479L35.6216 9.62417C35.6216 9.62417 39.3118 13.5144 39.4079 13.5272C39.4478 13.5318 40.517 13.0514 41.3785 11.8166V11.8166ZM40.4097 8.41737C40.4097 8.41737 40.8714 9.52964 40.2856 10.395L40.4097 8.41737Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.3969 27.339L17.0977 28.2643L20.2964 25.0035" fill="#666666"/>
|
||||
<path d="M17.0654 22.1143L23.8023 28.335M22.396 27.3391L17.0967 28.2644L20.2955 25.0036L22.396 27.3391Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="53" height="52" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/client/src/assets/images/Postman.png
Normal file
BIN
app/client/src/assets/images/Postman.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
app/client/src/assets/images/no_image.png
Normal file
BIN
app/client/src/assets/images/no_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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<WrappedFieldMetaProps>;
|
||||
showLineNumbers?: boolean;
|
||||
allowTabIndent?: boolean;
|
||||
singleLine: boolean;
|
||||
disabled?: boolean;
|
||||
leftImage?: string;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
type Props = ReduxStateProps &
|
||||
|
|
@ -199,7 +237,10 @@ class DynamicAutocompleteInput extends Component<Props, State> {
|
|||
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<string, any> = {
|
||||
"Ctrl-Space": "autocomplete",
|
||||
|
|
@ -350,7 +391,7 @@ class DynamicAutocompleteInput extends Component<Props, State> {
|
|||
};
|
||||
|
||||
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<Props, State> {
|
|||
hasError={hasError}
|
||||
singleLine={singleLine}
|
||||
isFocused={this.state.isFocused}
|
||||
disabled={disabled}
|
||||
>
|
||||
<HintStyles />
|
||||
<IconContainer>
|
||||
{this.props.leftIcon && <this.props.leftIcon />}
|
||||
</IconContainer>
|
||||
|
||||
{this.props.leftImage && (
|
||||
<img
|
||||
src={this.props.leftImage}
|
||||
alt="img"
|
||||
className="leftImageStyles"
|
||||
/>
|
||||
)}
|
||||
|
||||
<textarea
|
||||
ref={this.textArea}
|
||||
{..._.omit(this.props.input, ["onChange", "value"])}
|
||||
defaultValue={input.value}
|
||||
placeholder={this.props.placeholder}
|
||||
/>
|
||||
{this.props.link && (
|
||||
<React.Fragment>
|
||||
<a
|
||||
href={this.props.link}
|
||||
target="_blank"
|
||||
className="linkStyles"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
API documentation
|
||||
</a>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{this.props.rightIcon && (
|
||||
<HelperTooltip
|
||||
description={this.props.description}
|
||||
rightIcon={this.props.rightIcon}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
</ErrorTooltip>
|
||||
);
|
||||
|
|
|
|||
83
app/client/src/components/editorComponents/HelperTooltip.tsx
Normal file
83
app/client/src/components/editorComponents/HelperTooltip.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import React from "react";
|
||||
import { Popover, PopoverInteractionKind } from "@blueprintjs/core";
|
||||
import styled, { createGlobalStyle } from "styled-components";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const TooltipStyles = createGlobalStyle`
|
||||
.helper-tooltip{
|
||||
.bp3-popover {
|
||||
box-shadow: none;
|
||||
max-width: 258px;
|
||||
.bp3-popover-arrow {
|
||||
display: block;
|
||||
fill: none;
|
||||
}
|
||||
.bp3-popover-arrow-fill {
|
||||
fill: ${Colors.BLUE_CHARCOAL};
|
||||
}
|
||||
.bp3-popover-content {
|
||||
padding: 15px;
|
||||
background-color: ${Colors.BLUE_CHARCOAL};
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
border-radius: 4px;
|
||||
text-transform: initial;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
.bp3-icon {
|
||||
border-radius: 4px 0 0 4px;
|
||||
margin: 0;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #eef2f5;
|
||||
svg {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
path {
|
||||
fill: #979797;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bp3-popover-target {
|
||||
padding-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
description?: string;
|
||||
rightIcon: Function;
|
||||
}
|
||||
|
||||
const HelperTooltip = (props: Props) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TooltipStyles />
|
||||
<Popover
|
||||
autoFocus={true}
|
||||
canEscapeKeyClose={true}
|
||||
content={props.description}
|
||||
position="bottom"
|
||||
defaultIsOpen={false}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
usePortal
|
||||
portalClassName="helper-tooltip"
|
||||
>
|
||||
<IconContainer>
|
||||
<props.rightIcon width={22} height={22} />
|
||||
</IconContainer>
|
||||
</Popover>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelperTooltip;
|
||||
|
|
@ -6,6 +6,9 @@ import {
|
|||
BUILDER_URL,
|
||||
API_EDITOR_ID_URL,
|
||||
PAGE_LIST_EDITOR_URL,
|
||||
getCurlImportPageURL,
|
||||
API_EDITOR_URL_WITH_SELECTED_PAGE_ID,
|
||||
getProviderTemplatesURL,
|
||||
} from "constants/routes";
|
||||
import WidgetSidebar from "pages/Editor/WidgetSidebar";
|
||||
import ApiSidebar from "pages/Editor/ApiSidebar";
|
||||
|
|
@ -47,6 +50,25 @@ export const Sidebar = () => {
|
|||
component={PageListSidebar}
|
||||
name={"PageListSidebar"}
|
||||
/>
|
||||
<AppRoute
|
||||
exact
|
||||
path={getCurlImportPageURL()}
|
||||
component={ApiSidebar}
|
||||
name={"ApiSidebar"}
|
||||
/>
|
||||
<AppRoute
|
||||
exact
|
||||
path={getProviderTemplatesURL()}
|
||||
component={ApiSidebar}
|
||||
name={"ApiSidebar"}
|
||||
/>
|
||||
|
||||
<AppRoute
|
||||
exact
|
||||
path={API_EDITOR_URL_WITH_SELECTED_PAGE_ID()}
|
||||
component={ApiSidebar}
|
||||
name={"ApiSidebar"}
|
||||
/>
|
||||
</Switch>
|
||||
</SidebarWrapper>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const DatasourcesField = (
|
|||
const options = props.datasources.list
|
||||
.filter(r => r.pluginId === props.pluginId)
|
||||
.filter(r => r.datasourceConfiguration !== null)
|
||||
.filter(r => r.datasourceConfiguration.url)
|
||||
.map(r => ({
|
||||
label: r.datasourceConfiguration.url.endsWith("/")
|
||||
? r.datasourceConfiguration.url.slice(0, -1)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ interface DropdownFieldProps {
|
|||
}>;
|
||||
placeholder: string;
|
||||
width?: number;
|
||||
isSearchable?: boolean;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
const DropdownField = (props: DropdownFieldProps) => {
|
||||
|
|
@ -23,6 +25,8 @@ const DropdownField = (props: DropdownFieldProps) => {
|
|||
format={(value: string) => _.find(props.options, { value }) || ""}
|
||||
normalize={(option: { value: string }) => option.value}
|
||||
width={props.width}
|
||||
isSearchable={props.isSearchable}
|
||||
isDisabled={props.isDisabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { FieldArray, WrappedFieldArrayProps } from "redux-form";
|
||||
import styled from "styled-components";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import DynamicTextField from "./DynamicTextField";
|
||||
import FormRow from "components/editorComponents/FormRow";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import styled from "styled-components";
|
||||
|
||||
const FormRowWithLabel = styled(FormRow)`
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -20,7 +20,7 @@ const FormRowWithLabel = styled(FormRow)`
|
|||
const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
||||
useEffect(() => {
|
||||
// Always maintain 2 rows
|
||||
if (props.fields.length < 2) {
|
||||
if (props.fields.length < 2 && props.pushFields !== false) {
|
||||
for (let i = props.fields.length; i < 2; i += 1) {
|
||||
props.fields.push({ key: "", value: "" });
|
||||
}
|
||||
|
|
@ -29,37 +29,80 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{props.fields.map((field: any, index: number) => (
|
||||
<FormRowWithLabel key={index}>
|
||||
{index === 0 && <FormLabel>{props.label}</FormLabel>}
|
||||
<DynamicTextField
|
||||
name={`${field}.key`}
|
||||
placeholder="Key"
|
||||
singleLine
|
||||
/>
|
||||
<DynamicTextField
|
||||
name={`${field}.value`}
|
||||
placeholder="Value"
|
||||
singleLine
|
||||
/>
|
||||
{index === props.fields.length - 1 ? (
|
||||
<Icon
|
||||
icon="plus"
|
||||
iconSize={20}
|
||||
onClick={() => props.fields.push({ key: "", value: "" })}
|
||||
color={"#A3B3BF"}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
) : (
|
||||
<FormIcons.DELETE_ICON
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={() => props.fields.remove(index)}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
)}
|
||||
</FormRowWithLabel>
|
||||
))}
|
||||
{typeof props.fields.getAll() === "object" && (
|
||||
<React.Fragment>
|
||||
{props.fields.map((field: any, index: number) => (
|
||||
<FormRowWithLabel key={index}>
|
||||
{index === 0 && props.label !== "" && (
|
||||
<FormLabel>{props.label}</FormLabel>
|
||||
)}
|
||||
<DynamicTextField
|
||||
name={`${field}.key`}
|
||||
placeholder="Key"
|
||||
singleLine
|
||||
/>
|
||||
{!props.actionConfig && (
|
||||
<DynamicTextField
|
||||
name={`${field}.value`}
|
||||
placeholder="Value"
|
||||
singleLine
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.actionConfig && props.actionConfig[index] && (
|
||||
<React.Fragment>
|
||||
<DynamicTextField
|
||||
name={`${field}.value`}
|
||||
height={30}
|
||||
placeholder={
|
||||
props.placeholder
|
||||
? props.placeholder
|
||||
: props.actionConfig[index].mandatory &&
|
||||
props.actionConfig[index].type
|
||||
? `${props.actionConfig[index].type}`
|
||||
: props.actionConfig[index].type
|
||||
? `${props.actionConfig[index].type} (Optional)`
|
||||
: `(Optional)`
|
||||
}
|
||||
singleLine
|
||||
rightIcon={
|
||||
props.actionConfig[index].description && props.rightIcon
|
||||
}
|
||||
noArrows
|
||||
description={props.actionConfig[index].description}
|
||||
disabled={
|
||||
props.actionConfig[index].editable ||
|
||||
props.actionConfig[index].editable === undefined
|
||||
? false
|
||||
: true
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{props.addOrDeleteFields !== false && (
|
||||
<React.Fragment>
|
||||
{index === props.fields.length - 1 ? (
|
||||
<Icon
|
||||
icon="plus"
|
||||
iconSize={20}
|
||||
onClick={() => props.fields.push({ key: "", value: "" })}
|
||||
color={"#A3B3BF"}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
) : (
|
||||
<FormIcons.DELETE_ICON
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={() => props.fields.remove(index)}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</FormRowWithLabel>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
|
@ -67,6 +110,14 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
|||
type Props = {
|
||||
name: string;
|
||||
label: string;
|
||||
rightIcon?: Function;
|
||||
description?: string;
|
||||
actionConfig?: any;
|
||||
addOrDeleteFields?: boolean;
|
||||
mandatory?: boolean;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
pushFields?: boolean;
|
||||
};
|
||||
|
||||
const KeyValueFieldArray = (props: Props) => {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class TextField extends React.Component<
|
|||
component={BaseTextInput}
|
||||
{...this.props}
|
||||
noValidate
|
||||
disabled={this.props.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,17 @@ export const HTTP_METHOD_OPTIONS = HTTP_METHODS.map(method => ({
|
|||
}));
|
||||
|
||||
export const PLUGIN_NAME = "RestTemplatePluginExecutor";
|
||||
|
||||
export const REST_PLUGIN_PACKAGE_NAME = "restapi-plugin";
|
||||
export const DEFAULT_API_ACTION: Partial<RestAction> = {
|
||||
actionConfiguration: {
|
||||
httpMethod: HTTP_METHODS[0],
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_PROVIDER_OPTION = "Business Software";
|
||||
export const POST_BODY_FORMATS = ["application/json", "x-www-form-urlencoded"];
|
||||
|
||||
export const POST_BODY_FORMAT_OPTIONS = POST_BODY_FORMATS.map(method => ({
|
||||
label: method,
|
||||
value: method,
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export const Colors: Record<string, string> = {
|
|||
AZURE_RADIANCE: "#0384FE",
|
||||
OCEAN_GREEN: "#36AB80",
|
||||
BUTTER_CUP: "#F7AF22",
|
||||
BLUE_CHARCOAL: "#23292E",
|
||||
TROUT: "#4C565E",
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,22 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
FETCH_ACTIONS_FOR_PAGE_SUCCESS: "FETCH_ACTIONS_FOR_PAGE_SUCCESS",
|
||||
EXECUTE_API_ACTION_REQUEST: "EXECUTE_API_ACTION_REQUEST",
|
||||
EXECUTE_API_ACTION_SUCCESS: "EXECUTE_API_ACTION_SUCCESS",
|
||||
FETCH_IMPORTED_COLLECTIONS_INIT: "FETCH_IMPORTED_COLLECTIONS_INIT",
|
||||
FETCH_IMPORTED_COLLECTIONS_SUCCESS: "FETCH_IMPORTED_COLLECTIONS_SUCCESS",
|
||||
FETCH_PROVIDERS_INIT: "FETCH_PROVIDERS_INIT",
|
||||
FETCH_PROVIDERS_SUCCESS: "FETCH_PROVIDERS_SUCCESS",
|
||||
SUBMIT_CURL_FORM_INIT: "SUBMIT_CURL_FORM_INIT",
|
||||
SUBMIT_CURL_FORM_SUCCESS: "SUBMIT_CURL_FORM_SUCCESS",
|
||||
SET_WIDGET_DYNAMIC_PROPERTY: "SET_WIDGET_DYNAMIC_PROPERTY",
|
||||
FETCH_PROVIDER_TEMPLATES_INIT: "FETCH_PROVIDER_TEMPLATES_INIT",
|
||||
FETCH_PROVIDER_TEMPLATES_SUCCESS: "FETCH_PROVIDER_TEMPLATES_SUCCESS",
|
||||
ADD_API_TO_PAGE_INIT: "ADD_API_TO_PAGE_INIT",
|
||||
ADD_API_TO_PAGE_SUCCESS: "ADD_API_TO_PAGE_SUCCESS",
|
||||
FETCH_PROVIDERS_CATEGORIES_INIT: "FETCH_PROVIDERS_CATEGORIES_INIT",
|
||||
FETCH_PROVIDERS_CATEGORIES_SUCCESS: "FETCH_PROVIDERS_CATEGORIES_SUCCESS",
|
||||
FETCH_PROVIDERS_WITH_CATEGORY_INIT: "FETCH_PROVIDERS_WITH_CATEGORY_INIT",
|
||||
FETCH_PROVIDERS_WITH_CATEGORY_SUCCESS:
|
||||
"FETCH_PROVIDERS_WITH_CATEGORY_SUCCESS",
|
||||
SHOW_MODAL: "SHOW_MODAL",
|
||||
SHOW_MODAL_BY_NAME: "SHOW_MODAL_BY_NAME",
|
||||
CLOSE_MODAL: "CLOSE_MODAL",
|
||||
|
|
@ -158,6 +173,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
CREATE_MODAL_SUCCESS: "CREATE_MODAL_SUCCESS",
|
||||
UPDATE_CANVAS_SIZE: "UPDATE_CANVAS_SIZE",
|
||||
UPDATE_CURRENT_PAGE: "UPDATE_CURRENT_PAGE",
|
||||
CLEAR_PROVIDERS: "CLEAR_PROVIDERS",
|
||||
BATCHED_UPDATE: "BATCHED_UPDATE",
|
||||
EXECUTE_BATCH: "EXECUTE_BATCH",
|
||||
};
|
||||
|
|
@ -224,6 +240,13 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
UPDATE_WIDGET_NAME_ERROR: "UPDATE_WIDGET_NAME_ERROR",
|
||||
FETCH_ACTIONS_FOR_PAGE_ERROR: "FETCH_ACTIONS_FOR_PAGE_ERROR",
|
||||
EXECUTE_API_ACTION_ERROR: "EXECUTE_API_ACTION_ERROR",
|
||||
FETCH_IMPORTED_COLLECTIONS_ERROR: "FETCH_IMPORTED_COLLECTIONS_ERROR",
|
||||
FETCH_PROVIDERS_ERROR: "FETCH_PROVIDERS_ERROR",
|
||||
SUBMIT_CURL_FORM_ERROR: "SUBMIT_CURL_FORM_ERROR",
|
||||
FETCH_PROVIDER_TEMPLATES_ERROR: "FETCH_PROVIDER_TEMPLATES_ERROR",
|
||||
ADD_API_TO_PAGE_ERROR: "ADD_API_TO_PAGE_ERROR",
|
||||
FETCH_PROVIDERS_CATEGORIES_ERROR: "FETCH_PROVIDERS_CATEGORIES_ERROR",
|
||||
FETCH_PROVIDERS_WITH_CATEGORY_ERROR: "FETCH_PROVIDERS_WITH_CATEGORY_ERROR",
|
||||
CREATE_MODAL_ERROR: "CREATE_MODAL_ERROR",
|
||||
};
|
||||
|
||||
|
|
@ -231,6 +254,7 @@ export const ReduxFormActionTypes: { [key: string]: string } = {
|
|||
VALUE_CHANGE: "@@redux-form/CHANGE",
|
||||
UPDATE_FIELD_ERROR: "@@redux-form/UPDATE_SYNC_ERRORS",
|
||||
ARRAY_REMOVE: "@@redux-form/ARRAY_REMOVE",
|
||||
ARRAY_PUSH: "@@redux-form/ARRAY_PUSH",
|
||||
};
|
||||
|
||||
export type ReduxActionErrorType = typeof ReduxActionErrorTypes[keyof typeof ReduxActionErrorTypes];
|
||||
|
|
|
|||
21
app/client/src/constants/collectionsConstants.ts
Normal file
21
app/client/src/constants/collectionsConstants.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { ApiResponse } from "api/ApiResponses";
|
||||
|
||||
export type TemplateList = {
|
||||
id: string;
|
||||
delete: boolean;
|
||||
name: string;
|
||||
providerId: string;
|
||||
publisher: string;
|
||||
packageName: string;
|
||||
versionId: string;
|
||||
};
|
||||
|
||||
export type CollectionDataArray = {
|
||||
id: string;
|
||||
deleted: boolean;
|
||||
apiTemplateList: Array<TemplateList>;
|
||||
};
|
||||
|
||||
export type ImportedCollections = ApiResponse & {
|
||||
data: Array<CollectionDataArray>;
|
||||
};
|
||||
|
|
@ -12,3 +12,5 @@ export const RESET_PASSWORD_FORM_NAME = "ResetPasswordForm";
|
|||
export const CREATE_PASSWORD_FORM_NAME = "CreatePasswordForm";
|
||||
|
||||
export const CREATE_ORGANIZATION_FORM_NAME = "CreateOrganizationForm";
|
||||
export const CURL_IMPORT_FORM = "CurlImportForm";
|
||||
export const API_HOME_SCREEN_FORM = "APIHomeScreenForm";
|
||||
|
|
|
|||
|
|
@ -120,7 +120,10 @@ export const CREATE_PASSWORD_INVALID_TOKEN =
|
|||
"The invite link is invalid. Please try request a new invite";
|
||||
|
||||
export const DELETING_APPLICATION = "Deleting application...";
|
||||
|
||||
export const CURL_IMPORT_SUCCESS = "Curl Import Successfull";
|
||||
export const FORGOT_PASSWORD_PAGE_LOGIN_LINK = "Back to Login";
|
||||
export const ADD_API_TO_PAGE_SUCCESS_MESSAGE = "Api added to page.";
|
||||
export const INPUT_WIDGET_DEFAULT_VALIDATION_ERROR = "Invalid input";
|
||||
|
||||
export const AUTOFIT_ALL_COLUMNS = "Autofit all columns";
|
||||
|
|
|
|||
58
app/client/src/constants/providerConstants.ts
Normal file
58
app/client/src/constants/providerConstants.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { ApiResponse } from "api/ApiResponses";
|
||||
|
||||
export type ProvidersDataArray = ApiResponse & {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
};
|
||||
|
||||
export type ProvidersCategoriesResponse = ApiResponse & {
|
||||
data: string[];
|
||||
};
|
||||
|
||||
export type Providers = ApiResponse & {
|
||||
providers: ProvidersDataArray[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type ProviderTemplates = ApiResponse & {
|
||||
data: Array<ProviderTemplateArray>;
|
||||
length: number;
|
||||
templateId: string;
|
||||
};
|
||||
|
||||
export type ProviderTemplateArray = ApiResponse & {
|
||||
templateData: {
|
||||
id: string;
|
||||
deleted: boolean;
|
||||
name: string;
|
||||
providerId: string;
|
||||
publisher: string;
|
||||
packageName: string;
|
||||
versionId: string;
|
||||
apiTemplateConfiguration: {
|
||||
documentation: string;
|
||||
sampleResponse: {
|
||||
body: string;
|
||||
};
|
||||
};
|
||||
actionConfiguration: {
|
||||
timeoutInMillisecond: number;
|
||||
paginationType: string;
|
||||
path: string;
|
||||
httpMethod: string;
|
||||
headers: [];
|
||||
routeParameters: [];
|
||||
};
|
||||
datasourceConfiguration: {
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
isOpen: boolean;
|
||||
addToPageStatus: boolean;
|
||||
addToPageLoading: boolean;
|
||||
};
|
||||
|
||||
export const DEFAULT_TEMPLATE_TYPE = "TEMPLATE";
|
||||
|
|
@ -22,6 +22,13 @@ export type APIEditorRouteParams = {
|
|||
apiId?: string;
|
||||
};
|
||||
|
||||
export type ProviderViewerRouteParams = {
|
||||
applicationId: string;
|
||||
pageId: string;
|
||||
providerId: string;
|
||||
destinationPageId: string;
|
||||
};
|
||||
|
||||
export const BUILDER_BASE_URL = (applicationId = ":applicationId"): string =>
|
||||
`/applications/${applicationId}`;
|
||||
|
||||
|
|
@ -53,6 +60,17 @@ export const API_EDITOR_ID_URL = (
|
|||
apiId = ":apiId",
|
||||
): string => `${API_EDITOR_URL(applicationId, pageId)}/${apiId}`;
|
||||
|
||||
export const API_EDITOR_URL_WITH_SELECTED_PAGE_ID = (
|
||||
applicationId = ":applicationId",
|
||||
pageId = ":pageId",
|
||||
selectedPageId = ":importTo",
|
||||
): string => {
|
||||
return `${BUILDER_PAGE_URL(
|
||||
applicationId,
|
||||
pageId,
|
||||
)}/api?importTo=${selectedPageId}`;
|
||||
};
|
||||
|
||||
export const APP_VIEW_URL = `/applications/:applicationId`;
|
||||
|
||||
export const getApplicationViewerURL = (
|
||||
|
|
@ -83,6 +101,22 @@ function convertToQueryParams(params: Record<string, string> = {}): string {
|
|||
return queryParams ? "?" + queryParams : "";
|
||||
}
|
||||
|
||||
export const getCurlImportPageURL = (
|
||||
applicationId = ":applicationId",
|
||||
pageId = ":pageId",
|
||||
): string => `${API_EDITOR_URL(applicationId, pageId)}/curl/curl-import`;
|
||||
|
||||
export const getProviderTemplatesURL = (
|
||||
applicationId = ":applicationId",
|
||||
pageId = ":pageId",
|
||||
providerId = ":providerId",
|
||||
destinationPageId = ":destinationPageId",
|
||||
): string =>
|
||||
`${API_EDITOR_URL(
|
||||
applicationId,
|
||||
pageId,
|
||||
)}/provider/${providerId}/?importTo=${destinationPageId}`;
|
||||
|
||||
export const EDITOR_ROUTES = [
|
||||
{
|
||||
icon: MenuIcons.WIDGETS_ICON,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { Icon } from "@blueprintjs/core";
|
||||
import { IconNames } from "@blueprintjs/icons";
|
||||
import { IconProps, IconWrapper } from "constants/IconConstants";
|
||||
import { ReactComponent as InfoIcon } from "assets/icons/form/info-outline.svg";
|
||||
import { ReactComponent as DeleteIcon } from "assets/icons/form/trash.svg";
|
||||
import { ReactComponent as AddNewIcon } from "assets/icons/form/add-new.svg";
|
||||
|
||||
|
|
@ -10,6 +11,11 @@ import { ReactComponent as AddNewIcon } from "assets/icons/form/add-new.svg";
|
|||
export const FormIcons: {
|
||||
[id: string]: Function;
|
||||
} = {
|
||||
INFO_ICON: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<InfoIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
DELETE_ICON: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<DeleteIcon />
|
||||
|
|
@ -20,6 +26,11 @@ export const FormIcons: {
|
|||
<AddNewIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
CREATE_NEW_ICON: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<Icon icon={IconNames.PLUS} />
|
||||
</IconWrapper>
|
||||
),
|
||||
PLUS_ICON: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<Icon icon={IconNames.PLUS} color={props.color} iconSize={props.height} />
|
||||
|
|
|
|||
562
app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx
Normal file
562
app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { reduxForm, InjectedFormProps, getFormValues } from "redux-form";
|
||||
import { Icon, Card } from "@blueprintjs/core";
|
||||
import styled from "styled-components";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
import {
|
||||
DEFAULT_API_ACTION,
|
||||
DEFAULT_PROVIDER_OPTION,
|
||||
} from "constants/ApiEditorConstants";
|
||||
import {
|
||||
getCurlImportPageURL,
|
||||
getProviderTemplatesURL,
|
||||
} from "constants/routes";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
import ImageAlt from "assets/images/no_image.png";
|
||||
import { AppState } from "reducers";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import { getImportedCollections } from "selectors/applicationSelectors";
|
||||
import { TemplateList } from "constants/collectionsConstants";
|
||||
import { ProvidersDataArray } from "constants/providerConstants";
|
||||
import {
|
||||
getProviders,
|
||||
getProvidersLoadingState,
|
||||
} from "selectors/applicationSelectors";
|
||||
import { getProviderCategories } from "selectors/editorSelectors";
|
||||
import { createActionRequest } from "actions/actionActions";
|
||||
import { fetchImportedCollections } from "actions/collectionAction";
|
||||
import { API_HOME_SCREEN_FORM } from "constants/forms";
|
||||
import {
|
||||
fetchProviders,
|
||||
fetchProviderCategories,
|
||||
fetchProvidersWithCategory,
|
||||
clearProviders,
|
||||
} from "actions/providerActions";
|
||||
import { createNewApiName } from "utils/AppsmithUtils";
|
||||
import { Colors } from "constants/Colors";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
// import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
|
||||
import { API_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes";
|
||||
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
||||
import Spinner from "components/editorComponents/Spinner";
|
||||
// import PostmanLogo from "assets/images/Postman-logo.svg";
|
||||
import CurlLogo from "assets/images/Curl-logo.svg";
|
||||
import { FetchProviderWithCategoryRequest } from "api/ProvidersApi";
|
||||
|
||||
// const SearchContainer = styled.div`
|
||||
// display: flex;
|
||||
// width: 40%;
|
||||
// .closeBtn {
|
||||
// position: absolute;
|
||||
// left: 70%;
|
||||
// }
|
||||
// `;
|
||||
//
|
||||
// const SearchBar = styled(BaseTextInput)`
|
||||
// margin-bottom: 10px;
|
||||
// input {
|
||||
// background-color: ${Colors.WHITE};
|
||||
// 1px solid ${Colors.GEYSER};
|
||||
// }
|
||||
// `;
|
||||
|
||||
const ApiHomePage = styled.div`
|
||||
font-size: 20px;
|
||||
padding: 20px;
|
||||
margin-left: 10px;
|
||||
min-height: 95vh;
|
||||
max-height: 95vh;
|
||||
overflow: auto;
|
||||
padding-bottom: 50px;
|
||||
.closeBtn {
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
flex: 1;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.sectionHeadings {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
.link {
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
}
|
||||
.link:hover {
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.textBtn {
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
letter-spacing: -0.17px;
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
font-weight: 500;
|
||||
text-decoration: none !important;
|
||||
flex-wrap: wrap;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.link {
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
}
|
||||
a:hover {
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const ApiCard = styled.div`
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
margin-left: -10px;
|
||||
justify-content: flex-start;
|
||||
text-align: center;
|
||||
min-width: 150px;
|
||||
border-radius: 4px;
|
||||
|
||||
.apiImage {
|
||||
height: 50px;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin-top: -5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.curlImage {
|
||||
width: 60px;
|
||||
}
|
||||
.createIcon {
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.eachCard {
|
||||
margin: 15px;
|
||||
width: 140px;
|
||||
height: 110px;
|
||||
padding-bottom: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const CardList = styled.div`
|
||||
margin: 15px;
|
||||
.eachProviderCard {
|
||||
width: 140px;
|
||||
height: 110px;
|
||||
padding-bottom: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
// const NoCollections = styled.div`
|
||||
// padding-top: 52px;
|
||||
// padding-bottom: 50px;
|
||||
// text-align: center;
|
||||
// width: 438px;
|
||||
// margin: 0 auto;
|
||||
// color: #666666;
|
||||
// font-size: 14px;
|
||||
// line-height: 24px;
|
||||
// `;
|
||||
//
|
||||
// const ImportedApisCard = styled.div`
|
||||
// flex: 1;
|
||||
// display: inline-flex;
|
||||
// flex-wrap: wrap;
|
||||
// margin-left: -10px;
|
||||
// justify-content: flex-start;
|
||||
// text-align: center;
|
||||
// border-radius: 4px;
|
||||
//
|
||||
// .importedApiIcon {
|
||||
// display: flex;
|
||||
// height: 30px;
|
||||
// width: 30px;
|
||||
// margin-right: 15px;
|
||||
// color: ${Colors.OXFORD_BLUE};
|
||||
// }
|
||||
// .eachImportedApiCard {
|
||||
// margin: 10px;
|
||||
// width: 225px;
|
||||
// height: 60px;
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
// }
|
||||
// `;
|
||||
|
||||
const DropdownSelect = styled.div`
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
width: 232px;
|
||||
margin-right: 8%;
|
||||
margin-bottom: 25px;
|
||||
`;
|
||||
|
||||
const LoadingContainer = styled(CenteredWrapper)`
|
||||
height: 50%;
|
||||
`;
|
||||
|
||||
const PageLoadingContainer = styled(CenteredWrapper)`
|
||||
margin-top: 100px;
|
||||
height: 50%;
|
||||
`;
|
||||
|
||||
type ApiHomeScreenProps = {
|
||||
currentCategory: string;
|
||||
importedCollections: TemplateList[];
|
||||
fetchImportedCollections: () => void;
|
||||
providers: ProvidersDataArray[];
|
||||
fetchProviders: () => void;
|
||||
clearProviders: () => void;
|
||||
fetchProviderCategories: () => void;
|
||||
providerCategories: string[];
|
||||
pageId: string;
|
||||
applicationId: string;
|
||||
actions: ActionDataState;
|
||||
createAction: (data: Partial<RestAction>) => void;
|
||||
fetchProvidersWithCategory: (
|
||||
request: FetchProviderWithCategoryRequest,
|
||||
) => void;
|
||||
location: {
|
||||
search: string;
|
||||
};
|
||||
history: {
|
||||
replace: (data: string) => void;
|
||||
push: (data: string) => void;
|
||||
};
|
||||
isFetchingProviders: boolean;
|
||||
providersTotal: number;
|
||||
isSwitchingCategory: boolean;
|
||||
};
|
||||
|
||||
type ApiHomeScreenState = {
|
||||
page: number;
|
||||
};
|
||||
|
||||
type Props = ApiHomeScreenProps & InjectedFormProps<null, ApiHomeScreenProps>;
|
||||
|
||||
class ApiHomeScreen extends React.Component<Props, ApiHomeScreenState> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
page: 1,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { importedCollections } = this.props;
|
||||
this.props.fetchProviderCategories();
|
||||
if (importedCollections.length === 0) {
|
||||
this.props.fetchImportedCollections();
|
||||
}
|
||||
this.props.clearProviders();
|
||||
this.props.change("category", DEFAULT_PROVIDER_OPTION);
|
||||
this.props.fetchProvidersWithCategory({
|
||||
category: DEFAULT_PROVIDER_OPTION,
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.currentCategory !== this.props.currentCategory) {
|
||||
this.props.clearProviders();
|
||||
this.props.fetchProvidersWithCategory({
|
||||
category: this.props.currentCategory,
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleCreateNew = (params: string) => {
|
||||
const { actions } = this.props;
|
||||
const pageId = new URLSearchParams(params).get("importTo");
|
||||
if (pageId) {
|
||||
const newActionName = createNewApiName(actions, pageId);
|
||||
this.props.createAction({
|
||||
...DEFAULT_API_ACTION,
|
||||
name: newActionName,
|
||||
pageId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// handleSearchChange = (e: React.ChangeEvent<{ value: string }>) => {
|
||||
// const value = e.target.value;
|
||||
// };
|
||||
|
||||
handleFetchMoreProviders = (page: number) => {
|
||||
const { currentCategory } = this.props;
|
||||
this.props.fetchProvidersWithCategory({
|
||||
category: currentCategory,
|
||||
page: page,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
// importedCollections,
|
||||
providers,
|
||||
applicationId,
|
||||
pageId,
|
||||
location,
|
||||
history,
|
||||
// isFetchingProviders,
|
||||
providerCategories,
|
||||
providersTotal,
|
||||
isSwitchingCategory,
|
||||
} = this.props;
|
||||
const queryParams: string = location.search;
|
||||
let destinationPageId = new URLSearchParams(location.search).get(
|
||||
"importTo",
|
||||
);
|
||||
|
||||
if (!destinationPageId) {
|
||||
destinationPageId = pageId;
|
||||
history.push(
|
||||
API_EDITOR_URL_WITH_SELECTED_PAGE_ID(applicationId, pageId, pageId),
|
||||
);
|
||||
}
|
||||
const curlImportURL =
|
||||
getCurlImportPageURL(applicationId, pageId) + location.search;
|
||||
|
||||
const PROVIDER_CATEGORIES_OPTIONS = providerCategories.map(category => ({
|
||||
label: category,
|
||||
value: category,
|
||||
}));
|
||||
|
||||
const ApiHomepageTopSection = (
|
||||
<React.Fragment>
|
||||
{/* <SearchContainer>
|
||||
<SearchBar
|
||||
icon="search"
|
||||
input={{
|
||||
onChange: this.handleSearchChange,
|
||||
}}
|
||||
placeholder="Search"
|
||||
/>
|
||||
</SearchContainer> */}
|
||||
|
||||
<StyledContainer>
|
||||
<p className="sectionHeadings">{"Import API"}</p>
|
||||
<ApiCard>
|
||||
<Card
|
||||
interactive={false}
|
||||
className="eachCard"
|
||||
onClick={() => this.handleCreateNew(queryParams)}
|
||||
>
|
||||
<Icon icon="plus" iconSize={20} className="createIcon" />
|
||||
<p className="textBtn">Create new</p>
|
||||
</Card>
|
||||
|
||||
{/* <Card interactive={false} className="eachCard">
|
||||
<img src={PostmanLogo} className="apiImage" alt="Postman"></img>
|
||||
<p className="textBtn">Postman</p>
|
||||
</Card> */}
|
||||
|
||||
<Link to={curlImportURL}>
|
||||
<Card interactive={false} className="eachCard">
|
||||
<img src={CurlLogo} className="curlImage" alt="CURL" />
|
||||
<p className="textBtn">CURL</p>
|
||||
</Card>
|
||||
</Link>
|
||||
</ApiCard>
|
||||
</StyledContainer>
|
||||
|
||||
{/* Imported APIs section start */}
|
||||
{/* <StyledContainer>
|
||||
<p className="sectionHeadings">{"Imported APIs"}</p>
|
||||
|
||||
{importedCollections.length > 0 ? (
|
||||
<ImportedApisCard>
|
||||
{importedCollections.map(importedCollection => (
|
||||
<Card
|
||||
key={importedCollection.id}
|
||||
interactive={false}
|
||||
className="eachImportedApiCard"
|
||||
>
|
||||
<Icon
|
||||
icon="folder-close"
|
||||
iconSize={20}
|
||||
className="importedApiIcon"
|
||||
/>
|
||||
<p className="textBtn">{importedCollection.name}</p>
|
||||
</Card>
|
||||
))}
|
||||
</ImportedApisCard>
|
||||
) : (
|
||||
<NoCollections>
|
||||
<p>
|
||||
Your imported APIs will appear here. You can import APIs from
|
||||
the options in the above section.
|
||||
</p>
|
||||
</NoCollections>
|
||||
)}
|
||||
</StyledContainer> */}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ApiHomePage>
|
||||
{isSwitchingCategory ? (
|
||||
<>
|
||||
{ApiHomepageTopSection}
|
||||
<PageLoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</PageLoadingContainer>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
initialLoad={false}
|
||||
loadMore={this.handleFetchMoreProviders.bind(this)}
|
||||
useWindow={false}
|
||||
hasMore={providers.length < providersTotal}
|
||||
loader={
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
}
|
||||
>
|
||||
{ApiHomepageTopSection}
|
||||
{/* Marketplace APIs section start */}
|
||||
<StyledContainer>
|
||||
<p className="sectionHeadings">{"Marketplace APIs"}</p>
|
||||
<DropdownSelect>
|
||||
<DropdownField
|
||||
placeholder="All APIs"
|
||||
width={232}
|
||||
name="category"
|
||||
options={PROVIDER_CATEGORIES_OPTIONS}
|
||||
/>
|
||||
</DropdownSelect>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
{isSwitchingCategory ? (
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<ApiCard>
|
||||
{providers.map(provider => (
|
||||
<CardList key={provider.id}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: getProviderTemplatesURL(
|
||||
applicationId,
|
||||
pageId,
|
||||
provider.id,
|
||||
destinationPageId
|
||||
? destinationPageId
|
||||
: pageId,
|
||||
),
|
||||
state: {
|
||||
providerName: provider.name,
|
||||
providerImage: provider.imageUrl,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
interactive={false}
|
||||
className="eachProviderCard"
|
||||
>
|
||||
{provider.imageUrl ? (
|
||||
<img
|
||||
src={provider.imageUrl}
|
||||
className="apiImage"
|
||||
alt="Provider"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={ImageAlt}
|
||||
className="apiImage"
|
||||
alt="Provider"
|
||||
/>
|
||||
)}
|
||||
{provider.name && (
|
||||
<p
|
||||
className="textBtn"
|
||||
title={provider.name}
|
||||
>
|
||||
{provider.name}
|
||||
</p>
|
||||
)}
|
||||
</Card>
|
||||
</Link>
|
||||
</CardList>
|
||||
))}
|
||||
</ApiCard>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</StyledContainer>
|
||||
{/* Marketplace APIs section end */}
|
||||
</InfiniteScroll>
|
||||
</>
|
||||
)}
|
||||
</ApiHomePage>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
const { providers } = state.ui;
|
||||
const { providersTotal, isSwitchingCategory } = providers;
|
||||
const formData = getFormValues(API_HOME_SCREEN_FORM)(
|
||||
state,
|
||||
) as FetchProviderWithCategoryRequest;
|
||||
let category = DEFAULT_PROVIDER_OPTION;
|
||||
|
||||
if (formData) {
|
||||
category = formData.category;
|
||||
}
|
||||
|
||||
return {
|
||||
currentCategory: category,
|
||||
importedCollections: getImportedCollections(state),
|
||||
providers: getProviders(state),
|
||||
isFetchingProviders: getProvidersLoadingState(state),
|
||||
actions: state.entities.actions,
|
||||
providersTotal,
|
||||
providerCategories: getProviderCategories(state),
|
||||
isSwitchingCategory,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
fetchImportedCollections: () => dispatch(fetchImportedCollections()),
|
||||
fetchProviders: () => dispatch(fetchProviders()),
|
||||
clearProviders: () => dispatch(clearProviders()),
|
||||
fetchProviderCategories: () => dispatch(fetchProviderCategories()),
|
||||
fetchProvidersWithCategory: (request: FetchProviderWithCategoryRequest) =>
|
||||
dispatch(fetchProvidersWithCategory(request)),
|
||||
createAction: (data: Partial<RestAction>) =>
|
||||
dispatch(createActionRequest(data)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(
|
||||
reduxForm<null, ApiHomeScreenProps>({
|
||||
form: API_HOME_SCREEN_FORM,
|
||||
})(ApiHomeScreen),
|
||||
);
|
||||
194
app/client/src/pages/Editor/APIEditor/CurlImportForm.tsx
Normal file
194
app/client/src/pages/Editor/APIEditor/CurlImportForm.tsx
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import React from "react";
|
||||
import { reduxForm, InjectedFormProps, Form, Field } from "redux-form";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter, RouteComponentProps } from "react-router";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import styled from "styled-components";
|
||||
import { AppState } from "reducers";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import { CURL_IMPORT_FORM } from "constants/forms";
|
||||
import { BuilderRouteParams } from "constants/routes";
|
||||
import { curlImportFormValues, curlImportSubmitHandler } from "./helpers";
|
||||
import { createNewApiName } from "utils/AppsmithUtils";
|
||||
import { Colors } from "constants/Colors";
|
||||
import Button from "components/editorComponents/Button";
|
||||
import CurlLogo from "assets/images/Curl-logo.svg";
|
||||
|
||||
const CurlImportFormContainer = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
margin-top: 8px;
|
||||
overflow: auto;
|
||||
|
||||
.textAreaStyles {
|
||||
margin-bottom: 20px;
|
||||
min-height: 50vh;
|
||||
max-height: 50vh;
|
||||
padding: 10px;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
border-radius: 4px;
|
||||
border: 1px solid ${Colors.GEYSER};
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.importBtn {
|
||||
width: 111px;
|
||||
margin-right: 21px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CurlImport = styled.div`
|
||||
font-size: 20px;
|
||||
padding: 20px;
|
||||
|
||||
.backBtn {
|
||||
padding-bottom: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.backBtnText {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const CurlContainer = styled.div`
|
||||
margin-top: 8vh;
|
||||
padding-left: 70px;
|
||||
padding-right: 70px;
|
||||
|
||||
.inputLabel {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: ${Colors.BLUE_BAYOUX};
|
||||
}
|
||||
`;
|
||||
|
||||
const DividerLine = styled.div`
|
||||
border: 1px solid ${Colors.CONCRETE};
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
.header {
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
padding-left: 20px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.curlImage {
|
||||
float: left;
|
||||
margin-top: -5px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.divider {
|
||||
border: 1px solid ${Colors.CONCRETE};
|
||||
}
|
||||
.closeBtn {
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ReduxStateProps {
|
||||
actions: ActionDataState;
|
||||
initialValues: {};
|
||||
isImportingCurl: boolean;
|
||||
}
|
||||
|
||||
export type StateAndRouteProps = ReduxStateProps &
|
||||
RouteComponentProps<BuilderRouteParams>;
|
||||
|
||||
type Props = StateAndRouteProps &
|
||||
InjectedFormProps<curlImportFormValues, StateAndRouteProps>;
|
||||
|
||||
class CurlImportForm extends React.Component<Props> {
|
||||
render() {
|
||||
const { handleSubmit, history, isImportingCurl } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Header>
|
||||
<p className="header">
|
||||
<img src={CurlLogo} className="curlImage" alt="CURL"></img>
|
||||
Import from CURL
|
||||
</p>
|
||||
<hr className="divider" />
|
||||
</Header>
|
||||
<Form onSubmit={handleSubmit(curlImportSubmitHandler)}>
|
||||
<CurlImport>
|
||||
<Icon
|
||||
icon="chevron-left"
|
||||
iconSize={16}
|
||||
className="backBtn"
|
||||
onClick={() => history.goBack()}
|
||||
/>
|
||||
<span className="backBtnText" onClick={() => history.goBack()}>
|
||||
{" Back"}
|
||||
</span>
|
||||
<CurlContainer>
|
||||
<label className="inputLabel">{"Paste CURL Code Here"}</label>
|
||||
<CurlImportFormContainer>
|
||||
<Field
|
||||
name="curl"
|
||||
component="textarea"
|
||||
className="textAreaStyles"
|
||||
/>
|
||||
<Field type="hidden" name="pageId" component="input" />
|
||||
<Field type="hidden" name="name" component="input" />
|
||||
</CurlImportFormContainer>
|
||||
</CurlContainer>
|
||||
</CurlImport>
|
||||
<DividerLine />
|
||||
<CurlImportFormContainer>
|
||||
<Button
|
||||
text="Import"
|
||||
loading={isImportingCurl}
|
||||
onClick={handleSubmit(curlImportSubmitHandler)}
|
||||
intent="primary"
|
||||
filled
|
||||
size="small"
|
||||
className="t--application-publish-btn importBtn"
|
||||
/>
|
||||
</CurlImportFormContainer>
|
||||
</Form>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState, props: Props): ReduxStateProps => {
|
||||
const destinationPageId = new URLSearchParams(props.location.search).get(
|
||||
"importTo",
|
||||
);
|
||||
|
||||
if (destinationPageId) {
|
||||
return {
|
||||
actions: state.entities.actions,
|
||||
initialValues: {
|
||||
pageId: destinationPageId,
|
||||
name: createNewApiName(state.entities.actions, destinationPageId),
|
||||
},
|
||||
isImportingCurl: state.ui.imports.isImportingCurl,
|
||||
};
|
||||
}
|
||||
return {
|
||||
actions: state.entities.actions,
|
||||
initialValues: {},
|
||||
isImportingCurl: state.ui.imports.isImportingCurl,
|
||||
};
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps)(
|
||||
reduxForm<curlImportFormValues, StateAndRouteProps>({
|
||||
form: CURL_IMPORT_FORM,
|
||||
})(CurlImportForm),
|
||||
),
|
||||
);
|
||||
|
|
@ -1,7 +1,17 @@
|
|||
import React from "react";
|
||||
import { reduxForm, InjectedFormProps, FormSubmitHandler } from "redux-form";
|
||||
import { HTTP_METHOD_OPTIONS } from "constants/ApiEditorConstants";
|
||||
import React, { useEffect } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
reduxForm,
|
||||
InjectedFormProps,
|
||||
FormSubmitHandler,
|
||||
formValueSelector,
|
||||
} from "redux-form";
|
||||
import {
|
||||
HTTP_METHOD_OPTIONS,
|
||||
HTTP_METHODS,
|
||||
} from "constants/ApiEditorConstants";
|
||||
import styled from "styled-components";
|
||||
import PostBodyData from "./PostBodyData";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import FormRow from "components/editorComponents/FormRow";
|
||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||
|
|
@ -22,6 +32,7 @@ const Form = styled.form`
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - ${props => props.theme.headerHeight});
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
${FormLabel} {
|
||||
padding: ${props => props.theme.spaces[3]}px;
|
||||
|
|
@ -29,7 +40,7 @@ const Form = styled.form`
|
|||
${FormRow} {
|
||||
padding: ${props => props.theme.spaces[2]}px;
|
||||
& > * {
|
||||
margin-right: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
${FormLabel} {
|
||||
padding: 0;
|
||||
|
|
@ -40,20 +51,24 @@ const Form = styled.form`
|
|||
|
||||
const MainConfiguration = styled.div`
|
||||
padding-top: 10px;
|
||||
padding-left: 17px;
|
||||
`;
|
||||
|
||||
const SecondaryWrapper = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
border-top: 1px solid #d0d7dd;
|
||||
margin-top: 15px;
|
||||
`;
|
||||
|
||||
const RequestParamsWrapper = styled.div`
|
||||
flex: 4;
|
||||
border-right: 1px solid #d0d7dd;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
padding-top: 6px;
|
||||
padding-left: 17px;
|
||||
padding-right: 10px;
|
||||
`;
|
||||
|
||||
const ActionButtons = styled.div`
|
||||
|
|
@ -68,8 +83,8 @@ const ActionButton = styled(BaseButton)`
|
|||
}
|
||||
`;
|
||||
|
||||
const JSONEditorFieldWrapper = styled.div`
|
||||
margin: 5px;
|
||||
const HeadersSection = styled.div`
|
||||
margin-bottom: 32px;
|
||||
`;
|
||||
|
||||
const DatasourceWrapper = styled.div`
|
||||
|
|
@ -85,7 +100,6 @@ const TabbedViewContainer = styled.div`
|
|||
interface APIFormProps {
|
||||
pluginId: string;
|
||||
allowSave: boolean;
|
||||
allowPostBody: boolean;
|
||||
onSubmit: FormSubmitHandler<RestAction>;
|
||||
onSaveClick: () => void;
|
||||
onRunClick: (paginationField?: PaginationField) => void;
|
||||
|
|
@ -95,6 +109,15 @@ interface APIFormProps {
|
|||
isDeleting: boolean;
|
||||
paginationType: PaginationType;
|
||||
appName: string;
|
||||
actionConfiguration?: any;
|
||||
displayFormat: string;
|
||||
httpMethodFromForm: string;
|
||||
actionConfigurationBody: object | string;
|
||||
actionConfigurationHeaders?: any;
|
||||
contentType: {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
||||
|
|
@ -103,7 +126,6 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
const {
|
||||
pluginId,
|
||||
allowSave,
|
||||
allowPostBody,
|
||||
onSaveClick,
|
||||
onDeleteClick,
|
||||
onRunClick,
|
||||
|
|
@ -111,7 +133,28 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
isDeleting,
|
||||
isRunning,
|
||||
isSaving,
|
||||
actionConfiguration,
|
||||
actionConfigurationHeaders,
|
||||
actionConfigurationBody,
|
||||
httpMethodFromForm,
|
||||
contentType,
|
||||
displayFormat,
|
||||
} = props;
|
||||
const allowPostBody =
|
||||
httpMethodFromForm && httpMethodFromForm !== HTTP_METHODS[0];
|
||||
useEffect(() => {
|
||||
if (allowPostBody) {
|
||||
if (contentType) {
|
||||
if (!displayFormat) {
|
||||
props.change("displayFormat", contentType.value);
|
||||
} else {
|
||||
contentType.value = displayFormat;
|
||||
props.change("contentType.value", displayFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
{isSaving && <LoadingOverlayScreen>Saving...</LoadingOverlayScreen>}
|
||||
|
|
@ -178,27 +221,25 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
title: "API Input",
|
||||
panelComponent: (
|
||||
<RequestParamsWrapper>
|
||||
<KeyValueFieldArray
|
||||
name="actionConfiguration.headers"
|
||||
label="Headers"
|
||||
/>
|
||||
<HeadersSection>
|
||||
<KeyValueFieldArray
|
||||
name="actionConfiguration.headers"
|
||||
label="Headers"
|
||||
actionConfig={
|
||||
actionConfiguration && actionConfigurationHeaders
|
||||
}
|
||||
placeholder="Value"
|
||||
/>
|
||||
</HeadersSection>
|
||||
<KeyValueFieldArray
|
||||
name="actionConfiguration.queryParameters"
|
||||
label="Params"
|
||||
/>
|
||||
{allowPostBody && (
|
||||
<React.Fragment>
|
||||
<FormLabel>{"Post Body"}</FormLabel>
|
||||
<JSONEditorFieldWrapper>
|
||||
<DynamicTextField
|
||||
name="actionConfiguration.body"
|
||||
height={300}
|
||||
showLineNumbers
|
||||
allowTabIndent
|
||||
singleLine={false}
|
||||
/>
|
||||
</JSONEditorFieldWrapper>
|
||||
</React.Fragment>
|
||||
<PostBodyData
|
||||
actionConfiguration={actionConfigurationBody}
|
||||
change={props.change}
|
||||
/>
|
||||
)}
|
||||
</RequestParamsWrapper>
|
||||
),
|
||||
|
|
@ -223,7 +264,35 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default reduxForm<RestAction, APIFormProps>({
|
||||
form: API_EDITOR_FORM_NAME,
|
||||
enableReinitialize: true,
|
||||
})(ApiEditorForm);
|
||||
const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
||||
|
||||
export default connect(state => {
|
||||
const displayFormat = selector(state, "displayFormat");
|
||||
const httpMethodFromForm = selector(state, "actionConfiguration.httpMethod");
|
||||
const actionConfiguration = selector(state, "actionConfiguration");
|
||||
const actionConfigurationBody = selector(state, "actionConfiguration.body");
|
||||
const actionConfigurationHeaders = selector(
|
||||
state,
|
||||
"actionConfiguration.headers",
|
||||
);
|
||||
let contentType;
|
||||
if (actionConfigurationHeaders) {
|
||||
contentType = actionConfigurationHeaders.find(
|
||||
(header: any) => header.key === "content-type",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
displayFormat,
|
||||
httpMethodFromForm,
|
||||
actionConfiguration,
|
||||
actionConfigurationBody,
|
||||
contentType,
|
||||
actionConfigurationHeaders,
|
||||
};
|
||||
})(
|
||||
reduxForm<RestAction, APIFormProps>({
|
||||
form: API_EDITOR_FORM_NAME,
|
||||
destroyOnUnmount: false,
|
||||
})(ApiEditorForm),
|
||||
);
|
||||
|
|
|
|||
93
app/client/src/pages/Editor/APIEditor/PostBodyData.tsx
Normal file
93
app/client/src/pages/Editor/APIEditor/PostBodyData.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import { formValueSelector } from "redux-form";
|
||||
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
||||
|
||||
const DropDownContainer = styled.div`
|
||||
width: 232px;
|
||||
margin: 5px;
|
||||
margin-bottom: 21px;
|
||||
`;
|
||||
|
||||
const PostbodyContainer = styled.div`
|
||||
margin-top: 41px;
|
||||
`;
|
||||
|
||||
const JSONEditorFieldWrapper = styled.div`
|
||||
margin: 5px;
|
||||
`;
|
||||
export interface RapidApiAction {
|
||||
editable: boolean;
|
||||
mandatory: boolean;
|
||||
description: string;
|
||||
key: string;
|
||||
value?: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface PostDataProps {
|
||||
actionConfiguration: any;
|
||||
displayFormat: string;
|
||||
change: Function;
|
||||
}
|
||||
type Props = PostDataProps;
|
||||
|
||||
const PostBodyData = (props: Props) => {
|
||||
const { displayFormat } = props;
|
||||
|
||||
return (
|
||||
<PostbodyContainer>
|
||||
<FormLabel>{"Post Body"}</FormLabel>
|
||||
<DropDownContainer>
|
||||
<DropdownField
|
||||
placeholder="Format"
|
||||
name="displayFormat"
|
||||
isSearchable={false}
|
||||
width={232}
|
||||
options={POST_BODY_FORMAT_OPTIONS}
|
||||
/>
|
||||
</DropDownContainer>
|
||||
|
||||
{displayFormat === POST_BODY_FORMAT_OPTIONS[0].value && (
|
||||
<React.Fragment>
|
||||
<JSONEditorFieldWrapper>
|
||||
<DynamicTextField
|
||||
name="actionConfiguration.body[0]"
|
||||
height={300}
|
||||
showLineNumbers
|
||||
allowTabIndent
|
||||
singleLine={false}
|
||||
/>
|
||||
</JSONEditorFieldWrapper>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{displayFormat === POST_BODY_FORMAT_OPTIONS[1].value && (
|
||||
<React.Fragment>
|
||||
<KeyValueFieldArray name="actionConfiguration.body[1]" label="" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</PostbodyContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
||||
export default connect(state => {
|
||||
const displayFormat = selector(state, "displayFormat");
|
||||
const headers = selector(state, "actionConfiguration.headers");
|
||||
let contentType;
|
||||
if (headers) {
|
||||
contentType = headers.find((header: any) => header.key === "content-type");
|
||||
}
|
||||
|
||||
return {
|
||||
displayFormat,
|
||||
contentType,
|
||||
};
|
||||
})(PostBodyData);
|
||||
463
app/client/src/pages/Editor/APIEditor/ProviderTemplates.tsx
Normal file
463
app/client/src/pages/Editor/APIEditor/ProviderTemplates.tsx
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Icon, Collapse } from "@blueprintjs/core";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import styled from "styled-components";
|
||||
import ReactJson from "react-json-view";
|
||||
import { AppState } from "reducers";
|
||||
import Button from "components/editorComponents/Button";
|
||||
import { ProviderViewerRouteParams } from "constants/routes";
|
||||
import {
|
||||
getProviderTemplates,
|
||||
getProvidersTemplatesLoadingState,
|
||||
} from "selectors/applicationSelectors";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import {
|
||||
ProviderTemplateArray,
|
||||
DEFAULT_TEMPLATE_TYPE,
|
||||
} from "constants/providerConstants";
|
||||
import { AddApiToPageRequest } from "api/ProvidersApi";
|
||||
import ImageAlt from "assets/images/no_image.png";
|
||||
import { addApiToPage } from "actions/providerActions";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { getDuplicateName } from "utils/AppsmithUtils";
|
||||
import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
|
||||
import Spinner from "components/editorComponents/Spinner";
|
||||
|
||||
const TEMPLATES_TOP_SECTION_HEIGHT = "125px";
|
||||
|
||||
const SearchContainer = styled.div`
|
||||
display: flex;
|
||||
width: 40%;
|
||||
.closeBtn {
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
}
|
||||
`;
|
||||
|
||||
const SearchBar = styled(BaseTextInput)`
|
||||
margin-bottom: 10px;
|
||||
input {
|
||||
background-color: ${Colors.WHITE};
|
||||
1px solid ${Colors.GEYSER};
|
||||
}
|
||||
`;
|
||||
|
||||
const ProviderInfo = styled.div`
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
.providerImage {
|
||||
height: 40px;
|
||||
width: auto;
|
||||
margin-right: 11px;
|
||||
}
|
||||
.providerName {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
}
|
||||
`;
|
||||
|
||||
const TemplateDetailPage = styled.div`
|
||||
max-height: 95vh;
|
||||
font-size: 20px;
|
||||
padding: 20px;
|
||||
|
||||
.react-tabs__tab-list {
|
||||
border-bottom-color: transparent;
|
||||
color: #4e5d78;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.react-tabs__tab--selected {
|
||||
color: #2e3d49;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-color: transparent;
|
||||
border: transparent;
|
||||
border-bottom: 3px solid #2e3d49;
|
||||
border-radius: 0;
|
||||
}
|
||||
.backBtn {
|
||||
padding-bottom: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.backBtnText {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const LoadingContainer = styled(CenteredWrapper)`
|
||||
height: 50%;
|
||||
`;
|
||||
|
||||
const ProviderInfoTopSection = styled.div`
|
||||
height: ${TEMPLATES_TOP_SECTION_HEIGHT};
|
||||
`;
|
||||
|
||||
const TemplatesCardsContainer = styled.div`
|
||||
height: calc(
|
||||
100vh - ${TEMPLATES_TOP_SECTION_HEIGHT} -
|
||||
${props => props.theme.headerHeight}
|
||||
);
|
||||
overflow: auto;
|
||||
.noProvidersMessage {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const TemplateCard = styled.div`
|
||||
border: 1px solid #e8e8e8;
|
||||
min-height: 150px;
|
||||
padding: 17px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
.extraDescription {
|
||||
background: #363e44;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
}
|
||||
.string-value {
|
||||
word-break: break-word !important;
|
||||
}
|
||||
.variable-value {
|
||||
word-break: break-word !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const CardTopContent = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const TemplateCardLeftContent = styled.div`
|
||||
width: 70%;
|
||||
|
||||
.apiName {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const TemplateCardRightContent = styled.div`
|
||||
width: 30%;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
justify-content: center;
|
||||
.dropIcon {
|
||||
padding-left: 15%;
|
||||
color: #bcccd9;
|
||||
cursor: pointer;
|
||||
}
|
||||
.addToPageBtn {
|
||||
width: 100px !important;
|
||||
height: 30px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const URLContainer = styled.div`
|
||||
background: #ebeef0;
|
||||
padding: 1px;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
|
||||
.urlText {
|
||||
color: #4e5d78;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
padding-top: 10px !important;
|
||||
padding-left: 11px;
|
||||
}
|
||||
.endpoint {
|
||||
padding-left: 11px;
|
||||
}
|
||||
`;
|
||||
|
||||
type ProviderTemplatesProps = {
|
||||
providerTemplates: ProviderTemplateArray[];
|
||||
actions: ActionDataState;
|
||||
isFetchingProviderTemplates: boolean;
|
||||
getProviderTemplates: (providerId: string) => void;
|
||||
addApiToPage: (templateData: AddApiToPageRequest) => void;
|
||||
} & RouteComponentProps<ProviderViewerRouteParams>;
|
||||
|
||||
class ProviderTemplates extends React.Component<ProviderTemplatesProps> {
|
||||
public state = {
|
||||
isOpen: false,
|
||||
addedTemplates: Array<string>(),
|
||||
toggeledTemplates: Array<string>(),
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.setState({ isOpen: !this.state.isOpen });
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { providerId } = this.props.match.params;
|
||||
this.props.getProviderTemplates(providerId);
|
||||
}
|
||||
|
||||
addApiToPage = (templateData: ProviderTemplateArray) => {
|
||||
const { destinationPageId } = this.props.match.params;
|
||||
const pageApiNames = this.props.actions
|
||||
.filter(a => a.config.pageId === destinationPageId)
|
||||
.map(a => a.config.name);
|
||||
let name = templateData.templateData.name.replace(/ /g, "");
|
||||
if (pageApiNames.indexOf(name) > -1) {
|
||||
name = getDuplicateName(name, pageApiNames);
|
||||
}
|
||||
|
||||
const addApiRequestObject: AddApiToPageRequest = {
|
||||
name,
|
||||
pageId: destinationPageId,
|
||||
marketplaceElement: {
|
||||
type: DEFAULT_TEMPLATE_TYPE,
|
||||
item: templateData.templateData,
|
||||
},
|
||||
};
|
||||
const { addedTemplates } = this.state;
|
||||
this.props.addApiToPage(addApiRequestObject);
|
||||
this.setState({ addedTemplates });
|
||||
};
|
||||
|
||||
handleSearchChange = (e: React.ChangeEvent<{ value: string }>) => {
|
||||
const value = e.target.value;
|
||||
};
|
||||
|
||||
handleIsOpen = (templateId: string) => {
|
||||
const { toggeledTemplates } = this.state;
|
||||
|
||||
const toggleCheck = toggeledTemplates.includes(templateId);
|
||||
if (toggleCheck) {
|
||||
toggeledTemplates.splice(toggeledTemplates.indexOf(templateId), 1);
|
||||
this.setState({ toggeledTemplates });
|
||||
} else {
|
||||
toggeledTemplates.push(templateId);
|
||||
this.setState({ toggeledTemplates });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
providerTemplates,
|
||||
history,
|
||||
isFetchingProviderTemplates,
|
||||
} = this.props;
|
||||
let providerName;
|
||||
let providerImage;
|
||||
|
||||
if (this.props.location.state) {
|
||||
providerName = new URLSearchParams(this.props.location.state).get(
|
||||
"providerName",
|
||||
);
|
||||
|
||||
providerImage = new URLSearchParams(this.props.location.state).get(
|
||||
"providerImage",
|
||||
);
|
||||
}
|
||||
|
||||
if (isFetchingProviderTemplates) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TemplateDetailPage>
|
||||
<ProviderInfoTopSection>
|
||||
<SearchContainer>
|
||||
<SearchBar
|
||||
icon="search"
|
||||
input={{
|
||||
onChange: this.handleSearchChange,
|
||||
}}
|
||||
placeholder="Search"
|
||||
/>
|
||||
</SearchContainer>
|
||||
|
||||
<Icon
|
||||
icon="chevron-left"
|
||||
iconSize={16}
|
||||
className="backBtn"
|
||||
onClick={() => history.goBack()}
|
||||
/>
|
||||
<span className="backBtnText" onClick={() => history.goBack()}>
|
||||
{" Back"}
|
||||
</span>
|
||||
<br />
|
||||
|
||||
<ProviderInfo>
|
||||
{providerImage ? (
|
||||
<img
|
||||
src={providerImage}
|
||||
className="providerImage"
|
||||
alt="provider"
|
||||
></img>
|
||||
) : (
|
||||
<img
|
||||
src={ImageAlt}
|
||||
className="providerImage"
|
||||
alt="provider"
|
||||
></img>
|
||||
)}
|
||||
<p className="providerName">{providerName}</p>
|
||||
</ProviderInfo>
|
||||
</ProviderInfoTopSection>
|
||||
<TemplatesCardsContainer>
|
||||
{providerTemplates.length === 0 && !isFetchingProviderTemplates ? (
|
||||
<p className="noProvidersMessage">
|
||||
No API templates for this provider yet.
|
||||
</p>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{providerTemplates.map(template => (
|
||||
<TemplateCard key={template.templateData.id}>
|
||||
<CardTopContent>
|
||||
<TemplateCardLeftContent>
|
||||
<p className="apiName">{template.templateData.name}</p>
|
||||
<p className="desc">
|
||||
{
|
||||
template.templateData.apiTemplateConfiguration
|
||||
.documentation
|
||||
}
|
||||
</p>
|
||||
<URLContainer>
|
||||
<p className="urlText">
|
||||
<strong>
|
||||
{
|
||||
template.templateData.actionConfiguration
|
||||
.httpMethod
|
||||
}
|
||||
</strong>{" "}
|
||||
<span className="endpoint">
|
||||
{template.templateData.actionConfiguration.path}
|
||||
</span>
|
||||
</p>
|
||||
</URLContainer>
|
||||
</TemplateCardLeftContent>
|
||||
|
||||
<TemplateCardRightContent>
|
||||
{template.addToPageStatus ? (
|
||||
<Button
|
||||
text="Added"
|
||||
intent="none"
|
||||
filled
|
||||
size="small"
|
||||
disabled={true}
|
||||
className="addToPageBtn"
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
text="Add to page"
|
||||
intent="primary"
|
||||
filled
|
||||
size="small"
|
||||
onClick={() => this.addApiToPage(template)}
|
||||
disabled={false}
|
||||
loading={template.addToPageLoading}
|
||||
className="addToPageBtn"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
icon="chevron-down"
|
||||
iconSize={20}
|
||||
className="dropIcon"
|
||||
onClick={() =>
|
||||
this.handleIsOpen(template.templateData.id)
|
||||
}
|
||||
/>
|
||||
</TemplateCardRightContent>
|
||||
</CardTopContent>
|
||||
|
||||
<Collapse
|
||||
isOpen={this.state.toggeledTemplates.includes(
|
||||
template.templateData.id,
|
||||
)}
|
||||
transitionDuration={0}
|
||||
>
|
||||
<ReactJson
|
||||
src={template.templateData.actionConfiguration.headers}
|
||||
style={{
|
||||
marginTop: "12px",
|
||||
fontSize: "14px",
|
||||
padding: "15px",
|
||||
borderTopLeftRadius: "4px",
|
||||
borderTopRightRadius: "4px",
|
||||
width: "90%",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
name="Request header"
|
||||
theme="grayscale"
|
||||
displayObjectSize={false}
|
||||
displayDataTypes={false}
|
||||
enableClipboard={false}
|
||||
/>
|
||||
{template.templateData.apiTemplateConfiguration
|
||||
.sampleResponse ? (
|
||||
<ReactJson
|
||||
src={
|
||||
template.templateData.apiTemplateConfiguration
|
||||
.sampleResponse
|
||||
}
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
padding: "15px",
|
||||
borderBottomLeftRadius: "4px",
|
||||
borderBottomRightRadius: "4px",
|
||||
maxWidth: "90%",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
name="Response Body"
|
||||
theme="grayscale"
|
||||
displayObjectSize={false}
|
||||
displayDataTypes={false}
|
||||
enableClipboard={false}
|
||||
/>
|
||||
) : (
|
||||
<p></p>
|
||||
)}
|
||||
</Collapse>
|
||||
</TemplateCard>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</TemplatesCardsContainer>
|
||||
</TemplateDetailPage>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
providerTemplates: getProviderTemplates(state),
|
||||
isFetchingProviderTemplates: getProvidersTemplatesLoadingState(state),
|
||||
actions: state.entities.actions,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
getProviderTemplates: (providerId: string) =>
|
||||
dispatch({
|
||||
type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT,
|
||||
payload: {
|
||||
providerId,
|
||||
},
|
||||
}),
|
||||
addApiToPage: (templateData: AddApiToPageRequest) =>
|
||||
dispatch(addApiToPage(templateData)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProviderTemplates);
|
||||
320
app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx
Normal file
320
app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
reduxForm,
|
||||
InjectedFormProps,
|
||||
FormSubmitHandler,
|
||||
formValueSelector,
|
||||
} from "redux-form";
|
||||
import { POST_BODY_FORMAT_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,
|
||||
PaginationField,
|
||||
BodyFormData,
|
||||
Property,
|
||||
} from "api/ActionAPI";
|
||||
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
||||
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView";
|
||||
import Pagination, { PaginationType } from "./Pagination";
|
||||
|
||||
const Form = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 95vh;
|
||||
max-height: 95vh;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
${FormLabel} {
|
||||
padding: ${props => props.theme.spaces[3]}px;
|
||||
}
|
||||
${FormRow} {
|
||||
padding: ${props => props.theme.spaces[2]}px;
|
||||
& > * {
|
||||
margin-right: 10px;
|
||||
}
|
||||
${FormLabel} {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MainConfiguration = styled.div`
|
||||
padding-top: 10px;
|
||||
padding-left: 17px;
|
||||
`;
|
||||
|
||||
const SecondaryWrapper = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
border-top: 1px solid #d0d7dd;
|
||||
margin-top: 15px;
|
||||
`;
|
||||
|
||||
const RequestParamsWrapper = styled.div`
|
||||
flex: 4;
|
||||
border-right: 1px solid #d0d7dd;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-top: 6px;
|
||||
padding-left: 17px;
|
||||
padding-right: 10px;
|
||||
`;
|
||||
|
||||
const ActionButtons = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const ActionButton = styled(BaseButton)`
|
||||
&&& {
|
||||
max-width: 72px;
|
||||
margin: 0 5px;
|
||||
min-height: 30px;
|
||||
}
|
||||
`;
|
||||
|
||||
const DropDownContainer = styled.div`
|
||||
margin: 5px;
|
||||
margin-bottom: 21px;
|
||||
`;
|
||||
|
||||
const PostbodyContainer = styled.div`
|
||||
margin-top: 41px;
|
||||
`;
|
||||
|
||||
const HeadersSection = styled.div`
|
||||
margin-bottom: 32px;
|
||||
`;
|
||||
|
||||
const TabbedViewContainer = styled.div`
|
||||
flex: 1;
|
||||
padding-top: 12px;
|
||||
`;
|
||||
|
||||
interface APIFormProps {
|
||||
allowSave: boolean;
|
||||
onSubmit: FormSubmitHandler<RestAction>;
|
||||
onSaveClick: () => void;
|
||||
onRunClick: (paginationField?: PaginationField) => void;
|
||||
onDeleteClick: () => void;
|
||||
isSaving: boolean;
|
||||
isRunning: boolean;
|
||||
isDeleting: boolean;
|
||||
paginationType: PaginationType;
|
||||
appName: string;
|
||||
templateId: string;
|
||||
actionConfiguration?: any;
|
||||
actionConfigurationHeaders?: Property[];
|
||||
displayFormat: string;
|
||||
actionConfigurationBodyFormData: BodyFormData[];
|
||||
providerImage: string;
|
||||
providerURL: string;
|
||||
providerCredentialSteps: string;
|
||||
}
|
||||
|
||||
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
||||
|
||||
const RapidApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||
const {
|
||||
allowSave,
|
||||
onSaveClick,
|
||||
onDeleteClick,
|
||||
onRunClick,
|
||||
handleSubmit,
|
||||
isDeleting,
|
||||
isRunning,
|
||||
isSaving,
|
||||
templateId,
|
||||
actionConfiguration,
|
||||
actionConfigurationHeaders,
|
||||
actionConfigurationBodyFormData,
|
||||
providerImage,
|
||||
providerURL,
|
||||
} = props;
|
||||
|
||||
const postbodyResponsePresent =
|
||||
templateId &&
|
||||
actionConfiguration &&
|
||||
actionConfigurationBodyFormData.length > 0;
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
{isSaving && <LoadingOverlayScreen>Saving...</LoadingOverlayScreen>}
|
||||
<MainConfiguration>
|
||||
<FormRow>
|
||||
<DynamicTextField
|
||||
placeholder="Api name"
|
||||
name="name"
|
||||
singleLine
|
||||
link={providerURL && `http://${providerURL}`}
|
||||
/>
|
||||
<ActionButtons>
|
||||
<ActionButton
|
||||
text="Delete"
|
||||
accent="error"
|
||||
onClick={onDeleteClick}
|
||||
loading={isDeleting}
|
||||
/>
|
||||
<ActionButton
|
||||
text="Run"
|
||||
accent="secondary"
|
||||
onClick={() => {
|
||||
onRunClick();
|
||||
}}
|
||||
loading={isRunning}
|
||||
/>
|
||||
<ActionButton
|
||||
text="Save"
|
||||
accent="primary"
|
||||
filled
|
||||
onClick={onSaveClick}
|
||||
loading={isSaving}
|
||||
disabled={!allowSave}
|
||||
/>
|
||||
</ActionButtons>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<DynamicTextField
|
||||
placeholder="Provider name"
|
||||
name="provider.name"
|
||||
singleLine
|
||||
leftImage={providerImage}
|
||||
disabled={true}
|
||||
/>
|
||||
<DynamicTextField
|
||||
placeholder="v1/method"
|
||||
name="actionConfiguration.path"
|
||||
leftIcon={FormIcons.SLASH_ICON}
|
||||
singleLine
|
||||
disabled={true}
|
||||
/>
|
||||
</FormRow>
|
||||
</MainConfiguration>
|
||||
<SecondaryWrapper>
|
||||
<TabbedViewContainer>
|
||||
<BaseTabbedView
|
||||
tabs={[
|
||||
{
|
||||
key: "apiInput",
|
||||
title: "API Input",
|
||||
panelComponent: (
|
||||
<RequestParamsWrapper>
|
||||
<HeadersSection>
|
||||
<KeyValueFieldArray
|
||||
name="actionConfiguration.headers"
|
||||
label="Headers"
|
||||
actionConfig={
|
||||
actionConfiguration &&
|
||||
actionConfigurationHeaders &&
|
||||
actionConfigurationHeaders
|
||||
}
|
||||
/>
|
||||
</HeadersSection>
|
||||
<KeyValueFieldArray
|
||||
name="actionConfiguration.queryParameters"
|
||||
label="Params"
|
||||
/>
|
||||
{postbodyResponsePresent && (
|
||||
<PostbodyContainer>
|
||||
<FormLabel>{"Post Body"}</FormLabel>
|
||||
<DropDownContainer>
|
||||
<DropdownField
|
||||
placeholder={POST_BODY_FORMAT_OPTIONS[1].value}
|
||||
name="displayFormat"
|
||||
isSearchable={false}
|
||||
width={232}
|
||||
options={POST_BODY_FORMAT_OPTIONS}
|
||||
isDisabled={true}
|
||||
/>
|
||||
</DropDownContainer>
|
||||
{typeof actionConfigurationBodyFormData ===
|
||||
"object" && (
|
||||
<React.Fragment>
|
||||
<KeyValueFieldArray
|
||||
name="actionConfiguration.bodyFormData"
|
||||
label=""
|
||||
rightIcon={FormIcons.INFO_ICON}
|
||||
addOrDeleteFields={false}
|
||||
actionConfig={actionConfigurationBodyFormData}
|
||||
pushFields={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</PostbodyContainer>
|
||||
)}
|
||||
</RequestParamsWrapper>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "pagination",
|
||||
title: "Pagination",
|
||||
panelComponent: (
|
||||
<Pagination
|
||||
onTestClick={props.onRunClick}
|
||||
paginationType={props.paginationType}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TabbedViewContainer>
|
||||
|
||||
<ApiResponseView />
|
||||
</SecondaryWrapper>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
||||
|
||||
export default connect(state => {
|
||||
const displayFormat = selector(state, "displayFormat");
|
||||
const providerImage = selector(state, "provider.imageUrl");
|
||||
const providerURL = selector(state, "provider.url");
|
||||
const providerCredentialSteps = selector(state, "provider.credentialSteps");
|
||||
const templateId = selector(state, "templateId");
|
||||
const actionConfiguration = selector(state, "actionConfiguration");
|
||||
let actionConfigurationBodyFormData = selector(
|
||||
state,
|
||||
"actionConfiguration.bodyFormData",
|
||||
);
|
||||
const actionConfigurationHeaders = selector(
|
||||
state,
|
||||
"actionConfiguration.headers",
|
||||
);
|
||||
|
||||
if (
|
||||
typeof actionConfigurationBodyFormData === "string" &&
|
||||
(displayFormat === POST_BODY_FORMAT_OPTIONS[0].value ||
|
||||
displayFormat === POST_BODY_FORMAT_OPTIONS[1].value)
|
||||
) {
|
||||
actionConfigurationBodyFormData = JSON.parse(
|
||||
`${actionConfigurationBodyFormData}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
displayFormat,
|
||||
actionConfiguration,
|
||||
actionConfigurationHeaders,
|
||||
actionConfigurationBodyFormData,
|
||||
providerImage,
|
||||
providerURL,
|
||||
templateId,
|
||||
providerCredentialSteps,
|
||||
};
|
||||
})(
|
||||
reduxForm<RestAction, APIFormProps>({
|
||||
form: API_EDITOR_FORM_NAME,
|
||||
destroyOnUnmount: false,
|
||||
})(RapidApiEditorForm),
|
||||
);
|
||||
117
app/client/src/pages/Editor/APIEditor/ResultPagination.tsx
Normal file
117
app/client/src/pages/Editor/APIEditor/ResultPagination.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import React from "react";
|
||||
import Pagination from "react-paginating";
|
||||
import styled from "styled-components";
|
||||
import { theme } from "constants/DefaultTheme";
|
||||
|
||||
interface ResultPaginationProps {
|
||||
total: number;
|
||||
handlePageChange: (page: number | undefined) => void;
|
||||
currentPage: number | undefined;
|
||||
}
|
||||
|
||||
type Props = ResultPaginationProps;
|
||||
|
||||
const PaginationContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
background-color: white;
|
||||
width: 30px;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
border: 1.5px solid #dcdcdc;
|
||||
margin: 0 5px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
class ResultPagination extends React.Component<Props> {
|
||||
render() {
|
||||
const { total, handlePageChange, currentPage = 1 } = this.props;
|
||||
|
||||
return (
|
||||
<Pagination total={total} limit={40} pageCount={5}>
|
||||
{({
|
||||
pages,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
previousPage,
|
||||
nextPage,
|
||||
totalPages,
|
||||
getPageItemProps,
|
||||
}) => {
|
||||
console.log(hasNextPage, "hasNextPage");
|
||||
|
||||
return (
|
||||
<PaginationContainer>
|
||||
<div
|
||||
className="item"
|
||||
{...getPageItemProps({
|
||||
total: totalPages,
|
||||
pageValue: previousPage,
|
||||
onPageChange: hasPreviousPage ? handlePageChange : () => null,
|
||||
style: !hasPreviousPage
|
||||
? {
|
||||
backgroundColor: "#DCDCDC",
|
||||
cursor: "not-allowed",
|
||||
}
|
||||
: {},
|
||||
})}
|
||||
>
|
||||
{"<"}
|
||||
</div>
|
||||
|
||||
{pages.map((page, index) => {
|
||||
let activePage = undefined;
|
||||
if (currentPage === page) {
|
||||
activePage = {
|
||||
borderColor: theme.colors.primary,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="item"
|
||||
key={`marketPlace${index}`}
|
||||
{...getPageItemProps({
|
||||
total: totalPages,
|
||||
pageValue: page,
|
||||
style: activePage,
|
||||
onPageChange: handlePageChange,
|
||||
})}
|
||||
>
|
||||
{page}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div
|
||||
className="item"
|
||||
{...getPageItemProps({
|
||||
total: totalPages,
|
||||
pageValue: nextPage,
|
||||
onPageChange: hasNextPage ? handlePageChange : () => null,
|
||||
style: !hasNextPage
|
||||
? {
|
||||
backgroundColor: "#DCDCDC",
|
||||
cursor: "not-allowed",
|
||||
}
|
||||
: {},
|
||||
})}
|
||||
>
|
||||
{">"}
|
||||
</div>
|
||||
</PaginationContainer>
|
||||
);
|
||||
}}
|
||||
</Pagination>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ResultPagination;
|
||||
14
app/client/src/pages/Editor/APIEditor/helpers.ts
Normal file
14
app/client/src/pages/Editor/APIEditor/helpers.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { submitCurlImportForm } from "actions/importActions";
|
||||
|
||||
export type curlImportFormValues = {
|
||||
curl: string;
|
||||
pageId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const curlImportSubmitHandler = (
|
||||
values: curlImportFormValues,
|
||||
dispatch: any,
|
||||
) => {
|
||||
dispatch(submitCurlImportForm(values));
|
||||
};
|
||||
|
|
@ -2,35 +2,40 @@ import React from "react";
|
|||
import { connect } from "react-redux";
|
||||
import { submit, getFormValues } from "redux-form";
|
||||
import ApiEditorForm from "./Form";
|
||||
import RapidApiEditorForm from "./RapidApiEditorForm";
|
||||
import ApiHomeScreen from "./ApiHomeScreen";
|
||||
import {
|
||||
createActionRequest,
|
||||
runApiAction,
|
||||
deleteAction,
|
||||
updateAction,
|
||||
} from "actions/actionActions";
|
||||
import { RestAction, PaginationField } from "api/ActionAPI";
|
||||
import { RestAction, PaginationField, RapidApiAction } from "api/ActionAPI";
|
||||
import { AppState } from "reducers";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
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 { HTTP_METHODS, PLUGIN_NAME } from "constants/ApiEditorConstants";
|
||||
import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants";
|
||||
import _ from "lodash";
|
||||
import { getPluginIdOfName } from "selectors/entitiesSelector";
|
||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||
import { UserApplication } from "constants/userConstants";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { getCurrentPageName } from "selectors/editorSelectors";
|
||||
import { getCurrentPageName, getActionById } from "selectors/editorSelectors";
|
||||
import { Plugin } from "api/PluginApi";
|
||||
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||
|
||||
interface ReduxStateProps {
|
||||
actions: ActionDataState;
|
||||
apiPane: ApiPaneReduxState;
|
||||
formData: RestAction;
|
||||
pluginId: string | undefined;
|
||||
currentApplication: UserApplication;
|
||||
currentPageName: string | undefined;
|
||||
pages: any;
|
||||
plugins: Plugin[];
|
||||
pluginId: any;
|
||||
apiAction: RestAction | ActionData | RapidApiAction | undefined;
|
||||
data: RestAction | ActionData | RapidApiAction | undefined;
|
||||
}
|
||||
interface ReduxActionProps {
|
||||
submitForm: (name: string) => void;
|
||||
|
|
@ -49,14 +54,6 @@ type Props = ReduxActionProps &
|
|||
ReduxStateProps &
|
||||
RouteComponentProps<{ apiId: string; applicationId: string; pageId: string }>;
|
||||
|
||||
const EmptyStateContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
font-size: 20px;
|
||||
`;
|
||||
|
||||
class ApiEditor extends React.Component<Props> {
|
||||
handleSubmit = (values: RestAction) => {
|
||||
const { formData } = this.props;
|
||||
|
|
@ -94,66 +91,135 @@ class ApiEditor extends React.Component<Props> {
|
|||
this.props.runAction(this.props.match.params.apiId, paginationField);
|
||||
};
|
||||
|
||||
getPluginUiComponentOfId = (
|
||||
id: string,
|
||||
plugins: Plugin[],
|
||||
): string | undefined => {
|
||||
const plugin = plugins.find(plugin => plugin.id === id);
|
||||
if (!plugin) return undefined;
|
||||
return plugin.uiComponent;
|
||||
};
|
||||
|
||||
getPluginUiComponentOfName = (plugins: Plugin[]): string | undefined => {
|
||||
const plugin = plugins.find(
|
||||
plugin => plugin.packageName === REST_PLUGIN_PACKAGE_NAME,
|
||||
);
|
||||
if (!plugin) return undefined;
|
||||
return plugin.uiComponent;
|
||||
};
|
||||
|
||||
getAction = (apiId: string, actions: ActionDataState) => {
|
||||
const action = _.find(actions, a => a.config.id === apiId);
|
||||
if (action) {
|
||||
return action.config;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
apiPane,
|
||||
match: {
|
||||
params: { apiId },
|
||||
},
|
||||
formData,
|
||||
plugins,
|
||||
pluginId,
|
||||
data,
|
||||
} = this.props;
|
||||
if (!pluginId) {
|
||||
return (
|
||||
<EmptyStateContainer>{"Plugin is not installed"}</EmptyStateContainer>
|
||||
);
|
||||
|
||||
let formUiComponent: string | undefined;
|
||||
if (apiId) {
|
||||
if (pluginId) {
|
||||
formUiComponent = this.getPluginUiComponentOfId(pluginId, plugins);
|
||||
} else {
|
||||
formUiComponent = this.getPluginUiComponentOfName(plugins);
|
||||
}
|
||||
}
|
||||
|
||||
const { isSaving, isRunning, isDeleting, drafts } = apiPane;
|
||||
const httpMethod = _.get(formData, "actionConfiguration.httpMethod");
|
||||
const paginationType = _.get(
|
||||
formData,
|
||||
"actionConfiguration.paginationType",
|
||||
);
|
||||
const paginationType = _.get(data, "actionConfiguration.paginationType");
|
||||
return (
|
||||
<React.Fragment>
|
||||
{apiId ? (
|
||||
<ApiEditorForm
|
||||
pluginId={pluginId}
|
||||
allowSave={apiId in drafts}
|
||||
allowPostBody={httpMethod && httpMethod !== HTTP_METHODS[0]}
|
||||
paginationType={paginationType}
|
||||
isSaving={isSaving[apiId]}
|
||||
isRunning={isRunning[apiId]}
|
||||
isDeleting={isDeleting[apiId]}
|
||||
onSubmit={this.handleSubmit}
|
||||
onSaveClick={this.handleSaveClick}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
<>
|
||||
{formUiComponent === "ApiEditorForm" && (
|
||||
<ApiEditorForm
|
||||
pluginId={pluginId}
|
||||
allowSave={apiId in drafts}
|
||||
paginationType={paginationType}
|
||||
isSaving={isSaving[apiId]}
|
||||
isRunning={isRunning[apiId]}
|
||||
isDeleting={isDeleting[apiId]}
|
||||
onSubmit={this.handleSubmit}
|
||||
onSaveClick={this.handleSaveClick}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{formUiComponent === "RapidApiEditorForm" && (
|
||||
<RapidApiEditorForm
|
||||
allowSave={apiId in drafts}
|
||||
paginationType={paginationType}
|
||||
isSaving={isSaving[apiId]}
|
||||
isRunning={isRunning[apiId]}
|
||||
isDeleting={isDeleting[apiId]}
|
||||
onSubmit={this.handleSubmit}
|
||||
onSaveClick={this.handleSaveClick}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<EmptyStateContainer>
|
||||
{"Create / Select an API from the list"}
|
||||
</EmptyStateContainer>
|
||||
<ApiHomeScreen
|
||||
applicationId={this.props.match.params.applicationId}
|
||||
pageId={this.props.match.params.pageId}
|
||||
history={this.props.history}
|
||||
location={this.props.location}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||
pluginId: getPluginIdOfName(state, PLUGIN_NAME),
|
||||
actions: state.entities.actions,
|
||||
apiPane: state.ui.apiPane,
|
||||
currentApplication: getCurrentApplication(state),
|
||||
currentPageName: getCurrentPageName(state),
|
||||
pages: state.entities.pageList.pages,
|
||||
formData: getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction,
|
||||
});
|
||||
const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
||||
const formData = getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction;
|
||||
const apiAction = getActionById(state, props);
|
||||
|
||||
const { drafts } = state.ui.apiPane;
|
||||
let data: RestAction | ActionData | RapidApiAction | undefined;
|
||||
if (props.match.params.id in drafts) {
|
||||
data = drafts[props.match.params.id];
|
||||
} else {
|
||||
data = apiAction;
|
||||
}
|
||||
|
||||
return {
|
||||
actions: state.entities.actions,
|
||||
apiPane: state.ui.apiPane,
|
||||
currentApplication: getCurrentApplication(state),
|
||||
currentPageName: getCurrentPageName(state),
|
||||
pages: state.entities.pageList.pages,
|
||||
formData,
|
||||
data,
|
||||
plugins: state.entities.plugins.list,
|
||||
pluginId: _.get(data, "pluginId"),
|
||||
apiAction,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
||||
submitForm: (name: string) => dispatch(submit(name)),
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ import {
|
|||
import { changeApi, initApiPane } from "actions/apiPaneActions";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
import { getPluginIdOfName } from "selectors/entitiesSelector";
|
||||
import { DEFAULT_API_ACTION, PLUGIN_NAME } from "constants/ApiEditorConstants";
|
||||
import { PLUGIN_NAME } from "constants/ApiEditorConstants";
|
||||
import EditorSidebar from "pages/Editor/EditorSidebar";
|
||||
import { getNextEntityName } from "utils/AppsmithUtils";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { API_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes";
|
||||
|
||||
const HTTPMethod = styled.span<{ method?: string }>`
|
||||
flex: 1;
|
||||
|
|
@ -51,6 +52,7 @@ const ActionName = styled.span`
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100px;
|
||||
`;
|
||||
|
||||
interface ReduxStateProps {
|
||||
|
|
@ -92,19 +94,6 @@ class ApiSidebar extends React.Component<Props> {
|
|||
return nextProps.actions !== this.props.actions;
|
||||
}
|
||||
|
||||
handleCreateNew = () => {
|
||||
const { actions } = this.props;
|
||||
const { pageId } = this.props.match.params;
|
||||
const pageApiNames = actions
|
||||
.filter(a => a.config.pageId === pageId)
|
||||
.map(a => a.config.name);
|
||||
const newName = getNextEntityName("Api", pageApiNames);
|
||||
this.props.createAction({ ...DEFAULT_API_ACTION, name: newName, pageId });
|
||||
AnalyticsUtil.logEvent("CREATE_API_CLICK", {
|
||||
apiName: newName,
|
||||
});
|
||||
};
|
||||
|
||||
handleApiChange = (actionId: string) => {
|
||||
this.props.onApiChange(actionId);
|
||||
};
|
||||
|
|
@ -169,6 +158,18 @@ class ApiSidebar extends React.Component<Props> {
|
|||
);
|
||||
};
|
||||
|
||||
handleCreateNewApiClick = (selectedPageId: string) => {
|
||||
const { history } = this.props;
|
||||
const { pageId, applicationId } = this.props.match.params;
|
||||
history.push(
|
||||
API_EDITOR_URL_WITH_SELECTED_PAGE_ID(
|
||||
applicationId,
|
||||
pageId,
|
||||
selectedPageId,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
apiPane: { isFetching, drafts },
|
||||
|
|
@ -187,11 +188,12 @@ class ApiSidebar extends React.Component<Props> {
|
|||
selectedItemId={apiId}
|
||||
draftIds={Object.keys(drafts)}
|
||||
itemRender={this.renderItem}
|
||||
onItemCreateClick={this.handleCreateNew}
|
||||
onItemCreateClick={this.handleCreateNewApiClick}
|
||||
onItemSelected={this.handleApiChange}
|
||||
moveItem={this.handleMove}
|
||||
copyItem={this.handleCopy}
|
||||
deleteItem={this.handleDelete}
|
||||
createButtonTitle="Create new API"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ import { RouteComponentProps, withRouter } from "react-router";
|
|||
import styled from "styled-components";
|
||||
import { AppState } from "reducers";
|
||||
import { APIEditorRouteParams } from "constants/routes";
|
||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import { Spinner } from "@blueprintjs/core";
|
||||
import { Spinner, IIconProps } from "@blueprintjs/core";
|
||||
import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import Fuse from "fuse.js";
|
||||
import Button from "components/editorComponents/Button";
|
||||
import {
|
||||
DragDropContext,
|
||||
Draggable,
|
||||
|
|
@ -20,6 +19,7 @@ import {
|
|||
import { PageListPayload } from "constants/ReduxActionConstants";
|
||||
import ContextDropdown from "components/editorComponents/ContextDropdown";
|
||||
import { theme } from "constants/DefaultTheme";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const LoadingContainer = styled(CenteredWrapper)`
|
||||
height: 50%;
|
||||
|
|
@ -52,7 +52,6 @@ const SearchBar = styled(BaseTextInput)`
|
|||
|
||||
const PageContainer = styled.div`
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #485158;
|
||||
`;
|
||||
|
||||
const PageName = styled.h5<{ isMain: boolean }>`
|
||||
|
|
@ -72,15 +71,69 @@ const PageDropContainer = styled.div`
|
|||
min-height: 32px;
|
||||
margin: 5px;
|
||||
background-color: ${props => props.theme.colors.paneBG};
|
||||
|
||||
.createBtn {
|
||||
border: none;
|
||||
color: ${Colors.WHITE} !important;
|
||||
width: 100%;
|
||||
display: block !important;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
&:hover {
|
||||
color: ${Colors.WHITE} !important;
|
||||
background-color: ${Colors.BLUE_CHARCOAL} !important;
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: ${Colors.WHITE};
|
||||
}
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
color: ${Colors.WHITE} !important;
|
||||
background-color: ${Colors.BLUE_CHARCOAL} !important;
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: ${Colors.WHITE};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.highlightButton {
|
||||
color: ${Colors.WHITE} !important;
|
||||
background-color: ${Colors.BLUE_CHARCOAL} !important;
|
||||
width: 100%;
|
||||
display: block !important;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: ${Colors.WHITE};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.linkStyles {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const ItemsWrapper = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const NoItemMessage = styled.span`
|
||||
color: #cacaca;
|
||||
const NoItemMessage = styled.div`
|
||||
color: #d0d7dd;
|
||||
padding-left: 12px;
|
||||
padding-top: 23px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
padding-bottom: 10px;
|
||||
`;
|
||||
|
||||
const ItemContainer = styled.div<{
|
||||
|
|
@ -119,26 +172,14 @@ const DraftIconIndicator = styled.span<{ isHidden: boolean }>`
|
|||
opacity: ${({ isHidden }) => (isHidden ? 0 : 1)};
|
||||
`;
|
||||
|
||||
const CreateNewButton = styled(BaseButton)`
|
||||
&&&& {
|
||||
border: none;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
height: 32px;
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
&:hover {
|
||||
color: ${props => props.theme.colors.paneBG};
|
||||
svg {
|
||||
path {
|
||||
fill: ${props => props.theme.colors.paneBG};
|
||||
}
|
||||
}
|
||||
}
|
||||
svg {
|
||||
margin-top: 4px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
const StyledAddButton = styled(Button)<IIconProps>`
|
||||
&&& {
|
||||
outline: none;
|
||||
|
||||
padding: 10px !important;
|
||||
}
|
||||
span {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -155,11 +196,12 @@ type EditorSidebarComponentProps = {
|
|||
selectedItemId?: string;
|
||||
draftIds: string[];
|
||||
itemRender: (item: any) => JSX.Element;
|
||||
onItemCreateClick: () => void;
|
||||
onItemSelected: (itemId: string) => void;
|
||||
onItemCreateClick: (pageId: string) => void;
|
||||
onItemSelected: (itemId: string, itemPageId: string) => void;
|
||||
moveItem: (itemId: string, destinationPageId: string) => void;
|
||||
copyItem: (itemId: string, destinationPageId: string) => void;
|
||||
deleteItem: (itemId: string, itemName: string, pageName: string) => void;
|
||||
createButtonTitle: string;
|
||||
};
|
||||
|
||||
type Props = ReduxStateProps &
|
||||
|
|
@ -190,9 +232,6 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
itemDragging: "",
|
||||
};
|
||||
}
|
||||
handleCreateNew = () => {
|
||||
this.props.onItemCreateClick();
|
||||
};
|
||||
|
||||
handleSearchChange = (e: React.ChangeEvent<{ value: string }>) => {
|
||||
const value = e.target.value;
|
||||
|
|
@ -201,8 +240,8 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
});
|
||||
};
|
||||
|
||||
handleItemSelect = (itemId: string) => {
|
||||
this.props.onItemSelected(itemId);
|
||||
handleItemSelect = (itemId: string, itemPageId: string) => {
|
||||
this.props.onItemSelected(itemId, itemPageId);
|
||||
};
|
||||
|
||||
onDragEnd = (result: DropResult) => {
|
||||
|
|
@ -260,11 +299,27 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
});
|
||||
};
|
||||
|
||||
handleCreateNew = (pageId: string) => {
|
||||
this.props.onItemCreateClick(pageId);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isLoading, itemRender, selectedItemId, draftIds } = this.props;
|
||||
const {
|
||||
isLoading,
|
||||
itemRender,
|
||||
selectedItemId,
|
||||
draftIds,
|
||||
location,
|
||||
createButtonTitle,
|
||||
} = this.props;
|
||||
|
||||
const { search } = this.state;
|
||||
const filteredList = this.getSearchFilteredList();
|
||||
const pageWiseList = this.getPageWiseList(filteredList);
|
||||
const destinationPageId = new URLSearchParams(location.search).get(
|
||||
"importTo",
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isLoading ? (
|
||||
|
|
@ -283,11 +338,6 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
}}
|
||||
placeholder="Search"
|
||||
/>
|
||||
<CreateNewButton
|
||||
text="Create new API"
|
||||
icon={FormIcons.ADD_NEW_ICON()}
|
||||
onClick={this.handleCreateNew}
|
||||
/>
|
||||
</Controls>
|
||||
<DragDropContext
|
||||
onDragStart={this.onDragStart}
|
||||
|
|
@ -312,9 +362,21 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
>
|
||||
{provided.placeholder}
|
||||
<div>
|
||||
<StyledAddButton
|
||||
text={createButtonTitle}
|
||||
icon="plus"
|
||||
fluid
|
||||
className={
|
||||
destinationPageId === page.id
|
||||
? "highlightButton"
|
||||
: "createBtn"
|
||||
}
|
||||
style={{ padding: "10px" }}
|
||||
onClick={() => this.handleCreateNew(page.id)}
|
||||
/>
|
||||
{page.items.length === 0 && (
|
||||
<NoItemMessage>
|
||||
{"No item on this page yet"}
|
||||
{"No APIs on this page yet"}
|
||||
</NoItemMessage>
|
||||
)}
|
||||
{page.items.map((item: Item, index) => (
|
||||
|
|
@ -334,7 +396,10 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
onClick={() =>
|
||||
this.handleItemSelect(item.id)
|
||||
this.handleItemSelect(
|
||||
item.id,
|
||||
item.pageId,
|
||||
)
|
||||
}
|
||||
>
|
||||
{itemRender(item)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React from "react";
|
||||
import { Switch, withRouter, RouteComponentProps } from "react-router-dom";
|
||||
import ApiEditor from "./APIEditor";
|
||||
import CurlImportForm from "./APIEditor/CurlImportForm";
|
||||
import ProviderTemplates from "./APIEditor/ProviderTemplates";
|
||||
import {
|
||||
API_EDITOR_ID_URL,
|
||||
API_EDITOR_URL,
|
||||
|
|
@ -8,6 +10,9 @@ import {
|
|||
BUILDER_BASE_URL,
|
||||
BuilderRouteParams,
|
||||
APIEditorRouteParams,
|
||||
getCurlImportPageURL,
|
||||
API_EDITOR_URL_WITH_SELECTED_PAGE_ID,
|
||||
getProviderTemplatesURL,
|
||||
} from "constants/routes";
|
||||
import styled from "styled-components";
|
||||
import AppRoute from "pages/common/AppRoute";
|
||||
|
|
@ -30,7 +35,6 @@ const DrawerWrapper = styled.div<{
|
|||
background-color: white;
|
||||
width: ${props => (props.showOnlySidebar ? "0px" : "75%")};
|
||||
height: 100%;
|
||||
box-shadow: -1px 2px 3px 0px ${props => props.theme.colors.paneBG};
|
||||
`;
|
||||
|
||||
interface RouterState {
|
||||
|
|
@ -113,6 +117,23 @@ class EditorsRouter extends React.Component<
|
|||
component={ApiEditor}
|
||||
name={"ApiEditor"}
|
||||
/>
|
||||
<AppRoute
|
||||
exact
|
||||
path={API_EDITOR_URL_WITH_SELECTED_PAGE_ID()}
|
||||
component={ApiEditor}
|
||||
name={"ApiEditor"}
|
||||
/>
|
||||
<AppRoute
|
||||
exact
|
||||
path={getCurlImportPageURL()}
|
||||
component={CurlImportForm}
|
||||
name={"ApiEditor"}
|
||||
/>
|
||||
<AppRoute
|
||||
path={getProviderTemplatesURL()}
|
||||
component={ProviderTemplates}
|
||||
name={"ApiEditor"}
|
||||
/>
|
||||
</Switch>
|
||||
</DrawerWrapper>
|
||||
</Wrapper>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import {
|
|||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { ActionResponse, RestAction } from "api/ActionAPI";
|
||||
import { ActionResponse, RapidApiAction, RestAction } from "api/ActionAPI";
|
||||
import { ExecuteErrorPayload } from "constants/ActionConstants";
|
||||
import _ from "lodash";
|
||||
export interface ActionData {
|
||||
isLoading: boolean;
|
||||
config: RestAction;
|
||||
config: RestAction | RapidApiAction;
|
||||
data?: ActionResponse;
|
||||
}
|
||||
export type ActionDataState = ActionData[];
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ import { AuthState } from "reducers/uiReducers/authReducer";
|
|||
import { OrgReduxState } from "reducers/uiReducers/orgReducer";
|
||||
import { UsersReduxState } from "reducers/uiReducers/usersReducer";
|
||||
import { WidgetDragResizeState } from "reducers/uiReducers/dragResizeReducer";
|
||||
import { ImportedCollectionsReduxState } from "reducers/uiReducers/importedCollectionsReducer";
|
||||
import { ProvidersReduxState } from "reducers/uiReducers/providerReducer";
|
||||
import { MetaState } from "./entityReducers/metaReducer";
|
||||
import { ImportReduxState } from "reducers/uiReducers/importReducer";
|
||||
|
||||
const appReducer = combineReducers({
|
||||
entities: entityReducer,
|
||||
|
|
@ -44,6 +47,9 @@ export interface AppState {
|
|||
orgs: OrgReduxState;
|
||||
users: UsersReduxState;
|
||||
widgetDragResize: WidgetDragResizeState;
|
||||
importedCollections: ImportedCollectionsReduxState;
|
||||
providers: ProvidersReduxState;
|
||||
imports: ImportReduxState;
|
||||
};
|
||||
entities: {
|
||||
canvasWidgets: CanvasWidgetsReduxState;
|
||||
|
|
|
|||
37
app/client/src/reducers/uiReducers/importReducer.ts
Normal file
37
app/client/src/reducers/uiReducers/importReducer.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { createReducer } from "utils/AppsmithUtils";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
|
||||
const initialState: ImportReduxState = {
|
||||
isImportingCurl: false,
|
||||
errorPayload: {},
|
||||
};
|
||||
|
||||
const importReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.SUBMIT_CURL_FORM_INIT]: (state: ImportReduxState) => {
|
||||
return {
|
||||
...state,
|
||||
isImportingCurl: true,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS]: (state: ImportReduxState) => ({
|
||||
...state,
|
||||
isImportingCurl: false,
|
||||
}),
|
||||
[ReduxActionErrorTypes.SUBMIT_CURL_FORM_ERROR]: (
|
||||
state: ImportReduxState,
|
||||
action: ReduxAction<{ errorPayload: string }>,
|
||||
) => {
|
||||
return { ...state, errorPayload: action.payload, isImportingCurl: false };
|
||||
},
|
||||
});
|
||||
|
||||
export interface ImportReduxState {
|
||||
isImportingCurl: boolean;
|
||||
errorPayload: {};
|
||||
}
|
||||
|
||||
export default importReducer;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { createReducer } from "utils/AppsmithUtils";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
|
||||
import {
|
||||
TemplateList,
|
||||
CollectionDataArray,
|
||||
} from "constants/collectionsConstants";
|
||||
|
||||
const initialState: ImportedCollectionsReduxState = {
|
||||
isFetchingImportedCollections: false,
|
||||
importedCollections: [],
|
||||
};
|
||||
|
||||
const importedCollectionsReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.FETCH_IMPORTED_COLLECTIONS_INIT]: (
|
||||
state: ImportedCollectionsReduxState,
|
||||
) => ({ ...state, isFetchingImportedCollections: true }),
|
||||
[ReduxActionTypes.FETCH_IMPORTED_COLLECTIONS_SUCCESS]: (
|
||||
state: ImportedCollectionsReduxState,
|
||||
action: ReduxAction<Array<CollectionDataArray>>,
|
||||
) => ({
|
||||
...state,
|
||||
importedCollections: action.payload[0].apiTemplateList,
|
||||
isFetchingImportedCollections: false,
|
||||
}),
|
||||
[ReduxActionErrorTypes.FETCH_IMPORTED_COLLECTIONS_ERROR]: (
|
||||
state: ImportedCollectionsReduxState,
|
||||
) => {
|
||||
return { ...state, isFetchingImportedCollections: false };
|
||||
},
|
||||
});
|
||||
|
||||
export interface ImportedCollectionsReduxState {
|
||||
isFetchingImportedCollections: boolean;
|
||||
importedCollections: TemplateList[];
|
||||
}
|
||||
|
||||
export default importedCollectionsReducer;
|
||||
|
|
@ -10,6 +10,9 @@ import authReducer from "./authReducer";
|
|||
import orgReducer from "./orgReducer";
|
||||
import usersReducer from "./usersReducer";
|
||||
import { widgetDraggingReducer } from "./dragResizeReducer";
|
||||
import importedCollectionsReducer from "./importedCollectionsReducer";
|
||||
import providersReducer from "./providerReducer";
|
||||
import importReducer from "./importReducer";
|
||||
|
||||
const uiReducer = combineReducers({
|
||||
widgetSidebar: widgetSidebarReducer,
|
||||
|
|
@ -23,5 +26,8 @@ const uiReducer = combineReducers({
|
|||
orgs: orgReducer,
|
||||
users: usersReducer,
|
||||
widgetDragResize: widgetDraggingReducer,
|
||||
importedCollections: importedCollectionsReducer,
|
||||
providers: providersReducer,
|
||||
imports: importReducer,
|
||||
});
|
||||
export default uiReducer;
|
||||
|
|
|
|||
162
app/client/src/reducers/uiReducers/providerReducer.ts
Normal file
162
app/client/src/reducers/uiReducers/providerReducer.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import { createReducer } from "utils/AppsmithUtils";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
|
||||
import {
|
||||
Providers,
|
||||
ProvidersDataArray,
|
||||
ProviderTemplates,
|
||||
ProviderTemplateArray,
|
||||
ProvidersCategoriesResponse,
|
||||
} from "constants/providerConstants";
|
||||
|
||||
const initialState: ProvidersReduxState = {
|
||||
isFetchingProviders: false,
|
||||
providers: [],
|
||||
providersTotal: 0,
|
||||
providerTemplates: [],
|
||||
isFetchingProviderTemplates: false,
|
||||
providerCategories: [],
|
||||
providerCategoriesErrorPayload: {},
|
||||
isSwitchingCategory: true,
|
||||
};
|
||||
|
||||
const providersReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.FETCH_PROVIDERS_INIT]: (state: ProvidersReduxState) => ({
|
||||
...state,
|
||||
isFetchingProviders: true,
|
||||
}),
|
||||
[ReduxActionTypes.FETCH_PROVIDERS_WITH_CATEGORY_INIT]: (
|
||||
state: ProvidersReduxState,
|
||||
) => ({
|
||||
...state,
|
||||
isFetchingProviders: true,
|
||||
}),
|
||||
[ReduxActionTypes.ADD_API_TO_PAGE_INIT]: (
|
||||
state: ProvidersReduxState,
|
||||
action: any,
|
||||
) => {
|
||||
const updatedProviderTemplates = state.providerTemplates.map(item => {
|
||||
if (item.templateData.id === action.payload.marketplaceElement.item.id) {
|
||||
item.addToPageLoading = true;
|
||||
return item;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return { ...state, providerTemplates: updatedProviderTemplates };
|
||||
},
|
||||
[ReduxActionTypes.FETCH_PROVIDERS_SUCCESS]: (
|
||||
state: ProvidersReduxState,
|
||||
action: ReduxAction<Providers>,
|
||||
) => {
|
||||
if (state.providers.length === 0) {
|
||||
return {
|
||||
...state,
|
||||
providers: action.payload.providers,
|
||||
providersTotal: action.payload.total,
|
||||
isFetchingProviders: false,
|
||||
isSwitchingCategory: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
providers: [...state.providers, ...action.payload.providers],
|
||||
isFetchingProviders: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
[ReduxActionErrorTypes.FETCH_PROVIDERS_ERROR]: (
|
||||
state: ProvidersReduxState,
|
||||
) => {
|
||||
return { ...state, isFetchingProviders: false };
|
||||
},
|
||||
[ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT]: (
|
||||
state: ProvidersReduxState,
|
||||
) => ({
|
||||
...state,
|
||||
isFetchingProviderTemplates: true,
|
||||
}),
|
||||
[ReduxActionTypes.CLEAR_PROVIDERS]: (state: ProvidersReduxState) => ({
|
||||
...state,
|
||||
providers: [],
|
||||
isSwitchingCategory: true,
|
||||
}),
|
||||
[ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_SUCCESS]: (
|
||||
state: ProvidersReduxState,
|
||||
action: ReduxAction<ProviderTemplates[]>,
|
||||
) => {
|
||||
const templates = action.payload.map(payload => ({
|
||||
isOpen: false,
|
||||
templateData: payload,
|
||||
addToPageStatus: false,
|
||||
}));
|
||||
return {
|
||||
...state,
|
||||
providerTemplates: templates,
|
||||
isFetchingProviderTemplates: false,
|
||||
};
|
||||
},
|
||||
[ReduxActionErrorTypes.FETCH_PROVIDER_TEMPLATES_ERROR]: (
|
||||
state: ProvidersReduxState,
|
||||
) => {
|
||||
return { ...state, isFetchingProviderTemplates: false };
|
||||
},
|
||||
[ReduxActionTypes.ADD_API_TO_PAGE_SUCCESS]: (
|
||||
state: ProvidersReduxState,
|
||||
action: ProviderTemplates,
|
||||
) => {
|
||||
const updatedProviderTemplates = state.providerTemplates.map(item => {
|
||||
if (item.templateData.id === action.data.templateId) {
|
||||
item.addToPageStatus = true;
|
||||
item.addToPageLoading = false;
|
||||
return item;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return { ...state, providerTemplates: updatedProviderTemplates };
|
||||
},
|
||||
[ReduxActionErrorTypes.ADD_API_TO_PAGE_ERROR]: (
|
||||
state: ProvidersReduxState,
|
||||
action: any,
|
||||
) => {
|
||||
const updatedProviderTemplates = state.providerTemplates.map(item => {
|
||||
if (item.templateData.id === action.payload.templateId) {
|
||||
item.addToPageLoading = false;
|
||||
return item;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return { ...state, providerTemplates: updatedProviderTemplates };
|
||||
},
|
||||
[ReduxActionTypes.FETCH_PROVIDERS_CATEGORIES_SUCCESS]: (
|
||||
state: ProvidersReduxState,
|
||||
action: ReduxAction<ProvidersCategoriesResponse>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
providerCategories: action.payload,
|
||||
};
|
||||
},
|
||||
[ReduxActionErrorTypes.FETCH_PROVIDERS_CATEGORIES_ERROR]: (
|
||||
state: ProvidersReduxState,
|
||||
action: ReduxAction<{ providerCategoriesErrorPayload: string }>,
|
||||
) => {
|
||||
return { ...state, providerCategoriesErrorPayload: action.payload };
|
||||
},
|
||||
});
|
||||
|
||||
export interface ProvidersReduxState {
|
||||
isFetchingProviders: boolean;
|
||||
providers: ProvidersDataArray[];
|
||||
providersTotal: number;
|
||||
providerTemplates: ProviderTemplateArray[];
|
||||
isFetchingProviderTemplates: boolean;
|
||||
providerCategories: string[];
|
||||
providerCategoriesErrorPayload: {};
|
||||
isSwitchingCategory: boolean;
|
||||
}
|
||||
|
||||
export default providersReducer;
|
||||
|
|
@ -10,7 +10,8 @@ import {
|
|||
ReduxFormActionTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { getFormData } from "selectors/formSelectors";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
import { API_EDITOR_FORM_NAME, API_HOME_SCREEN_FORM } from "constants/forms";
|
||||
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants";
|
||||
import history from "utils/history";
|
||||
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "constants/routes";
|
||||
import {
|
||||
|
|
@ -29,6 +30,7 @@ import {
|
|||
UNIQUE_NAME_ERROR,
|
||||
VALID_FUNCTION_NAME_ERROR,
|
||||
} from "constants/messages";
|
||||
import { fetchProvidersWithCategory } from "actions/providerActions";
|
||||
|
||||
const getApiDraft = (state: AppState, id: string) => {
|
||||
const drafts = state.ui.apiPane.drafts;
|
||||
|
|
@ -62,6 +64,7 @@ function* syncApiParamsSaga(
|
|||
) {
|
||||
const field = actionPayload.meta.field;
|
||||
const value = actionPayload.payload;
|
||||
|
||||
if (field === "actionConfiguration.path") {
|
||||
if (value.indexOf("?") > -1) {
|
||||
const paramsString = value.substr(value.indexOf("?") + 1);
|
||||
|
|
@ -125,7 +128,70 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
}
|
||||
const action = yield select(getAction, id);
|
||||
const draft = yield select(getApiDraft, id);
|
||||
const data = _.isEmpty(draft) ? action : draft;
|
||||
let fromDraft = false;
|
||||
let data = {} as any;
|
||||
if (_.isEmpty(draft)) {
|
||||
data = {
|
||||
displayFormat: "",
|
||||
...action,
|
||||
actionConfiguration: {
|
||||
headers: [
|
||||
{ key: "", value: "" },
|
||||
{ key: "", value: "" },
|
||||
],
|
||||
queryParameters: [
|
||||
{ key: "", value: "" },
|
||||
{ key: "", value: "" },
|
||||
],
|
||||
path: "",
|
||||
...action.actionConfiguration,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
fromDraft = true;
|
||||
data = draft;
|
||||
}
|
||||
if (data.actionConfiguration.path) {
|
||||
if (data.actionConfiguration.path.charAt(0) === "/")
|
||||
data.actionConfiguration.path = data.actionConfiguration.path.substring(
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
data.actionConfiguration.httpMethod !== "GET" &&
|
||||
!fromDraft &&
|
||||
!data.providerId
|
||||
) {
|
||||
let contentType;
|
||||
const headers = data.actionConfiguration.headers;
|
||||
if (headers) {
|
||||
contentType = headers.find(
|
||||
(header: any) => header.key === "content-type",
|
||||
);
|
||||
}
|
||||
const actionConfigurationBody = data.actionConfiguration.body;
|
||||
data.actionConfiguration.body = [];
|
||||
if (contentType) {
|
||||
if (
|
||||
contentType.value === POST_BODY_FORMAT_OPTIONS[0].value &&
|
||||
data.actionConfiguration.body
|
||||
) {
|
||||
data.actionConfiguration.body[0] = actionConfigurationBody;
|
||||
} else {
|
||||
if (typeof actionConfigurationBody !== "object") {
|
||||
data.actionConfiguration.body[1] = JSON.parse(
|
||||
actionConfigurationBody,
|
||||
);
|
||||
} else {
|
||||
data.actionConfiguration.body[1] = actionConfigurationBody;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data.displayFormat = POST_BODY_FORMAT_OPTIONS[0].value;
|
||||
}
|
||||
}
|
||||
|
||||
yield put(initialize(API_EDITOR_FORM_NAME, data));
|
||||
history.push(API_EDITOR_ID_URL(applicationId, pageId, id));
|
||||
if (data.actionConfiguration && data.actionConfiguration.queryParameters) {
|
||||
|
|
@ -141,19 +207,22 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
}
|
||||
|
||||
function* updateDraftsSaga() {
|
||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||
const { values, initialValues } = 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 {
|
||||
|
||||
if (!_.isEqual(values, initialValues)) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.UPDATE_API_DRAFT,
|
||||
payload: { id: values.id, draft: values },
|
||||
});
|
||||
} else {
|
||||
yield put({
|
||||
type: ReduxActionTypes.DELETE_API_DRAFT,
|
||||
payload: { id: values.id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,15 +267,66 @@ function* validateInputSaga(
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
function* updateFormFields(
|
||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||
) {
|
||||
const field = actionPayload.meta.field;
|
||||
const value = actionPayload.payload;
|
||||
|
||||
if (field === "actionConfiguration.httpMethod") {
|
||||
if (value !== "GET") {
|
||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||
const { actionConfiguration } = values;
|
||||
const actionConfigurationHeaders = actionConfiguration.headers;
|
||||
let contentType;
|
||||
|
||||
if (actionConfigurationHeaders) {
|
||||
contentType = actionConfigurationHeaders.find(
|
||||
(header: any) => header.key === "content-type",
|
||||
);
|
||||
}
|
||||
|
||||
if (!contentType) {
|
||||
yield put(
|
||||
autofill(API_EDITOR_FORM_NAME, "actionConfiguration.headers", [
|
||||
...actionConfigurationHeaders,
|
||||
{
|
||||
key: "content-type",
|
||||
value: POST_BODY_FORMAT_OPTIONS[0].value,
|
||||
},
|
||||
]),
|
||||
);
|
||||
yield put(
|
||||
autofill(
|
||||
API_EDITOR_FORM_NAME,
|
||||
"displayFormat",
|
||||
POST_BODY_FORMAT_OPTIONS[0].value,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
yield put(autofill(API_EDITOR_FORM_NAME, "displayFormat", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* formValueChangeSaga(
|
||||
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
|
||||
) {
|
||||
const { form } = actionPayload.meta;
|
||||
if (form === API_HOME_SCREEN_FORM) {
|
||||
yield put(
|
||||
fetchProvidersWithCategory({ category: actionPayload.payload, page: 1 }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (form !== API_EDITOR_FORM_NAME) return;
|
||||
yield all([
|
||||
call(validateInputSaga, actionPayload),
|
||||
call(updateDraftsSaga),
|
||||
call(syncApiParamsSaga, actionPayload),
|
||||
call(updateFormFields, actionPayload),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -267,5 +387,6 @@ export default function* root() {
|
|||
// Intercepting the redux-form change actionType
|
||||
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
||||
takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga),
|
||||
takeEvery(ReduxFormActionTypes.ARRAY_PUSH, formValueChangeSaga),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
39
app/client/src/sagas/CollectionSagas.ts
Normal file
39
app/client/src/sagas/CollectionSagas.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { call, takeLatest, put, all } from "redux-saga/effects";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { validateResponse } from "sagas/ErrorSagas";
|
||||
import ImportedCollectionsApi from "api/CollectionApi";
|
||||
import { ImportedCollections } from "constants/collectionsConstants";
|
||||
|
||||
export function* fetchImportedCollectionsSaga() {
|
||||
try {
|
||||
const response: ImportedCollections = yield call(
|
||||
ImportedCollectionsApi.fetchImportedCollections,
|
||||
);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_IMPORTED_COLLECTIONS_SUCCESS,
|
||||
payload: response.data,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FETCH_IMPORTED_COLLECTIONS_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function* importedCollectionsSagas() {
|
||||
yield all([
|
||||
takeLatest(
|
||||
ReduxActionTypes.FETCH_IMPORTED_COLLECTIONS_INIT,
|
||||
fetchImportedCollectionsSaga,
|
||||
),
|
||||
]);
|
||||
}
|
||||
69
app/client/src/sagas/CurlImportSagas.ts
Normal file
69
app/client/src/sagas/CurlImportSagas.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { takeLatest, put, all, select } from "redux-saga/effects";
|
||||
import { initialize } from "redux-form";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxAction,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
import { validateResponse } from "sagas/ErrorSagas";
|
||||
import CurlImportApi, { CurlImportRequest } from "api/ImportApi";
|
||||
import { ApiResponse } from "api/ApiResponses";
|
||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||
import { ToastType } from "react-toastify";
|
||||
import { CURL_IMPORT_SUCCESS } from "constants/messages";
|
||||
import { API_EDITOR_ID_URL } from "constants/routes";
|
||||
import history from "utils/history";
|
||||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
} from "selectors/editorSelectors";
|
||||
import { fetchActions } from "actions/actionActions";
|
||||
|
||||
export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
|
||||
const { type, pageId, name } = action.payload;
|
||||
let { curl } = action.payload;
|
||||
try {
|
||||
if (curl.charAt(0) === '"' && curl.charAt(curl.length - 1) === '"') {
|
||||
curl = curl;
|
||||
} else {
|
||||
curl = '"' + curl + '"';
|
||||
}
|
||||
const request: CurlImportRequest = { type, pageId, name, curl };
|
||||
|
||||
const response: ApiResponse = yield CurlImportApi.curlImport(request);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
const applicationId = yield select(getCurrentApplicationId);
|
||||
const currentPageId = yield select(getCurrentPageId);
|
||||
|
||||
if (isValidResponse) {
|
||||
AppToaster.show({
|
||||
message: CURL_IMPORT_SUCCESS,
|
||||
type: ToastType.SUCCESS,
|
||||
});
|
||||
yield put({
|
||||
type: ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
|
||||
payload: response.data,
|
||||
});
|
||||
yield put(fetchActions(applicationId));
|
||||
const data = { ...response.data };
|
||||
yield put(initialize(API_EDITOR_FORM_NAME, data));
|
||||
history.push(
|
||||
API_EDITOR_ID_URL(applicationId, currentPageId, response.data.id),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.SUBMIT_CURL_FORM_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function* curlImportSagas() {
|
||||
yield all([
|
||||
takeLatest(ReduxActionTypes.SUBMIT_CURL_FORM_INIT, curlImportSaga),
|
||||
]);
|
||||
}
|
||||
148
app/client/src/sagas/ProvidersSaga.ts
Normal file
148
app/client/src/sagas/ProvidersSaga.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import { call, takeLatest, put, all, select } from "redux-saga/effects";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxActionWithPromise,
|
||||
ReduxAction,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { validateResponse } from "sagas/ErrorSagas";
|
||||
import ProvidersApi, {
|
||||
FetchProviderTemplateResponse,
|
||||
FetchProviderTemplatesRequest,
|
||||
AddApiToPageRequest,
|
||||
FetchProviderCategoriesResponse,
|
||||
} from "api/ProvidersApi";
|
||||
import { Providers } from "constants/providerConstants";
|
||||
import { FetchProviderWithCategoryRequest } from "api/ProvidersApi";
|
||||
import { fetchActions } from "actions/actionActions";
|
||||
import { getCurrentApplicationId } from "selectors/editorSelectors";
|
||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||
import { ToastType } from "react-toastify";
|
||||
import { ADD_API_TO_PAGE_SUCCESS_MESSAGE } from "constants/messages";
|
||||
|
||||
export function* fetchProviderTemplatesSaga(
|
||||
action: ReduxActionWithPromise<FetchProviderTemplatesRequest>,
|
||||
) {
|
||||
const { providerId } = action.payload;
|
||||
try {
|
||||
const request: FetchProviderTemplatesRequest = { providerId };
|
||||
|
||||
const response: FetchProviderTemplateResponse = yield ProvidersApi.fetchProviderTemplates(
|
||||
request,
|
||||
);
|
||||
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_SUCCESS,
|
||||
payload: response.data,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FETCH_PROVIDER_TEMPLATES_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* addApiToPageSaga(
|
||||
action: ReduxActionWithPromise<AddApiToPageRequest>,
|
||||
) {
|
||||
const request: AddApiToPageRequest = action.payload;
|
||||
try {
|
||||
const response: FetchProviderTemplateResponse = yield ProvidersApi.addApiToPage(
|
||||
request,
|
||||
);
|
||||
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
|
||||
if (isValidResponse) {
|
||||
AppToaster.show({
|
||||
message: ADD_API_TO_PAGE_SUCCESS_MESSAGE,
|
||||
type: ToastType.SUCCESS,
|
||||
});
|
||||
yield put({
|
||||
type: ReduxActionTypes.ADD_API_TO_PAGE_SUCCESS,
|
||||
data: response.data,
|
||||
});
|
||||
const applicationId = yield select(getCurrentApplicationId);
|
||||
yield put(fetchActions(applicationId));
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.ADD_API_TO_PAGE_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
templateId: request.marketplaceElement.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* fetchProvidersWithCategorySaga(
|
||||
action: ReduxAction<FetchProviderWithCategoryRequest>,
|
||||
) {
|
||||
try {
|
||||
const request: FetchProviderWithCategoryRequest = action.payload;
|
||||
const response: Providers = yield ProvidersApi.fetchProvidersWithCategory(
|
||||
request,
|
||||
);
|
||||
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_PROVIDERS_SUCCESS,
|
||||
payload: response.data,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FETCH_PROVIDERS_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* fetchProvidersCategoriesSaga() {
|
||||
try {
|
||||
const response: FetchProviderCategoriesResponse = yield call(
|
||||
ProvidersApi.fetchProvidersCategories,
|
||||
);
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_PROVIDERS_CATEGORIES_SUCCESS,
|
||||
payload: response.data,
|
||||
});
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FETCH_PROVIDERS_CATEGORIES_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function* providersSagas() {
|
||||
yield all([
|
||||
takeLatest(
|
||||
ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT,
|
||||
fetchProviderTemplatesSaga,
|
||||
),
|
||||
takeLatest(ReduxActionTypes.ADD_API_TO_PAGE_INIT, addApiToPageSaga),
|
||||
takeLatest(
|
||||
ReduxActionTypes.FETCH_PROVIDERS_CATEGORIES_INIT,
|
||||
fetchProvidersCategoriesSaga,
|
||||
),
|
||||
takeLatest(
|
||||
ReduxActionTypes.FETCH_PROVIDERS_WITH_CATEGORY_INIT,
|
||||
fetchProvidersWithCategorySaga,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
|
@ -12,6 +12,9 @@ import apiPaneSagas from "./ApiPaneSagas";
|
|||
import userSagas from "./userSagas";
|
||||
import pluginSagas from "./PluginSagas";
|
||||
import orgSagas from "./OrgSagas";
|
||||
import importedCollectionsSagas from "./CollectionSagas";
|
||||
import providersSagas from "./ProvidersSaga";
|
||||
import curlImportSagas from "./CurlImportSagas";
|
||||
import modalSagas from "./ModalSagas";
|
||||
import batchSagas from "./BatchSagas";
|
||||
|
||||
|
|
@ -30,6 +33,9 @@ export function* rootSaga() {
|
|||
spawn(userSagas),
|
||||
spawn(pluginSagas),
|
||||
spawn(orgSagas),
|
||||
spawn(importedCollectionsSagas),
|
||||
spawn(providersSagas),
|
||||
spawn(curlImportSagas),
|
||||
spawn(modalSagas),
|
||||
spawn(batchSagas),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,17 @@ const getApplicationSearchKeyword = (state: AppState) =>
|
|||
export const getIsDeletingApplication = (state: AppState) =>
|
||||
state.ui.applications.deletingApplication;
|
||||
|
||||
export const getImportedCollections = (state: AppState) =>
|
||||
state.ui.importedCollections.importedCollections;
|
||||
|
||||
export const getProviders = (state: AppState) => state.ui.providers.providers;
|
||||
export const getProvidersLoadingState = (state: AppState) =>
|
||||
state.ui.providers.isFetchingProviders;
|
||||
export const getProviderTemplates = (state: AppState) =>
|
||||
state.ui.providers.providerTemplates;
|
||||
export const getProvidersTemplatesLoadingState = (state: AppState) =>
|
||||
state.ui.providers.isFetchingProviderTemplates;
|
||||
|
||||
export const getApplicationList = createSelector(
|
||||
getApplications,
|
||||
getApplicationSearchKeyword,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ const getEditorState = (state: AppState) => state.ui.editor;
|
|||
const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig;
|
||||
const getWidgetSideBar = (state: AppState) => state.ui.widgetSidebar;
|
||||
const getPageListState = (state: AppState) => state.entities.pageList;
|
||||
const getActions = (state: AppState) => state.entities.actions;
|
||||
|
||||
export const getProviderCategories = (state: AppState) =>
|
||||
state.ui.providers.providerCategories;
|
||||
|
||||
const getWidgets = (state: AppState): CanvasWidgetsReduxState =>
|
||||
state.entities.canvasWidgets;
|
||||
|
|
@ -193,3 +197,15 @@ export const getOccupiedSpaces = createSelector(
|
|||
return Object.keys(occupiedSpaces).length > 0 ? occupiedSpaces : undefined;
|
||||
},
|
||||
);
|
||||
|
||||
export const getActionById = createSelector(
|
||||
[getActions, (state: any, props: any) => props.match.params.apiId],
|
||||
(actions, id) => {
|
||||
const action = actions.find(action => action.config.id === id);
|
||||
if (action) {
|
||||
return action.config;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getFormValues, isValid } from "redux-form";
|
||||
import { getFormValues, isValid, getFormInitialValues } from "redux-form";
|
||||
import { AppState } from "reducers";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
|
||||
|
|
@ -8,9 +8,10 @@ type GetFormData = (
|
|||
) => { values: object; dirty: boolean; valid: boolean };
|
||||
|
||||
export const getFormData: GetFormData = (state, formName) => {
|
||||
const initialValues = getFormInitialValues(formName)(state) as RestAction;
|
||||
const values = getFormValues(formName)(state) as RestAction;
|
||||
const drafts = state.ui.apiPane.drafts;
|
||||
const dirty = values.id in drafts;
|
||||
const valid = isValid(formName)(state);
|
||||
return { values, dirty, valid };
|
||||
return { initialValues, values, dirty, valid };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { RestAction } from "api/ActionAPI";
|
||||
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants";
|
||||
|
||||
export const transformRestAction = (data: RestAction): RestAction => {
|
||||
export const transformRestAction = (data: any): any => {
|
||||
let action = { ...data };
|
||||
if (
|
||||
data.actionConfiguration.queryParameters &&
|
||||
|
|
@ -17,5 +18,37 @@ export const transformRestAction = (data: RestAction): RestAction => {
|
|||
};
|
||||
}
|
||||
}
|
||||
if (
|
||||
data.displayFormat &&
|
||||
data.displayFormat === POST_BODY_FORMAT_OPTIONS[0].value
|
||||
) {
|
||||
if (data.actionConfiguration.body[0]) {
|
||||
const body = data.actionConfiguration.body[0];
|
||||
action = {
|
||||
...data,
|
||||
actionConfiguration: {
|
||||
...data.actionConfiguration,
|
||||
body,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
if (
|
||||
data.displayFormat &&
|
||||
data.displayFormat === POST_BODY_FORMAT_OPTIONS[1].value
|
||||
) {
|
||||
if (data.actionConfiguration.body[1]) {
|
||||
const body = data.actionConfiguration.body[1];
|
||||
if (typeof data.actionConfiguration.body === "object") {
|
||||
action = {
|
||||
...data,
|
||||
actionConfiguration: {
|
||||
...data.actionConfiguration,
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return action;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Property } from "api/ActionAPI";
|
|||
import _ from "lodash";
|
||||
import moment from "moment-timezone";
|
||||
import ValidationRegistry from "./ValidationRegistry";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import * as log from "loglevel";
|
||||
import { LogLevelDesc } from "loglevel";
|
||||
|
||||
|
|
@ -77,6 +78,31 @@ export const getNextEntityName = (prefix: string, existingNames: string[]) => {
|
|||
return prefix + (lastIndex + 1);
|
||||
};
|
||||
|
||||
export const getDuplicateName = (prefix: string, existingNames: string[]) => {
|
||||
const trimmedPrefix = prefix.replace(/ /g, "");
|
||||
const regex = new RegExp(`^${trimmedPrefix}(\\d+)$`);
|
||||
const usedIndices: number[] = existingNames.map(name => {
|
||||
if (name && regex.test(name)) {
|
||||
const matches = name.match(regex);
|
||||
const ind =
|
||||
matches && Array.isArray(matches) ? parseInt(matches[1], 10) : 0;
|
||||
return Number.isNaN(ind) ? 0 : ind;
|
||||
}
|
||||
return 0;
|
||||
}) as number[];
|
||||
|
||||
const lastIndex = Math.max(...usedIndices, ...[0]);
|
||||
|
||||
return trimmedPrefix + `_${lastIndex + 1}`;
|
||||
};
|
||||
|
||||
export const createNewApiName = (actions: ActionDataState, pageId: string) => {
|
||||
const pageApiNames = actions
|
||||
.filter(a => a.config.pageId === pageId)
|
||||
.map(a => a.config.name);
|
||||
return getNextEntityName("Api", pageApiNames);
|
||||
};
|
||||
|
||||
export const noop = () => {
|
||||
console.log("noop");
|
||||
};
|
||||
|
|
|
|||
1
app/client/typings/react-infinite-scroller/index.d.ts
vendored
Normal file
1
app/client/typings/react-infinite-scroller/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
declare module "react-infinite-scroller";
|
||||
1
app/client/typings/react-json-view/index.d.ts
vendored
Normal file
1
app/client/typings/react-json-view/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
declare module "react-json-view";
|
||||
|
|
@ -3718,6 +3718,11 @@ balanced-match@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
||||
base16@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||
integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=
|
||||
|
||||
base64-js@^1.0.2:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
|
|
@ -4649,6 +4654,11 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1:
|
|||
version "3.6.4"
|
||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
|
||||
|
||||
core-js@^1.0.0:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
|
||||
|
||||
core-js@^2.4.0, core-js@^2.6.5:
|
||||
version "2.6.11"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
|
||||
|
|
@ -5524,6 +5534,13 @@ encodeurl@~1.0.2:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
|
||||
encoding@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
||||
integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
|
||||
dependencies:
|
||||
iconv-lite "~0.4.13"
|
||||
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
|
|
@ -6138,6 +6155,26 @@ fb-watchman@^2.0.0:
|
|||
dependencies:
|
||||
bser "2.1.1"
|
||||
|
||||
fbemitter@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865"
|
||||
integrity sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=
|
||||
dependencies:
|
||||
fbjs "^0.8.4"
|
||||
|
||||
fbjs@^0.8.0, fbjs@^0.8.4:
|
||||
version "0.8.17"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
|
||||
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
isomorphic-fetch "^2.1.1"
|
||||
loose-envify "^1.0.0"
|
||||
object-assign "^4.1.0"
|
||||
promise "^7.1.1"
|
||||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.18"
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
|
|
@ -6324,6 +6361,14 @@ flush-write-stream@^1.0.0:
|
|||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
flux@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/flux/-/flux-3.1.3.tgz#d23bed515a79a22d933ab53ab4ada19d05b2f08a"
|
||||
integrity sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=
|
||||
dependencies:
|
||||
fbemitter "^2.0.0"
|
||||
fbjs "^0.8.0"
|
||||
|
||||
focus-lock@^0.6.6:
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.6.tgz#98119a755a38cfdbeda0280eaa77e307eee850c7"
|
||||
|
|
@ -7149,7 +7194,7 @@ husky@^3.0.5:
|
|||
run-node "^1.0.0"
|
||||
slash "^3.0.0"
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
dependencies:
|
||||
|
|
@ -7707,7 +7752,7 @@ is-shallow-equal@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-shallow-equal/-/is-shallow-equal-1.0.1.tgz#c410b51eb1c12ee50cd02891d32d1691a132d73c"
|
||||
|
||||
is-stream@^1.1.0:
|
||||
is-stream@^1.0.1, is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
|
||||
|
|
@ -7793,6 +7838,14 @@ isobject@^4.0.0:
|
|||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
|
||||
|
||||
isomorphic-fetch@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
|
||||
dependencies:
|
||||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
|
@ -8665,10 +8718,20 @@ lodash._reinterpolate@^3.0.0:
|
|||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
|
||||
lodash.curry@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
|
||||
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
|
||||
lodash.flow@^3.3.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
|
||||
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
|
||||
|
||||
lodash.isempty@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e"
|
||||
|
|
@ -9391,6 +9454,14 @@ node-environment-flags@1.0.6:
|
|||
object.getownpropertydescriptors "^2.0.3"
|
||||
semver "^5.7.0"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
|
||||
dependencies:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-fetch@^2.1.2, node-fetch@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
|
|
@ -9875,6 +9946,11 @@ p-try@^2.0.0:
|
|||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
|
||||
paging-algorithm@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/paging-algorithm/-/paging-algorithm-1.0.1.tgz#18abe482a6a202bfaab4b023a407c8cc2072cb8a"
|
||||
integrity sha512-hYLG10yTytEcE7gaeLw5z1Rert4MHYiQNxzwLt59LHb3bKRBShDX/0BqX4tQoAK11tQzoqSFkUelc6ptagfy5w==
|
||||
|
||||
pako@~1.0.5:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
|
|
@ -10877,9 +10953,10 @@ promise.prototype.finally@^3.1.0:
|
|||
es-abstract "^1.17.0-next.0"
|
||||
function-bind "^1.1.1"
|
||||
|
||||
promise@^7.0.1:
|
||||
promise@^7.0.1, promise@^7.1.1:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
||||
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
|
|
@ -11071,6 +11148,11 @@ punycode@^2.1.0, punycode@^2.1.1:
|
|||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
|
||||
pure-color@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
|
||||
integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=
|
||||
|
||||
q@^1.1.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
|
|
@ -11188,6 +11270,16 @@ react-base-table@^1.9.1:
|
|||
react-virtualized-auto-sizer "^1.0.2"
|
||||
react-window "^1.8.2"
|
||||
|
||||
react-base16-styling@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
|
||||
integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=
|
||||
dependencies:
|
||||
base16 "^1.0.0"
|
||||
lodash.curry "^4.0.1"
|
||||
lodash.flow "^3.3.0"
|
||||
pure-color "^1.2.0"
|
||||
|
||||
react-beautiful-dnd@^12.2.0:
|
||||
version "12.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-12.2.0.tgz#e5f6222f9e7934c6ed4ee09024547f9e353ae423"
|
||||
|
|
@ -11407,6 +11499,13 @@ react-hotkeys@2.0.0:
|
|||
dependencies:
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react-infinite-scroller@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz#f67eaec4940a4ce6417bebdd6e3433bfc38826e9"
|
||||
integrity sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw==
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-input-autosize@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2"
|
||||
|
|
@ -11417,10 +11516,27 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i
|
|||
version "16.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
|
||||
|
||||
react-json-view@^1.19.1:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c"
|
||||
integrity sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==
|
||||
dependencies:
|
||||
flux "^3.1.3"
|
||||
react-base16-styling "^0.6.0"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
react-textarea-autosize "^6.1.0"
|
||||
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
|
||||
react-paginating@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-paginating/-/react-paginating-1.4.0.tgz#f19c4871d60428a7a2a0d49981da0e1df148e129"
|
||||
integrity sha512-fVUQil9O8R5t7ggPvMqW6lz5/0TLbPvrfJ+54NykeURqmxAk3TBbIOL9SS7KRmpggDkym+6BNnSMdQ45Fh2wvg==
|
||||
dependencies:
|
||||
paging-algorithm "^1.0.1"
|
||||
|
||||
react-pdf@^4.0.5:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-4.1.0.tgz#fcb874f28050fe9593c4e04652c7bff94bb1acf9"
|
||||
|
|
@ -11609,6 +11725,13 @@ react-test-renderer@^16.11.0:
|
|||
react-is "^16.8.6"
|
||||
scheduler "^0.18.0"
|
||||
|
||||
react-textarea-autosize@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5"
|
||||
integrity sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==
|
||||
dependencies:
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-textarea-autosize@^7.1.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda"
|
||||
|
|
@ -12496,9 +12619,10 @@ set-value@^2.0.0, set-value@^2.0.1:
|
|||
is-plain-object "^2.0.3"
|
||||
split-string "^3.0.1"
|
||||
|
||||
setimmediate@^1.0.4:
|
||||
setimmediate@^1.0.4, setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
|
||||
|
||||
setprototypeof@1.1.0:
|
||||
version "1.1.0"
|
||||
|
|
@ -13597,6 +13721,11 @@ typescript@^3.6.3:
|
|||
version "3.7.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.21"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
|
||||
integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
|
||||
|
||||
uglify-js@^2.6.1:
|
||||
version "2.8.29"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
|
||||
|
|
@ -14177,9 +14306,10 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
|
|||
dependencies:
|
||||
iconv-lite "0.4.24"
|
||||
|
||||
whatwg-fetch@^3.0.0:
|
||||
whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
||||
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
||||
|
||||
whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
|
||||
version "2.3.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user