-
Notifications
You must be signed in to change notification settings - Fork 1
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
Convert parameter metadata into a front-end form #23
Conversation
import type { EventHandler, EventHandlerRequest } from "h3"; | ||
import type { EventHandler, H3Event } from "h3"; | ||
import type { ApiResponse } from "@/types/daedalusApiResponseTypes"; | ||
|
||
export const defineEventHandlerWithErrors = <T extends EventHandlerRequest, D>( | ||
handler: EventHandler<T, D>, | ||
): EventHandler<T, D> => | ||
defineEventHandler<T>(async (event) => { | ||
const response = await handler(event) as ApiResponse; | ||
// A wrapper for Nuxt's defineEventHandler that handles errors from the R API. | ||
export const defineRApiEventHandler = ( | ||
callback: (event: H3Event) => Promise<ApiResponse>, | ||
): EventHandler => | ||
defineEventHandler(async (event) => { | ||
const response = await callback(event) as ApiResponse; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes were motivated by this comment: #10 (comment)
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23 +/- ##
==========================================
+ Coverage 54.02% 58.70% +4.67%
==========================================
Files 22 26 +4
Lines 509 724 +215
Branches 35 63 +28
==========================================
+ Hits 275 425 +150
- Misses 226 290 +64
- Partials 8 9 +1 ☔ View full report in Codecov by Sentry. |
8511426
to
c092e14
Compare
This was to avoid having a handler fed into a another defineEventHandler which would wrap the first EventHandler in a second EventHandler. Although the docs pointed us in the direction of doing such a double-wrapping, we found this way to avoid doing so. A downside of this is that endpoints are forced to use a specific function (e.g. 'defineEventHandler' or in this case 'defineCachedEventHandler') since they all route through the wrapper's choice of function - this means if we wanted to use e.g. defineLazyEventHandler from h3 in some endpoint, it (probably?) couldn't use our custom wrapper, unless we introduce some switch statement that lets endpoints specify which EventHandler wrapping function to use. I am aware of the following wrapping function choices: - defineEventHandler - defineCachedEventHandler - defineLazyEventHandler
Cacheing was too complicated - there was no easy way to purge the cache for testing different responses from the same endpoint within a test suite.
fcf2714
to
40bcb92
Compare
40bcb92
to
0ee4b83
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Couple of minor comments. That coreui eccentricity is a bit concerning...
components/ParameterForm.vue
Outdated
case "vaccine": | ||
return "cilIndustry"; | ||
case "pathogen": | ||
return "cilBug"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bug is cute but I wonder if it's going to make people immersed in disease response think about vector-borne illness particularly. And maybe cute isn't really appropriate anyway!.. Not sure - see what other people think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also - arguably the preferred icon should be provided as part of the metadata too.. It is a presentation detail, but there could be a layer of abstraction from API icon type to front end icon type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
arguably the preferred icon should be provided as part of the metadata too
I'm not sure I see the benefit. If there is an abstraction layer, we'd still end up with the web app having a dict look up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You make a good point about the bug icon, I wasn't sure myself. I will look for a little virus icon that matches our style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The colour's a little off, making my eye twitch, but I hope people can live with it for now because I couldn't quickly fix it (when you have inline svg you can change a fill attribute, but that doesn't seem to work when it's an svg loaded in an img tag)
components/ParameterForm.vue
Outdated
<CFormSelect | ||
:id="globeParameter.id" | ||
v-model="formData[globeParameter.id]" | ||
:data-test="`${globeParameter.id}-select`" | ||
:label="globeParameter.label" | ||
:aria-label="globeParameter.label" | ||
:options="selectOptions(globeParameter)" | ||
:size="largeScreen ? 'lg' : ''" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there's a bit of repetition here with the select parameter types, I wonder if it would be as well to just have a single v-for
for both select and globe parameter types. That way you'll also be able to include the drop-down for the globe parameter in the position returned by the metadata, which would be more flexible I think. It doesn't necessarily feel the most natural to have the country parameter last (or first).
(And if we're going to honour the parameter order defined by the metadata it should be the same v-for
for numeric parameters too eventually.)
Would mean slightly rejigging the logic for getting the icons etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point
components/ParameterForm.vue
Outdated
|
||
const props = defineProps<{ | ||
globeParameter: Parameter | undefined | ||
metaData: MetaData | undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metaData: MetaData | undefined | |
metadata: Metadata | undefined |
🙏
components/ParameterForm.vue
Outdated
} | ||
|
||
return accumulator; | ||
}, {} as { [key: string]: any }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}, {} as { [key: string]: any }), | |
}, {} as { [key: string]: string | number }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out one of the 3rd-party components we're using, the radio button one, dictates that the type signature for the 'v-model' prop -- (which it translates into 'modelValue' behind the scenes!) -- is string | boolean | unknown[] | undefined
. Notice it does not allow number.
From this, I will omit the Array type because we expect not to have multi-selects here.
I think I will keep the number type around as suggested, so it can be used by numeric inputs and we can do calculations with the value; and I'll just cast all values into strings when they are passed into the radio button v-model prop.
components/ParameterForm.vue
Outdated
}>(); | ||
|
||
const formData = ref( | ||
// Create a new object with keys set to the id values of the metaData.parameters array of objects, and all values set to refs with default values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than these individual refs, how about using reactive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to work:
const formData = ref(
props.metadata?.parameters.reduce((acc, { id, defaultOption, options }) => {
acc[id] = defaultOption || options[0].id;
return acc;
}, {} as { [key: string]: string | number }),
);
And swapping out ref
/reactive
still passes all tests. So we can avoid having individual ref
s as I had before while still wrapping the whole thing in a ref()
instead of a reactive()
. Personally, I prefer the explicitness of having to type .value
, which reminds me when I'm using something that's reactive.
components/ParameterForm.vue
Outdated
const eachOptionIsASingleWord = parameter.options.filter((option) => { | ||
return option.label.includes(" "); | ||
}).length === 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const eachOptionIsASingleWord = parameter.options.filter((option) => { | |
return option.label.includes(" "); | |
}).length === 0; | |
const eachOptionIsASingleWord = !parameter.options.some((option) => { | |
return option.label.includes(" "); | |
}); |
const largeScreen = ref(true); | ||
const breakpoint = 992; // CoreUI's "lg" breakpoint | ||
const setFieldSizes = () => { | ||
if (window.innerWidth < breakpoint) { | ||
largeScreen.value = false; | ||
} else { | ||
largeScreen.value = true; | ||
} | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is repeated in SideBar - should really only be implemented in one place and ideally called in one place and then made available to all components. Could be put in the store and made reactive (in another ticket!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add this to the app-store plan!
@@ -7,6 +7,7 @@ ENV NODE_ENV=production | |||
WORKDIR /src | |||
|
|||
COPY . . | |||
RUN npm install -g npm@latest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did out of date npm cause problems in this ticket? Could update the base image if you want to use a more recent npm. Oh, is this related to rollup issue you describe below? Either way could maybe put a comment here because I'm a still a bit confused!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it was related, but it was something I did while trying to debug the rollup issue. While working on that, I updated my local npm version, which then put my machine 1 minor version ahead of the one used in the Docker container. It seemed like a good idea to use the latest npm available in both places?
package.json
Outdated
"3": "the installation section of https://pinia.vuejs.org/ssr/nuxt.html", | ||
"4": "==============================", | ||
"5": "@rollup/rollup-linux-x64-gnu is an optional dependency as a fix for the issue that Rollup", | ||
"6": "describes here: https://github.com/rollup/rollup/blob/f83b3151e93253a45f5b8ccb9ccb2e04214bc490/native.js#L59", | ||
"7": "and which occurred for us when doing an installation with npm on Docker on CI. Their suggested fix", | ||
"8": "does not work for our use case, because removing package-lock.json prevents the use of `npm ci`, so instead", | ||
"9": "we use the solution suggested here: https://github.com/vitejs/vite/discussions/15532#discussioncomment-10192839" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbh, I find this trick for putting comments into package.json pretty unreadable. Given that the dependencies etc being referred to aren't actually in the vicinity of the relevant section of package.json anyway, I'm not sure there's much benefit in keeping them here. How about moving them to a section in the README file dedicated to npm dependency notes? That way you could make all these urls into working links!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds better - they're pretty fiddly to write as well because of the line numbers!
types/daedalusApiResponseTypes.ts
Outdated
ordered: boolean | ||
options: Array<ParameterOption> | ||
} | ||
export interface MetaData { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think MetaData needs a hump in the middle... 🐫
This helps us to preserve the order of parameters in the metadata.
We found the documentation and behavior of the CFormSelect component too confusing to be worth any benefit. In particular, it did not seem to honour the initial v-model value, so we had to manually set the 'selected' attribute for each option (which results in a recalculation of options every time one is selected) in order to get the correct default value to be honoured - and even then, it was confusingly slow or flakey about it.
6a756bb
to
529fefc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good! Are we supposed to include an attribution for that wikimedia svg?
Well caught. Yes we are, "in any reasonable manner". I shall add a note to the ticket for the acknowledgements page (that sounds like less work than adapting a new svg with a more licentious license). https://commons.wikimedia.org/wiki/File:Coronavirus_icon.svg |
Thanks, sounds good |
In this PR we go as far as using the metadata response from the R API (which contains information about the model parameters) to render a form. This front-end component (ParameterForm.vue) can currently cope with two types of parameterType: select, and globeSelect, both of which result in the same kind of form input. The resulting form input is rendered depending on the number and length of the options either as a select input or a set of radio buttons (stylised to look like a row of buttons).
This form can't yet be submitted - that's for the next PR.
We could have put metadata info into a pinia AppStore or some such, but since it's only used in one page (two components) for now, I haven't bothered with it yet; but I'm bearing in mind that this might be the simpler way forward. I will probably do that in the next PR too. This AppStore could also hold the 'versions' API response.