Skip to content

Commit

Permalink
[FIX] web: display "Oh snap" dialog when needed
Browse files Browse the repository at this point in the history
Before this commit, the "Oh snap!" dialog could be displayed anytime
a server validation error was returned by an RPC. Also, clicking
on the "discard" button in this dialog always triggered an `historyBack`
even when we open a new action.

Now, this dialog is only opened on error while saving and does not
`historyBack` anymore.

closes odoo#101802

X-original-commit: 31c6ecb
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
  • Loading branch information
mcm-odoo committed Oct 1, 2022
1 parent 1b1fa9e commit 9087b3c
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 71 deletions.
8 changes: 5 additions & 3 deletions addons/web/static/src/search/action_menus/action_menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ export class ActionMenus extends Component {
* @param {Object} item
*/
async onItemSelected(item) {
await this.props.onBeforeAction(item);
if (!(await this.props.shouldExecuteAction(item))) {
return;
}
if (item.callback) {
item.callback([item]);
} else if (item.action) {
Expand Down Expand Up @@ -143,10 +145,10 @@ ActionMenus.props = {
},
},
onActionExecuted: { type: Function, optional: true },
onBeforeAction: { type: Function, optional: true },
shouldExecuteAction: { type: Function, optional: true },
};
ActionMenus.defaultProps = {
onActionExecuted: () => {},
onBeforeAction: () => {},
shouldExecuteAction: () => true,
};
ActionMenus.template = "web.ActionMenus";
26 changes: 24 additions & 2 deletions addons/web/static/src/views/basic_relational_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -701,9 +701,19 @@ export class Record extends DataPoint {
* @param {boolean} [options.stayInEdition=false]
* @param {boolean} [options.noReload=false] prevents the record from
* reloading after changes are applied, typically used to defer the load.
* @param {boolean} [options.useSaveErrorDialog=false] displays a custom
* dialog and await the response from this dialog when an error is
* returned by the server.
* @returns {Promise<boolean>}
*/
async save(options = { stayInEdition: true, noReload: false, savePoint: false }) {
async save(
options = {
stayInEdition: true,
noReload: false,
savePoint: false,
useSaveErrorDialog: false,
}
) {
const shouldSwitchToReadonly = !options.stayInEdition && this.isInEdition;
let resolveSavePromise;
this._savePromise = new Promise((r) => {
Expand Down Expand Up @@ -732,11 +742,23 @@ export class Record extends DataPoint {
await this.model.__bm__.save(this.__bm_handle__, saveOptions);
} catch (_e) {
resolveSavePromise();
let canProceed = false;
if (options.useSaveErrorDialog) {
_e.__raisedOnFormSave = true;
canProceed = await new Promise((resolve) => {
_e.onDiscard = async () => {
await this.discard();
resolve(true);
};
_e.onStayHere = () => resolve(false);
});
}

if (!this.isInEdition) {
await this.load();
this.model.notify();
}
return false;
return canProceed;
}
this.__syncData(true);
if (shouldSwitchToReadonly) {
Expand Down
55 changes: 29 additions & 26 deletions addons/web/static/src/views/form/form_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { isX2Many } from "@web/views/utils";
import { useViewButtons } from "@web/views/view_button/view_button_hook";
import { useSetupView } from "@web/views/view_hook";
import { FormStatusIndicator } from "./form_status_indicator/form_status_indicator";
import { useFormErrorDialog } from "./form_error_dialog/form_error_dialog";

const { Component, onWillStart, useEffect, useRef, onRendered, useState, toRaw } = owl;

Expand Down Expand Up @@ -100,10 +99,6 @@ export class FormController extends Component {
isDisabled: false,
});
useBus(this.ui.bus, "resize", this.render);
useFormErrorDialog(async () => {
await this.discard();
this.env.config.historyBack();
});

this.archInfo = this.props.archInfo;
const activeFields = this.archInfo.activeFields;
Expand Down Expand Up @@ -181,7 +176,11 @@ export class FormController extends Component {
rootRef,
beforeLeave: () => {
if (this.model.root.isDirty) {
return this.model.root.save({ noReload: true, stayInEdition: true });
return this.model.root.save({
noReload: true,
stayInEdition: true,
useSaveErrorDialog: true,
});
}
},
beforeUnload: () => this.beforeUnload(),
Expand All @@ -206,7 +205,10 @@ export class FormController extends Component {
await this.model.root.askChanges(); // ensures that isDirty is correct
let canProceed = true;
if (this.model.root.isDirty) {
canProceed = await this.model.root.save({ stayInEdition: true });
canProceed = await this.model.root.save({
stayInEdition: true,
useSaveErrorDialog: true,
});
}
if (canProceed) {
return this.model.load({ resId: resIds[offset] });
Expand Down Expand Up @@ -302,7 +304,7 @@ export class FormController extends Component {
callback: () => this.duplicateRecord(),
});
}
if (this.archInfo.activeActions.delete) {
if (this.archInfo.activeActions.delete && !this.model.root.isVirtual) {
otherActionItems.push({
key: "delete",
description: this.env._t("Delete"),
Expand All @@ -313,10 +315,11 @@ export class FormController extends Component {
return Object.assign({}, this.props.info.actionMenus, { other: otherActionItems });
}

async beforeAction(item) {
async shouldExecuteAction(item) {
if ((this.model.root.isDirty || this.model.root.isVirtual) && !item.skipSave) {
await this.model.root.save({ stayInEdition: true });
return this.model.root.save({ stayInEdition: true, useSaveErrorDialog: true });
}
return true;
}

async duplicateRecord() {
Expand Down Expand Up @@ -347,12 +350,14 @@ export class FormController extends Component {

async beforeExecuteActionButton(clickParams) {
if (clickParams.special !== "cancel") {
return this.model.root.save({ stayInEdition: true }).then((saved) => {
if (saved && this.props.onSave) {
this.props.onSave(this.model.root);
}
return saved;
});
return this.model.root
.save({ stayInEdition: true, useSaveErrorDialog: true })
.then((saved) => {
if (saved && this.props.onSave) {
this.props.onSave(this.model.root);
}
return saved;
});
} else if (this.props.onDiscard) {
this.props.onDiscard(this.model.root);
}
Expand All @@ -368,7 +373,10 @@ export class FormController extends Component {
await this.model.root.askChanges(); // ensures that isDirty is correct
let canProceed = true;
if (this.model.root.isDirty) {
canProceed = await this.model.root.save({ stayInEdition: true });
canProceed = await this.model.root.save({
stayInEdition: true,
useSaveErrorDialog: true,
});
}
if (canProceed) {
this.disableButtons();
Expand All @@ -391,15 +399,10 @@ export class FormController extends Component {
...record.dirtyTranslatableFields,
]);
}
try {
if (this.props.saveRecord) {
saved = await this.props.saveRecord(record, params);
} else {
saved = await record.save();
}
} catch {
// if the save failed, we want to re-enable buttons
this.enableButtons();
if (this.props.saveRecord) {
saved = await this.props.saveRecord(record, params);
} else {
saved = await record.save();
}
this.enableButtons();
if (saved && this.props.onSave) {
Expand Down
2 changes: 1 addition & 1 deletion addons/web/static/src/views/form/form_controller.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
resModel="model.root.resModel"
domain="props.domain"
onActionExecuted="() => model.load()"
onBeforeAction.bind="beforeAction"
shouldExecuteAction.bind="shouldExecuteAction"
/>
</t>
</t>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/** @odoo-module **/

import { Dialog } from "@web/core/dialog/dialog";
import { RPCError } from "@web/core/network/rpc_service";
import { registry } from "@web/core/registry";

const { Component, onWillDestroy } = owl;
const { Component } = owl;
const errorHandlerRegistry = registry.category("error_handlers");

export class FormErrorDialog extends Component {
setup() {
Expand All @@ -20,47 +20,39 @@ export class FormErrorDialog extends Component {
await this.props.onDiscard();
this.props.close();
}

async stay() {
await this.props.onStayHere();
this.props.close();
}
}
FormErrorDialog.template = "web.FormErrorDialog";
FormErrorDialog.components = { Dialog };

function makeFormErrorHandler(onDiscard) {
return (env, error, originalError) => {
if (
originalError &&
originalError.legacy &&
originalError.message &&
originalError.message instanceof RPCError
) {
const event = originalError.event;
originalError = originalError.message;
error.unhandledRejectionEvent.preventDefault();
if (event.isDefaultPrevented()) {
// in theory, here, event was already handled
return true;
}
event.preventDefault();

env.services.dialog.add(FormErrorDialog, {
message: originalError.message,
data: originalError.data,
onDiscard,
});

function formSaveErrorHandler(env, error, originalError) {
if (originalError.__raisedOnFormSave) {
const event = originalError.event;
error.unhandledRejectionEvent.preventDefault();
if (event.isDefaultPrevented()) {
// in theory, here, event was already handled
return true;
}
return false;
};
}

let formId = 0;
event.preventDefault();

env.services.dialog.add(
FormErrorDialog,
{
message: originalError.message.message,
data: originalError.message.data,
onDiscard: originalError.onDiscard,
onStayHere: originalError.onStayHere,
},
{
onClose: originalError.onStayHere,
}
);

export function useFormErrorDialog(onDiscard) {
const errorHandlerKey = `form_error_handler_${++formId}`;
registry
.category("error_handlers")
.add(errorHandlerKey, makeFormErrorHandler(onDiscard), { sequence: 0 });
onWillDestroy(() => {
registry.category("error_handlers").remove(errorHandlerKey);
});
return true;
}
}
errorHandlerRegistry.add("formSaveErrorHandler", formSaveErrorHandler, { sequence: 1 });
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<p t-esc="message" style="white-space: pre-wrap;"/>
</div>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="props.close">Stay here</button>
<button class="btn btn-primary" t-on-click="stay">Stay here</button>
<button class="btn btn-secondary" t-on-click="discard">Discard changes</button>
</t>
</Dialog>
Expand Down
Loading

0 comments on commit 9087b3c

Please sign in to comment.