Merge pull request #1285 from gabrielburnworth/staging

Prevent saving of empty Farm Events
pull/1286/head
Rick Carlino 2019-07-17 15:20:33 -05:00 committed by GitHub
commit 8bc3849e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 24 deletions

View File

@ -23,16 +23,15 @@ import { isString, isFunction } from "lodash";
import { repeatOptions } from "../map_state_to_props_add_edit";
import { SpecialStatus, ParameterApplication } from "farmbot";
import moment from "moment";
import { fakeState } from "../../../__test_support__/fake_state";
import { history } from "../../../history";
import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
import { fakeVariableNameSet } from "../../../__test_support__/fake_variables";
import { clickButton } from "../../../__test_support__/helpers";
import { destroy } from "../../../api/crud";
import { destroy, save } from "../../../api/crud";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
import { error, success, warning } from "../../../toast/toast";
import { error, success } from "../../../toast/toast";
const mockSequence = fakeSequence();
@ -275,10 +274,14 @@ describe("<FarmEventForm/>", () => {
it("warns about missed regimen items", async () => {
const p = props();
const state = fakeState();
state.resources.index.references = { [p.farmEvent.uuid]: p.farmEvent };
p.dispatch = jest.fn(x => { isFunction(x) && x(); return Promise.resolve(); });
p.farmEvent.body.executable_type = "Regimen";
const regimen = fakeRegimen();
regimen.body.regimen_items = [
{ sequence_id: -1, time_offset: 0 },
{ sequence_id: -1, time_offset: 1000000000 },
];
p.findExecutable = () => regimen;
p.dispatch = jest.fn(x => { isFunction(x) && x(); return Promise.resolve(); });
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
const i = instance(p);
@ -334,14 +337,15 @@ describe("<FarmEventForm/>", () => {
expectStartTimeToBeRejected();
});
it("doesn't display error message on edit: start time has passed", () => {
it("displays error message on edit: start time has passed", () => {
const p = props();
p.title = "edit";
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
const i = instance(p);
i.commitViewModel(moment("2017-06-22T05:00:00.000Z"));
expect(error).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(expect.stringContaining(
"Nothing to run."), "Unable to save event.");
});
it("displays error message on save: no items", async () => {
@ -351,8 +355,22 @@ describe("<FarmEventForm/>", () => {
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
const i = instance(p);
await i.commitViewModel(moment("2017-06-22T05:00:00.000Z"));
expect(warning).toHaveBeenCalledWith(expect.stringContaining(
"Nothing to run."));
expect(error).toHaveBeenCalledWith(expect.stringContaining(
"Nothing to run."), "Unable to save event.");
});
it("displays error message on save: save error", async () => {
const p = props();
p.dispatch = jest.fn()
.mockResolvedValueOnce("")
.mockRejectedValueOnce("error");
p.shouldDisplay = () => true;
p.farmEvent.body.start_time = "2017-07-22T05:00:00.000Z";
p.farmEvent.body.end_time = "2017-07-22T06:00:00.000Z";
const i = instance(p);
await i.commitViewModel(moment("2017-06-22T05:00:00.000Z"));
await expect(save).toHaveBeenCalled();
expect(error).toHaveBeenCalledWith("Unable to save event.");
});
it("allows start time: edit with unsupported OS", () => {

View File

@ -1,11 +1,14 @@
import { maybeWarnAboutMissedTasks } from "../util";
import { fakeFarmEvent } from "../../../__test_support__/fake_state/resources";
import moment from "moment";
import { ExecutableType } from "farmbot/dist/resources/api_resources";
describe("maybeWarnAboutMissedTasks()", () => {
function testWarn(time: string): () => void {
function testWarn(
time: string, executableType: ExecutableType = "Regimen"
): () => void {
const callback = jest.fn();
const fe = fakeFarmEvent("Regimen", 1);
const fe = fakeFarmEvent(executableType, 1);
fe.body.start_time = "2017-05-21T22:00:00.000";
maybeWarnAboutMissedTasks(fe,
() => callback("missed event warning"),
@ -21,4 +24,9 @@ describe("maybeWarnAboutMissedTasks()", () => {
const cb = testWarn("2017-05-01T22:00:00.000");
expect(cb).not.toHaveBeenCalled();
});
it("doesn't warn when not a regimen", () => {
const cb = testWarn("2017-05-21T22:00:00.000", "Sequence");
expect(cb).not.toHaveBeenCalled();
});
});

View File

@ -1,6 +1,6 @@
import * as React from "react";
import moment from "moment";
import { success, error, warning } from "../../toast/toast";
import { success, error } from "../../toast/toast";
import {
TaggedFarmEvent, SpecialStatus, TaggedSequence, TaggedRegimen,
ParameterApplication
@ -94,6 +94,13 @@ const startTimeWarning = () => {
error(message, title);
};
const nothingToRunWarning = () => {
const message =
t("All items scheduled before the start time. Nothing to run.");
const title = t("Unable to save event.");
error(message, title);
};
type RecombineOptions = { forceRegimensToMidnight: boolean };
/** Take a FormViewModel and recombine the fields into a FarmEvent
@ -323,9 +330,8 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
return recombine(vm, opts);
}
/** Use the next item run time to display toast messages and return to
* the form if necessary. */
nextRunTimeActions = (now = moment()): boolean => {
/** Use the next item run time to display toast messages. */
nextRunTimeActions = (now = moment()) => {
const nextRun = this.nextItemTime(this.props.farmEvent.body, now);
if (nextRun) {
const nextRunText = this.props.autoSyncEnabled
@ -335,10 +341,6 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
you must first SYNC YOUR DEVICE. If you do not sync, the event will
not run.`.replace(/\s+/g, " "), { timeFromNow: nextRun.from(now) });
success(nextRunText);
return true;
} else {
warning(t("All items scheduled before the start time. Nothing to run."));
return false;
}
}
@ -346,24 +348,27 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
* - Regimen Farm Event:
* * If scheduled for today, warn about the possibility of missing tasks.
* * Display the start time difference from now and maybe prompt to sync.
* * Return to calendar view if items exist to be run past the start time.
* * Return to calendar view.
* - Sequence Farm Event:
* * Determine the time for the next item to be run.
* * If auto-sync is disabled, prompt the user to sync.
* * Return to calendar view only if more items exist to be run.
* * Return to calendar view.
*/
commitViewModel = (now = moment()) => {
if (this.maybeRejectStartTime(this.updatedFarmEvent)) {
return startTimeWarning();
}
if (!this.nextItemTime(this.updatedFarmEvent, now)) {
return nothingToRunWarning();
}
this.dispatch(overwrite(this.props.farmEvent, this.updatedFarmEvent));
this.dispatch(save(this.props.farmEvent.uuid))
.then(() => {
this.setState({ specialStatusLocal: SpecialStatus.SAVED });
this.dispatch(maybeWarnAboutMissedTasks(this.props.farmEvent,
() => alert(t(Content.REGIMEN_TODAY_SKIPPED_ITEM_RISK)), now));
const itemsScheduled = this.nextRunTimeActions(now);
if (itemsScheduled) { history.push("/app/designer/events"); }
this.nextRunTimeActions(now);
history.push("/app/designer/events");
})
.catch(() => {
error(t("Unable to save event."));