diff --git a/app/static/src/app/components/header/AppHeader.vue b/app/static/src/app/components/header/AppHeader.vue index 9b9906ea0..20f03cd3e 100644 --- a/app/static/src/app/components/header/AppHeader.vue +++ b/app/static/src/app/components/header/AppHeader.vue @@ -14,8 +14,14 @@ Edit Label -
-
  • All Sessions
  • + @@ -69,6 +75,7 @@ export default defineComponent({ const sessionId = computed(() => store.state.sessionId); const sessionLabel = computed(() => store.state.sessionLabel); + const sessionPersisted = computed(() => store.state.persisted); const sessionMenuHeader = computed(() => { return sessionLabel.value ? `Session: ${sessionLabel.value}` : "Sessions"; @@ -87,6 +94,7 @@ export default defineComponent({ sessionId, sessionLabel, sessionMenuHeader, + sessionPersisted, languagesKeys }; } diff --git a/app/static/src/app/components/sessions/SessionsPage.vue b/app/static/src/app/components/sessions/SessionsPage.vue index ef18dbff7..3f409b087 100644 --- a/app/static/src/app/components/sessions/SessionsPage.vue +++ b/app/static/src/app/components/sessions/SessionsPage.vue @@ -43,12 +43,12 @@ +

    Previous sessions

    +

    + + +

    +

    + Saved sessions will appear here. +

    {{ messages.loading }}
    @@ -171,7 +174,7 @@ export default defineComponent({ const showUnlabelledSessions = computed({ get() { - return store.state.userPreferences.showUnlabelledSessions; + return store.state.userPreferences?.showUnlabelledSessions; }, set(newValue: boolean) { store.dispatch(AppStateAction.SaveUserPreferences, { showUnlabelledSessions: newValue }); diff --git a/app/static/src/app/serialise.ts b/app/static/src/app/serialise.ts index 0760d2f1d..fe97fc2f2 100644 --- a/app/static/src/app/serialise.ts +++ b/app/static/src/app/serialise.ts @@ -163,7 +163,8 @@ export const serialiseState = (state: AppState): string => { export const deserialiseState = (targetState: AppState, serialised: SerialisedAppState): void => { Object.assign(targetState, { ...targetState, - ...serialised + ...serialised, + persisted: true }); // Initialise selected variables if required diff --git a/app/static/src/app/store/appState/actions.ts b/app/static/src/app/store/appState/actions.ts index 3cff18fc3..8e94a0cea 100644 --- a/app/static/src/app/store/appState/actions.ts +++ b/app/static/src/app/store/appState/actions.ts @@ -27,7 +27,7 @@ async function immediateUploadState(context: ActionContext) commit(AppStateMutation.SetStateUploadInProgress, true); await api(context) - .ignoreSuccess() + .withSuccess(AppStateMutation.SetPersisted) .withError(ErrorsMutation.AddError) .post(`/${appsPath}/${appName}/sessions/${sessionId}`, serialiseState(state)); commit(AppStateMutation.SetStateUploadInProgress, false); diff --git a/app/static/src/app/store/appState/mutations.ts b/app/static/src/app/store/appState/mutations.ts index e5591bbba..67128a618 100644 --- a/app/static/src/app/store/appState/mutations.ts +++ b/app/static/src/app/store/appState/mutations.ts @@ -1,5 +1,5 @@ import { MutationTree } from "vuex"; -import {AppState, UserPreferences, VisualisationTab} from "./state"; +import { AppState, UserPreferences, VisualisationTab } from "./state"; import { AppConfig } from "../../types/responseTypes"; import { SetAppPayload } from "../../types/payloadTypes"; import registerTranslations from "../../../../translationPackage/registerTranslations"; @@ -14,7 +14,8 @@ export enum AppStateMutation { SetStateUploadInProgress = "SetStateUploadInProgress", SetSessionLabel = "SetSessionLabel", SetConfigured = "SetConfigured", - SetUserPreferences = "SetUserPreferences" + SetUserPreferences = "SetUserPreferences", + SetPersisted = "SetPersisted" } export const StateUploadMutations = [ @@ -64,5 +65,9 @@ export const appStateMutations: MutationTree = { [AppStateMutation.SetUserPreferences](state: AppState, payload: UserPreferences) { state.userPreferences = payload; + }, + + [AppStateMutation.SetPersisted](state: AppState) { + state.persisted = true; } }; diff --git a/app/static/src/app/store/appState/state.ts b/app/static/src/app/store/appState/state.ts index 29a864d9c..daa8539e4 100644 --- a/app/static/src/app/store/appState/state.ts +++ b/app/static/src/app/store/appState/state.ts @@ -43,7 +43,8 @@ export interface AppState { multiSensitivity: MultiSensitivityState, graphSettings: GraphSettingsState, versions: VersionsState, - configured: boolean, // true if configuration has been loaded or rehydrated and defaults set language: LanguageState, + configured: boolean, // true if configuration has been loaded or rehydrated and defaults set + persisted: boolean, // true if we have done an initial save of the session to the back-end userPreferences: UserPreferences } diff --git a/app/static/src/app/store/basic/basic.ts b/app/static/src/app/store/basic/basic.ts index 08c1ef804..ecd2c3a83 100644 --- a/app/static/src/app/store/basic/basic.ts +++ b/app/static/src/app/store/basic/basic.ts @@ -32,7 +32,8 @@ const defaultState: () => any = () => { config: null, queuedStateUploadIntervalId: -1, stateUploadInProgress: false, - configured: false + configured: false, + persisted: false }; }; diff --git a/app/static/src/app/store/fit/fit.ts b/app/static/src/app/store/fit/fit.ts index 178767cbc..c4a20f8d8 100644 --- a/app/static/src/app/store/fit/fit.ts +++ b/app/static/src/app/store/fit/fit.ts @@ -34,7 +34,8 @@ const defaultState: () => any = () => { config: null, queuedStateUploadIntervalId: -1, stateUploadInProgress: false, - configured: false + configured: false, + persisted: false }; }; diff --git a/app/static/src/app/store/stochastic/stochastic.ts b/app/static/src/app/store/stochastic/stochastic.ts index 56c5dddb2..2d5d78b43 100644 --- a/app/static/src/app/store/stochastic/stochastic.ts +++ b/app/static/src/app/store/stochastic/stochastic.ts @@ -32,7 +32,8 @@ const defaultState: () => any = () => { config: null, queuedStateUploadIntervalId: -1, stateUploadInProgress: false, - configured: false + configured: false, + persisted: false }; }; diff --git a/app/static/tests/mocks.ts b/app/static/tests/mocks.ts index c49d450b5..7aed2cd12 100644 --- a/app/static/tests/mocks.ts +++ b/app/static/tests/mocks.ts @@ -213,6 +213,7 @@ export const mockBasicState = (state: Partial = {}): BasicState => { versions: mockVersionsState(), graphSettings: mockGraphSettingsState(), configured: false, + persisted: true, language: mockLanguageState(), ...state }; @@ -264,6 +265,7 @@ export const mockFitState = (state: Partial = {}): FitState => { versions: mockVersionsState(), graphSettings: mockGraphSettingsState(), configured: false, + persisted: false, language: mockLanguageState(), ...state }; @@ -294,6 +296,7 @@ export const mockStochasticState = (state: Partial = {}): Stoch versions: mockVersionsState(), graphSettings: mockGraphSettingsState(), configured: false, + persisted: false, language: mockLanguageState(), ...state }; diff --git a/app/static/tests/unit/components/sessions/sessionsPage.test.ts b/app/static/tests/unit/components/sessions/sessionsPage.test.ts index 3b208c5d7..fc2fa40af 100644 --- a/app/static/tests/unit/components/sessions/sessionsPage.test.ts +++ b/app/static/tests/unit/components/sessions/sessionsPage.test.ts @@ -149,6 +149,8 @@ describe("SessionsPage", () => { expect(current.findComponent(RouterLink).props("to")).toBe("/"); expect(current.find("a").text()).toBe("make a copy of the current session."); + expect(wrapper.find("h3").text()).toBe("Previous sessions"); + expect(wrapper.find("p#previous-sessions-placeholder").text()).toBe("Saved sessions will appear here."); expect(wrapper.find("#previous-sessions-headers").exists()).toBe(false); expect(wrapper.findAll(".previous-session-row").length).toBe(0); }); diff --git a/app/static/tests/unit/components/versions/appHeader.test.ts b/app/static/tests/unit/components/versions/appHeader.test.ts index 6e2410aaf..dddfea543 100644 --- a/app/static/tests/unit/components/versions/appHeader.test.ts +++ b/app/static/tests/unit/components/versions/appHeader.test.ts @@ -12,10 +12,15 @@ import { LanguageSwitcher } from "../../../../translationPackage"; describe("AppHeader", () => { const getWrapper = (appName: string | null = "test", sessionLabel: string | null = null, - sessionId = "testSessionId") => { + sessionId = "testSessionId", persisted = true) => { const store = new Vuex.Store({ state: mockBasicState({ - appName, sessionLabel, sessionId, baseUrl: "http://localhost:3000", appsPath: "apps" + appName, + sessionLabel, + sessionId, + baseUrl: "http://localhost:3000", + appsPath: "apps", + persisted }) }); @@ -33,7 +38,7 @@ describe("AppHeader", () => { return shallowMount(AppHeader, options); }; - it("renders as expected", () => { + it("renders as expected when session has been persisted", () => { const wrapper = getWrapper(); expect(wrapper.find("a.navbar-brand").text()).toBe("Test Course Title"); expect(wrapper.find("a.navbar-brand").attributes("href")).toBe("http://localhost:3000"); @@ -45,6 +50,7 @@ describe("AppHeader", () => { expect(editLabelItem.text()).toBe("Edit Label"); expect(editLabelItem.findComponent(VueFeather).props("type")).toBe("edit-2"); + expect(sessionsDropDown.find("hr").exists()).toBe(true); const routerLink = sessionsDropDown.findAll("ul.dropdown-menu li").at(1)!.findComponent(RouterLink); expect(routerLink.attributes("to")).toBe("/sessions"); @@ -56,6 +62,19 @@ describe("AppHeader", () => { expect(wrapper.findAllComponents(LanguageSwitcher)).toHaveLength(1); }); + it("renders as expected when session has not been persisted", () => { + const wrapper = getWrapper("test", null, "testSessionId", false); + const sessionsDropDown = wrapper.find(".dropdown"); + expect(sessionsDropDown.find("a#sessions-menu").text()).toBe("Sessions"); + + expect(sessionsDropDown.find("hr").exists()).toBe(false); + const routerLink = sessionsDropDown.findComponent(RouterLink); + expect(routerLink.exists()).toBe(false); + + const editDlg = wrapper.findComponent(EditSessionLabel); + expect(editDlg.exists()).toBe(true); + }); + it("renders version menu as expected", () => { const wrapper = getWrapper(); diff --git a/app/static/tests/unit/serialiser.test.ts b/app/static/tests/unit/serialiser.test.ts index b2e2b3291..4ccfba4a2 100644 --- a/app/static/tests/unit/serialiser.test.ts +++ b/app/static/tests/unit/serialiser.test.ts @@ -350,6 +350,7 @@ describe("serialise", () => { yAxisRange: [1, 2] }, configured: false, + persisted: true, language: langaugeState }; @@ -378,6 +379,7 @@ describe("serialise", () => { yAxisRange: [1, 2] }, configured: false, + persisted: true, language: langaugeState }; @@ -646,6 +648,7 @@ describe("serialise", () => { deserialiseState(target, serialised); expect(target).toStrictEqual({ + persisted: true, sessionId: "123", appType: AppType.Fit, config: {}, diff --git a/app/static/tests/unit/store/appState/actions.test.ts b/app/static/tests/unit/store/appState/actions.test.ts index f27392441..4c40cd916 100644 --- a/app/static/tests/unit/store/appState/actions.test.ts +++ b/app/static/tests/unit/store/appState/actions.test.ts @@ -292,9 +292,10 @@ describe("AppState actions", () => { // use a real timer to wait for the final commit after mock axios returns! jest.useRealTimers(); setTimeout(() => { - expect(commit).toHaveBeenCalledTimes(5); - expect(commit.mock.calls[4][0]).toBe(AppStateMutation.SetStateUploadInProgress); - expect(commit.mock.calls[4][1]).toBe(false); + expect(commit).toHaveBeenCalledTimes(6); + expect(commit.mock.calls[4][0]).toBe(AppStateMutation.SetPersisted); + expect(commit.mock.calls[5][0]).toBe(AppStateMutation.SetStateUploadInProgress); + expect(commit.mock.calls[5][1]).toBe(false); done(); }); }); diff --git a/app/static/tests/unit/store/appState/mutations.test.ts b/app/static/tests/unit/store/appState/mutations.test.ts index 2871206a8..bccaaaf97 100644 --- a/app/static/tests/unit/store/appState/mutations.test.ts +++ b/app/static/tests/unit/store/appState/mutations.test.ts @@ -70,4 +70,10 @@ describe("AppState mutations", () => { appStateMutations.SetConfigured(state); expect(state.configured).toBe(true); }); + + it("sets persisted", () => { + const state = mockBasicState(); + appStateMutations.SetPersisted(state); + expect(state.persisted).toBe(true); + }); });