From 1e0e97d3f7907bb9f4178ae8b773d13a06946db6 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Tue, 12 Sep 2023 16:40:58 +0100 Subject: [PATCH 1/4] deserialise and rehydrate --- app/static/src/app/serialise.ts | 17 ++++++++++++++++- .../src/app/store/multiSensitivity/actions.ts | 11 ++++++++++- app/static/src/app/store/sensitivity/actions.ts | 14 +++++++++----- app/static/src/app/store/sessions/actions.ts | 4 ++++ app/static/src/app/types/serialisationTypes.ts | 8 ++++++++ 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/app/static/src/app/serialise.ts b/app/static/src/app/serialise.ts index 6413448dd..2961f2c9c 100644 --- a/app/static/src/app/serialise.ts +++ b/app/static/src/app/serialise.ts @@ -11,10 +11,11 @@ import { SerialisedAppState, SerialisedModelState, SerialisedRunState, SerialisedSensitivityState, - SerialisedRunResult, SerialisedSensitivityResult + SerialisedRunResult, SerialisedSensitivityResult, SerialisedMultiSensitivityState } from "./types/serialisationTypes"; import { GraphSettingsState } from "./store/graphSettings/state"; import { Dict } from "./types/utilTypes"; +import {MultiSensitivityState} from "./store/multiSensitivity/state"; function serialiseCode(code: CodeState) : CodeState { return { @@ -95,6 +96,19 @@ function serialiseSensitivity(sensitivity: SensitivityState): SerialisedSensitiv }; } +function serialiseMultiSensitivity(multiSensitivity: MultiSensitivityState): SerialisedMultiSensitivityState { + return { + running: false, + paramSettings: multiSensitivity.paramSettings, + sensitivityUpdateRequired: multiSensitivity.sensitivityUpdateRequired, + result: multiSensitivity.result ? { + inputs: multiSensitivity.result.inputs, + hasResult: !!multiSensitivity.result.batch, + error: multiSensitivity.result.error + } : null + }; +} + function serialiseFitData(fitData: FitDataState) : FitDataState { return { data: fitData.data, @@ -129,6 +143,7 @@ export const serialiseState = (state: AppState) => { model: serialiseModel(state.model), run: serialiseRun(state.run), sensitivity: serialiseSensitivity(state.sensitivity), + multiSensitivity: serialiseMultiSensitivity(state.multiSensitivity), graphSettings: serialiseGraphSettings(state.graphSettings) }; diff --git a/app/static/src/app/store/multiSensitivity/actions.ts b/app/static/src/app/store/multiSensitivity/actions.ts index 8df225271..4791316f4 100644 --- a/app/static/src/app/store/multiSensitivity/actions.ts +++ b/app/static/src/app/store/multiSensitivity/actions.ts @@ -1,11 +1,16 @@ import { ActionTree } from "vuex"; import { AppState } from "../appState/state"; -import { baseSensitivityActions, runSensitivity } from "../sensitivity/actions"; +import { + baseSensitivityActions, + runSensitivity, + runSensitivityOnRehydrate +} from "../sensitivity/actions"; import { MultiSensitivityState } from "./state"; import { BaseSensitivityGetter } from "../sensitivity/getters"; export enum MultiSensitivityAction { RunMultiSensitivity = "RunMultiSensitivity", + RunMultiSensitivityOnRehydrate = "RunMultiSensitivityOnRehydrate" } export const actions: ActionTree = { @@ -16,5 +21,9 @@ export const actions: ActionTree = { const batchPars = getters[BaseSensitivityGetter.batchPars]; runSensitivity(batchPars, endTime, context, true); + }, + + [MultiSensitivityAction.RunMultiSensitivityOnRehydrate](context) { + runSensitivityOnRehydrate(context); } }; diff --git a/app/static/src/app/store/sensitivity/actions.ts b/app/static/src/app/store/sensitivity/actions.ts index 0d97ff7cf..6b84bf57e 100644 --- a/app/static/src/app/store/sensitivity/actions.ts +++ b/app/static/src/app/store/sensitivity/actions.ts @@ -143,6 +143,14 @@ export const runSensitivity = ( } }; +export const runSensitivityOnRehydrate = (context: ActionContext) => { + const { state, rootState } = context; + const { endTime } = rootState.run; + const { pars } = state.result!.inputs; + + runSensitivity(pars, endTime, context); +} + export const baseSensitivityActions: ActionTree = { [BaseSensitivityAction.ComputeNext](context, batch: Batch) { const { @@ -181,10 +189,6 @@ export const actions: ActionTree = { }, [SensitivityAction.RunSensitivityOnRehydrate](context) { - const { state, rootState } = context; - const { endTime } = rootState.run; - const { pars } = state.result!.inputs; - - runSensitivity(pars, endTime, context); + runSensitivityOnRehydrate(context); } }; diff --git a/app/static/src/app/store/sessions/actions.ts b/app/static/src/app/store/sessions/actions.ts index 06651f216..6c4c7004d 100644 --- a/app/static/src/app/store/sessions/actions.ts +++ b/app/static/src/app/store/sessions/actions.ts @@ -12,6 +12,7 @@ import { SerialisedAppState } from "../../types/serialisationTypes"; import { deserialiseState } from "../../serialise"; import { SensitivityAction } from "../sensitivity/actions"; import { AppStateGetter } from "../appState/getters"; +import {MultiSensitivityAction} from "../multiSensitivity/actions"; export enum SessionsAction { GetSessions = "GetSessions", @@ -67,6 +68,9 @@ export const actions: ActionTree = { if (sessionData.sensitivity.result?.hasResult) { dispatch(`sensitivity/${SensitivityAction.RunSensitivityOnRehydrate}`, null, rootOption); } + if (sessionData.multiSensitivity.result?.hasResult) { + dispatch(`multiSensitivity/${MultiSensitivityAction.RunMultiSensitivityOnRehydrate}`, null, rootOption); + } } } }, diff --git a/app/static/src/app/types/serialisationTypes.ts b/app/static/src/app/types/serialisationTypes.ts index 3419ac6c0..6d28e27c1 100644 --- a/app/static/src/app/types/serialisationTypes.ts +++ b/app/static/src/app/types/serialisationTypes.ts @@ -65,6 +65,13 @@ export interface SerialisedSensitivityState { parameterSetResults: Dict } +export interface SerialisedMultiSensitivityState { + running: boolean, + paramSettings: SensitivityParameterSettings[] + sensitivityUpdateRequired: SensitivityUpdateRequiredReasons, + result: null | SerialisedSensitivityResult +} + export interface SerialisedModelFitState { fitUpdateRequired: FitUpdateRequiredReasons, iterations: number | null, @@ -80,6 +87,7 @@ export interface SerialisedAppState { model: SerialisedModelState, run: SerialisedRunState, sensitivity: SerialisedSensitivityState, + multiSensitivity: SerialisedMultiSensitivityState, graphSettings: GraphSettingsState, fitData?: FitDataState, modelFit?: SerialisedModelFitState, From 79d9fe04cf2fe196839b07d93535612b945932a7 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Tue, 12 Sep 2023 17:52:02 +0100 Subject: [PATCH 2/4] unit tests --- app/static/src/app/serialise.ts | 32 ++--- app/static/tests/unit/serialiser.test.ts | 111 ++++++++++++++++-- .../store/multiSensitivity/actions.test.ts | 8 +- .../unit/store/sensitivity/actions.test.ts | 99 ++++++++-------- 4 files changed, 176 insertions(+), 74 deletions(-) diff --git a/app/static/src/app/serialise.ts b/app/static/src/app/serialise.ts index 2961f2c9c..7a26c8ffe 100644 --- a/app/static/src/app/serialise.ts +++ b/app/static/src/app/serialise.ts @@ -3,7 +3,7 @@ import { FitState } from "./store/fit/state"; import { CodeState } from "./store/code/state"; import { ModelState } from "./store/model/state"; import { RunState } from "./store/run/state"; -import { SensitivityState } from "./store/sensitivity/state"; +import {BaseSensitivityState, SensitivityState} from "./store/sensitivity/state"; import { FitDataState } from "./store/fitData/state"; import { ModelFitState } from "./store/modelFit/state"; import { OdinFitResult, OdinRunResultDiscrete, OdinRunResultOde } from "./types/wrapperTypes"; @@ -71,6 +71,18 @@ function serialiseRun(run: RunState): SerialisedRunState { }; } +function serialiseBaseSensitivity(sensitivity: BaseSensitivityState) { + return { + running: false, + sensitivityUpdateRequired: sensitivity.sensitivityUpdateRequired, + result: sensitivity.result ? { + inputs: sensitivity.result.inputs, + hasResult: !!sensitivity.result.batch, + error: sensitivity.result.error + } : null + } +} + function serialiseSensitivity(sensitivity: SensitivityState): SerialisedSensitivityState { const serialisedParameterSetResults = {} as Dict; Object.keys(sensitivity.parameterSetResults).forEach((name) => { @@ -83,29 +95,17 @@ function serialiseSensitivity(sensitivity: SensitivityState): SerialisedSensitiv }); return { - running: false, + ...serialiseBaseSensitivity(sensitivity), paramSettings: sensitivity.paramSettings, - sensitivityUpdateRequired: sensitivity.sensitivityUpdateRequired, plotSettings: sensitivity.plotSettings, - result: sensitivity.result ? { - inputs: sensitivity.result.inputs, - hasResult: !!sensitivity.result.batch, - error: sensitivity.result.error - } : null, parameterSetResults: serialisedParameterSetResults }; } function serialiseMultiSensitivity(multiSensitivity: MultiSensitivityState): SerialisedMultiSensitivityState { return { - running: false, - paramSettings: multiSensitivity.paramSettings, - sensitivityUpdateRequired: multiSensitivity.sensitivityUpdateRequired, - result: multiSensitivity.result ? { - inputs: multiSensitivity.result.inputs, - hasResult: !!multiSensitivity.result.batch, - error: multiSensitivity.result.error - } : null + ...serialiseBaseSensitivity(multiSensitivity), + paramSettings: multiSensitivity.paramSettings }; } diff --git a/app/static/tests/unit/serialiser.test.ts b/app/static/tests/unit/serialiser.test.ts index f6da475c4..b2e2b3291 100644 --- a/app/static/tests/unit/serialiser.test.ts +++ b/app/static/tests/unit/serialiser.test.ts @@ -20,6 +20,7 @@ import { defaultState as defaultGraphSettingsState } from "../../src/app/store/g import { Language } from "../../src/app/types/languageTypes"; import { AdvancedOptions } from "../../src/app/types/responseTypes"; import { AdvancedComponentType } from "../../src/app/store/run/state"; +import { noSensitivityUpdateRequired } from "../../src/app/store/sensitivity/sensitivity"; describe("serialise", () => { const codeState = { @@ -212,6 +213,73 @@ describe("serialise", () => { } }; + const multiSensitivityBatchPars = { + base: { alpha: 1, beta: 1.1 }, + varying: [ + { + name: "alpha", + values: [0.5, 0.75, 1, 1.25, 1.5] + }, + { + name: "beta", + values: [1, 1.1, 2] + } + ] + }; + + const multiSensitivityState = { + running: true, + loading: false, + downloading: false, + userSummaryDownloadFileName: "", + paramSettings: [ + { + parameterToVary: "alpha", + scaleType: SensitivityScaleType.Arithmetic, + variationType: SensitivityVariationType.Percentage, + variationPercentage: 50, + rangeFrom: 0, + rangeTo: 1, + numberOfRuns: 5, + customValues: [] + }, + { + parameterToVary: "beta", + scaleType: SensitivityScaleType.Arithmetic, + variationType: SensitivityVariationType.Percentage, + variationPercentage: 10, + rangeFrom: 0, + rangeTo: 1, + numberOfRuns: 3, + customValues: [] + } + ], + sensitivityUpdateRequired: { + modelChanged: false, + parameterValueChanged: false, + endTimeChanged: false, + sensitivityOptionsChanged: false, + numberOfReplicatesChanged: false, + advancedSettingsChanged: false + }, + result: { + inputs: { + endTime: 10, + pars: multiSensitivityBatchPars + }, + batch: { + pars: multiSensitivityBatchPars, + solutions: [jest.fn(), jest.fn()], + errors: [], + successfulVaryingParams: [], + valueAtTime: jest.fn(), + extreme: jest.fn(), + compute: jest.fn() + }, + error: { error: "multiSensitivity error", detail: "multiSensitivity error detail" } + } + }; + const fitDataState = { data: [{ time: 0, cases: 0 }, { time: 1, cases: 2 }], columns: ["time", "cases"], @@ -274,7 +342,7 @@ describe("serialise", () => { model: modelState, run: runState, sensitivity: sensitivityState, - multiSensitivity: mockMultiSensitivityState(), + multiSensitivity: multiSensitivityState, versions: { versions: null }, graphSettings: { logScaleYAxis: true, @@ -300,7 +368,7 @@ describe("serialise", () => { model: modelState, run: runState, sensitivity: sensitivityState, - multiSensitivity: mockMultiSensitivityState(), + multiSensitivity: multiSensitivityState, fitData: fitDataState, modelFit: modelFitState, versions: { versions: null }, @@ -364,14 +432,7 @@ describe("serialise", () => { const expectedSensitivity = { running: false, paramSettings: sensitivityState.paramSettings, - sensitivityUpdateRequired: { - modelChanged: false, - parameterValueChanged: false, - endTimeChanged: false, - sensitivityOptionsChanged: false, - numberOfReplicatesChanged: false, - advancedSettingsChanged: false - }, + sensitivityUpdateRequired: noSensitivityUpdateRequired(), plotSettings: sensitivityState.plotSettings, result: { inputs: sensitivityState.result.inputs, @@ -387,6 +448,17 @@ describe("serialise", () => { } }; + const expectedMultiSensitivity = { + running: false, + paramSettings: multiSensitivityState.paramSettings, + sensitivityUpdateRequired: noSensitivityUpdateRequired(), + result: { + inputs: multiSensitivityState.result.inputs, + hasResult: true, + error: multiSensitivityState.result.error + } + }; + const expectedGraphSettings = { logScaleYAxis: true, lockYAxis: true, @@ -431,6 +503,7 @@ describe("serialise", () => { model: expectedModel, run: expectedRun, sensitivity: expectedSensitivity, + multiSensitivity: expectedMultiSensitivity, graphSettings: expectedGraphSettings }; expect(JSON.parse(serialised)).toStrictEqual(expected); @@ -444,6 +517,7 @@ describe("serialise", () => { model: expectedModel, run: expectedRun, sensitivity: expectedSensitivity, + multiSensitivity: expectedMultiSensitivity, fitData: expectedFitData, modelFit: expectedModelFit, graphSettings: expectedGraphSettings @@ -472,6 +546,13 @@ describe("serialise", () => { batch: null } }, + multiSensitivity: { + ...multiSensitivityState, + result: { + ...multiSensitivityState.result, + batch: null + } + }, modelFit: { ...modelFitState, result: { @@ -495,6 +576,10 @@ describe("serialise", () => { ...expectedSensitivity, result: { ...expectedSensitivity.result, hasResult: false } }, + multiSensitivity: { + ...expectedMultiSensitivity, + result: { ...expectedMultiSensitivity.result, hasResult: false } + }, fitData: expectedFitData, modelFit: { ...expectedModelFit, @@ -511,6 +596,7 @@ describe("serialise", () => { model: { ...modelState, odin: null }, run: { ...runState, resultOde: null, resultDiscrete: null }, sensitivity: { ...sensitivityState, result: null }, + multiSensitivity: { ...multiSensitivityState, result: null }, modelFit: { ...modelFitState, result: null } }; const serialised = serialiseState(state); @@ -521,6 +607,7 @@ describe("serialise", () => { model: { ...expectedModel, hasOdin: false }, run: { ...expectedRun, resultOde: null, resultDiscrete: null }, sensitivity: { ...expectedSensitivity, result: null }, + multiSensitivity: { ...expectedMultiSensitivity, result: null }, fitData: expectedFitData, modelFit: { ...expectedModelFit, result: null }, graphSettings: expectedGraphSettings @@ -535,6 +622,7 @@ describe("serialise", () => { model: mockModelState(), run: { ...mockRunState(), advancedSettings: expectedRun.advancedSettings }, sensitivity: mockSensitivityState(), + multiSensitivity: mockMultiSensitivityState(), fitData: mockFitDataState(), modelFit: mockModelFitState(), versions: mockVersionsState(), @@ -566,6 +654,7 @@ describe("serialise", () => { model: mockModelState(), run: mockRunState(), sensitivity: mockSensitivityState(), + multiSensitivity: mockMultiSensitivityState(), fitData: mockFitDataState(), modelFit: mockModelFitState(), versions: mockVersionsState(), @@ -588,6 +677,7 @@ describe("serialise", () => { } as any), run: { ...mockRunState(), advancedSettings: expectedRun.advancedSettings }, sensitivity: mockSensitivityState(), + multiSensitivity: mockMultiSensitivityState(), fitData: mockFitDataState(), modelFit: mockModelFitState(), versions: mockVersionsState() @@ -619,6 +709,7 @@ describe("serialise", () => { model: mockModelState(), run: { ...mockRunState(), advancedSettings: expectedRun.advancedSettings }, sensitivity: mockSensitivityState(), + multiSensitivity: mockMultiSensitivityState(), fitData: mockFitDataState(), modelFit: mockModelFitState(), versions: mockVersionsState() diff --git a/app/static/tests/unit/store/multiSensitivity/actions.test.ts b/app/static/tests/unit/store/multiSensitivity/actions.test.ts index 70f5414cc..e8402e110 100644 --- a/app/static/tests/unit/store/multiSensitivity/actions.test.ts +++ b/app/static/tests/unit/store/multiSensitivity/actions.test.ts @@ -6,10 +6,11 @@ import { mockRunState, mockRunnerOde, rootGetters, - testCommonRunSensitivity + testCommonRunSensitivity, expectRunOnRehydrateToUseParametersFromResult } from "../sensitivity/actions.test"; import { AppType } from "../../../../src/app/store/appState/state"; import { BaseSensitivityMutation } from "../../../../src/app/store/sensitivity/mutations"; +import {SensitivityAction} from "../../../../src/app/store/sensitivity/actions"; jest.mock("../../../../src/app/excel/wodinSensitivitySummaryDownload"); @@ -66,4 +67,9 @@ describe("multiSensitivity actions", () => { ); expect(dispatch).not.toHaveBeenCalled(); }); + + it("run multiSensitivity on rehydrate uses parameters from result", () => { + expectRunOnRehydrateToUseParametersFromResult( + actions[MultiSensitivityAction.RunMultiSensitivityOnRehydrate] as any); + }); }); diff --git a/app/static/tests/unit/store/sensitivity/actions.test.ts b/app/static/tests/unit/store/sensitivity/actions.test.ts index 4a4e801d5..e7a7f7882 100644 --- a/app/static/tests/unit/store/sensitivity/actions.test.ts +++ b/app/static/tests/unit/store/sensitivity/actions.test.ts @@ -1,4 +1,4 @@ -import { Action } from "vuex"; +import {Action, ActionTree} from "vuex"; import { actions, BaseSensitivityAction, SensitivityAction } from "../../../../src/app/store/sensitivity/actions"; import { BaseSensitivityMutation, SensitivityMutation } from "../../../../src/app/store/sensitivity/mutations"; import { ModelGetter } from "../../../../src/app/store/model/getters"; @@ -8,6 +8,7 @@ import { AdvancedOptions } from "../../../../src/app/types/responseTypes"; import { AdvancedComponentType } from "../../../../src/app/store/run/state"; import { WodinSensitivitySummaryDownload } from "../../../../src/app/excel/wodinSensitivitySummaryDownload"; import Mock = jest.Mock; +import {BaseSensitivityState} from "../../../../src/app/store/sensitivity/state"; jest.mock("../../../../src/app/excel/wodinSensitivitySummaryDownload"); @@ -63,6 +64,55 @@ export const rootGetters = { [`model/${ModelGetter.hasRunner}`]: true }; +export const expectRunOnRehydrateToUseParametersFromResult = (action: Action) => { + const mockResultBatchPars = {}; + const rootState = { + model: mockModelState, + run: mockRunState + }; + const state = { + result: { + inputs: { + pars: mockResultBatchPars + } + } + }; + + const commit = jest.fn(); + const dispatch = jest.fn(); + + (action as any)({ + rootState, getters, commit, dispatch, state, rootGetters + }); + + expect(commit).toHaveBeenCalledTimes(2); + expect(commit.mock.calls[0][0]).toBe(BaseSensitivityMutation.SetResult); + expect(commit.mock.calls[0][1]).toStrictEqual({ + inputs: { endTime: 99, pars: mockResultBatchPars }, + batch: mockBatch, + error: null + }); + expect(commit.mock.calls[1][0]).toBe(BaseSensitivityMutation.SetUpdateRequired); + expect(commit.mock.calls[1][1]).toStrictEqual({ + endTimeChanged: false, + modelChanged: false, + parameterValueChanged: false, + sensitivityOptionsChanged: false, + numberOfReplicatesChanged: false, + advancedSettingsChanged: false + }); + + expect(mockRunnerOde.batchRun).toHaveBeenCalledWith( + rootState.model.odin, + mockBatchPars, + 0, + 99, + defaultAdvanced + ); + + expect(dispatch).not.toHaveBeenCalled(); +}; + describe("BaseSensitivity actions", () => { afterEach(() => { jest.clearAllMocks(); @@ -510,51 +560,6 @@ describe("Sensitivity actions", () => { }); it("run sensitivity on rehydrate uses parameters from result", () => { - const mockResultBatchPars = {}; - const rootState = { - model: mockModelState, - run: mockRunState - }; - const state = { - result: { - inputs: { - pars: mockResultBatchPars - } - } - }; - - const commit = jest.fn(); - const dispatch = jest.fn(); - - (actions[SensitivityAction.RunSensitivityOnRehydrate] as any)({ - rootState, getters, commit, dispatch, state, rootGetters - }); - - expect(commit).toHaveBeenCalledTimes(2); - expect(commit.mock.calls[0][0]).toBe(BaseSensitivityMutation.SetResult); - expect(commit.mock.calls[0][1]).toStrictEqual({ - inputs: { endTime: 99, pars: mockResultBatchPars }, - batch: mockBatch, - error: null - }); - expect(commit.mock.calls[1][0]).toBe(BaseSensitivityMutation.SetUpdateRequired); - expect(commit.mock.calls[1][1]).toStrictEqual({ - endTimeChanged: false, - modelChanged: false, - parameterValueChanged: false, - sensitivityOptionsChanged: false, - numberOfReplicatesChanged: false, - advancedSettingsChanged: false - }); - - expect(mockRunnerOde.batchRun).toHaveBeenCalledWith( - rootState.model.odin, - mockBatchPars, - 0, - 99, - defaultAdvanced - ); - - expect(dispatch).not.toHaveBeenCalled(); + expectRunOnRehydrateToUseParametersFromResult(actions[SensitivityAction.RunSensitivityOnRehydrate] as any); }); }); From a56c1d5bedc2f6caccb996246e65193b7918d900 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Wed, 13 Sep 2023 10:42:43 +0100 Subject: [PATCH 3/4] more tests and lint --- app/static/src/app/serialise.ts | 6 +- .../src/app/store/sensitivity/actions.ts | 2 +- app/static/src/app/store/sessions/actions.ts | 5 +- app/static/tests/e2e/download.etest.ts | 1 + .../tests/e2e/multiSensitivity.etest.ts | 24 ++------ app/static/tests/e2e/sessions.etest.ts | 18 +++++- app/static/tests/e2e/utils.ts | 15 +++++ .../store/multiSensitivity/actions.test.ts | 5 +- .../unit/store/sensitivity/actions.test.ts | 4 +- .../tests/unit/store/sessions/actions.test.ts | 58 ++++++++++++++++--- 10 files changed, 99 insertions(+), 39 deletions(-) diff --git a/app/static/src/app/serialise.ts b/app/static/src/app/serialise.ts index 7a26c8ffe..fe8bb3628 100644 --- a/app/static/src/app/serialise.ts +++ b/app/static/src/app/serialise.ts @@ -3,7 +3,7 @@ import { FitState } from "./store/fit/state"; import { CodeState } from "./store/code/state"; import { ModelState } from "./store/model/state"; import { RunState } from "./store/run/state"; -import {BaseSensitivityState, SensitivityState} from "./store/sensitivity/state"; +import { BaseSensitivityState, SensitivityState } from "./store/sensitivity/state"; import { FitDataState } from "./store/fitData/state"; import { ModelFitState } from "./store/modelFit/state"; import { OdinFitResult, OdinRunResultDiscrete, OdinRunResultOde } from "./types/wrapperTypes"; @@ -15,7 +15,7 @@ import { } from "./types/serialisationTypes"; import { GraphSettingsState } from "./store/graphSettings/state"; import { Dict } from "./types/utilTypes"; -import {MultiSensitivityState} from "./store/multiSensitivity/state"; +import { MultiSensitivityState } from "./store/multiSensitivity/state"; function serialiseCode(code: CodeState) : CodeState { return { @@ -80,7 +80,7 @@ function serialiseBaseSensitivity(sensitivity: BaseSensitivityState) { hasResult: !!sensitivity.result.batch, error: sensitivity.result.error } : null - } + }; } function serialiseSensitivity(sensitivity: SensitivityState): SerialisedSensitivityState { diff --git a/app/static/src/app/store/sensitivity/actions.ts b/app/static/src/app/store/sensitivity/actions.ts index 6b84bf57e..ad43e8480 100644 --- a/app/static/src/app/store/sensitivity/actions.ts +++ b/app/static/src/app/store/sensitivity/actions.ts @@ -149,7 +149,7 @@ export const runSensitivityOnRehydrate = (context: ActionContext = { [BaseSensitivityAction.ComputeNext](context, batch: Batch) { diff --git a/app/static/src/app/store/sessions/actions.ts b/app/static/src/app/store/sessions/actions.ts index 6c4c7004d..16e167b05 100644 --- a/app/static/src/app/store/sessions/actions.ts +++ b/app/static/src/app/store/sessions/actions.ts @@ -12,7 +12,7 @@ import { SerialisedAppState } from "../../types/serialisationTypes"; import { deserialiseState } from "../../serialise"; import { SensitivityAction } from "../sensitivity/actions"; import { AppStateGetter } from "../appState/getters"; -import {MultiSensitivityAction} from "../multiSensitivity/actions"; +import { MultiSensitivityAction } from "../multiSensitivity/actions"; export enum SessionsAction { GetSessions = "GetSessions", @@ -69,7 +69,8 @@ export const actions: ActionTree = { dispatch(`sensitivity/${SensitivityAction.RunSensitivityOnRehydrate}`, null, rootOption); } if (sessionData.multiSensitivity.result?.hasResult) { - dispatch(`multiSensitivity/${MultiSensitivityAction.RunMultiSensitivityOnRehydrate}`, null, rootOption); + dispatch(`multiSensitivity/${MultiSensitivityAction.RunMultiSensitivityOnRehydrate}`, null, + rootOption); } } } diff --git a/app/static/tests/e2e/download.etest.ts b/app/static/tests/e2e/download.etest.ts index 84a767e8d..dbb29bf69 100644 --- a/app/static/tests/e2e/download.etest.ts +++ b/app/static/tests/e2e/download.etest.ts @@ -21,6 +21,7 @@ test.describe("Download tests", () => { }; test("can download from Run tab", async ({ page }) => { + console.log("start download"); await page.click("#download-btn"); await expect(await page.inputValue("#download-file-name input")).toContain("day2-run"); await testCanInputFileNameAndDownload(page); diff --git a/app/static/tests/e2e/multiSensitivity.etest.ts b/app/static/tests/e2e/multiSensitivity.etest.ts index 3bcaf7abe..8aae6b8ac 100644 --- a/app/static/tests/e2e/multiSensitivity.etest.ts +++ b/app/static/tests/e2e/multiSensitivity.etest.ts @@ -1,11 +1,11 @@ import { expect, Page, test } from "@playwright/test"; import { SensitivityScaleType, SensitivityVariationType } from "../../src/app/store/sensitivity/state"; import PlaywrightConfig from "../../playwright.config"; -import { writeCode } from "./utils"; +import { expectCanRunMultiSensitivity, writeCode } from "./utils"; -test.describe("Multi-sensitivity tests", () => { - const { timeout } = PlaywrightConfig; +const { timeout } = PlaywrightConfig; +test.describe("Multi-sensitivity tests", () => { test.beforeEach(async ({ page }) => { await page.goto("/apps/day1"); // Open Options tab @@ -122,22 +122,8 @@ test.describe("Multi-sensitivity tests", () => { SensitivityVariationType.Percentage, 5, null, null, 10, "1.900, 1.922, 1.944, ..., 2.100"); }); - const expectCanRunMultiSensitivity = async (page: Page) => { - // add a second varying parameter with default 10 values - should get 100 solutions from the 2 varying - await page.click("#add-param-settings"); - await expect(await page.locator("#edit-param-to-vary select")).toBeVisible(); - await page.click("#ok-settings"); - await expect(await page.locator(".sensitivity-options-settings").count()).toBe(2); - - await expect(await page.innerText(".multi-sensitivity-status")).toBe("Multi-sensitivity has not been run."); - await page.click("#run-multi-sens-btn"); - await expect(await page.locator("#run-multi-sens-btn")).toBeEnabled(); - await expect(await page.locator(".multi-sensitivity-status")) - .toHaveText("Multi-sensitivity run produced 100 solutions.", { timeout }); - }; - test("can run multi-sensitivity", async ({ page }) => { - await expectCanRunMultiSensitivity(page); + await expectCanRunMultiSensitivity(page, timeout); // shows update required message when update code await page.click(":nth-match(.wodin-left .nav-tabs a, 1)"); @@ -152,7 +138,7 @@ test.describe("Multi-sensitivity tests", () => { await page.click(":nth-match(.wodin-left .nav-tabs a, 2)"); await page.click(":nth-match(.wodin-right .nav-tabs a, 4)"); - await expectCanRunMultiSensitivity(page); + await expectCanRunMultiSensitivity(page, timeout); }); test("can show error in multi-sensitivity run", async ({ page }) => { diff --git a/app/static/tests/e2e/sessions.etest.ts b/app/static/tests/e2e/sessions.etest.ts index e4a9631c9..2d2a87dbe 100644 --- a/app/static/tests/e2e/sessions.etest.ts +++ b/app/static/tests/e2e/sessions.etest.ts @@ -1,5 +1,7 @@ import { - expect, test, chromium, Page + expect, test, + chromium, + Page } from "@playwright/test"; import * as os from "os"; import { @@ -7,7 +9,8 @@ import { newFitCode, realisticFitData, startModelFit, - waitForModelFitCompletion, expectWodinPlotDataSummary + waitForModelFitCompletion, expectWodinPlotDataSummary, + expectCanRunMultiSensitivity } from "./utils"; import PlaywrightConfig from "../../playwright.config"; @@ -64,6 +67,10 @@ test.describe("Sessions tests", () => { // 5 * 10 sensitivity traces, 5 central traces, 1 data plot expect(await page.locator(".wodin-plot-data-summary-series").count()).toBe(56); + // Run multi-sensitivity + await page.click(":nth-match(.wodin-right .nav-tabs a, 4)"); + expectCanRunMultiSensitivity(page, timeout); + // give the page a chance to save the session to back end await page.waitForTimeout(saveSessionTimeout); @@ -182,6 +189,13 @@ test.describe("Sessions tests", () => { const sensitivityDataSummary = await page.locator(":nth-match(.wodin-plot-data-summary-series, 56)"); await expectWodinPlotDataSummary(sensitivityDataSummary, "Cases", 32, 0, 31, 0, 13, "markers", null, "#cccc00"); + // Check multi-sensitivity result + await page.click(":nth-match(.wodin-right .nav-tabs a, 4)"); // Multi-sensitivity tab + await expect(await page.locator(".multi-sensitivity-status")) + .toHaveText("Multi-sensitivity run produced 100 solutions.", { timeout }); + await expect(await page.locator("#download-summary-btn")).toBeEnabled(); + await expect(await page.locator("#run-multi-sens-btn")).toBeEnabled(); + // Expect to be able to navigate to the share link we copied earlier - check it has some rehydrated data await page.goto(copiedLinkText); await expect(await page.innerText("#data-upload-success")).toBe(" Uploaded 32 rows and 2 columns"); diff --git a/app/static/tests/e2e/utils.ts b/app/static/tests/e2e/utils.ts index 632b98a9e..fa2728309 100644 --- a/app/static/tests/e2e/utils.ts +++ b/app/static/tests/e2e/utils.ts @@ -148,3 +148,18 @@ export const expectSummaryValues = async (page: Page, idx: number, name: string, expect(await page.getAttribute(locator, "y-max")).toBe(yMax); } }; + +export const expectCanRunMultiSensitivity = async (page: Page, timeout = 10000) => { + // add a second varying parameter with default 10 values - should get 100 solutions from the 2 varying + await page.click("#add-param-settings"); + await expect(await page.locator("#edit-param-to-vary select")).toBeVisible(); + await page.click("#ok-settings"); + await expect(await page.locator(".sensitivity-options-settings").count()).toBe(2); + + await expect(await page.innerText(".multi-sensitivity-status")).toBe("Multi-sensitivity has not been run."); + await page.click("#run-multi-sens-btn"); + await expect(await page.locator("#run-multi-sens-btn")).toBeEnabled(); + await expect(await page.locator(".multi-sensitivity-status")) + .toHaveText("Multi-sensitivity run produced 100 solutions.", { timeout }); + await expect(await page.locator("#download-summary-btn")).toBeEnabled(); +}; diff --git a/app/static/tests/unit/store/multiSensitivity/actions.test.ts b/app/static/tests/unit/store/multiSensitivity/actions.test.ts index e8402e110..876a097cf 100644 --- a/app/static/tests/unit/store/multiSensitivity/actions.test.ts +++ b/app/static/tests/unit/store/multiSensitivity/actions.test.ts @@ -10,7 +10,7 @@ import { } from "../sensitivity/actions.test"; import { AppType } from "../../../../src/app/store/appState/state"; import { BaseSensitivityMutation } from "../../../../src/app/store/sensitivity/mutations"; -import {SensitivityAction} from "../../../../src/app/store/sensitivity/actions"; +import { SensitivityAction } from "../../../../src/app/store/sensitivity/actions"; jest.mock("../../../../src/app/excel/wodinSensitivitySummaryDownload"); @@ -70,6 +70,7 @@ describe("multiSensitivity actions", () => { it("run multiSensitivity on rehydrate uses parameters from result", () => { expectRunOnRehydrateToUseParametersFromResult( - actions[MultiSensitivityAction.RunMultiSensitivityOnRehydrate] as any); + actions[MultiSensitivityAction.RunMultiSensitivityOnRehydrate] as any + ); }); }); diff --git a/app/static/tests/unit/store/sensitivity/actions.test.ts b/app/static/tests/unit/store/sensitivity/actions.test.ts index e7a7f7882..8667f124e 100644 --- a/app/static/tests/unit/store/sensitivity/actions.test.ts +++ b/app/static/tests/unit/store/sensitivity/actions.test.ts @@ -1,4 +1,4 @@ -import {Action, ActionTree} from "vuex"; +import { Action, ActionTree } from "vuex"; import { actions, BaseSensitivityAction, SensitivityAction } from "../../../../src/app/store/sensitivity/actions"; import { BaseSensitivityMutation, SensitivityMutation } from "../../../../src/app/store/sensitivity/mutations"; import { ModelGetter } from "../../../../src/app/store/model/getters"; @@ -8,7 +8,7 @@ import { AdvancedOptions } from "../../../../src/app/types/responseTypes"; import { AdvancedComponentType } from "../../../../src/app/store/run/state"; import { WodinSensitivitySummaryDownload } from "../../../../src/app/excel/wodinSensitivitySummaryDownload"; import Mock = jest.Mock; -import {BaseSensitivityState} from "../../../../src/app/store/sensitivity/state"; +import { BaseSensitivityState } from "../../../../src/app/store/sensitivity/state"; jest.mock("../../../../src/app/excel/wodinSensitivitySummaryDownload"); diff --git a/app/static/tests/unit/store/sessions/actions.test.ts b/app/static/tests/unit/store/sessions/actions.test.ts index e339509f1..5e50d725a 100644 --- a/app/static/tests/unit/store/sessions/actions.test.ts +++ b/app/static/tests/unit/store/sessions/actions.test.ts @@ -10,6 +10,7 @@ import { ModelAction } from "../../../../src/app/store/model/actions"; import { RunAction } from "../../../../src/app/store/run/actions"; import { SensitivityAction } from "../../../../src/app/store/sensitivity/actions"; import { AppStateGetter } from "../../../../src/app/store/appState/getters"; +import { MultiSensitivityAction } from "../../../../src/app/store/multiSensitivity/actions"; describe("SessionsActions", () => { const getSessionIdsSpy = jest.spyOn(localStorageManager, "getSessionIds") @@ -22,7 +23,8 @@ describe("SessionsActions", () => { }); const getSessionData = (hasOdin: boolean, compileRequired: boolean, - runHasResultOde: boolean, runHasResultDiscrete: boolean, sensitivityHasResult: boolean) => { + runHasResultOde: boolean, runHasResultDiscrete: boolean, sensitivityHasResult: boolean, + multiSensitivityHasResult: boolean) => { return { code: { currentCode: ["some saved code"] @@ -44,6 +46,11 @@ describe("SessionsActions", () => { result: { hasResult: sensitivityHasResult } + }, + multiSensitivity: { + result: { + hasResult: multiSensitivityHasResult + } } }; }; @@ -53,7 +60,7 @@ describe("SessionsActions", () => { }; const testRehydrate = async (stochastic: boolean) => { - const mockSessionData = getSessionData(true, false, !stochastic, stochastic, true); + const mockSessionData = getSessionData(true, false, !stochastic, stochastic, true, true); mockAxios.onGet("/apps/testApp/sessions/1234") .reply(200, mockSuccess(mockSessionData)); @@ -64,7 +71,7 @@ describe("SessionsActions", () => { expect(rootState.code.currentCode).toStrictEqual(["some saved code"]); expect(commit).toHaveBeenCalledTimes(1); expect(commit.mock.calls[0][0]).toBe(AppStateMutation.SetConfigured); - expect(dispatch).toHaveBeenCalledTimes(4); + expect(dispatch).toHaveBeenCalledTimes(5); expect(dispatch.mock.calls[0][0]).toBe(`model/${ModelAction.FetchOdinRunner}`); expect(dispatch.mock.calls[0][1]).toBe(null); expect(dispatch.mock.calls[0][2]).toStrictEqual({ root: true }); @@ -77,6 +84,10 @@ describe("SessionsActions", () => { expect(dispatch.mock.calls[3][0]).toBe(`sensitivity/${SensitivityAction.RunSensitivityOnRehydrate}`); expect(dispatch.mock.calls[3][1]).toBe(null); expect(dispatch.mock.calls[3][2]).toStrictEqual({ root: true }); + expect(dispatch.mock.calls[4][0]) + .toBe(`multiSensitivity/${MultiSensitivityAction.RunMultiSensitivityOnRehydrate}`); + expect(dispatch.mock.calls[4][1]).toBe(null); + expect(dispatch.mock.calls[4][2]).toStrictEqual({ root: true }); }; it("Rehydrates as expected for non-stochastic", async () => { @@ -88,7 +99,7 @@ describe("SessionsActions", () => { }); it("Rehydrate does not compile or run if no odin model", async () => { - const mockSessionData = getSessionData(false, false, true, false, true); + const mockSessionData = getSessionData(false, false, true, false, true, false); mockAxios.onGet("/apps/testApp/sessions/1234") .reply(200, mockSuccess(mockSessionData)); @@ -106,7 +117,7 @@ describe("SessionsActions", () => { }); it("Rehydrate does not compile or run if compile required is true", async () => { - const mockSessionData = getSessionData(true, true, true, false, true); + const mockSessionData = getSessionData(true, true, true, false, true, false); mockAxios.onGet("/apps/testApp/sessions/1234") .reply(200, mockSuccess(mockSessionData)); @@ -124,7 +135,7 @@ describe("SessionsActions", () => { }); it("Rehydrate does not run model if run has no result", async () => { - const mockSessionData = getSessionData(true, false, false, false, true); + const mockSessionData = getSessionData(true, false, false, false, true, false); mockAxios.onGet("/apps/testApp/sessions/1234") .reply(200, mockSuccess(mockSessionData)); @@ -148,7 +159,7 @@ describe("SessionsActions", () => { }); it("Rehydrate does not run sensitivity if sensitivity has no result", async () => { - const mockSessionData = getSessionData(true, false, true, false, false); + const mockSessionData = getSessionData(true, false, true, false, false, true); mockAxios.onGet("/apps/testApp/sessions/1234") .reply(200, mockSuccess(mockSessionData)); @@ -159,7 +170,7 @@ describe("SessionsActions", () => { expect(rootState.code.currentCode).toStrictEqual(["some saved code"]); expect(commit).toHaveBeenCalledTimes(1); expect(commit.mock.calls[0][0]).toBe(AppStateMutation.SetConfigured); - expect(dispatch).toHaveBeenCalledTimes(3); + expect(dispatch).toHaveBeenCalledTimes(4); expect(dispatch.mock.calls[0][0]).toBe(`model/${ModelAction.FetchOdinRunner}`); expect(dispatch.mock.calls[0][1]).toBe(null); expect(dispatch.mock.calls[0][2]).toStrictEqual({ root: true }); @@ -169,6 +180,37 @@ describe("SessionsActions", () => { expect(dispatch.mock.calls[2][0]).toBe(`run/${RunAction.RunModelOnRehydrate}`); expect(dispatch.mock.calls[2][1]).toBe(null); expect(dispatch.mock.calls[2][2]).toStrictEqual({ root: true }); + expect(dispatch.mock.calls[3][0]) + .toBe(`multiSensitivity/${MultiSensitivityAction.RunMultiSensitivityOnRehydrate}`); + expect(dispatch.mock.calls[3][1]).toBe(null); + expect(dispatch.mock.calls[3][2]).toStrictEqual({ root: true }); + }); + + it("Rehydrate does not run multiSensitivity if sensitivity has no result", async () => { + const mockSessionData = getSessionData(true, false, true, false, true, false); + mockAxios.onGet("/apps/testApp/sessions/1234") + .reply(200, mockSuccess(mockSessionData)); + + const commit = jest.fn(); + const dispatch = jest.fn(); + const rootState = { appName: "testApp", baseUrl: "", appsPath: "apps" } as any; + await (actions[SessionsAction.Rehydrate] as any)({ commit, dispatch, rootState }, "1234"); + expect(rootState.code.currentCode).toStrictEqual(["some saved code"]); + expect(commit).toHaveBeenCalledTimes(1); + expect(commit.mock.calls[0][0]).toBe(AppStateMutation.SetConfigured); + expect(dispatch).toHaveBeenCalledTimes(4); + expect(dispatch.mock.calls[0][0]).toBe(`model/${ModelAction.FetchOdinRunner}`); + expect(dispatch.mock.calls[0][1]).toBe(null); + expect(dispatch.mock.calls[0][2]).toStrictEqual({ root: true }); + expect(dispatch.mock.calls[1][0]).toBe(`model/${ModelAction.CompileModelOnRehydrate}`); + expect(dispatch.mock.calls[1][1]).toBe(null); + expect(dispatch.mock.calls[1][2]).toStrictEqual({ root: true }); + expect(dispatch.mock.calls[2][0]).toBe(`run/${RunAction.RunModelOnRehydrate}`); + expect(dispatch.mock.calls[2][1]).toBe(null); + expect(dispatch.mock.calls[2][2]).toStrictEqual({ root: true }); + expect(dispatch.mock.calls[3][0]).toBe(`sensitivity/${SensitivityAction.RunSensitivityOnRehydrate}`); + expect(dispatch.mock.calls[3][1]).toBe(null); + expect(dispatch.mock.calls[3][2]).toStrictEqual({ root: true }); }); it("GetSessions fetches and commits session metadata", async () => { From b413ac8d10ea9648a26ca587415081e5671cf1b3 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Wed, 13 Sep 2023 10:44:10 +0100 Subject: [PATCH 4/4] remove debug --- app/static/tests/e2e/download.etest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/static/tests/e2e/download.etest.ts b/app/static/tests/e2e/download.etest.ts index dbb29bf69..84a767e8d 100644 --- a/app/static/tests/e2e/download.etest.ts +++ b/app/static/tests/e2e/download.etest.ts @@ -21,7 +21,6 @@ test.describe("Download tests", () => { }; test("can download from Run tab", async ({ page }) => { - console.log("start download"); await page.click("#download-btn"); await expect(await page.inputValue("#download-file-name input")).toContain("day2-run"); await testCanInputFileNameAndDownload(page);