Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make run button functional #25

Merged

Conversation

david-mears-2
Copy link
Contributor

  • Add a functional submit button and all the related gubbins: api endpoint and handler, tests.
  • Use a pinia store for largeScreen

You can test this locally by seeing if clicking the run button takes you to a page showing you the dummy run id.

Copy link

codecov bot commented Sep 6, 2024

Codecov Report

Attention: Patch coverage is 85.45455% with 16 lines in your changes missing coverage. Please review.

Project coverage is 61.02%. Comparing base (f430419) to head (3e43583).
Report is 24 commits behind head on main.

Files with missing lines Patch % Lines
server/api/scenarios.post.ts 0.00% 9 Missing and 1 partial ⚠️
layouts/default.vue 81.25% 2 Missing and 1 partial ⚠️
components/ParameterForm.vue 93.54% 2 Missing ⚠️
types/storeTypes.ts 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #25      +/-   ##
==========================================
+ Coverage   58.70%   61.02%   +2.32%     
==========================================
  Files          26       32       +6     
  Lines         724      798      +74     
  Branches       63       74      +11     
==========================================
+ Hits          425      487      +62     
- Misses        290      300      +10     
- Partials        9       11       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@absternator absternator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work !!! looks clean and works great!!!!!! just left some minor comments

formSubmitting.value = true;
const response = await $fetch<NewScenarioData>("/api/scenarios", {
method: "POST",
query: { // Using query instead of body because I couldn't work out how to send a body in the integration test.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should defs use body here... your code shouldn't change to accommodate the tests...

Copy link
Contributor Author

@david-mears-2 david-mears-2 Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a reason to prefer body over query; is there one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantically the use of query params is incorrect here... they are more for filtering, sorting / picking certain ids... when doing post request and sending data its semantically correct to use a body

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - structured data should be sent in a POST body not in a single qs param, that's our standard approach for submitting a run. It's easier to validate, more readable etc.
Must be some way of getting the integration test to accept a body - if not, we might need a different integration test approach!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried for some time to use the nuxt test utils to pass through a body, but there didn't seem to be a way.
To be clear, the R API is still receiving a body, it's only the web app that's taking a query parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried again today with a fresh brain and am making headway :)

});

const appStore = useAppStore();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should move this at top with all variable definitions... nice to keep nicely separated with methods

components/ParameterForm.vue Show resolved Hide resolved
components/ParameterForm.vue Show resolved Hide resolved

if (response) {
const { runId } = response;
navigateToData.value = `/scenarios/${runId}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
navigateToData.value = `/scenarios/${runId}`;
await navigateTo(`/scenarios/${runId}`)

unless reason for assignment before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for testing only, because I couldn't get a spy on navigateTo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this seems like an issue with the testing approach that isn't ideally solved by putting unnecessary stuff in the code. And if we really really need the crud it ought to be commented as such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the comment in the test file - there are two other places it could be, here, and by the data attribute itself.

Copy link
Contributor Author

@david-mears-2 david-mears-2 Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our only option besides this is just not to test the form submission as part of component tests. That means we would rely only on e2e tests for the form submission. But since our e2e test doesn't mock the R API, we wouldn't be able to verify that the correct parameters were passed to it by the component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, until such time as it can test the result page, I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emma found this solution: #29

components/SideBar.vue Outdated Show resolved Hide resolved

export default defineRApiEventHandler(
async (event): Promise<NewScenarioResponse> => {
const query = getQuery(event);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as per prev comment should be reading body not query params


const modelParameters = JSON.parse(query.parameters as string) as ParameterDict;

// Delegate to runScenario so that the logic can be unit-tested.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont need this comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved this comment - which was getting duplicated for each api endpoint - into a new .md "API design decisions.md"


const rApiNewScenarioEndpoint = "/scenario/run";

// Send a request to the R API for a job run.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont neeed comments again.. the code you wrote is clean and understandable

initializeAppState(): void {
this.largeScreen = true;
},
setScreenSize(bool: boolean): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be setScreenSizeToLarge .. also function param should be descrptive .. eg. isLargeScreen rather than bool

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed the function param to be called large, otherwise kept this the same

Base automatically changed from jidea-57-non-country-inputs-front-end to main September 9, 2024 15:18
Copy link
Contributor

@EmmaLRussell EmmaLRussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff, good to get the store in.

Seems like some of the testing was a headache, which I'm probably adding to with some of these comments.. I think it would be really good to avoid some of these test-related-code-polluting additions if possible. Have made a couple of suggestions which you may have already tried. Happy to have a look at any of these if you're sick of bashing against them yourself - can't guarantee to get anywhere, as this is mostly all new to me too, but I think it's worth solving these now if we can, and can avoid proliferation.

@@ -1,9 +1,11 @@
<template>
<div>
<div v-show="pageMounted">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this actually necessary? A Playwright timing issue? It should be possible to write Playwright tests so that they wait for a condition to become true. I think I'd rather have messy tests than somewhat inexplicable properties of the code that are only there because of test behaviour. If it really isn't then let's have a comment explaining it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Luckily, this problem disappears next PR, because by then we can wait for version data as our signal that everything is mounted.

<CForm
v-if="props.metadata && formData"
class="inputs"
:data-test="JSON.stringify(formData)"
role="form"
:data-test-form-data="JSON.stringify(formData)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this feels like it should be avoidable - if it's for component testing, can't we just examine the values in the rendered elements?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the dependency on this from tests. In the component test, I think the test relying on this was redundant anyway.

@@ -36,6 +38,7 @@
autocomplete="off"
:label="option.label"
:value="option.id"
:disabled="!pageMounted"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be shown if page isn't mounted, so not sure this is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in next PR.

@@ -49,7 +52,8 @@
:id="parameter.id"
v-model="formData[parameter.id]"
:aria-label="parameter.label"
class="form-select" :class="[largeScreen ? 'form-select-lg' : '']"
class="form-select" :class="[screenIsLarge ? 'form-select-lg' : '']"
:disabled="!pageMounted"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..ditto..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in next PR.


if (response) {
const { runId } = response;
navigateToData.value = `/scenarios/${runId}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this seems like an issue with the testing approach that isn't ideally solved by putting unnecessary stuff in the code. And if we really really need the crud it ought to be commented as such.

components/SideBar.vue Outdated Show resolved Hide resolved
appStore.initializeAppState();

const setScreenSize = () => {
// As this function uses window, it can only be run client-side, so we have to pass the $pinia instance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// As this function uses window, it can only be run client-side, so we have to pass the $pinia instance
// As this function uses window, it can only be run client-side (post-setup), so we have to pass the $pinia instance

The linkage between these clauses wasn't immediately obvious to me!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through an adventure since writing this, submitted a PR to clarify the docs of the nuxt pinia module, and eventually the maintainer let me know that in fact "you don't need to pass the pinia instance within setup"
vuejs/pinia#2766

As such, the next PR removes this, here: https://github.com/jameel-institute/daedalus-web-app/pull/27/files#diff-433a9abd4ca70520f69fc064c160bb093918ce26dda6087a8bbfdbe2895d1944

await page.click('div[aria-label="Advance vaccine investment"] label[for="medium"]');
await page.click('div[aria-label="Advance vaccine investment"] label[for="low"]');

await expect(page.getByRole("form")).toHaveAttribute("data-test-form-data", "{\"country\":\"United States\",\"pathogen\":\"influenza-1957\",\"response\":\"elimination\",\"vaccine\":\"low\"}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be resorting to hidden attribute tests in e2e tests of forms - we should just be able to look at the values of the inputs.

Copy link
Contributor Author

@david-mears-2 david-mears-2 Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can be confident that the values of the inputs are what we just selected. I think the test to do for the e2e would be to wait until the result page is implemented, and test that the displayed parameters are correct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hang on, that doesn't look like the line (I thought) this was a comment for anymore, I'm pretty sure this was about the stringified JSON - or I thought it was!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looks like it's commenting on the stringified JSON to me!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I'm just blind as a 🦇

it("returns a successful response when the mock server responds successfully", async () => {
const queryString = `parameters={"mockoonResponse":"successful","country":"Thailand","pathogen":"sars-cov-1","response":"no_closure","vaccine":"none"}`;

const response = await nuxtTestUtilsFetch(`/api/scenarios?${queryString}`, { method: "POST" });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the test that was a problem for POSTing a body? I'm not completely sure, but it looks to me like the nuxt test utils uses ofetch: https://github.com/nuxt/test-utils/blob/f7afe52243a044694402fa16ddf51e80626133ab/src/core/server.ts#L4
..and that you should be able to include a POST body in the body option:
https://github.com/unjs/ofetch?tab=readme-ov-file#%EF%B8%8F-json-body

Copy link
Contributor Author

@david-mears-2 david-mears-2 Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My problem had been that I was trying to pass objects into the 'body' property that just got lost in the post, as it were. Strings and objects evaporated. I eventually solved this by finding that I could wrap the parameters in a FormData object, which fetch does accept, and decompose that at the other end.

Comment on lines 167 to 168
// I couldn't find a way to spy on the navigateTo function, so this is a second-best test that we
// at least construct the correct path to navigate to.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, let's not hold up this branch, but it would be really good to find a way to mock global methods, and it seems really weird if the alleged mocking capacities of vitest/nuxt test utils aren't going to let us to that. Tbh if that proves impossible personally I'd rather not clutter up the code with extra props for the sake of unit tests, as long as the navigation etc gets tested in e2e tests. These components are going to be busy and confusing enough as it is, without a bunch of extra stuff that doesn't really need to be there.

Copy link
Contributor

@EmmaLRussell EmmaLRussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think at some point we're going to have to work out how to POST JSON with Nuxt tests, as it really ought to be possible 😆 It's not terrible to post form values as form data but it would be nice to have the option not to..

See previous comment on the mocking navigate controversy - I'd rather take out the props that are just there for unit tests if it's really not possible to mock, and make sure the navigration is tested in e2e. I might have a fiddle around with the mocking, maybe I'll stumble on some other way of doing it... but that can go in another branch.

Comment on lines 137 to 140
const formDataObject = new FormData();
Object.entries(formData.value).forEach(([key, value]) => {
formDataObject.append(key, value.toString());
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd assumed that we were just going to post JSON in the same format that the R API accepts and pass it straight through? You're saying that doesn't work (in the tests), that ordinary objects get turned into... what? Completely empty body?

It's not something to do with needing to set the content type is it? Does it default to POSTing form encoded data so it barfs on plain objects but accepts FormData? Hm, the ofetch docs suggest it should deal with that transparently and add the header itself, but it is supicious... Could try setting that header explicitly?

Copy link
Contributor Author

@david-mears-2 david-mears-2 Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have found something that appears to be working - JSON stringifying the body (in the test). The oftech docs claimed that this happened automatically, but evidently not 😕

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that still it behaves differently under test than in real life, so I had to add in a check for whether the body was being received as an object (as in real life, and in e2e tests) or a JSON parsable string (as in the integration test). I hope this is tolerable, as the price for moving away from FormData.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed. maybe try adding header Content-Type: application/json when doing the POST.

@EmmaLRussell
Copy link
Contributor

See previous comment on the mocking navigate controversy - I'd rather take out the props that are just there for unit tests if it's really not possible to mock, and make sure the navigration is tested in e2e. I might have a fiddle around with the mocking, maybe I'll stumble on some other way of doing it... but that can go in another branch.

This seemed to work for me: https://github.com/jameel-institute/daedalus-web-app/pull/29/files#

@david-mears-2 david-mears-2 force-pushed the jidea-57-non-country-inputs-rebasing-onto-front-end-brach branch from 6530321 to 2a03920 Compare September 12, 2024 12:14
Copy link
Contributor

@EmmaLRussell EmmaLRussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK this one's cooked as far as I'm concerned!

@david-mears-2 david-mears-2 merged commit cdb8997 into main Sep 13, 2024
6 checks passed
@david-mears-2 david-mears-2 deleted the jidea-57-non-country-inputs-rebasing-onto-front-end-brach branch September 13, 2024 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants