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
+
+
+
+
+ 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
+
+
+
+
- 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);
+ });
});