Merge branch 'fix/api-pane-fixes' into 'release'

Fix provider cards ui fixes

See merge request theappsmith/internal-tools-client!514
This commit is contained in:
Hetu Nandu 2020-04-28 10:47:59 +00:00
commit 20921d4578
19 changed files with 674 additions and 132 deletions

View File

@ -14,6 +14,33 @@ export const initApiPane = (urlId?: string): ReduxAction<{ id?: string }> => {
};
};
export const setCurrentCategory = (
category: string,
): ReduxAction<{ category: string }> => {
return {
type: ReduxActionTypes.SET_CURRENT_CATEGORY,
payload: { category },
};
};
export const setLastUsedEditorPage = (
path: string,
): ReduxAction<{ path: string }> => {
return {
type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE,
payload: { path },
};
};
export const setLastSelectedPage = (
selectedPageId: string,
): ReduxAction<{ selectedPageId: string }> => {
return {
type: ReduxActionTypes.SET_LAST_SELECTED_PAGE_PAGE,
payload: { selectedPageId },
};
};
export const createNewApiAction = (
pageId: string,
): ReduxAction<{ pageId: string }> => ({

View File

@ -17,9 +17,16 @@ export const fetchProviderCategories = () => {
};
};
export const fetchProviderTemplates = () => {
export const getProviderDetailsByProviderId = (providerId: string) => {
return {
type: ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT,
payload: { providerId },
};
};
export const fetchProviderTemplates = (providerId: string) => {
return {
type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT,
payload: { providerId },
};
};

View File

@ -1,12 +1,20 @@
import { AxiosPromise } from "axios";
import Api from "./Api";
import { ApiResponse } from "./ApiResponses";
import { Providers, ProviderTemplates } from "constants/providerConstants";
import {
Providers,
ProviderTemplates,
ProvidersDataArray,
} from "constants/providerConstants";
export interface FetchProvidersResponse extends ApiResponse {
data: Providers;
}
export interface FetchProviderDetailsResponse extends ApiResponse {
data: ProvidersDataArray;
}
export interface FetchProviderCategoriesResponse extends ApiResponse {
data: string[];
}
@ -19,6 +27,10 @@ export interface FetchProviderTemplatesRequest {
providerId: string;
}
export interface FetchProviderDetailsByProviderIdRequest {
providerId: string;
}
export interface FetchProviderWithCategoryRequest {
category: string;
page: number;
@ -34,6 +46,10 @@ export class ProvidersApi extends Api {
static providersURL = "v1/providers";
static providerCategoriesURL = "v1/providers/categories";
static providerDetailsByIdURL = (providerId: string) => {
return `v1/marketplace/providers/${providerId}`;
};
static providerTemplateURL = (providerId: string) => {
return `v1/marketplace/templates?providerId=${providerId}`;
};
@ -73,6 +89,13 @@ export class ProvidersApi extends Api {
ProvidersApi.providersWithCategoryURL(request.category, page),
);
}
static fetchProviderDetailsByProviderId(
request: FetchProviderDetailsByProviderIdRequest,
): AxiosPromise<FetchProviderDetailsResponse> {
const { providerId } = request;
return Api.get(ProvidersApi.providerDetailsByIdURL(providerId));
}
}
export default ProvidersApi;

View File

@ -0,0 +1,126 @@
import React from "react";
import { Popover, PopoverInteractionKind } from "@blueprintjs/core";
import styled, { createGlobalStyle } from "styled-components";
import { Colors } from "constants/Colors";
import { FormIcons } from "icons/FormIcons";
const CredentialTooltipWrapper = styled.div`
.credentialTooltipContainer {
display: flex;
margin-left: 5px;
}
.infoIconDiv {
margin-top: -2px;
margin-left: 2px;
}
.credentialTitle {
color: ${Colors.CADET_BLUE};
}
`;
const TooltipStyles = createGlobalStyle`
.credentials-tooltip{
.bp3-popover {
box-shadow: none;
max-width: 340px;
.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};
max-height: 300px;
overflow: auto;
color: ${Colors.WHITE};
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: ${Colors.AQUA_HAZE};
svg {
height: 20px;
width: 20px;
path {
fill: #979797;
}
}
}
.bp3-popover-target {
padding-right: 10px;
}
`;
interface Props {
providerCredentialSteps: string;
}
const HelperTooltip = (props: Props) => {
return (
<CredentialTooltipWrapper>
<div className="credentialTooltipContainer">
<div>
<span className="credentialTitle">How to get Credentials?</span>
</div>
<div className="infoIconDiv">
<TooltipStyles />
<Popover
autoFocus={true}
canEscapeKeyClose={true}
content={
<div>
<span>How to get credentials: </span>
<p
style={{ color: "#d0d7dd", fontWeight: 100 }}
dangerouslySetInnerHTML={{
__html: props.providerCredentialSteps
.split("\\n\\n")
.join("<br />")
.split("\\n")
.join("<br /><br />"),
}}
></p>
</div>
}
position="bottom"
defaultIsOpen={false}
interactionKind={PopoverInteractionKind.HOVER}
usePortal
portalClassName="credentials-tooltip"
>
<IconContainer style={{ display: "inline-block" }}>
<FormIcons.INFO_ICON
width={22}
height={22}
style={{
cursor: "pointer",
}}
/>
</IconContainer>
</Popover>
</div>
</div>
</CredentialTooltipWrapper>
);
};
export default HelperTooltip;

View File

@ -41,6 +41,7 @@ export const Colors: Record<string, string> = {
BUTTER_CUP: "#F7AF22",
BLUE_CHARCOAL: "#23292E",
TROUT: "#4C565E",
JAFFA_DARK: "#EF7541",
};
export type Color = typeof Colors[keyof typeof Colors];

View File

@ -185,6 +185,13 @@ export const ReduxActionTypes: { [key: string]: string } = {
BATCHED_UPDATE: "BATCHED_UPDATE",
EXECUTE_BATCH: "EXECUTE_BATCH",
CREATE_NEW_API_ACTION: "CREATE_NEW_API_ACTION",
SET_CURRENT_CATEGORY: "SET_CURRENT_CATEGORY",
SET_LAST_USED_EDITOR_PAGE: "SET_LAST_USED_EDITOR_PAGE",
SET_LAST_SELECTED_PAGE_PAGE: "SET_LAST_SELECTED_PAGE_PAGE",
FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT:
"FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT",
FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS:
"FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS",
};
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
@ -259,6 +266,8 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
FETCH_PROVIDERS_CATEGORIES_ERROR: "FETCH_PROVIDERS_CATEGORIES_ERROR",
FETCH_PROVIDERS_WITH_CATEGORY_ERROR: "FETCH_PROVIDERS_WITH_CATEGORY_ERROR",
CREATE_MODAL_ERROR: "CREATE_MODAL_ERROR",
FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR:
"FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR",
};
export const ReduxFormActionTypes: { [key: string]: string } = {

View File

@ -12,6 +12,10 @@ export type ProvidersCategoriesResponse = ApiResponse & {
data: string[];
};
export type FetchProviderDetailsResponse = ApiResponse & {
data: ProvidersDataArray;
};
export type Providers = ApiResponse & {
providers: ProvidersDataArray[];
total: number;
@ -56,3 +60,12 @@ export type ProviderTemplateArray = ApiResponse & {
};
export const DEFAULT_TEMPLATE_TYPE = "TEMPLATE";
export const providerBackgroundColors = [
"#5F60B4",
"#0BA780",
"#929500",
"#1F97D3",
"#B32FA5",
"#1A29B1",
];

View File

@ -26,7 +26,6 @@ export type ProviderViewerRouteParams = {
applicationId: string;
pageId: string;
providerId: string;
destinationPageId: string;
};
export const BUILDER_BASE_URL = (applicationId = ":applicationId"): string =>
@ -122,12 +121,7 @@ export const getProviderTemplatesURL = (
applicationId = ":applicationId",
pageId = ":pageId",
providerId = ":providerId",
destinationPageId = ":destinationPageId",
): string =>
`${API_EDITOR_URL(
applicationId,
pageId,
)}/provider/${providerId}/?importTo=${destinationPageId}`;
): string => `${API_EDITOR_URL(applicationId, pageId)}/provider/${providerId}`;
export const EDITOR_ROUTES = [
{

View File

@ -41,7 +41,12 @@ import Spinner from "components/editorComponents/Spinner";
import CurlLogo from "assets/images/Curl-logo.svg";
import { FetchProviderWithCategoryRequest } from "api/ProvidersApi";
import { Plugin } from "api/PluginApi";
import { createNewApiAction } from "actions/apiPaneActions";
import {
createNewApiAction,
setCurrentCategory,
setLastUsedEditorPage,
} from "actions/apiPaneActions";
import { getInitialsAndColorCode } from "utils/AppsmithUtils";
// const SearchContainer = styled.div`
// display: flex;
@ -110,24 +115,14 @@ const StyledContainer = styled.div`
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 {
object-fit: contain;
height: 50px;
width: auto;
max-width: 100%;
margin-top: -5px;
margin-bottom: 10px;
min-height: 50px;
}
.curlImage {
width: 60px;
@ -143,7 +138,29 @@ const ApiCard = styled.div`
height: 110px;
padding-bottom: 0px;
cursor: pointer;
border: 1px solid #e6e6e6;
box-shadow: none;
}
.eachCard:active {
border: 1px solid ${Colors.JAFFA_DARK};
background: rgba(242, 153, 74, 0.17);
}
.eachCard:hover {
border: 1px solid ${Colors.JAFFA_DARK};
}
`;
const ApiCard = styled.div`
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
column-gap: 10px;
flex-wrap: wrap;
margin-left: -10px;
text-align: center;
min-width: 150px;
border-radius: 4px;
width: 100%;
`;
const CardList = styled.div`
@ -153,6 +170,15 @@ const CardList = styled.div`
height: 110px;
padding-bottom: 0px;
cursor: pointer;
border: 1px solid #e6e6e6;
box-shadow: none;
}
.eachProviderCard:active {
border: 1px solid ${Colors.JAFFA_DARK};
background: rgba(242, 153, 74, 0.17);
}
.eachProviderCard:hover {
border: 1px solid ${Colors.JAFFA_DARK};
}
`;
@ -196,7 +222,7 @@ const DropdownSelect = styled.div`
font-size: 14px;
float: right;
width: 232px;
margin-right: 8%;
margin-right: 25px;
margin-bottom: 25px;
`;
@ -210,6 +236,9 @@ const PageLoadingContainer = styled(CenteredWrapper)`
`;
type ApiHomeScreenProps = {
initialValues: {
category: string;
};
currentCategory: string;
importedCollections: TemplateList[];
fetchImportedCollections: () => void;
@ -229,6 +258,9 @@ type ApiHomeScreenProps = {
location: {
search: string;
};
match: {
url: string;
};
history: {
replace: (data: string) => void;
push: (data: string) => void;
@ -237,13 +269,17 @@ type ApiHomeScreenProps = {
providersTotal: number;
isSwitchingCategory: boolean;
createNewApiAction: (pageId: string) => void;
setCurrentCategory: (category: string) => void;
setLastUsedEditorPage: (path: string) => void;
previouslySetCategory: string;
};
type ApiHomeScreenState = {
page: number;
};
type Props = ApiHomeScreenProps & InjectedFormProps<null, ApiHomeScreenProps>;
type Props = ApiHomeScreenProps &
InjectedFormProps<{ category: string }, ApiHomeScreenProps>;
class ApiHomeScreen extends React.Component<Props, ApiHomeScreenState> {
constructor(props: Props) {
@ -255,21 +291,34 @@ class ApiHomeScreen extends React.Component<Props, ApiHomeScreenState> {
}
componentDidMount() {
const { importedCollections } = this.props;
this.props.fetchProviderCategories();
const {
importedCollections,
providersTotal,
providerCategories,
} = this.props;
if (providerCategories.length === 0) {
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,
});
if (!providersTotal) {
this.props.clearProviders();
this.props.change("category", DEFAULT_PROVIDER_OPTION);
this.props.fetchProvidersWithCategory({
category: DEFAULT_PROVIDER_OPTION,
page: 1,
});
}
this.props.setLastUsedEditorPage(this.props.match.url);
}
componentDidUpdate(prevProps: Props) {
if (prevProps.currentCategory !== this.props.currentCategory) {
if (
prevProps.currentCategory !== this.props.currentCategory &&
this.props.currentCategory !== this.props.previouslySetCategory
) {
this.props.setCurrentCategory(this.props.currentCategory);
this.props.clearProviders();
this.props.fetchProvidersWithCategory({
category: this.props.currentCategory,
@ -427,17 +476,22 @@ class ApiHomeScreen extends React.Component<Props, ApiHomeScreenState> {
{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>
<div>
<div style={{ float: "left" }}>
<p className="sectionHeadings">{"Marketplace APIs"}</p>
</div>
<div>
<DropdownSelect>
<DropdownField
placeholder="All APIs"
width={232}
name="category"
options={PROVIDER_CATEGORIES_OPTIONS}
/>
</DropdownSelect>
</div>
</div>
<br />
<br />
<br />
@ -449,51 +503,61 @@ class ApiHomeScreen extends React.Component<Props, ApiHomeScreenState> {
<>
<div>
<ApiCard>
{providers.map(provider => (
<CardList key={provider.id}>
<Link
to={{
pathname: getProviderTemplatesURL(
{providers.map((provider, index) => (
<CardList
key={index}
onClick={() =>
history.push(
getProviderTemplatesURL(
applicationId,
pageId,
provider.id,
destinationPageId
? destinationPageId
: pageId,
provider.id +
`/?importTo=${destinationPageId}`,
),
state: {
providerName: provider.name,
providerImage: provider.imageUrl,
},
}}
)
}
>
<Card
interactive={false}
className="eachProviderCard"
>
<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>
{provider.imageUrl ? (
<img
src={provider.imageUrl}
className="apiImage"
alt="Provider"
/>
) : (
<div
className="innerBox"
style={{
backgroundColor: getInitialsAndColorCode(
provider.name,
)[1],
padding: 11,
margin: "auto auto 10px",
width: 70,
color: "#fff",
borderRadius: 2,
fontSize: 24,
fontWeight: "bold",
}}
>
<span>
{
getInitialsAndColorCode(
provider.name,
)[0]
}
</span>
</div>
)}
{provider.name && (
<p className="textBtn" title={provider.name}>
{provider.name}
</p>
)}
</Card>
</CardList>
))}
</ApiCard>
@ -523,6 +587,13 @@ const mapStateToProps = (state: AppState) => {
category = formData.category;
}
let initialCategoryValue;
if (state.ui.apiPane.currentCategory === "") {
initialCategoryValue = DEFAULT_PROVIDER_OPTION;
} else {
initialCategoryValue = state.ui.apiPane.currentCategory;
}
return {
currentCategory: category,
importedCollections: getImportedCollections(state),
@ -533,6 +604,8 @@ const mapStateToProps = (state: AppState) => {
providerCategories: getProviderCategories(state),
plugins: state.entities.plugins.list,
isSwitchingCategory,
previouslySetCategory: state.ui.apiPane.currentCategory,
initialValues: { category: initialCategoryValue },
};
};
@ -546,13 +619,17 @@ const mapDispatchToProps = (dispatch: any) => ({
createAction: (data: Partial<RestAction>) =>
dispatch(createActionRequest(data)),
createNewApiAction: (pageId: string) => dispatch(createNewApiAction(pageId)),
setCurrentCategory: (category: string) =>
dispatch(setCurrentCategory(category)),
setLastUsedEditorPage: (path: string) =>
dispatch(setLastUsedEditorPage(path)),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
reduxForm<null, ApiHomeScreenProps>({
reduxForm<{ category: string }, ApiHomeScreenProps>({
form: API_HOME_SCREEN_FORM,
})(ApiHomeScreen),
);

View File

@ -16,6 +16,7 @@ import FormLabel from "components/editorComponents/FormLabel";
import FormRow from "components/editorComponents/FormRow";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import { RestAction, PaginationField } from "api/ActionAPI";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import TextField from "components/editorComponents/form/fields/TextField";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import DropdownField from "components/editorComponents/form/fields/DropdownField";
@ -118,6 +119,10 @@ interface APIFormProps {
key: string;
value: string;
};
location: {
pathname: string;
};
dispatch: any;
}
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
@ -139,6 +144,8 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
httpMethodFromForm,
contentType,
displayFormat,
location,
dispatch,
} = props;
const allowPostBody =
httpMethodFromForm && httpMethodFromForm !== HTTP_METHODS[0];
@ -153,6 +160,12 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
}
}
}
dispatch({
type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE,
payload: {
path: location.pathname,
},
});
});
return (

View File

@ -2,7 +2,6 @@ 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";
@ -20,11 +19,21 @@ import {
} from "constants/providerConstants";
import { AddApiToPageRequest } from "api/ProvidersApi";
import ImageAlt from "assets/images/no_image.png";
import { addApiToPage } from "actions/providerActions";
import {
setLastUsedEditorPage,
setLastSelectedPage,
} from "actions/apiPaneActions";
import {
getProviderDetailsByProviderId,
fetchProviderTemplates,
addApiToPage,
} from "actions/providerActions";
import { Colors } from "constants/Colors";
import { getDuplicateName } from "utils/AppsmithUtils";
import { API_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes";
import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
import Spinner from "components/editorComponents/Spinner";
import { getInitialsAndColorCode } from "utils/AppsmithUtils";
const TEMPLATES_TOP_SECTION_HEIGHT = "125px";
@ -158,7 +167,7 @@ const TemplateCardRightContent = styled.div`
margin: auto;
justify-content: center;
.dropIcon {
padding-left: 15%;
margin-left: 50px;
color: #bcccd9;
cursor: pointer;
}
@ -189,9 +198,13 @@ const URLContainer = styled.div`
type ProviderTemplatesProps = {
providerTemplates: ProviderTemplateArray[];
providerDetails: any;
actions: ActionDataState;
isFetchingProviderTemplates: boolean;
getProviderDetailsByProviderId: (providerId: string) => void;
getProviderTemplates: (providerId: string) => void;
setLastUsedEditorPage: (path: string) => void;
setLastSelectedPage: (selectedPageId: string) => void;
addApiToPage: (templateData: AddApiToPageRequest) => void;
} & RouteComponentProps<ProviderViewerRouteParams>;
@ -207,12 +220,27 @@ class ProviderTemplates extends React.Component<ProviderTemplatesProps> {
};
componentDidMount() {
const { providerId } = this.props.match.params;
const { pageId, providerId } = this.props.match.params;
let destinationPageId = new URLSearchParams(this.props.location.search).get(
"importTo",
);
if (!destinationPageId) {
destinationPageId = pageId;
}
this.props.getProviderDetailsByProviderId(providerId);
this.props.getProviderTemplates(providerId);
this.props.setLastUsedEditorPage(this.props.match.url);
this.props.setLastSelectedPage(destinationPageId);
}
addApiToPage = (templateData: ProviderTemplateArray) => {
const { destinationPageId } = this.props.match.params;
const { pageId } = this.props.match.params;
let destinationPageId = new URLSearchParams(this.props.location.search).get(
"importTo",
);
if (!destinationPageId) {
destinationPageId = pageId;
}
const pageApiNames = this.props.actions
.filter(a => a.config.pageId === destinationPageId)
.map(a => a.config.name);
@ -256,18 +284,15 @@ class ProviderTemplates extends React.Component<ProviderTemplatesProps> {
providerTemplates,
history,
isFetchingProviderTemplates,
providerDetails,
} = this.props;
let providerName;
let providerImage;
const { applicationId, pageId } = this.props.match.params;
if (this.props.location.state) {
providerName = new URLSearchParams(this.props.location.state).get(
"providerName",
);
providerImage = new URLSearchParams(this.props.location.state).get(
"providerImage",
);
let destinationPageId = new URLSearchParams(this.props.location.search).get(
"importTo",
);
if (destinationPageId === null || destinationPageId === undefined) {
destinationPageId = pageId;
}
if (isFetchingProviderTemplates) {
@ -294,28 +319,67 @@ class ProviderTemplates extends React.Component<ProviderTemplatesProps> {
icon="chevron-left"
iconSize={16}
className="backBtn"
onClick={() => history.goBack()}
onClick={() =>
history.push(
API_EDITOR_URL_WITH_SELECTED_PAGE_ID(
applicationId,
pageId,
destinationPageId ? destinationPageId : pageId,
),
)
}
/>
<span className="backBtnText" onClick={() => history.goBack()}>
<span
className="backBtnText"
onClick={() =>
history.push(
API_EDITOR_URL_WITH_SELECTED_PAGE_ID(
applicationId,
pageId,
destinationPageId ? destinationPageId : pageId,
),
)
}
>
{" Back"}
</span>
<br />
<ProviderInfo>
{providerImage ? (
{providerDetails.imageUrl ? (
<img
src={providerImage}
src={providerDetails.imageUrl}
className="providerImage"
alt="provider"
></img>
) : (
<img
src={ImageAlt}
className="providerImage"
alt="provider"
></img>
<div>
{providerDetails.name && (
<div
className="innerBox"
style={{
backgroundColor: getInitialsAndColorCode(
providerDetails.name,
)[1],
padding: 5,
margin: "auto",
width: 60,
color: "#fff",
borderRadius: 2,
fontSize: 18,
fontWeight: "bold",
textAlign: "center",
marginRight: 10,
}}
>
<span>
{getInitialsAndColorCode(providerDetails.name)[0]}
</span>
</div>
)}
</div>
)}
<p className="providerName">{providerName}</p>
<p className="providerName">{providerDetails.name}</p>
</ProviderInfo>
</ProviderInfoTopSection>
<TemplatesCardsContainer>
@ -446,16 +510,22 @@ const mapStateToProps = (state: AppState) => ({
providerTemplates: getProviderTemplates(state),
isFetchingProviderTemplates: getProvidersTemplatesLoadingState(state),
actions: state.entities.actions,
providerDetails: state.ui.providers.providerDetailsByProviderId,
});
const mapDispatchToProps = (dispatch: any) => ({
getProviderDetailsByProviderId: (providerId: string) =>
dispatch(getProviderDetailsByProviderId(providerId)),
getProviderTemplates: (providerId: string) =>
dispatch({
type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT,
payload: {
providerId,
},
}),
dispatch(fetchProviderTemplates(providerId)),
setLastUsedEditorPage: (path: string) =>
dispatch(setLastUsedEditorPage(path)),
setLastSelectedPage: (selectedPageId: string) =>
dispatch(setLastSelectedPage(selectedPageId)),
addApiToPage: (templateData: AddApiToPageRequest) =>
dispatch(addApiToPage(templateData)),
});

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import {
reduxForm,
@ -17,12 +17,14 @@ import {
BodyFormData,
Property,
} from "api/ActionAPI";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
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 CredentialsTooltip from "components/editorComponents/form/CredentialsTooltip";
import { FormIcons } from "icons/FormIcons";
import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView";
import Pagination, { PaginationType } from "./Pagination";
@ -52,13 +54,16 @@ const Form = styled.form`
const MainConfiguration = styled.div`
padding-top: 10px;
padding-left: 17px;
span.bp3-popover-target {
display: inline-block;
}
`;
const SecondaryWrapper = styled.div`
display: flex;
height: 100%;
border-top: 1px solid #d0d7dd;
margin-top: 15px;
margin-top: 10px;
`;
const RequestParamsWrapper = styled.div`
@ -120,6 +125,10 @@ interface APIFormProps {
providerImage: string;
providerURL: string;
providerCredentialSteps: string;
location: {
pathname: string;
};
dispatch: any;
}
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
@ -140,6 +149,9 @@ const RapidApiEditorForm: React.FC<Props> = (props: Props) => {
actionConfigurationBodyFormData,
providerImage,
providerURL,
providerCredentialSteps,
location,
dispatch,
} = props;
const postbodyResponsePresent =
@ -147,6 +159,26 @@ const RapidApiEditorForm: React.FC<Props> = (props: Props) => {
actionConfiguration &&
actionConfigurationBodyFormData.length > 0;
// let credentialStepsData;
// if (providerCredentialSteps.length !== 0) {
// credentialStepsData = providerCredentialSteps.split("\\n");
// }
// console.log(credentialStepsData, "credentialStepsData");
useEffect(() => {
dispatch({
type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE,
payload: {
path: location.pathname,
},
});
});
// const abc = (text: string) => {
// console.log(text, "text");
// }
return (
<Form onSubmit={handleSubmit}>
{isSaving && <LoadingOverlayScreen>Saving...</LoadingOverlayScreen>}
@ -199,6 +231,12 @@ const RapidApiEditorForm: React.FC<Props> = (props: Props) => {
disabled={true}
/>
</FormRow>
{/* Display How to get Credentials info if it is present */}
{providerCredentialSteps && providerCredentialSteps !== "" && (
<CredentialsTooltip
providerCredentialSteps={providerCredentialSteps}
/>
)}
</MainConfiguration>
<SecondaryWrapper>
<TabbedViewContainer>

View File

@ -155,6 +155,7 @@ class ApiEditor extends React.Component<Props> {
pageId={this.props.match.params.pageId}
history={this.props.history}
location={this.props.location}
match={this.props.match}
/>
);
const defaultHomeScreen = (
@ -185,6 +186,7 @@ class ApiEditor extends React.Component<Props> {
? this.props.currentApplication.name
: ""
}
location={this.props.location}
/>
)}
@ -204,6 +206,7 @@ class ApiEditor extends React.Component<Props> {
? this.props.currentApplication.name
: ""
}
location={this.props.location}
/>
)}
</>

View File

@ -14,6 +14,9 @@ const initialState: ApiPaneReduxState = {
isRunning: {},
isSaving: {},
isDeleting: {},
currentCategory: "",
lastUsedEditorPage: "",
lastSelectedPage: "",
};
export interface ApiPaneReduxState {
@ -23,6 +26,9 @@ export interface ApiPaneReduxState {
isRunning: Record<string, boolean>;
isSaving: Record<string, boolean>;
isDeleting: Record<string, boolean>;
currentCategory: string;
lastUsedEditorPage: string;
lastSelectedPage: string;
}
const apiPaneReducer = createReducer(initialState, {
@ -159,6 +165,27 @@ const apiPaneReducer = createReducer(initialState, {
...state,
lastUsed: "",
}),
[ReduxActionTypes.SET_CURRENT_CATEGORY]: (
state: ApiPaneReduxState,
action: ReduxAction<{ category: string }>,
) => ({
...state,
currentCategory: action.payload.category,
}),
[ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE]: (
state: ApiPaneReduxState,
action: ReduxAction<{ path: string }>,
) => ({
...state,
lastUsedEditorPage: action.payload.path,
}),
[ReduxActionTypes.SET_LAST_SELECTED_PAGE_PAGE]: (
state: ApiPaneReduxState,
action: ReduxAction<{ selectedPageId: string }>,
) => ({
...state,
lastSelectedPage: action.payload.selectedPageId,
}),
});
export default apiPaneReducer;

View File

@ -11,6 +11,7 @@ import {
ProviderTemplates,
ProviderTemplateArray,
ProvidersCategoriesResponse,
FetchProviderDetailsResponse,
} from "constants/providerConstants";
const initialState: ProvidersReduxState = {
@ -22,6 +23,9 @@ const initialState: ProvidersReduxState = {
providerCategories: [],
providerCategoriesErrorPayload: {},
isSwitchingCategory: true,
lastUsedProviderId: "",
providerDetailsByProviderId: {},
providerDetailsErrorPayload: {},
};
const providersReducer = createReducer(initialState, {
@ -75,10 +79,14 @@ const providersReducer = createReducer(initialState, {
},
[ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT]: (
state: ProvidersReduxState,
) => ({
...state,
isFetchingProviderTemplates: true,
}),
action: any,
) => {
return {
...state,
isFetchingProviderTemplates: true,
lastUsedProviderId: action.payload.providerId,
};
},
[ReduxActionTypes.CLEAR_PROVIDERS]: (state: ProvidersReduxState) => ({
...state,
providers: [],
@ -146,6 +154,21 @@ const providersReducer = createReducer(initialState, {
) => {
return { ...state, providerCategoriesErrorPayload: action.payload };
},
[ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS]: (
state: ProvidersReduxState,
action: ReduxAction<FetchProviderDetailsResponse>,
) => {
return {
...state,
providerDetailsByProviderId: action.payload,
};
},
[ReduxActionErrorTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR]: (
state: ProvidersReduxState,
action: ReduxAction<{ providerDetailsErrorPayload: string }>,
) => {
return { ...state, providerDetailsErrorPayload: action.payload };
},
});
export interface ProvidersReduxState {
@ -157,6 +180,9 @@ export interface ProvidersReduxState {
providerCategories: string[];
providerCategoriesErrorPayload: {};
isSwitchingCategory: boolean;
lastUsedProviderId: string;
providerDetailsByProviderId: {};
providerDetailsErrorPayload: {};
}
export default providersReducer;

View File

@ -10,18 +10,23 @@ import {
ReduxFormActionTypes,
} from "constants/ReduxActionConstants";
import { getFormData } from "selectors/formSelectors";
import { API_EDITOR_FORM_NAME, API_HOME_SCREEN_FORM } from "constants/forms";
import { API_EDITOR_FORM_NAME } from "constants/forms";
import {
DEFAULT_API_ACTION,
POST_BODY_FORMAT_OPTIONS,
REST_PLUGIN_PACKAGE_NAME,
} from "constants/ApiEditorConstants";
import history from "utils/history";
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "constants/routes";
import {
API_EDITOR_ID_URL,
API_EDITOR_URL,
getProviderTemplatesURL,
} from "constants/routes";
import {
getCurrentApplicationId,
getCurrentPageId,
getIsEditorInitialized,
getLastSelectedPage,
} from "selectors/editorSelectors";
import { initialize, autofill } from "redux-form";
import { getAction } from "./ActionSagas";
@ -34,7 +39,6 @@ import {
UNIQUE_NAME_ERROR,
VALID_FUNCTION_NAME_ERROR,
} from "constants/messages";
import { fetchProvidersWithCategory } from "actions/providerActions";
import { createNewApiName } from "utils/AppsmithUtils";
import { getPluginIdOfPackageName } from "sagas/selectors";
import { getActions } from "selectors/entitiesSelector";
@ -51,6 +55,10 @@ const getActionConfigs = (state: AppState): ActionData["config"][] =>
state.entities.actions.map(a => a.config);
const getLastUsedAction = (state: AppState) => state.ui.apiPane.lastUsed;
const getLastUsedEditorPage = (state: AppState) =>
state.ui.apiPane.lastUsedEditorPage;
const getLastUsedProvider = (state: AppState) =>
state.ui.providers.lastUsedProviderId;
function* initApiPaneSaga(actionPayload: ReduxAction<{ id?: string }>) {
const isInitialized = yield select(getIsEditorInitialized);
@ -59,13 +67,32 @@ function* initApiPaneSaga(actionPayload: ReduxAction<{ id?: string }>) {
}
const urlId = actionPayload.payload.id;
const lastUsedId = yield select(getLastUsedAction);
const lastUsedProviderId = yield select(getLastUsedProvider);
const applicationId = yield select(getCurrentApplicationId);
const pageId = yield select(getCurrentPageId);
const lastUsedEditorPage = yield select(getLastUsedEditorPage);
let lastSelectedPage = yield select(getLastSelectedPage);
if (lastSelectedPage === "") {
lastSelectedPage = pageId;
}
let id = "";
if (urlId) {
id = urlId;
} else if (lastUsedId) {
id = lastUsedId;
}
yield put(changeApi(id));
if (lastUsedProviderId && lastUsedEditorPage.includes("provider")) {
history.push(
getProviderTemplatesURL(
applicationId,
pageId,
lastUsedProviderId + `/?importTo=${lastSelectedPage}`,
),
);
} else {
yield put(changeApi(id));
}
}
function* syncApiParamsSaga(
@ -324,12 +351,6 @@ 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),

View File

@ -11,6 +11,8 @@ import ProvidersApi, {
FetchProviderTemplatesRequest,
AddApiToPageRequest,
FetchProviderCategoriesResponse,
FetchProviderDetailsByProviderIdRequest,
FetchProviderDetailsResponse,
} from "api/ProvidersApi";
import { Providers } from "constants/providerConstants";
import { FetchProviderWithCategoryRequest } from "api/ProvidersApi";
@ -129,6 +131,35 @@ export function* fetchProvidersCategoriesSaga() {
}
}
export function* fetchProviderDetailsByProviderIdSaga(
action: ReduxActionWithPromise<FetchProviderTemplatesRequest>,
) {
const { providerId } = action.payload;
try {
const request: FetchProviderDetailsByProviderIdRequest = { providerId };
const response: FetchProviderDetailsResponse = yield ProvidersApi.fetchProviderDetailsByProviderId(
request,
);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
yield put({
type: ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS,
payload: response.data,
});
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR,
payload: {
error,
},
});
}
}
export default function* providersSagas() {
yield all([
takeLatest(
@ -144,5 +175,9 @@ export default function* providersSagas() {
ReduxActionTypes.FETCH_PROVIDERS_WITH_CATEGORY_INIT,
fetchProvidersWithCategorySaga,
),
takeLatest(
ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT,
fetchProviderDetailsByProviderIdSaga,
),
]);
}

View File

@ -25,6 +25,8 @@ 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 getLastSelectedPage = (state: AppState) =>
state.ui.apiPane.lastSelectedPage;
export const getProviderCategories = (state: AppState) =>
state.ui.providers.providerCategories;

View File

@ -10,6 +10,7 @@ import _ from "lodash";
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import * as log from "loglevel";
import { LogLevelDesc } from "loglevel";
import { providerBackgroundColors } from "constants/providerConstants";
export const createReducer = (
initialState: any,
@ -119,3 +120,32 @@ const getEnvLogLevel = (configLevel: LogLevelDesc): LogLevelDesc => {
if (localStorageLevel) logLevel = localStorageLevel;
return logLevel;
};
export const getInitialsAndColorCode = (fullName: any): string[] => {
console.log(fullName, "fullName");
let inits = "";
// if name contains space. eg: "Full Name"
if (fullName.includes(" ")) {
const namesArr = fullName.split(" ");
let initials = namesArr.map((name: string) => name.charAt(0));
initials = initials.join("").toUpperCase();
inits = initials.slice(0, 2);
} else {
// handle for camelCase
const str = fullName.replace(/([a-z])([A-Z])/g, "$1 $2");
const namesArr = str.split(" ");
let initials = namesArr.map((name: string) => name.charAt(0));
initials = initials.join("").toUpperCase();
inits = initials.slice(0, 2);
}
const colorCode = getColorCode(inits);
return [inits, colorCode];
};
const getColorCode = (initials: string): string => {
let asciiSum = 0;
for (let i = 0; i < initials.length; i++) {
asciiSum += initials[i].charCodeAt(0);
}
return providerBackgroundColors[asciiSum % providerBackgroundColors.length];
};