Skip to content

Commit

Permalink
lock operation execution to prevent accidental concurrent executions
Browse files Browse the repository at this point in the history
  • Loading branch information
mapacheL committed Nov 11, 2021
1 parent 61f1652 commit 27cc951
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 162 deletions.
11 changes: 10 additions & 1 deletion Signum.React.Extensions/Alerts/AlertsClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,16 @@ export function start(options: { routes: JSX.Element[], showAlerts?: (typeName:
}));

Operations.addSettings(new EntityOperationSettings(AlertOperation.Delay, {
onClick: (eoc) => chooseDate().then(d => d && eoc.defaultClick(d.toISO())).done(),
onClick: (eoc) => {
return chooseDate().then(d => {
if (d) {
return eoc.defaultClick(d.toISO());
}
else {
return;
}
})
},
hideOnCanExecute: true,
contextual: { onClick: (coc) => chooseDate().then(d => d && coc.defaultContextualClick(d.toISO())).done() },
contextualFromMany: { onClick: (coc) => chooseDate().then(d => d && coc.defaultContextualClick(d.toISO())).done() },
Expand Down
12 changes: 6 additions & 6 deletions Signum.React.Extensions/Dynamic/DynamicTypeClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,24 @@ export function start(options: { routes: JSX.Element[] }) {
onClick: eoc => {
(eoc.frame.entityComponent as DynamicTypeComponent).beforeSave();

Operations.API.executeEntity(eoc.entity, eoc.operationInfo.key)
return Operations.API.executeEntity(eoc.entity, eoc.operationInfo.key)
.then(pack => { eoc.frame.onReload(pack); Operations.notifySuccess(); })
.then(() => {
if (AuthClient.isPermissionAuthorized(DynamicPanelPermission.ViewDynamicPanel)) {
MessageModal.show({
return MessageModal.show({
title: DynamicTypeMessage.TypeSaved.niceToString(),
message: DynamicTypeMessage.DynamicType0SucessfullySavedGoToDynamicPanelNow.niceToString(eoc.entity.typeName),
buttons: "yes_no",
style: "success",
icon: "success"
}).then(result => {
if (result == "yes")
if (result == "yes")
window.open(AppContext.toAbsoluteUrl("~/dynamic/panel"));
}).done();
return;
});
}
})
.catch(ifError(ValidationError, e => eoc.frame.setError(e.modelState, "entity")))
.done();
.catch(ifError(ValidationError, e => eoc.frame.setError(e.modelState, "entity")));
},
alternatives: eoc => [],
}));
Expand Down
8 changes: 4 additions & 4 deletions Signum.React.Extensions/Dynamic/DynamicViewClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ export function start(options: { routes: JSX.Element[] }) {
onClick: ctx => {
(ctx.frame.entityComponent as DynamicViewEntityComponent).beforeSave();
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
}
}));

Operations.addSettings(new EntityOperationSettings(DynamicViewOperation.Delete, {
onClick: ctx => {
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
},
contextual: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
contextualFromMany: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
Expand All @@ -80,14 +80,14 @@ export function start(options: { routes: JSX.Element[] }) {
Operations.addSettings(new EntityOperationSettings(DynamicViewSelectorOperation.Save, {
onClick: ctx => {
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
}
}));

Operations.addSettings(new EntityOperationSettings(DynamicViewSelectorOperation.Delete, {
onClick: ctx => {
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
},
contextual: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
contextualFromMany: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
Expand Down
5 changes: 2 additions & 3 deletions Signum.React.Extensions/MachineLearning/PredictorClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@ export function start(options: { routes: JSX.Element[] }) {
Operations.addSettings(new EntityOperationSettings(PredictorOperation.Publish, {
hideOnCanExecute: true,
onClick: eoc => {
API.publications(eoc.entity.mainQuery.query!.key)
return API.publications(eoc.entity.mainQuery.query!.key)
.then(pubs => SelectorModal.chooseElement(pubs, { buttonDisplay: a => symbolNiceName(a), buttonName: a => a.key }))
.then(pps => pps && eoc.defaultClick(pps))
.done();
.then(pps => { if (pps) { return eoc.defaultClick(pps); } else { return; } });
},
contextual: {
onClick: coc => {
Expand Down
8 changes: 3 additions & 5 deletions Signum.React.Extensions/Mailing/MailingClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,11 @@ export function start(options: {
var promise: Promise<string | undefined> = ctx.entity.model ? API.getConstructorType(ctx.entity.model) : Promise.resolve(undefined);
promise

Finder.find({ queryName: ctx.entity.query!.key }).then(lite => {
return Finder.find({ queryName: ctx.entity.query!.key }).then(lite => {
if (!lite)
return;
Navigator.API.fetchAndForget(lite).then(entity =>
ctx.defaultClick(entity))
.done();
}).done();
return Navigator.API.fetchAndForget(lite).then(entity => { return ctx.defaultClick(entity); });
});
}
}));

Expand Down
2 changes: 1 addition & 1 deletion Signum.React.Extensions/Tree/TreeClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function start(options: { routes: JSX.Element[] }) {
new EntityOperationSettings(TreeOperation.Copy, {
onClick: ctx => copyModal(toLite(ctx.entity)).then(m => {
if (m) {
ctx.onConstructFromSuccess = pack => Operations.notifySuccess();
ctx.onConstructFromSuccess = pack => { Operations.notifySuccess(); return Promise.resolve(); };
ctx.defaultClick(m);
}
}),
Expand Down
6 changes: 3 additions & 3 deletions Signum.React.Extensions/Word/WordClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function start(options: { routes: JSX.Element[], contextual: boolean, que
onClick: ctx => {

var promise: Promise<string | undefined> = ctx.entity.model ? API.getConstructorType(ctx.entity.model) : Promise.resolve(undefined);
promise
return promise
.then<Response | undefined>(ct => {
var template = toLite(ctx.entity);

Expand All @@ -77,8 +77,8 @@ export function start(options: { routes: JSX.Element[], contextual: boolean, que
if (!response)
return;

saveFile(response);
}).done();
return saveFile(response);
});
}
}));

Expand Down
32 changes: 28 additions & 4 deletions Signum.React.Extensions/Workflow/Case/CaseFrameModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface CaseFrameModalState {
show: boolean;
prefix?: string;
refreshCount: number;
executing?: boolean;
}

var modalCount = 0;
Expand Down Expand Up @@ -65,7 +66,7 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
.done();
}

handleKeyDown(e: KeyboardEvent) {
handleKeyDown(e: KeyboardEvent) {
this.buttonBar && this.buttonBar.handleKeyDown(e);
}

Expand Down Expand Up @@ -176,7 +177,19 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: this.prefix
prefix: this.prefix,
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });
action().then(
() => { this.setState({ executing: undefined }) },
() => { this.setState({ executing: undefined }) }
).done();
}

};

var activityPack = { entity: pack.activity, canExecute: pack.canExecuteActivity };
Expand Down Expand Up @@ -229,7 +242,18 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: this.prefix
prefix: this.prefix,
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });
action().then(
() => { this.setState({ executing: undefined }) },
() => { this.setState({ executing: undefined }) }
).done();
}
};

var ti = this.getMainTypeInfo();
Expand All @@ -247,7 +271,7 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
};

return (
<div className="sf-main-entity case-main-entity" data-main-entity={entityInfo(mainEntity)}>
<div className="sf-main-entity case-main-entity" style={this.state.executing == true ? { opacity: ".7" } : undefined} data-main-entity={entityInfo(mainEntity)}>
<div className="sf-button-widget-container">
{renderWidgets(wc)}
{this.entityComponent && !mainEntity.isNew && !pack.activity.doneBy ? <ButtonBar ref={bb => this.buttonBar = bb} frame={mainFrame} pack={mainFrame.pack} /> : <br />}
Expand Down
29 changes: 26 additions & 3 deletions Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface CaseFramePageState {
pack?: WorkflowClient.CaseEntityPack;
getComponent?: (ctx: TypeContext<Entity>) => React.ReactElement<any>;
refreshCount: number;
executing?: boolean;
}

export default class CaseFramePage extends React.Component<CaseFramePageProps, CaseFramePageState> implements IHasCaseActivity {
Expand Down Expand Up @@ -161,7 +162,18 @@ export default class CaseFramePage extends React.Component<CaseFramePageProps, C
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: "caseFrame"
prefix: "caseFrame",
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });
action().then(
() => { this.setState({ executing: undefined }) },
() => { this.setState({ executing: undefined }) }
).done();
}
};


Expand Down Expand Up @@ -232,7 +244,18 @@ export default class CaseFramePage extends React.Component<CaseFramePageProps, C
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: "caseFrame"
prefix: "caseFrame",
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });
action().then(
() => { this.setState({ executing: undefined }) },
() => { this.setState({ executing: undefined }) }
).done();
}
};

var ti = this.getMainTypeInfo();
Expand All @@ -252,7 +275,7 @@ export default class CaseFramePage extends React.Component<CaseFramePageProps, C
};

return (
<div className="sf-main-entity case-main-entity" data-main-entity={entityInfo(mainEntity)}>
<div className="sf-main-entity case-main-entity" style={this.state.executing == true ? { opacity: ".7" } : undefined} data-main-entity={entityInfo(mainEntity)}>
<div className="sf-button-widget-container">
{renderWidgets(wc)}
{this.entityComponent && !mainEntity.isNew && !pack.activity.doneBy ? <ButtonBar ref={a => this.buttonBar = a} frame={mainFrame} pack={mainFrame.pack} /> : <br />}
Expand Down
35 changes: 28 additions & 7 deletions Signum.React.Extensions/Workflow/WorkflowClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,13 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi
}));

Operations.addSettings(new EntityOperationSettings(CaseOperation.SetTags, { isVisible: ctx => false }));
Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Register, { hideOnCanExecute: true, color: "primary", onClick: eoc => executeCaseActivity(eoc, e => e.defaultClick()), }));
Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Register, { hideOnCanExecute: true, color: "primary", onClick: eoc => { return Promise.resolve(executeCaseActivity(eoc, e => e.defaultClick())); }, }));
Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Delete, { hideOnCanExecute: true, isVisible: ctx => false, contextual: { isVisible: ctx => true } }));
Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Jump, {
icon: "share",
iconColor: "blue",
hideOnCanExecute: true,
onClick: eoc => executeCaseActivity(eoc, executeWorkflowJump),
onClick: eoc => { return Promise.resolve(executeCaseActivity(eoc, executeWorkflowJump)); },
contextual: { isVisible: ctx => true, onClick: executeWorkflowJumpContextual }
}));
Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Timer, { isVisible: ctx => false }));
Expand All @@ -216,7 +216,7 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi
Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Next, {
hideOnCanExecute: true,
color: "primary",
onClick: eoc => executeCaseActivity(eoc, executeAndClose),
onClick: eoc => { return Promise.resolve(executeCaseActivity(eoc, executeAndClose)); },
createButton: (eoc, group) => {
const wa = eoc.entity.workflowActivity as WorkflowActivityEntity;
const s = eoc.settings;
Expand Down Expand Up @@ -265,7 +265,9 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi
Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Undo, {
hideOnCanExecute: true,
color: "danger",
onClick: eoc => executeCaseActivity(eoc, executeAndClose),
onClick: eoc => {
return Promise.resolve(executeCaseActivity(eoc, executeAndClose));
},
contextual: { isVisible: ctx => true },
contextualFromMany: {
isVisible: ctx => true,
Expand All @@ -278,16 +280,34 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi
workflowActivityMonitorUrl(ctx.lite),
{ icon: "tachometer-alt", iconColor: "green" }));

Operations.addSettings(new EntityOperationSettings(WorkflowOperation.Save, { color: "primary", onClick: executeWorkflowSave, alternatives: eoc => [] }));
Operations.addSettings(new EntityOperationSettings(WorkflowOperation.Save, { color: "primary", onClick: eoc => { executeWorkflowSave; return Promise.resolve(); }, alternatives: eoc => [] }));
Operations.addSettings(new EntityOperationSettings(WorkflowOperation.Delete, { contextualFromMany: { isVisible: ctx => false } }));
Operations.addSettings(new EntityOperationSettings(WorkflowOperation.Activate, {
contextual: { icon: "heartbeat", iconColor: "red" },
contextualFromMany: { icon: "heartbeat", iconColor: "red" },
}));
Operations.addSettings(new EntityOperationSettings(WorkflowOperation.Deactivate, {
onClick: eoc => chooseWorkflowExpirationDate([toLite(eoc.entity)]).then(val => val && eoc.defaultClick(val)).done(),
onClick: eoc => {
return chooseWorkflowExpirationDate([toLite(eoc.entity)]).then(val => {
if (val) {
return eoc.defaultClick(val);
}
else {
return;
}
})
},
contextual: {
onClick: coc => chooseWorkflowExpirationDate(coc.context.lites).then(val => val && coc.defaultContextualClick(val)).done(),
onClick: coc => {
return chooseWorkflowExpirationDate(coc.context.lites).then(val => {
if (val) {
return coc.defaultContextualClick(val);
}
else {
return;
}
})
},
icon: ["far", "heart"],
iconColor: "gray"
},
Expand Down Expand Up @@ -548,6 +568,7 @@ export function executeWorkflowJump(eoc: Operations.EntityOperationContext<CaseA
eoc.onExecuteSuccess = pack => {
Operations.notifySuccess();
eoc.frame.onClose(pack);
return Promise.resolve();
}

getWorkflowJumpSelector(toLite(eoc.entity.workflowActivity as WorkflowActivityEntity))
Expand Down
2 changes: 1 addition & 1 deletion Signum.React/Scripts/Frames/ButtonBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const ButtonBar = React.forwardRef(function ButtonBar(p: ButtonBarProps,

var shortcuts = buttons.filter(a => a!.shortcut != null).map(a => a!.shortcut!);

function handleKeyDown(e: KeyboardEvent) {
function handleKeyDown(e: KeyboardEvent) {
var s = shortcuts;
if (s != null) {
for (var i = 0; i < s.length; i++) {
Expand Down
Loading

0 comments on commit 27cc951

Please sign in to comment.