From 2630eaa4cab4589ec1de2a85e0228cc2309ad6b3 Mon Sep 17 00:00:00 2001 From: Akash N Date: Tue, 19 May 2020 06:10:59 +0000 Subject: [PATCH] Feature/datasources draft --- app/client/src/actions/datasourceActions.ts | 7 ++ .../formControls/KeyValueInputControl.tsx | 10 --- .../src/constants/ReduxActionConstants.tsx | 3 + .../src/pages/Editor/DataSourceSidebar.tsx | 42 ++++++---- .../uiReducers/datasourcePaneReducer.ts | 22 +++++ app/client/src/sagas/DatasourcesSagas.ts | 84 ++++++++++++++++++- app/client/src/selectors/entitiesSelector.ts | 15 ++++ 7 files changed, 155 insertions(+), 28 deletions(-) diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index 2afa942ec9..f6044a47ea 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -22,6 +22,13 @@ export const updateDatasource = (payload: Datasource) => { }; }; +export const changeDatasource = (payload: Datasource) => { + return { + type: ReduxActionTypes.CHANGE_DATASOURCE, + payload, + }; +}; + export const testDatasource = (payload: Partial) => { return { type: ReduxActionTypes.TEST_DATASOURCE_INIT, diff --git a/app/client/src/components/formControls/KeyValueInputControl.tsx b/app/client/src/components/formControls/KeyValueInputControl.tsx index 7c0c3b675a..6d6c832eb7 100644 --- a/app/client/src/components/formControls/KeyValueInputControl.tsx +++ b/app/client/src/components/formControls/KeyValueInputControl.tsx @@ -26,16 +26,6 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => { } }, [props.fields]); - useEffect(() => { - if (typeof props.fields.getAll() === "string") { - const fieldsValue: [] = JSON.parse(`${props.fields.getAll()}`); - props.fields.removeAll(); - fieldsValue.forEach((value, index) => { - props.fields.insert(index, value); - }); - } - }, [props.fields]); - return (
{typeof props.fields.getAll() === "object" && ( diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 499b95a581..099d26176b 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -67,9 +67,12 @@ export const ReduxActionTypes: { [key: string]: string } = { CREATE_DATASOURCE_FROM_FORM_INIT: "CREATE_DATASOURCE_FROM_FORM_INIT", UPDATE_DATASOURCE_INIT: "UPDATE_DATASOURCE_INIT", UPDATE_DATASOURCE_SUCCESS: "UPDATE_DATASOURCE_SUCCESS", + CHANGE_DATASOURCE: "CHANGE_DATASOURCE", SELECT_PLUGIN: "SELECT_PLUGIN", TEST_DATASOURCE_INIT: "TEST_DATASOURCE_INIT", TEST_DATASOURCE_SUCCESS: "TEST_DATASOURCE_SUCCESS", + DELETE_DATASOURCE_DRAFT: "DELETE_DATASOURCE_DRAFT", + UPDATE_DATASOURCE_DRAFT: "UPDATE_DATASOURCE_DRAFT", FETCH_PUBLISHED_PAGE_INIT: "FETCH_PUBLISHED_PAGE_INIT", FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS", DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT", diff --git a/app/client/src/pages/Editor/DataSourceSidebar.tsx b/app/client/src/pages/Editor/DataSourceSidebar.tsx index 6131ff82a3..ce982268da 100644 --- a/app/client/src/pages/Editor/DataSourceSidebar.tsx +++ b/app/client/src/pages/Editor/DataSourceSidebar.tsx @@ -16,6 +16,7 @@ import { initDatasourcePane, storeDatastoreRefs, deleteDatasource, + changeDatasource, } from "actions/datasourceActions"; import { ControlIcons } from "icons/ControlIcons"; import { theme } from "constants/DefaultTheme"; @@ -25,10 +26,7 @@ import ImageAlt from "assets/images/placeholder-image.svg"; import Postgres from "assets/images/Postgress.png"; import MongoDB from "assets/images/MongoDB.png"; import RestTemplateImage from "assets/images/RestAPI.png"; -import { - DATA_SOURCES_EDITOR_ID_URL, - DATA_SOURCES_EDITOR_URL, -} from "constants/routes"; +import { DATA_SOURCES_EDITOR_URL } from "constants/routes"; import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants"; import { PLUGIN_PACKAGE_POSTGRES, @@ -44,6 +42,7 @@ interface ReduxDispatchProps { storeDatastoreRefs: (refsList: []) => void; fetchFormConfig: (id: string) => void; deleteDatasource: (id: string) => void; + onDatasourceChange: (datasource: Datasource) => void; } interface ReduxStateProps { @@ -51,6 +50,7 @@ interface ReduxStateProps { plugins: Plugin[]; datastoreRefs: Record; formConfigs: Record; + drafts: Record; } type DataSourceSidebarProps = {}; @@ -172,6 +172,15 @@ const Container = styled.div` } `; +const DraftIconIndicator = styled.span<{ isHidden: boolean }>` + width: 8px; + height: 8px; + border-radius: 8px; + background-color: #f2994a; + margin: 0 5px; + opacity: ${({ isHidden }) => (isHidden ? 0 : 1)}; +`; + type Props = DataSourceSidebarProps & RouteComponentProps<{ pageId: string; @@ -202,6 +211,13 @@ class DataSourceSidebar extends React.Component { storeDatastoreRefs(this.refsCollection); } + shouldComponentUpdate(nextProps: Readonly): boolean { + if (Object.keys(nextProps.drafts) !== Object.keys(this.props.drafts)) { + return true; + } + return nextProps.dataSources !== this.props.dataSources; + } + handleCreateNewDatasource = () => { const { history } = this.props; const { pageId, applicationId } = this.props.match.params; @@ -210,17 +226,7 @@ class DataSourceSidebar extends React.Component { }; handleItemSelected = (datasource: Datasource) => { - const { history, formConfigs } = this.props; - const { pageId, applicationId } = this.props.match.params; - - this.props.initializeForm(datasource); - this.props.selectPlugin(datasource.pluginId); - if (!formConfigs[datasource.pluginId]) { - this.props.fetchFormConfig(datasource.pluginId); - } - history.push( - DATA_SOURCES_EDITOR_ID_URL(applicationId, pageId, datasource.id), - ); + this.props.onDatasourceChange(datasource); }; handleSearchChange = (e: React.ChangeEvent<{ value: string }>) => { @@ -253,6 +259,7 @@ class DataSourceSidebar extends React.Component { }, datastoreRefs, deleteDatasource, + drafts, } = this.props; return datasources.map(datasource => { @@ -272,6 +279,7 @@ class DataSourceSidebar extends React.Component { /> {datasource.name} + { @@ -335,17 +343,21 @@ class DataSourceSidebar extends React.Component { } const mapStateToProps = (state: AppState): ReduxStateProps => { + const { drafts } = state.ui.datasourcePane; return { formConfigs: state.entities.plugins.formConfigs, dataSources: getDataSources(state), plugins: getPlugins(state), datastoreRefs: state.ui.datasourcePane.datasourceRefs, + drafts, }; }; const mapDispatchToProps = (dispatch: Function): ReduxDispatchProps => ({ initDatasourcePane: (pluginType: string, urlId?: string) => dispatch(initDatasourcePane(pluginType, urlId)), + onDatasourceChange: (datasource: Datasource) => + dispatch(changeDatasource(datasource)), fetchFormConfig: (id: string) => dispatch(fetchPluginForm({ id })), selectPlugin: (pluginId: string) => dispatch(selectPlugin(pluginId)), initializeForm: (data: Record) => diff --git a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts index 59d706c1da..5a84c73649 100644 --- a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts +++ b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts @@ -2,15 +2,18 @@ import React from "react"; import { createReducer } from "utils/AppsmithUtils"; import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; import { Datasource } from "api/DatasourcesApi"; +import _ from "lodash"; const initialState: DatasourcePaneReduxState = { selectedPlugin: "", datasourceRefs: {}, + drafts: {}, }; export interface DatasourcePaneReduxState { selectedPlugin: string; datasourceRefs: {}; + drafts: Record; } const datasourcePaneReducer = createReducer(initialState, { @@ -42,6 +45,25 @@ const datasourcePaneReducer = createReducer(initialState, { }, }; }, + [ReduxActionTypes.UPDATE_DATASOURCE_DRAFT]: ( + state: DatasourcePaneReduxState, + action: ReduxAction<{ id: string; draft: Partial }>, + ) => { + return { + ...state, + drafts: { + ...state.drafts, + [action.payload.id]: action.payload.draft, + }, + }; + }, + [ReduxActionTypes.DELETE_DATASOURCE_DRAFT]: ( + state: DatasourcePaneReduxState, + action: ReduxAction<{ id: string }>, + ) => ({ + ...state, + drafts: _.omit(state.drafts, action.payload.id), + }), }); export default datasourcePaneReducer; diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index 8fee85a881..094db1272a 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -1,16 +1,25 @@ -import { all, put, takeEvery, select } from "redux-saga/effects"; -import { change, initialize } from "redux-form"; +import { all, put, takeEvery, select, call } from "redux-saga/effects"; +import { change, initialize, getFormValues } from "redux-form"; import _ from "lodash"; import { ReduxAction, ReduxActionErrorTypes, ReduxActionTypes, + ReduxFormActionTypes, + ReduxActionWithMeta, } from "constants/ReduxActionConstants"; import { getCurrentApplicationId, getCurrentPageId, } from "selectors/editorSelectors"; -import { getDatasourceRefs, getPluginForm } from "selectors/entitiesSelector"; +import { + getDatasourceRefs, + getPluginForm, + getDatasource, + getDatasourceDraft, +} from "selectors/entitiesSelector"; +import { selectPlugin } from "actions/datasourceActions"; +import { fetchPluginForm } from "actions/pluginActions"; import { GenericApiResponse } from "api/ApiResponses"; import DatasourcesApi, { CreateDatasourceConfig, @@ -99,6 +108,12 @@ export function* deleteDatasourceSaga( type: ReduxActionTypes.DELETE_DATASOURCE_SUCCESS, payload: response.data, }); + yield put({ + type: ReduxActionTypes.DELETE_DATASOURCE_DRAFT, + payload: { + id: response.data.id, + }, + }); history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId)); } } catch (error) { @@ -125,6 +140,12 @@ function* updateDatasourceSaga(actionPayload: ReduxAction) { type: ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS, payload: response.data, }); + yield put({ + type: ReduxActionTypes.DELETE_DATASOURCE_DRAFT, + payload: { + id: response.data.id, + }, + }); } } catch (error) { yield put({ @@ -269,6 +290,60 @@ function* createDatasourceFromFormSaga( } } +function* updateDraftsSaga() { + const values = yield select(getFormValues(DATASOURCE_DB_FORM)); + + if (!values.id) return; + const datasource = yield select(getDatasource, values.id); + + if (_.isEqual(values, datasource)) { + yield put({ + type: ReduxActionTypes.DELETE_DATASOURCE_DRAFT, + payload: { id: values.id }, + }); + } else { + yield put({ + type: ReduxActionTypes.UPDATE_DATASOURCE_DRAFT, + payload: { id: values.id, draft: values }, + }); + } +} + +function* changeDatasourceSaga(actionPayload: ReduxAction) { + const { id, pluginId } = actionPayload.payload; + const datasource = actionPayload.payload; + const state = yield select(); + const draft = yield select(getDatasourceDraft, id); + const formConfigs = state.entities.plugins.formConfigs; + const applicationId = yield select(getCurrentApplicationId); + const pageId = yield select(getCurrentPageId); + let data; + + if (_.isEmpty(draft)) { + data = actionPayload.payload; + } else { + data = draft; + } + + yield put(initialize(DATASOURCE_DB_FORM, data)); + yield put(selectPlugin(pluginId)); + + if (!formConfigs[pluginId]) { + yield put(fetchPluginForm({ id: pluginId })); + } + history.push( + DATA_SOURCES_EDITOR_ID_URL(applicationId, pageId, datasource.id), + ); +} + +function* formValueChangeSaga( + actionPayload: ReduxActionWithMeta, +) { + const { form } = actionPayload.meta; + if (form !== DATASOURCE_DB_FORM) return; + yield all([call(updateDraftsSaga)]); +} + export function* watchDatasourcesSagas() { yield all([ takeEvery(ReduxActionTypes.FETCH_DATASOURCES_INIT, fetchDatasourcesSaga), @@ -280,5 +355,8 @@ export function* watchDatasourcesSagas() { takeEvery(ReduxActionTypes.UPDATE_DATASOURCE_INIT, updateDatasourceSaga), takeEvery(ReduxActionTypes.TEST_DATASOURCE_INIT, testDatasourceSaga), takeEvery(ReduxActionTypes.DELETE_DATASOURCE_INIT, deleteDatasourceSaga), + takeEvery(ReduxActionTypes.CHANGE_DATASOURCE, changeDatasourceSaga), + // Intercepting the redux-form change actionType + takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga), ]); } diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index 38c4423025..6ac1d16d87 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -8,6 +8,7 @@ import { QUERY_CONSTANT } from "constants/QueryEditorConstants"; import { API_CONSTANT } from "constants/ApiEditorConstants"; import { createSelector } from "reselect"; import { Page } from "constants/ReduxActionConstants"; +import { Datasource } from "api/DatasourcesApi"; export const getEntities = (state: AppState): AppState["entities"] => state.entities; @@ -107,6 +108,20 @@ export const getActions = (state: AppState): ActionDataState => export const getDatasourceRefs = (state: AppState): any => state.ui.datasourcePane.datasourceRefs; +export const getDatasource = ( + state: AppState, + datasourceId: string, +): Partial | undefined => + state.entities.datasources.list.find( + datasource => datasource.id === datasourceId, + ); + +export const getDatasourceDraft = (state: AppState, id: string) => { + const drafts = state.ui.datasourcePane.drafts; + if (id in drafts) return drafts[id]; + return {}; +}; + export const getPlugins = (state: AppState) => state.entities.plugins.list; export const getApiActions = (state: AppState): ActionDataState => {