diff --git a/app/client/package.json b/app/client/package.json index 323e77bd63..04ed424355 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -83,7 +83,7 @@ "build": "craco build", "test": "craco test", "eject": "react-scripts eject", - "flow": "flow" + "start-prod": "REACT_APP_ENVIRONMENT=PRODUCTION craco start" }, "resolution": { "jest": "24.8.0" diff --git a/app/client/src/actions/pluginActions.ts b/app/client/src/actions/pluginActions.ts new file mode 100644 index 0000000000..8e38885bbb --- /dev/null +++ b/app/client/src/actions/pluginActions.ts @@ -0,0 +1,8 @@ +import { + ReduxActionTypes, + ReduxActionWithoutPayload, +} from "constants/ReduxActionConstants"; + +export const fetchPlugins = (): ReduxActionWithoutPayload => ({ + type: ReduxActionTypes.FETCH_PLUGINS_REQUEST, +}); diff --git a/app/client/src/api/PluginApi.ts b/app/client/src/api/PluginApi.ts new file mode 100644 index 0000000000..ebb5dbb751 --- /dev/null +++ b/app/client/src/api/PluginApi.ts @@ -0,0 +1,18 @@ +import Api from "./Api"; +import { AxiosPromise } from "axios"; +import { GenericApiResponse } from "api/ApiResponses"; + +export interface Plugin { + id: string; + name: string; + type: "API" | "DB"; +} + +class PluginsApi extends Api { + static url = "v1/plugins"; + static fetchPlugins(): AxiosPromise> { + return Api.get(PluginsApi.url); + } +} + +export default PluginsApi; diff --git a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx index 293a02f3b8..c4b1db75fe 100644 --- a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx @@ -4,14 +4,15 @@ import styled, { css } from "styled-components"; import { TextComponentProps } from "./TextComponent"; import { ButtonStyle } from "widgets/ButtonWidget"; import { Theme } from "constants/DefaultTheme"; +import _ from "lodash"; const getButtonColorStyles = (props: { theme: Theme } & ButtonStyleProps) => { if (props.filled) return props.theme.colors.textOnDarkBG; - if (props.styleName) { - if (props.styleName === "secondary") { + if (props.accent) { + if (props.accent === "secondary") { return props.theme.colors.OXFORD_BLUE; } - return props.theme.colors[props.styleName]; + return props.theme.colors[props.accent]; } }; @@ -22,18 +23,20 @@ const ButtonColorStyles = css` } `; -const ButtonWrapper = styled(AnchorButton)` +const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => ( + +))` && { ${ButtonColorStyles}; width: 100%; height: 100%; transition: background-color 0.2s; background-color: ${props => - props.filled && props.styleName && props.theme.colors[props.styleName]}; + props.filled && props.accent && props.theme.colors[props.accent]}; border: 1px solid ${props => - props.styleName - ? props.theme.colors[props.styleName] + props.accent + ? props.theme.colors[props.accent] : props.theme.colors.secondary}; border-radius: 4px; font-weight: ${props => props.theme.fontWeights[2]}; @@ -44,14 +47,14 @@ const ButtonWrapper = styled(AnchorButton)` ${ButtonColorStyles}; background-color: ${props => { if (!props.filled) return props.theme.colors.secondaryDarker; - if (props.styleName !== "secondary") { - return props.theme.colors[`${props.styleName}Darker`]; + if (props.accent !== "secondary") { + return props.theme.colors[`${props.accent}Darker`]; } }}; border-color: ${props => { if (!props.filled) return; - if (props.styleName !== "secondary") { - return props.theme.colors[`${props.styleName}Darker`]; + if (props.accent !== "secondary") { + return props.theme.colors[`${props.accent}Darker`]; } }}; } @@ -59,14 +62,14 @@ const ButtonWrapper = styled(AnchorButton)` ${ButtonColorStyles}; background-color: ${props => { if (!props.filled) return props.theme.colors.secondaryDarkest; - if (props.styleName !== "secondary") { - return props.theme.colors[`${props.styleName}Darkest`]; + if (props.accent !== "secondary") { + return props.theme.colors[`${props.accent}Darkest`]; } }}; border-color: ${props => { if (!props.filled) return; - if (props.styleName !== "secondary") { - return props.theme.colors[`${props.styleName}Darkest`]; + if (props.accent !== "secondary") { + return props.theme.colors[`${props.accent}Darkest`]; } }}; } @@ -79,7 +82,7 @@ const ButtonWrapper = styled(AnchorButton)` export type ButtonStyleName = "primary" | "secondary" | "error"; type ButtonStyleProps = { - styleName?: ButtonStyleName; + accent?: ButtonStyleName; filled?: boolean; }; @@ -89,7 +92,7 @@ export const BaseButton = (props: IButtonProps & ButtonStyleProps) => { }; BaseButton.defaultProps = { - styleName: "secondary", + accent: "secondary", disabled: false, text: "Button Text", minimal: true, @@ -122,7 +125,7 @@ const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => { icon={props.icon} text={props.text} filled={props.buttonStyle === "PRIMARY_BUTTON"} - styleName={mapButtonStyleToStyleName(props.buttonStyle)} + accent={mapButtonStyleToStyleName(props.buttonStyle)} onClick={props.onClick} disabled={props.disabled} /> diff --git a/app/client/src/components/designSystems/blueprint/TextComponent.tsx b/app/client/src/components/designSystems/blueprint/TextComponent.tsx index f974d25bde..593c88d5a1 100644 --- a/app/client/src/components/designSystems/blueprint/TextComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/TextComponent.tsx @@ -5,7 +5,7 @@ import { ComponentProps } from "components/designSystems/appsmith/BaseComponent" import { TextStyle } from "widgets/TextWidget"; type TextStyleProps = { - styleName: "primary" | "secondary" | "error"; + accent: "primary" | "secondary" | "error"; }; export const BaseText = styled(Text)``; diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index 01b715b57a..2cd95c4dfa 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -120,7 +120,7 @@ const ApiResponseView = (props: Props) => { {response.statusCode && ( Status: {response.statusCode} @@ -128,12 +128,12 @@ const ApiResponseView = (props: Props) => { )} {response.duration && ( - + Time: {response.duration} ms )} {response.size && ( - + Size: {formatBytes(parseInt(response.size))} )} diff --git a/app/client/src/components/editorComponents/DropdownComponent.tsx b/app/client/src/components/editorComponents/DropdownComponent.tsx index 57fe6d9ffa..52aa051982 100644 --- a/app/client/src/components/editorComponents/DropdownComponent.tsx +++ b/app/client/src/components/editorComponents/DropdownComponent.tsx @@ -46,7 +46,7 @@ class DropdownComponent extends Component { const displayMode = ( { > {this.props.toggle || ( diff --git a/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx b/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx index 432a1360c2..054143d214 100644 --- a/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx +++ b/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx @@ -6,7 +6,6 @@ import { AppState } from "reducers"; import { DatasourceDataState } from "reducers/entityReducers/datasourceReducer"; import _ from "lodash"; import { createDatasource } from "actions/datasourcesActions"; -import { REST_PLUGIN_ID } from "constants/ApiEditorConstants"; interface ReduxStateProps { datasources: DatasourceDataState; @@ -17,12 +16,14 @@ interface ReduxActionProps { interface ComponentProps { name: string; + pluginId: string; } const DatasourcesField = ( props: ReduxActionProps & ReduxStateProps & ComponentProps, ) => { const options = props.datasources.list + .filter(r => r.pluginId === props.pluginId) .filter(r => r.datasourceConfiguration !== null) .map(r => ({ label: r.datasourceConfiguration.url.endsWith("/") @@ -49,7 +50,10 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({ datasources: state.entities.datasources, }); -const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({ +const mapDispatchToProps = ( + dispatch: any, + ownProps: ComponentProps, +): ReduxActionProps => ({ createDatasource: (value: string) => dispatch( createDatasource({ @@ -59,7 +63,7 @@ const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({ // Datasource url should end with / url: value.endsWith("/") ? value : `${value}/`, }, - pluginId: REST_PLUGIN_ID, + pluginId: ownProps.pluginId, }), ), }); diff --git a/app/client/src/constants/ApiEditorConstants.ts b/app/client/src/constants/ApiEditorConstants.ts index 1dacfac0ff..d2fc8aca6b 100644 --- a/app/client/src/constants/ApiEditorConstants.ts +++ b/app/client/src/constants/ApiEditorConstants.ts @@ -31,4 +31,4 @@ export const FORM_INITIAL_VALUES = { }, }; -export const REST_PLUGIN_ID = "5ca385dc81b37f0004b4db85"; +export const PLUGIN_NAME = "RestTemplatePluginExecutor"; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 3e5499550a..6431376b2e 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -87,6 +87,8 @@ export const ReduxActionTypes: { [key: string]: string } = { UPDATE_API_DRAFT: "UPDATE_API_DRAFT", DELETE_API_DRAFT: "DELETE_API_DRAFT", UPDATE_ROUTES_PARAMS: "UPDATE_ROUTES_PARAMS", + FETCH_PLUGINS_REQUEST: "FETCH_PLUGINS_REQUEST", + FETCH_PLUGINS_SUCCESS: "FETCH_PLUGINS_SUCCESS", }; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; @@ -122,6 +124,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { FETCH_APPLICATION_LIST_ERROR: "FETCH_APPLICATION_LIST_ERROR", CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR", SAVE_JS_EXECUTION_RECORD: "SAVE_JS_EXECUTION_RECORD", + FETCH_PLUGINS_ERROR: "FETCH_PLUGINS_ERROR", }; export const ReduxFormActionTypes: { [key: string]: string } = { diff --git a/app/client/src/pages/Applications/ApplicationCard.tsx b/app/client/src/pages/Applications/ApplicationCard.tsx index 5195f769b6..d653a988e6 100644 --- a/app/client/src/pages/Applications/ApplicationCard.tsx +++ b/app/client/src/pages/Applications/ApplicationCard.tsx @@ -194,7 +194,7 @@ export const ApplicationCard = (props: ApplicationCardProps) => { diff --git a/app/client/src/pages/Editor/APIEditor/Form.tsx b/app/client/src/pages/Editor/APIEditor/Form.tsx index 64f38fe46a..d6673ba7cb 100644 --- a/app/client/src/pages/Editor/APIEditor/Form.tsx +++ b/app/client/src/pages/Editor/APIEditor/Form.tsx @@ -69,6 +69,7 @@ const JSONEditorFieldWrapper = styled.div` `; interface APIFormProps { + pluginId: string; allowSave: boolean; allowPostBody: boolean; onSubmit: FormSubmitHandler; @@ -84,6 +85,7 @@ type Props = APIFormProps & InjectedFormProps; const ApiEditorForm: React.FC = (props: Props) => { const { + pluginId, allowSave, allowPostBody, onSaveClick, @@ -102,19 +104,19 @@ const ApiEditorForm: React.FC = (props: Props) => { = (props: Props) => { name="actionConfiguration.httpMethod" options={HTTP_METHOD_OPTIONS} /> - + void; @@ -70,12 +72,19 @@ class ApiEditor extends React.Component { params: { apiId }, }, formData, + pluginId, } = this.props; const httpMethod = _.get(formData, "actionConfiguration.httpMethod"); + if (!pluginId) { + return ( + {"Plugin is not installed"} + ); + } return ( {apiId ? ( { } const mapStateToProps = (state: AppState): ReduxStateProps => ({ + pluginId: getPluginIdOfName(state, PLUGIN_NAME), actions: state.entities.actions, apiPane: state.ui.apiPane, formData: getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction, diff --git a/app/client/src/pages/Editor/ApiSidebar.tsx b/app/client/src/pages/Editor/ApiSidebar.tsx index 1b99c604d6..3b5b8cf49a 100644 --- a/app/client/src/pages/Editor/ApiSidebar.tsx +++ b/app/client/src/pages/Editor/ApiSidebar.tsx @@ -16,6 +16,8 @@ import { changeApi, initApiPane } from "actions/apiPaneActions"; import { RestAction } from "api/ActionAPI"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import Fuse from "fuse.js"; +import { getPluginIdOfName } from "selectors/entitiesSelector"; +import { PLUGIN_NAME } from "constants/ApiEditorConstants"; const LoadingContainer = styled(CenteredWrapper)` height: 50%; @@ -133,6 +135,7 @@ const CreateApiWrapper = styled.div` interface ReduxStateProps { actions: ActionDataState; apiPane: ApiPaneReduxState; + pluginId: string | undefined; } interface ReduxDispatchProps { @@ -226,7 +229,9 @@ class ApiSidebar extends React.Component { apiPane: { isFetching, isSaving, drafts }, match, actions: { data }, + pluginId, } = this.props; + if (!pluginId) return null; const { isCreating, search, name } = this.state; const activeActionId = match.params.apiId; const fuse = new Fuse(data, fuseOptions); @@ -277,7 +282,7 @@ class ApiSidebar extends React.Component { /> { } const mapStateToProps = (state: AppState): ReduxStateProps => ({ + pluginId: getPluginIdOfName(state, PLUGIN_NAME), actions: state.entities.actions, apiPane: state.ui.apiPane, }); diff --git a/app/client/src/pages/Editor/EditorHeader.tsx b/app/client/src/pages/Editor/EditorHeader.tsx index a714b49315..283ed53de1 100644 --- a/app/client/src/pages/Editor/EditorHeader.tsx +++ b/app/client/src/pages/Editor/EditorHeader.tsx @@ -110,7 +110,7 @@ export const EditorHeader = (props: EditorHeaderProps) => { onClick={props.onPublish} text="Publish" loading={props.isPublishing} - styleName="primary" + accent="primary" filled /> diff --git a/app/client/src/reducers/entityReducers/datasourceReducer.ts b/app/client/src/reducers/entityReducers/datasourceReducer.ts index 84d22ee073..c68f10a627 100644 --- a/app/client/src/reducers/entityReducers/datasourceReducer.ts +++ b/app/client/src/reducers/entityReducers/datasourceReducer.ts @@ -1,7 +1,6 @@ import { createReducer } from "utils/AppsmithUtils"; import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; import { Datasource } from "api/DatasourcesApi"; -import { REST_PLUGIN_ID } from "constants/ApiEditorConstants"; export interface DatasourceDataState { list: Datasource[]; @@ -27,8 +26,7 @@ const datasourceReducer = createReducer(initialState, { return { ...state, loading: false, - // TODO(hetu) Once plugins are being pulled get Ids from there - list: action.payload.filter(r => r.pluginId === REST_PLUGIN_ID), + list: action.payload, }; }, [ReduxActionTypes.CREATE_DATASOURCE_SUCCESS]: ( diff --git a/app/client/src/reducers/entityReducers/index.tsx b/app/client/src/reducers/entityReducers/index.tsx index 91609d1326..27279756de 100644 --- a/app/client/src/reducers/entityReducers/index.tsx +++ b/app/client/src/reducers/entityReducers/index.tsx @@ -9,6 +9,7 @@ import datasourceReducer from "./datasourceReducer"; import bindingsReducer from "./bindingsReducer"; import pageListReducer from "./pageListReducer"; import jsExecutionsReducer from "./jsExecutionsReducer"; +import pluginsReducer from "reducers/entityReducers/pluginsReducer"; const entityReducer = combineReducers({ canvasWidgets: canvasWidgetsReducer, @@ -21,6 +22,7 @@ const entityReducer = combineReducers({ nameBindings: bindingsReducer, pageList: pageListReducer, jsExecutions: jsExecutionsReducer, + plugins: pluginsReducer, }); export default entityReducer; diff --git a/app/client/src/reducers/entityReducers/pluginsReducer.ts b/app/client/src/reducers/entityReducers/pluginsReducer.ts new file mode 100644 index 0000000000..70e2da40b4 --- /dev/null +++ b/app/client/src/reducers/entityReducers/pluginsReducer.ts @@ -0,0 +1,37 @@ +import { createReducer } from "utils/AppsmithUtils"; +import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; +import { Plugin } from "api/PluginApi"; + +export interface PluginDataState { + list: Plugin[]; + loading: boolean; +} + +const initialState: PluginDataState = { + list: [], + loading: false, +}; + +const pluginsReducer = createReducer(initialState, { + [ReduxActionTypes.FETCH_PLUGINS_REQUEST]: (state: PluginDataState) => { + return { ...state, loading: true }; + }, + [ReduxActionTypes.FETCH_PLUGINS_SUCCESS]: ( + state: PluginDataState, + action: ReduxAction, + ) => { + return { + ...state, + loading: false, + list: action.payload, + }; + }, + [ReduxActionTypes.FETCH_PLUGINS_ERROR]: (state: PluginDataState) => { + return { + ...state, + loading: false, + }; + }, +}); + +export default pluginsReducer; diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx index 9b4d83a76a..92a120194f 100644 --- a/app/client/src/reducers/index.tsx +++ b/app/client/src/reducers/index.tsx @@ -19,6 +19,7 @@ import { BindingsDataState } from "./entityReducers/bindingsReducer"; import { PageListReduxState } from "./entityReducers/pageListReducer"; import { ApiPaneReduxState } from "./uiReducers/apiPaneReducer"; import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer"; +import { PluginDataState } from "reducers/entityReducers/pluginsReducer"; const appReducer = combineReducers({ entities: entityReducer, @@ -49,6 +50,7 @@ export interface AppState { datasources: DatasourceDataState; nameBindings: BindingsDataState; pageList: PageListReduxState; + plugins: PluginDataState; }; } diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index 6dfc38caaa..3446dbfdd6 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -10,6 +10,7 @@ import { fetchPageList } from "actions/pageActions"; import { fetchActions } from "actions/actionActions"; import { fetchDatasources } from "actions/datasourcesActions"; import { initBindingMapListener } from "actions/bindingActions"; +import { fetchPlugins } from "actions/pluginActions"; function* initializeEditorSaga( initializeEditorAction: ReduxAction, @@ -17,6 +18,7 @@ function* initializeEditorSaga( const { applicationId } = initializeEditorAction.payload; // Step 1: Start getting all the data needed by the yield all([ + put(fetchPlugins()), put(fetchPageList(applicationId)), put(fetchEditorConfigs()), put(initBindingMapListener()), @@ -25,6 +27,7 @@ function* initializeEditorSaga( ]); // Step 2: Wait for all data to be in the state yield all([ + take(ReduxActionTypes.FETCH_PLUGINS_SUCCESS), take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS), take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS), take(ReduxActionTypes.FETCH_DATASOURCES_SUCCESS), diff --git a/app/client/src/sagas/PluginSagas.ts b/app/client/src/sagas/PluginSagas.ts new file mode 100644 index 0000000000..bc533e40ff --- /dev/null +++ b/app/client/src/sagas/PluginSagas.ts @@ -0,0 +1,30 @@ +import { all, takeEvery, call, put } from "redux-saga/effects"; +import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import PluginsApi from "api/PluginApi"; +import { validateResponse } from "sagas/ErrorSagas"; + +function* fetchPluginsSaga() { + try { + const pluginsResponse = yield call(PluginsApi.fetchPlugins); + const isValid = yield validateResponse(pluginsResponse); + if (isValid) { + yield put({ + type: ReduxActionTypes.FETCH_PLUGINS_SUCCESS, + payload: pluginsResponse.data, + }); + } + } catch (error) { + yield put({ + type: ReduxActionTypes.FETCH_PLUGINS_ERROR, + payload: { error }, + }); + } +} + +function* root() { + yield all([ + takeEvery(ReduxActionTypes.FETCH_PLUGINS_REQUEST, fetchPluginsSaga), + ]); +} + +export default root; diff --git a/app/client/src/sagas/index.tsx b/app/client/src/sagas/index.tsx index ce587ca1a4..97afa3ad22 100644 --- a/app/client/src/sagas/index.tsx +++ b/app/client/src/sagas/index.tsx @@ -13,6 +13,7 @@ import watchActionWidgetMapSagas, { watchPropertyAndBindingUpdate, } from "./ActionWidgetMapSagas"; import apiPaneSagas from "./ApiPaneSagas"; +import pluginSagas from "./PluginSagas"; export function* rootSaga() { yield all([ @@ -29,5 +30,6 @@ export function* rootSaga() { spawn(watchActionWidgetMapSagas), spawn(watchPropertyAndBindingUpdate), spawn(apiPaneSagas), + spawn(pluginSagas), ]); } diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index e9da541133..54bf9f2baa 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -1,3 +1,14 @@ import { AppState, DataTree } from "reducers"; export const getDataTree = (state: AppState): DataTree => state.entities; + +export const getPluginIdOfName = ( + state: AppState, + name: string, +): string | undefined => { + const plugin = state.entities.plugins.list.find( + plugin => plugin.name === name, + ); + if (!plugin) return undefined; + return plugin.id; +};