Merge branch 'feature/datasourcesDraft' into 'release'

Feature/datasources draft

Add drafts for the datasource pane.

See merge request theappsmith/internal-tools-client!600
This commit is contained in:
Arpit Mohan 2020-05-19 06:10:59 +00:00
commit 7e173fc2a7
7 changed files with 155 additions and 28 deletions

View File

@ -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<Datasource>) => {
return {
type: ReduxActionTypes.TEST_DATASOURCE_INIT,

View File

@ -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 (
<div>
{typeof props.fields.getAll() === "object" && (

View File

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

View File

@ -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<string, any>;
formConfigs: Record<string, []>;
drafts: Record<string, Datasource>;
}
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<Props, State> {
storeDatastoreRefs(this.refsCollection);
}
shouldComponentUpdate(nextProps: Readonly<Props>): 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<Props, State> {
};
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<Props, State> {
},
datastoreRefs,
deleteDatasource,
drafts,
} = this.props;
return datasources.map(datasource => {
@ -272,6 +279,7 @@ class DataSourceSidebar extends React.Component<Props, State> {
/>
<ActionName>{datasource.name}</ActionName>
</ActionItem>
<DraftIconIndicator isHidden={!(datasource.id in drafts)} />
<TreeDropdown
defaultText=""
onSelect={() => {
@ -335,17 +343,21 @@ class DataSourceSidebar extends React.Component<Props, State> {
}
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<string, any>) =>

View File

@ -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<string, Datasource>;
}
const datasourcePaneReducer = createReducer(initialState, {
@ -42,6 +45,25 @@ const datasourcePaneReducer = createReducer(initialState, {
},
};
},
[ReduxActionTypes.UPDATE_DATASOURCE_DRAFT]: (
state: DatasourcePaneReduxState,
action: ReduxAction<{ id: string; draft: Partial<Datasource> }>,
) => {
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;

View File

@ -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<Datasource>) {
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<Datasource>) {
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<string, { field: string; form: string }>,
) {
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),
]);
}

View File

@ -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<Datasource> | 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 => {