From b1e4979f18843363e3d79cf5066c18c0863f00ef Mon Sep 17 00:00:00 2001 From: "alex.hill@gmail.com" Date: Thu, 24 Oct 2024 13:25:23 +0100 Subject: [PATCH] add end sesison button --- src/RootContext.ts | 1 + src/components/ManageDatasets.tsx | 5 ++-- src/components/TopNav.tsx | 18 +++++++++++- src/reducers/rootReducer.ts | 4 ++- src/services/dataService.ts | 7 +++++ ...taset.test.tsx => ManageDatasets.test.tsx} | 0 test/components/TopNav.test.tsx | 29 +++++++++++++++++++ test/rootReducer.test.ts | 13 ++++++++- 8 files changed, 71 insertions(+), 6 deletions(-) rename test/components/{ChooseDataset.test.tsx => ManageDatasets.test.tsx} (100%) create mode 100644 test/components/TopNav.test.tsx diff --git a/src/RootContext.ts b/src/RootContext.ts index dcf097e..1f28a25 100644 --- a/src/RootContext.ts +++ b/src/RootContext.ts @@ -28,6 +28,7 @@ export enum ActionType { SELECT_SCALE = "SELECT_SCALE", SET_SPLINE_OPTIONS = "SET_SPLINE_OPTIONS", SET_INDIVIDUAL_OPTIONS = "SET_INDIVIDUAL_OPTIONS", + SESSION_ENDED = "SESSION_ENDED" } export interface RootAction { diff --git a/src/components/ManageDatasets.tsx b/src/components/ManageDatasets.tsx index 7e513a7..d4229e9 100644 --- a/src/components/ManageDatasets.tsx +++ b/src/components/ManageDatasets.tsx @@ -23,9 +23,8 @@ export function ManageDatasets() {

Files you upload are - only accessible to you and - will be deleted automatically when you close your - browser.

+ only accessible to you. Files will be deleted automatically when you close your + browser, or instantly when you click the "End session" link on the top right.

{state.datasetNames.length > 0 && diff --git a/src/components/TopNav.tsx b/src/components/TopNav.tsx index 8dc99a0..b800a9f 100644 --- a/src/components/TopNav.tsx +++ b/src/components/TopNav.tsx @@ -1,6 +1,8 @@ import {Container, Nav, Navbar} from "react-bootstrap"; import ThemeSwitch from "./ThemeSwitch"; -import React from "react"; +import React, {useContext} from "react"; +import {RootContext, RootDispatchContext} from "../RootContext"; +import {dataService} from "../services/dataService"; interface Props { theme: string @@ -18,6 +20,15 @@ function GithubLogo() { export default function TopNav({ theme, setTheme }: Props) { + + const state = useContext(RootContext); + const dispatch = useContext(RootDispatchContext); + + const endSession = async () => { + await dataService(state.language, dispatch) + .endSession() + } + return SeroViz logoDocs + diff --git a/src/reducers/rootReducer.ts b/src/reducers/rootReducer.ts index 1cc71fe..3612aee 100644 --- a/src/reducers/rootReducer.ts +++ b/src/reducers/rootReducer.ts @@ -1,5 +1,5 @@ import {AppState} from "../types"; -import {ActionType, RootAction} from "../RootContext"; +import {ActionType, initialState, RootAction} from "../RootContext"; import {datasetReducer} from "./datasetReducer"; export const rootReducer = (state: AppState, action: RootAction): AppState => { @@ -26,6 +26,8 @@ export const rootReducer = (state: AppState, action: RootAction): AppState => { return {...state, uploadError: null} case ActionType.DATASET_NAMES_FETCHED: return {...state, datasetNames: action.payload} + case ActionType.SESSION_ENDED: + return {...initialState} default: return datasetReducer(state, action) } diff --git a/src/services/dataService.ts b/src/services/dataService.ts index a865343..e0c7f7a 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -106,6 +106,13 @@ export class DataService { .withError(ActionType.ERROR_ADDED) .delete("/dataset/" + dataset + "/") } + + async endSession() { + return await this._api + .withSuccess(ActionType.SESSION_ENDED) + .ignoreErrors() + .delete("/session/") + } } export const dataService = (lang: string, dispatch: Dispatch) => new DataService(new APIService(lang, dispatch)); diff --git a/test/components/ChooseDataset.test.tsx b/test/components/ManageDatasets.test.tsx similarity index 100% rename from test/components/ChooseDataset.test.tsx rename to test/components/ManageDatasets.test.tsx diff --git a/test/components/TopNav.test.tsx b/test/components/TopNav.test.tsx new file mode 100644 index 0000000..6b36e56 --- /dev/null +++ b/test/components/TopNav.test.tsx @@ -0,0 +1,29 @@ +import {mockAppState, mockAxios, mockSuccess} from "../mocks"; +import {userEvent} from "@testing-library/user-event"; +import {render, screen} from "@testing-library/react"; +import { + ActionType, + RootContext, + RootDispatchContext +} from "../../src/RootContext"; +import TopNav from "../../src/components/TopNav"; + +test("user can end session", async () => { + mockAxios.onDelete(`/session/`) + .reply(200, mockSuccess("OK")); + + let state = mockAppState(); + const dispatch = jest.fn(); + const user = userEvent.setup(); + + render( + + + ); + + const links = screen.getAllByRole("button") as HTMLAnchorElement[]; + const endSession = links.find(l => l.textContent === "End session")!!; + await user.click(endSession); + expect(dispatch.mock.calls[1][0].type).toBe(ActionType.SESSION_ENDED); +}); diff --git a/test/rootReducer.test.ts b/test/rootReducer.test.ts index 3a79391..5adf01d 100644 --- a/test/rootReducer.test.ts +++ b/test/rootReducer.test.ts @@ -5,7 +5,7 @@ import { mockDatasetNames, mockDatasetSettings, mockError } from "./mocks"; -import {ActionType} from "../src/RootContext"; +import {ActionType, initialState} from "../src/RootContext"; import {rootReducer} from "../src/reducers/rootReducer"; describe("rootReducer", () => { @@ -200,4 +200,15 @@ describe("rootReducer", () => { {type: ActionType.DATASET_DELETED, payload: "d1"}); expect(newState.datasetNames).toEqual(["d2"]); }); + + it("should revert to initial state on SESSION_DELETED", () => { + const state = mockAppState({ + datasetNames: ["d1", "d2"], + selectedDataset: "d1", + selectedPlot: "individual" + }); + const newState = rootReducer(state, + {type: ActionType.SESSION_ENDED, payload: null}); + expect(newState).toEqual(initialState); + }); });