From 44273a000a9692b4efd919c8339054fb882636ce Mon Sep 17 00:00:00 2001 From: Dushan <47317567+dushan-madetech@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:43:06 +0100 Subject: [PATCH] Form documentation (#722) * Form documentation Adding documentation for the form definition including elements such as the sections, subsections, pages and questions * update docs * add link to new docs to form runner doc --- docs/form/form.md | 86 +++++++++++++++++++++++++++++++++++++++++ docs/form/page.md | 28 ++++++++++++++ docs/form/question.md | 82 +++++++++++++++++++++++++++++++++++++++ docs/form/section.md | 26 +++++++++++++ docs/form/subsection.md | 28 ++++++++++++++ docs/form_builder.md | 4 ++ docs/form_runner.md | 4 ++ 7 files changed, 258 insertions(+) create mode 100644 docs/form/form.md create mode 100644 docs/form/page.md create mode 100644 docs/form/question.md create mode 100644 docs/form/section.md create mode 100644 docs/form/subsection.md diff --git a/docs/form/form.md b/docs/form/form.md new file mode 100644 index 000000000..9a3658d64 --- /dev/null +++ b/docs/form/form.md @@ -0,0 +1,86 @@ +## Form Definition + +The current system is built around a form definition written in JSON. At the top level every form will expect to have the following attributes: + +- Form type: this is to define whether the form is a lettings form or a sales form. The questions will differ between the types. +- Start date: the start of the collection window for the form, this will usually be in April. +- End date: the end date of the collection window for the form, this will usually be in July, a year after the start date. +- Sections: the sections in the form, this block is where the bulk of the form definition will be. + +An example of this might look like the following: +```JSON +{ + "form_type": "lettings", + "start_date": "2021-04-01T00:00:00.000+01:00", + "end_date": "2022-07-01T00:00:00.000+01:00", + "sections": { + ... + } +} +``` + +Note that the end date of one form will overlap the start date of another to allow for late submissions. This means that every year there will be a period of time in which two forms are running simultaneously. + +### How is the form split up? + +A summary of how the form is split up is as follows: + +- A form is divided up into one or more sections. +- Each section can have one or more subsections. +- Each subsection can have one or more pages. +- Each page can have one or more questions. + +More information about these form elements can be found in the following links: + +- [Section](docs/form/section.md) +- [Subsection](docs/form/subsection.md) +- [Page](docs/form/page.md) +- [Question](docs/form/question.md) + +### The Form Model, Views and Controller + +Rails uses the Model, View, Controller (MVC) pattern which we follow. + +#### The Form Model + +There is no need to manually initialise a form object as this is handled by the FormHandler class at boot time. If a new form needs to be added then a JSON file containing the form definition should be added to `config/forms` where the FormHandler will be able to locate it and instantiate it. + +A form has the following attributes: + +- name: The name of the form +- setup_sections: The setup section (this is not defined in the JSON, for more information see this) +- form_definition: The parsed form JSON +- form_sections: The sections found within the form definition JSON +- type: The type of form (this is used to indicate if the form is for a sale or a letting) +- sections: The combination of the setup section with those found in the JSON definition +- subsections: The subsections of the form (these live under the sections) +- pages: The pages of the form (these live under the subsections) +- questions: The questions of the form (these live under the pages) +- start_date: The start date of the form, in iso8601 format +- end_date: The end date of the form, in iso8601 format + + +#### The Form Views + +The main view used for rendering the form is the `app/views/form/page.html.erb` view as the Form contains multiple pages (which live in subsections within sections). This page view then renders the appropriate partials for the question types of the questions on the current page. + +We currently have views for the following question types: +- Numerical +- Date +- Checkbox +- Radio +- Select +- Text +- Textarea +- Interruption screen + +Interruption screen questions are radio questions used for soft validation of fields. They usually have yes and no options for a user to confirm a value is correct. + +#### The Form Controller + +The form controller handles the form submission as well as the rendering of the check answers page and the review page. + +### The FormHandler helper class + +The FormHandler helper is a helper that loads all of the defined forms and initialises them as Form objects. It can also be used to get specific forms if needed. + diff --git a/docs/form/page.md b/docs/form/page.md new file mode 100644 index 000000000..ab47b4f0d --- /dev/null +++ b/docs/form/page.md @@ -0,0 +1,28 @@ +## Page + +Pages are under the subsection level of the form definition. A example page might look something like this: + +```JSON +"property_postcode": { + "header": "", + "description": "", + "questions": { + ... + }, + "depends_on": [ + { + "needstype": 1 + } + ] +} +``` + +In the above example the the subsection has the id `property_postcode`. This id is used for the url of the web page, but the underscore is replaced with a hash, so the url for this page would be `[environment-url]/logs/[log-id]/property-postcode` e.g. on staging this url might look like the following: `https://dluhc-core-staging.london.cloudapps.digital/logs/1234/property-postcode`. + +The header is optional but if provided is used for the heading displayed on the page. + +The description is optional but if provided is used for a paragraph displayed under the page header. + +It's worth noting that like subsections a page can also have a `depends_on` which contains the set of conditions that must be met for the section to be accessibile to a data provider. If the conditions are not met then the page is not routed to as part of the form flow. The `depends_on` for a page will usually depend on answers given to questions, most likely to be questions in the setup section. In the above example the page is dependent on the answer to the `needstype` question being `1`, which corresponds to picking `General needs` on that question as displayed to the data provider. + +Pages can contain one or more questions. \ No newline at end of file diff --git a/docs/form/question.md b/docs/form/question.md new file mode 100644 index 000000000..4050531f9 --- /dev/null +++ b/docs/form/question.md @@ -0,0 +1,82 @@ +## Question + +Questions are under the page level of the form definition. A example question might look something like this: + +```JSON +"postcode_known": { + "check_answer_label": "Do you know the property postcode?", + "header": "Do you know the property’s postcode?", + "hint_text": "", + "type": "radio", + "answer_options": { + "1": { + "value": "Yes" + }, + "0": { + "value": "No" + } + }, + "conditional_for": { + "postcode_full": [1] + }, + "hidden_in_check_answers": true +} +``` + +In the above example the the question has the id `postcode_known`. + +The `check_answer_label` contains the text that will be displayed in the label of the table on the check answers page. + +The header is text that is displayed for the question. + +Hint text is optional, but if provided it sits under the header and is normally given to provide the data inputters with guidance when answering the question, for example it might inform them about terms used in the question. + +The type is question type, which is used to determine the view rendered for the question. In the above example the question is a radio type so the `app/views/form/_radio_question.html.erb` partial will be rendered on the page when this question is displayed to the user. + +The `conditional_for` contains the value needed to be selected by the data inputter in order to display another question that appears on the same page. In the example above the `postcode_full` question depends on the answer to `postcode_known` being selected as `1` or `Yes`, this would then display the `postcode_full` underneath the `Yes` option on the page, allowing the provide the provide the postcode if they have indicated they know it. If the user has JavaScript enabled then this realtime conditional display is handled by the `app/frontend/controllers/conditional_question_controller.js` file. + +the `hidden_in_check_answers` is used to hide a value from displaying on the check answers page. You only need to provide this if you want to set it to true in order to hide the value for some reason e.g. it's one of two questions appearing on a page and the other question is displayed on the check answers page. It's also worth noting that you can declare this as a with a `depends_on` which can be useful for conditionally displaying values on the check answers page. For example: + +```JSON +"hidden_in_check_answers": { + "depends_on": [ + { + "age6_known": 0 + }, + { + "age6_known": 1 + } + ] +} +``` + +Would mean the question the above is attached to would be hidden in the check answers page if the value of age6_known is either `0` or `1`. + +The answer the data inputter provides to some questions allows us to infer the values of other questions we might have asked in the form, allowing us to save the data inputters some time. An example of how this might look is as follows: + +```JSON +"postcode_full": { + "check_answer_label": "Postcode", + "header": "What is the property’s postcode?", + "hint_text": "", + "type": "text", + "width": 5, + "inferred_answers": { + "la": { + "is_la_inferred": true + } + }, + "inferred_check_answers_value": { + "condition": { + "postcode_known": 0 + }, + "value": "Not known" + } +} +``` + +In the above example the width is an optional attribute and can be provided for text type questions to determine the width of the text box on the page when when the question is displayed to a user (this allows you to match the width of the text box on the page to that of the design for a question). + +The above example links to the first example as both of these questions would be on the same page. The `inferred_check_answers_value` is what should be displayed on the check answers page for this question if we infer it. If the value of `postcode_known` was given as `0` (which is a no), as seen in the condition part of `inferred_check_answers_value` then we can infer that the data inputter does not know the postcode and so we would display the value of `Not known` on the check answers page for the postcode. + +In the above example the `inferred_answers` refers to a question where we can infer the answer based on the answer of this question. In this case the `la` question can be inferred from the postcode value given by the data inputter as we are able to lookup the local authority based on the postcode given. We then set a property on the case log `is_la_inferred` to true to indicate that this is an answer we've inferred. \ No newline at end of file diff --git a/docs/form/section.md b/docs/form/section.md new file mode 100644 index 000000000..9454a6569 --- /dev/null +++ b/docs/form/section.md @@ -0,0 +1,26 @@ +## Section + +Sections are under the top level of the form definition. A example section might look something like this: + +```JSON +"sections": { + "tenancy_and_property": { + "label": "Property and tenancy information", + "subsections": { + "property_information": { + ... + }, + "tenancy_information": { + ... + } + } + }, + ... +} +``` + +In the above example the section id would be `tenancy_and_property` and its subsections would be `property_information` and `tenancy_information`. + +The label contains the text that users will see for that section in the tasklist page of a case log. + +Sections can contain one or more subsections. \ No newline at end of file diff --git a/docs/form/subsection.md b/docs/form/subsection.md new file mode 100644 index 000000000..b65f064c6 --- /dev/null +++ b/docs/form/subsection.md @@ -0,0 +1,28 @@ +## Subsection + +Subsections are under the section level of the form definition. A example subsection might look something like this: + +```JSON +"property_information": { + "label": "Property information", + "depends_on": [ + { + "setup": "completed" + } + ], + "pages": { + "property_postcode": { + ... + }, + "property_local_authority": { + ... + } + } +} +``` + +In the above example the the subsection has the id `property_information`. The `depends_on` contains the set of conditions that must be met for the section to be accessibile to a data provider, in this example subsection depends on the completion of the setup section/subsection (note that this is a common condition as the answers provided to questions in the setup subsection often have an impact on what questions are asked of the data provider in later subsections of the form). + +The label contains the text that users will see for that subsection in the tasklist page of a case log. + +The pages of the subsection in the example would be `property_postcode` and `property_local_authority`. Subsections can contain one or more pages. \ No newline at end of file diff --git a/docs/form_builder.md b/docs/form_builder.md index 556b086d0..cc1c16db6 100644 --- a/docs/form_builder.md +++ b/docs/form_builder.md @@ -181,6 +181,10 @@ rake form_definition:validate_all This will validate all forms in directories `["config/forms", "spec/fixtures/forms"]` +## Form models and definition + +For information about the form model and related models (section, subsection, page, question) and how these relate to each other follow [this link](docs/form/form.md) + ## Improvements that could be made - JSON schema definition could be expanded such that we can better automatically validate that a given config is valid and internally consistent diff --git a/docs/form_runner.md b/docs/form_runner.md index 23173c74b..cec88153f 100644 --- a/docs/form_runner.md +++ b/docs/form_runner.md @@ -19,3 +19,7 @@ ERB templates: Routes for each form page are generated by looping over each Page instance in each Form instance held by the form handler and defining a `GET` path. The corresponding controller method is also auto-generated with meta-programming via the same looping in `app/controllers/form_controller.rb` All form pages submit to the same controller method (`app/controllers/form_controller.rb#submit_form`) which validates and persists the data, and then redirects to the next form page that identifies as `routed_to` given the current case log state. + +## Form models and definition + +For information about the form model and related models (section, subsection, page, question) and how these relate to each other follow [this link](docs/form/form.md) \ No newline at end of file