Merge branch 'fix/hot-fixes-2' into 'master'

Action draft fixes

See merge request theappsmith/internal-tools-client!753
This commit is contained in:
Hetu Nandu 2020-06-18 14:16:49 +00:00
commit 60b9bba24b
16 changed files with 76 additions and 178 deletions

View File

@ -158,11 +158,6 @@ export const executeApiActionSuccess = (payload: {
payload: payload,
});
export const editApiName = (payload: { id: string; value: string }) => ({
type: ReduxActionTypes.EDIT_API_NAME,
payload: payload,
});
export const saveApiName = (payload: { id: string; name: string }) => ({
type: ReduxActionTypes.SAVE_API_NAME,
payload: payload,

View File

@ -24,6 +24,7 @@ import { Colors } from "constants/Colors";
import AnalyticsUtil from "utils/AnalyticsUtil";
import TernServer from "utils/autocomplete/TernServer";
import KeyboardShortcuts from "constants/KeyboardShortcuts";
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
const LightningMenu = lazy(() =>
import("components/editorComponents/LightningMenu"),
);
@ -166,7 +167,7 @@ const EditorWrapper = styled.div<{
position: absolute;
right: 0;
left: 0;
top: 0;
top: 0;
`
: `z-index: 0; position: relative;`}
background-color: ${props =>
@ -180,7 +181,7 @@ const EditorWrapper = styled.div<{
flex-direction: row;
text-transform: none;
min-height: 32px;
height: auto;
${props =>
props.setMaxHeight &&
@ -459,8 +460,8 @@ class DynamicAutocompleteInput extends Component<Props, State> {
// Update the dynamic bindings for autocomplete
if (prevProps.dynamicData !== this.props.dynamicData) {
if (this.ternServer) {
// const dataTreeDef = dataTreeTypeDefCreator(this.props.dynamicData);
// this.ternServer.updateDef("dataTree", dataTreeDef);
const dataTreeDef = dataTreeTypeDefCreator(this.props.dynamicData);
this.ternServer.updateDef("dataTree", dataTreeDef);
} else {
this.editor.setOption("hintOptions", {
completeSingle: false,

View File

@ -220,7 +220,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
SET_PROVIDERS_LENGTH: "SET_PROVIDERS_LENGTH",
SET_DEFAULT_REFINEMENT: "SET_DEFAULT_REFINEMENT",
SET_HELP_MODAL_OPEN: "SET_HELP_MODAL_OPEN",
EDIT_API_NAME: "EDIT_API_NAME",
SAVE_API_NAME: "SAVE_API_NAME",
SAVE_API_NAME_SUCCESS: "SAVE_API_NAME_SUCCESS",
UPDATE_API_NAME_DRAFT: "UPDATE_API_NAME_DRAFT",

View File

@ -28,7 +28,6 @@ import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleH
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
import PostBodyData from "./PostBodyData";
import ApiResponseView from "components/editorComponents/ApiResponseView";
import { ApiNameValidation } from "reducers/uiReducers/apiPaneReducer";
import { AppState } from "reducers";
import { getApiName } from "selectors/formSelectors";
import ActionNameEditor from "components/editorComponents/ActionNameEditor";
@ -132,7 +131,6 @@ interface APIFormProps {
dispatch: any;
datasourceFieldText: string;
apiName: string;
apiNameValidation: ApiNameValidation;
}
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;

View File

@ -22,7 +22,6 @@ import { FormIcons } from "icons/FormIcons";
import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView";
import Pagination from "./Pagination";
import { PaginationType, RestAction } from "entities/Action";
import { ApiNameValidation } from "reducers/uiReducers/apiPaneReducer";
import ActionNameEditor from "components/editorComponents/ActionNameEditor";
import { NameWrapper } from "./Form";
const Form = styled.form`
@ -118,7 +117,6 @@ interface APIFormProps {
};
apiName: string;
apiId: string;
apiNameValidation: ApiNameValidation;
dispatch: any;
}

View File

@ -33,10 +33,6 @@ interface ReduxStateProps {
isDeleting: Record<string, boolean>;
allowSave: boolean;
apiName: string;
apiNameValidation: {
isValid: boolean;
validationMessage: string;
};
currentApplication: UserApplication;
currentPageName: string | undefined;
pages: any;
@ -197,7 +193,6 @@ class ApiEditor extends React.Component<Props> {
: ""
}
apiName={this.props.apiName}
apiNameValidation={this.props.apiNameValidation}
onChange={this.onChangeHandler}
location={this.props.location}
/>
@ -206,7 +201,6 @@ class ApiEditor extends React.Component<Props> {
{formUiComponent === "RapidApiEditorForm" && (
<RapidApiEditorForm
apiName={this.props.apiName}
apiNameValidation={this.props.apiNameValidation}
apiId={this.props.match.params.apiId}
paginationType={paginationType}
isRunning={isRunning[apiId]}
@ -236,27 +230,10 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
const formData = getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction;
const apiAction = getActionById(state, props);
const apiName = getApiName(state, props.match.params.apiId);
const apiNameDraft =
state.ui.apiPane.apiName.drafts[props.match.params.apiId];
let apiNameValidation = {
isValid: true,
validationMessage: "",
};
if (apiNameDraft && apiNameDraft.validation) {
apiNameValidation = apiNameDraft.validation;
}
const { drafts, isDeleting, isRunning } = state.ui.apiPane;
let data: RestAction | ActionData | RapidApiAction | undefined;
let allowSave;
if (apiAction && apiAction.id in drafts) {
data = drafts[apiAction.id];
allowSave = true;
} else {
data = apiAction;
allowSave = false;
}
const { isDeleting, isRunning } = state.ui.apiPane;
const actionDrafts = state.entities.actionDrafts;
const allowSave = !!(apiAction && apiAction.id in actionDrafts);
const datasourceFieldText =
state.ui.apiPane.datasourceFieldText[formData?.id ?? ""] || "";
@ -267,10 +244,9 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
currentPageName: getCurrentPageName(state),
pages: state.entities.pageList.pages,
apiName: apiName || "",
apiNameValidation: apiNameValidation,
plugins: state.entities.plugins.list,
pluginId: _.get(data, "pluginId"),
paginationType: _.get(data, "actionConfiguration.paginationType"),
pluginId: _.get(apiAction, "pluginId"),
paginationType: _.get(apiAction, "actionConfiguration.paginationType"),
apiAction,
isRunning,
isDeleting,

View File

@ -24,6 +24,7 @@ import { getNextEntityName } from "utils/AppsmithUtils";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { Page } from "constants/ReduxActionConstants";
import { RestAction } from "entities/Action";
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
const HTTPMethod = styled.span<{ method?: string }>`
flex: 1;
@ -63,6 +64,7 @@ const ActionName = styled.span`
interface ReduxStateProps {
actions: ActionDataState;
actionDrafts: ActionDraftsState;
apiPane: ApiPaneReduxState;
pages: Page[];
}
@ -92,8 +94,8 @@ class ApiSidebar extends React.Component<Props> {
shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
if (
Object.keys(nextProps.apiPane.drafts) !==
Object.keys(this.props.apiPane.drafts)
Object.keys(nextProps.actionDrafts) !==
Object.keys(this.props.actionDrafts)
) {
return true;
}
@ -195,7 +197,8 @@ class ApiSidebar extends React.Component<Props> {
render() {
const {
apiPane: { isFetching, drafts },
actionDrafts,
apiPane: { isFetching },
match: {
params: { apiId },
},
@ -207,7 +210,7 @@ class ApiSidebar extends React.Component<Props> {
isLoading={isFetching}
list={data}
selectedItemId={apiId}
draftIds={Object.keys(drafts)}
draftIds={Object.keys(actionDrafts)}
itemRender={this.renderItem}
onItemCreateClick={this.handleCreateNewApiClick}
onItemSelected={this.handleApiChange}
@ -222,6 +225,7 @@ class ApiSidebar extends React.Component<Props> {
const mapStateToProps = (state: AppState): ReduxStateProps => ({
actions: state.entities.actions,
actionDrafts: state.entities.actionDrafts,
apiPane: state.ui.apiPane,
pages: state.entities.pageList.pages,
});

View File

@ -33,6 +33,7 @@ import { getCurrentApplication } from "selectors/applicationSelectors";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import { QueryAction, RestAction } from "entities/Action";
import { getPluginImage } from "pages/Editor/QueryEditor/helpers";
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
const EmptyStateContainer = styled.div`
display: flex;
@ -46,7 +47,7 @@ type QueryPageProps = {
queryPane: QueryPaneReduxState;
formData: RestAction;
isCreating: boolean;
apiPane: ApiPaneReduxState;
actionDrafts: ActionDraftsState;
initialValues: RestAction;
pluginIds: Array<string> | undefined;
submitForm: (name: string) => void;
@ -97,7 +98,7 @@ class QueryEditor extends React.Component<Props> {
pluginIds,
executedQueryData,
selectedPluginPackage,
apiPane,
actionDrafts,
isCreating,
runErrorMessage,
} = this.props;
@ -108,7 +109,6 @@ class QueryEditor extends React.Component<Props> {
<EmptyStateContainer>{"Plugin is not installed"}</EmptyStateContainer>
);
}
const { drafts } = apiPane;
const { isSaving, isRunning, isDeleting } = queryPane;
const validDataSources: Array<Datasource> = [];
@ -132,7 +132,7 @@ class QueryEditor extends React.Component<Props> {
location={this.props.location}
applicationId={applicationId}
pageId={pageId}
allowSave={queryId in drafts}
allowSave={queryId in actionDrafts}
isSaving={isSaving[queryId]}
isRunning={isRunning[queryId]}
isDeleting={isDeleting[queryId]}
@ -177,7 +177,7 @@ const mapStateToProps = (state: AppState): any => {
return {
plugins: getPlugins(state),
runErrorMessage,
apiPane: state.ui.apiPane,
actionDrafts: state.entities.actionDrafts,
pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS),
dataSources: getDataSources(state),
executedQueryData: state.ui.queryPane.runQuerySuccessData,

View File

@ -27,6 +27,7 @@ import { getDataSources } from "selectors/editorSelectors";
import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes";
import { RestAction } from "entities/Action";
import { Colors } from "constants/Colors";
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
const ActionItem = styled.div`
flex: 1;
@ -59,6 +60,7 @@ interface ReduxStateProps {
plugins: Plugin[];
queries: ActionDataState;
apiPane: ApiPaneReduxState;
actionDrafts: ActionDraftsState;
actions: ActionDataState;
dataSources: Datasource[];
}
@ -88,8 +90,8 @@ class QuerySidebar extends React.Component<Props> {
shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
if (
Object.keys(nextProps.apiPane.drafts) !==
Object.keys(this.props.apiPane.drafts)
Object.keys(nextProps.actionDrafts) !==
Object.keys(this.props.actionDrafts)
) {
return true;
}
@ -180,29 +182,21 @@ class QuerySidebar extends React.Component<Props> {
render() {
const {
apiPane: { drafts },
actionDrafts,
apiPane: { isFetching },
match: {
params: { queryId },
},
queries,
dataSources,
} = this.props;
const data = queries.map(a => a.config);
const validDataSources: Array<Datasource> = [];
dataSources.forEach(dataSource => {
if (dataSource.isValid) {
validDataSources.push(dataSource);
}
});
return (
<EditorSidebar
isLoading={isFetching}
list={data}
selectedItemId={queryId}
draftIds={Object.keys(drafts)}
draftIds={Object.keys(actionDrafts)}
itemRender={this.renderItem}
onItemCreateClick={this.handleCreateNewQueryClick}
onItemSelected={this.handleQueryChange}
@ -218,6 +212,7 @@ class QuerySidebar extends React.Component<Props> {
const mapStateToProps = (state: AppState): ReduxStateProps => ({
plugins: getPlugins(state),
queries: getQueryActions(state),
actionDrafts: state.entities.actionDrafts,
apiPane: state.ui.apiPane,
actions: state.entities.actions,
dataSources: getDataSources(state),

View File

@ -4,13 +4,11 @@ import {
ReduxActionErrorTypes,
ReduxAction,
} from "constants/ReduxActionConstants";
import _ from "lodash";
import { RestAction } from "entities/Action";
const initialState: ApiPaneReduxState = {
lastUsed: "",
isFetching: false,
drafts: {},
isRunning: {},
isSaving: {},
isDeleting: {},
@ -19,19 +17,11 @@ const initialState: ApiPaneReduxState = {
lastSelectedPage: "",
extraformData: {},
datasourceFieldText: {},
apiName: {
drafts: {},
isSaving: {},
},
};
export interface ApiNameValidation {
isValid: boolean;
validationMessage: string;
}
export interface ApiPaneReduxState {
lastUsed: string;
isFetching: boolean;
drafts: Record<string, RestAction>;
isRunning: Record<string, boolean>;
isSaving: Record<string, boolean>;
isDeleting: Record<string, boolean>;
@ -40,16 +30,6 @@ export interface ApiPaneReduxState {
datasourceFieldText: Record<string, string>;
lastSelectedPage: string;
extraformData: Record<string, any>;
apiName: {
drafts: Record<
string,
{
value: string;
validation: ApiNameValidation;
}
>;
isSaving: Record<string, boolean>;
};
}
const apiPaneReducer = createReducer(initialState, {
@ -158,23 +138,6 @@ const apiPaneReducer = createReducer(initialState, {
[action.payload.id]: false,
},
}),
[ReduxActionTypes.UPDATE_API_DRAFT]: (
state: ApiPaneReduxState,
action: ReduxAction<{ id: string; draft: Partial<RestAction> }>,
) => ({
...state,
drafts: {
...state.drafts,
[action.payload.id]: action.payload.draft,
},
}),
[ReduxActionTypes.DELETE_API_DRAFT]: (
state: ApiPaneReduxState,
action: ReduxAction<{ id: string }>,
) => ({
...state,
drafts: _.omit(state.drafts, action.payload.id),
}),
[ReduxActionTypes.API_PANE_CHANGE_API]: (
state: ApiPaneReduxState,
action: ReduxAction<{ id: string }>,
@ -233,35 +196,6 @@ const apiPaneReducer = createReducer(initialState, {
},
};
},
[ReduxActionTypes.UPDATE_API_NAME_DRAFT]: (
state: ApiPaneReduxState,
action: ReduxAction<{
id: string;
draft?: {
value: string;
validation: {
isValid: boolean;
validationMessage: string;
};
};
}>,
) => {
const { id, draft } = action.payload;
let nameDrafts = {
...state.apiName.drafts,
[id]: draft,
};
if (!draft) {
nameDrafts = _.omit(nameDrafts, id);
}
return {
...state,
apiName: {
drafts: nameDrafts,
},
};
},
});
export default apiPaneReducer;

View File

@ -754,30 +754,6 @@ function* copyActionSaga(
}
}
function* editApiNameSaga(action: ReduxAction<{ id: string; value: string }>) {
const actionNames = yield select(state =>
state.entities.actions.map((action: ActionData) => action.config.name),
);
const draftActionNames = yield select(state =>
Object.values(state.ui.apiPane.apiName.drafts),
);
//TODO: If an api is in saving state, then it should not use that name as well.
const validation = validateEntityName(action.payload.value, [
...actionNames,
...draftActionNames,
]);
yield put(
updateApiNameDraft({
id: action.payload.id,
draft: {
value: action.payload.value,
validation: validation,
},
}),
);
}
export function* refactorActionName(
id: string,
pageId: string,
@ -822,14 +798,13 @@ export function* refactorActionName(
function* saveApiNameSaga(action: ReduxAction<{ id: string; name: string }>) {
// Takes from drafts, checks if the name isValid, saves
const apiId = action.payload.id;
const api = yield select(state =>
state.entities.actions.find(
(action: ActionData) => action.config.id === apiId,
),
);
try {
const apiId = action.payload.id;
const api = yield select(state =>
state.entities.actions.find(
(action: ActionData) => action.config.id === apiId,
),
);
yield refactorActionName(
api.config.id,
api.config.pageId,
@ -841,6 +816,7 @@ function* saveApiNameSaga(action: ReduxAction<{ id: string; name: string }>) {
type: ReduxActionErrorTypes.SAVE_API_NAME_ERROR,
payload: {
actionId: action.payload.id,
oldName: api.config.name,
},
});
AppToaster.show({
@ -859,7 +835,6 @@ export function* watchActionSagas() {
takeEvery(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
takeLatest(ReduxActionTypes.EDIT_API_NAME, editApiNameSaga),
takeLatest(ReduxActionTypes.SAVE_API_NAME, saveApiNameSaga),
takeLatest(
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,

View File

@ -15,6 +15,7 @@ import {
import { getFormSyncErrors } from "redux-form";
import {
ReduxAction,
ReduxActionErrorTypes,
ReduxActionTypes,
ReduxActionWithMeta,
ReduxFormActionTypes,
@ -65,7 +66,7 @@ import { RestAction } from "entities/Action";
import { isDynamicValue } from "utils/DynamicBindingUtils";
const getApiDraft = (state: AppState, id: string) => {
const drafts = state.ui.apiPane.drafts;
const drafts = state.entities.actionDrafts;
if (id in drafts) return drafts[id];
return {};
};
@ -230,7 +231,7 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
data = draft;
}
yield put(initialize(API_EDITOR_FORM_NAME, _.omit(data, "name")));
yield put(initialize(API_EDITOR_FORM_NAME, data));
history.push(API_EDITOR_ID_URL(applicationId, pageId, id));
yield call(initializeExtraFormDataSaga);
@ -248,6 +249,8 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
}
function* updateDraftsSaga() {
// debounce
// TODO check for save
const result = yield race({
change: take(ReduxFormActionTypes.VALUE_CHANGE),
timeout: delay(300),
@ -540,6 +543,16 @@ function* handleCreateNewQueryActionSaga(
}
}
function* handleApiNameChangeSaga(action: ReduxAction<{ name: string }>) {
yield put(change(API_EDITOR_FORM_NAME, "name", action.payload.name));
}
function* handleApiNameChangeFailureSaga(
action: ReduxAction<{ oldName: string }>,
) {
yield put(change(API_EDITOR_FORM_NAME, "name", action.payload.oldName));
}
export default function* root() {
yield all([
takeEvery(ReduxActionTypes.INIT_API_PANE, initApiPaneSaga),
@ -549,6 +562,11 @@ export default function* root() {
takeEvery(ReduxActionTypes.DELETE_ACTION_SUCCESS, handleActionDeletedSaga),
takeEvery(ReduxActionTypes.MOVE_ACTION_SUCCESS, handleMoveOrCopySaga),
takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga),
takeEvery(ReduxActionTypes.SAVE_API_NAME, handleApiNameChangeSaga),
takeEvery(
ReduxActionErrorTypes.SAVE_API_NAME_ERROR,
handleApiNameChangeFailureSaga,
),
takeEvery(
ReduxActionTypes.CREATE_NEW_API_ACTION,
handleCreateNewApiActionSaga,

View File

@ -48,7 +48,7 @@ import { getQueryName } from "selectors/entitiesSelector";
import { RestAction } from "entities/Action";
const getQueryDraft = (state: AppState, id: string) => {
const drafts = state.ui.apiPane.drafts;
const drafts = state.entities.actionDrafts;
if (id in drafts) return drafts[id];
return {};
};

View File

@ -11,21 +11,14 @@ type GetFormData = (
export const getFormData: GetFormData = (state, formName) => {
const initialValues = getFormInitialValues(formName)(state) as RestAction;
const values = getFormValues(formName)(state) as RestAction;
const drafts = state.ui.apiPane.drafts;
const drafts = state.entities.actionDrafts;
const dirty = values.id in drafts;
const valid = isValid(formName)(state);
return { initialValues, values, dirty, valid };
};
export const getApiName = (state: AppState, id: string) => {
const apiNameDraft = state.ui.apiPane.apiName.drafts[id]?.value;
if (apiNameDraft === undefined) {
return state.entities.actions.find(
(action: ActionData) => action.config.id === id,
)?.config.name;
} else {
// If there is something in drafts, return draft value.
return apiNameDraft;
}
return state.entities.actions.find(
(action: ActionData) => action.config.id === id,
)?.config.name;
};

View File

@ -18,6 +18,8 @@ import {
import * as log from "loglevel";
import equal from "fast-deep-equal/es6";
import WidgetFactory from "utils/WidgetFactory";
import { AppToaster } from "components/editorComponents/ToastComponent";
import { ToastType } from "react-toastify";
export const removeBindingsFromObject = (obj: object) => {
const string = JSON.stringify(obj);
@ -382,6 +384,10 @@ export const createDependencyTree = (
return { sortedDependencies, dependencyMap, dependencyTree };
} catch (e) {
console.error(e);
AppToaster.show({
message: e.message,
type: ToastType.ERROR,
});
return { sortedDependencies: [], dependencyMap: {}, dependencyTree: [] };
}
};

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-ignore */
// Heavily inspired from https://github.com/codemirror/CodeMirror/blob/master/addon/tern/tern.js
import { DataTree } from "entities/DataTree/dataTreeFactory";
import tern, { Server } from "tern";
import tern, { Server, Def } from "tern";
import ecma from "tern/defs/ecmascript.json";
import lodash from "constants/defs/lodash.json";
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
@ -64,6 +64,12 @@ class TernServer {
});
}
updateDef(name: string, def: Def) {
this.server.deleteDefs(name);
// @ts-ignore
this.server.addDefs(def, true);
}
getHint(cm: CodeMirror.Editor) {
return new Promise(resolve => {
this.request(