Add support for plugin name based filtering

This commit is contained in:
Hetu Nandu 2019-11-29 05:22:49 +00:00
parent 743984938d
commit 6cea0e80ad
17 changed files with 146 additions and 10 deletions

View File

@ -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"

View File

@ -0,0 +1,8 @@
import {
ReduxActionTypes,
ReduxActionWithoutPayload,
} from "constants/ReduxActionConstants";
export const fetchPlugins = (): ReduxActionWithoutPayload => ({
type: ReduxActionTypes.FETCH_PLUGINS_REQUEST,
});

View File

@ -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<GenericApiResponse<Plugin[]>> {
return Api.get(PluginsApi.url);
}
}
export default PluginsApi;

View File

@ -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,
}),
),
});

View File

@ -31,4 +31,4 @@ export const FORM_INITIAL_VALUES = {
},
};
export const REST_PLUGIN_ID = "5ca385dc81b37f0004b4db85";
export const PLUGIN_NAME = "RestTemplatePluginExecutor";

View File

@ -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];
@ -121,6 +123,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
FETCH_PAGE_LIST_ERROR: "FETCH_PAGE_LIST_ERROR",
FETCH_APPLICATION_LIST_ERROR: "FETCH_APPLICATION_LIST_ERROR",
CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR",
FETCH_PLUGINS_ERROR: "FETCH_PLUGINS_ERROR",
};
export const ReduxFormActionTypes: { [key: string]: string } = {

View File

@ -69,6 +69,7 @@ const JSONEditorFieldWrapper = styled.div`
`;
interface APIFormProps {
pluginId: string;
allowSave: boolean;
allowPostBody: boolean;
onSubmit: FormSubmitHandler<RestAction>;
@ -84,6 +85,7 @@ type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
const ApiEditorForm: React.FC<Props> = (props: Props) => {
const {
pluginId,
allowSave,
allowPostBody,
onSaveClick,
@ -128,7 +130,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
name="actionConfiguration.httpMethod"
options={HTTP_METHOD_OPTIONS}
/>
<DatasourcesField name="datasource.id" />
<DatasourcesField name="datasource.id" pluginId={pluginId} />
<TextField
placeholder="API Path"
name="actionConfiguration.path"

View File

@ -15,13 +15,15 @@ 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 } from "constants/ApiEditorConstants";
import { HTTP_METHODS, PLUGIN_NAME } from "constants/ApiEditorConstants";
import _ from "lodash";
import { getPluginIdOfName } from "selectors/entitiesSelector";
interface ReduxStateProps {
actions: ActionDataState;
apiPane: ApiPaneReduxState;
formData: RestAction;
pluginId: string | undefined;
}
interface ReduxActionProps {
submitForm: (name: string) => void;
@ -70,12 +72,19 @@ class ApiEditor extends React.Component<Props> {
params: { apiId },
},
formData,
pluginId,
} = this.props;
const httpMethod = _.get(formData, "actionConfiguration.httpMethod");
if (!pluginId) {
return (
<EmptyStateContainer>{"Plugin is not installed"}</EmptyStateContainer>
);
}
return (
<React.Fragment>
{apiId ? (
<ApiEditorForm
pluginId={pluginId}
allowSave={apiId in drafts}
allowPostBody={httpMethod && httpMethod !== HTTP_METHODS[0]}
isSaving={isSaving}
@ -97,6 +106,7 @@ class ApiEditor extends React.Component<Props> {
}
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,

View File

@ -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<Props, State> {
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);
@ -303,6 +308,7 @@ class ApiSidebar extends React.Component<Props, State> {
}
const mapStateToProps = (state: AppState): ReduxStateProps => ({
pluginId: getPluginIdOfName(state, PLUGIN_NAME),
actions: state.entities.actions,
apiPane: state.ui.apiPane,
});

View File

@ -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]: (

View File

@ -8,6 +8,7 @@ import propertyPaneConfigReducer from "./propertyPaneConfigReducer";
import datasourceReducer from "./datasourceReducer";
import bindingsReducer from "./bindingsReducer";
import pageListReducer from "./pageListReducer";
import pluginsReducer from "reducers/entityReducers/pluginsReducer";
const entityReducer = combineReducers({
canvasWidgets: canvasWidgetsReducer,
@ -19,6 +20,7 @@ const entityReducer = combineReducers({
datasources: datasourceReducer,
nameBindings: bindingsReducer,
pageList: pageListReducer,
plugins: pluginsReducer,
});
export default entityReducer;

View File

@ -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<Plugin[]>,
) => {
return {
...state,
loading: false,
list: action.payload,
};
},
[ReduxActionTypes.FETCH_PLUGINS_ERROR]: (state: PluginDataState) => {
return {
...state,
loading: false,
};
},
});
export default pluginsReducer;

View File

@ -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;
};
}

View File

@ -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<InitializeEditorPayload>,
@ -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),

View File

@ -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;

View File

@ -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),
]);
}

View File

@ -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;
};