diff --git a/back-end/feedback/views.py b/back-end/feedback/views.py index 6ff6005..d3fbfb7 100644 --- a/back-end/feedback/views.py +++ b/back-end/feedback/views.py @@ -75,7 +75,7 @@ def question_answers(request: HttpRequest, key: str): @_feedback_error_as_json def answer(request: HttpRequest, id_: str): - if request.method != "PATCH": + if request.method not in ["GET", "PATCH"]: raise FeedbackError("Method not allowed", 405) try: @@ -83,6 +83,9 @@ def answer(request: HttpRequest, id_: str): except Answer.DoesNotExist: raise FeedbackError("Answer not found", 404) + if request.method == "GET": + return JsonResponse(a.json) + data = _parse_json_body(request) if data.pop("submit", None): a.submitted_at = datetime.now(timezone.utc) diff --git a/front-end/default.conf.template b/front-end/default.conf.template index b482609..292d48f 100644 --- a/front-end/default.conf.template +++ b/front-end/default.conf.template @@ -6,7 +6,7 @@ server { location / { root /usr/share/nginx/html; - index index.html index.htm; + try_files $uri $uri/ /index.html /index.htm; } location /static/ { diff --git a/front-end/src/App.svelte b/front-end/src/App.svelte index 3ddef1d..71c554b 100644 --- a/front-end/src/App.svelte +++ b/front-end/src/App.svelte @@ -5,6 +5,7 @@ import RadioGroup from "./lib/RadioGroup.svelte"; import { createAnswer, + getAnswer, getQuestion, getSummary, updateAnswer, @@ -23,6 +24,7 @@ import Footer from "./lib/Footer.svelte"; let loading = $state(true); + let view = $state("form"); let error = $state(null); let question = $state(null); let answer = $state(null); @@ -34,9 +36,15 @@ // Wait until the server has started. await waitUntilLive(); - // Parse the question key from URL query parameters. + // Parse the question key from URL path or URL query parameters. + const pathComponents = window.location.pathname.split("/").slice(1); const query = new URLSearchParams(window.location.search); - const key = query.get("key"); + + const key = pathComponents[0] || query.get("key"); + // Replace path if query parameter is used. This is for backwards compatibility. + if (key && !pathComponents[0]) { + window.history.replaceState({}, "", `/${key}`); + } if (!key) { error = { status: 404, @@ -45,6 +53,15 @@ return; } + view = pathComponents[1] || "form"; + if (["form", "summary"].includes(view) === false) { + error = { + status: 404, + title: "Page not found", + }; + return; + } + // Fetch the question using the key. const qr = await getQuestion(key); if (qr.error) { @@ -53,13 +70,22 @@ } question = qr.data; - // Initialize the answer. - const ar = await createAnswer(key); - if (ar.error) { - error = ar.error; - return; + // Initialize new answer or get existings answer. + const id = query.get("id"); + if (view === "form") { + const ar = id ? await getAnswer(id) : await createAnswer(key); + if (ar.error) { + error = ar.error; + return; + } + answer = ar.data; + window.history.replaceState({}, "", `/${key}?id=${answer.id}`); + } + + // Fetch the summary. + if (view === "summary") { + fetchSummary(key); } - answer = ar.data; } catch (err) { error = { status: 500, @@ -70,6 +96,10 @@ } }; + window.addEventListener("popstate", (event) => { + initAnswer(); + }); + initAnswer(); }); @@ -79,7 +109,7 @@ } try { - const ar = await updateAnswer(answer?.key, answer?.id, payload); + const ar = await updateAnswer(answer?.id, payload); if (ar.error) { error = ar.error; return; @@ -93,11 +123,10 @@ } }; - const handleSubmit = async () => { + const fetchSummary = async (key?: string) => { loading = true; - await handleChange({ submit: true }); try { - const sr = await getSummary(answer?.key ?? ""); + const sr = await getSummary(key ?? ""); if (sr.error) { error = sr.error; return; @@ -112,6 +141,15 @@ loading = false; } }; + + const handleSubmit = async () => { + loading = true; + await handleChange({ submit: true }); + view = "summary"; + window.history.pushState({id: answer?.id}, "", `/${answer?.key}/summary`); + await fetchSummary(answer?.key); + loading = false; + };
@@ -125,7 +163,7 @@ {:else if error} - {:else if question && !answer?.submitted_at} + {:else if question && view === "form"}
{question.choice_text} {/if} - {:else if question && answer?.submitted_at && summary} + {:else if question && view === "summary" && summary}

Thank you for your feedback!

diff --git a/front-end/src/lib/api.ts b/front-end/src/lib/api.ts index 088f33f..0bdca32 100644 --- a/front-end/src/lib/api.ts +++ b/front-end/src/lib/api.ts @@ -68,6 +68,11 @@ export const getQuestion = async ( return buildResponse(response); }; +export const getAnswer = async (id: string): Promise> => { + const response = await fetch(`${baseUrl()}/answer/${id}`); + return buildResponse(response); +}; + export const createAnswer = async ( key: string, ): Promise> => { @@ -82,7 +87,6 @@ export const createAnswer = async ( }; export const updateAnswer = async ( - key: string, id: string, payload: UpdateAnswerPayload, ): Promise> => { diff --git a/test/suites/send_feedback.robot b/test/suites/send_feedback.robot index 6e14c3e..298214d 100644 --- a/test/suites/send_feedback.robot +++ b/test/suites/send_feedback.robot @@ -10,7 +10,7 @@ ${URL} %{BASE_URL} *** Test cases *** Open feedback form - New Page ${URL}?key=thumbs + New Page ${URL}/thumbs Wait For Elements State text=How are you feeling? timeout=1 minute Send positive feedback @@ -21,13 +21,13 @@ Check feedback was recorded Get value count Thumbs up 1 Send another positive feedback and check summary - Reload + New Page ${URL}/thumbs Click text=👍 Click text=Submit Get value count Thumbs up 2 Send negative feedback and check summary - Reload + New Page ${URL}/thumbs Click text=👎 Click text=Submit Get value count Thumbs down 1