In the old codebase, Questions were associated with Datasets at the site level, i.e. one set of Questions was used for all Datasets. This is no longer a restriction. Questions are grouped in QuestionSets, and each site can configure one QuestionSet for use by all of the site's Datasets, or a QuestionSet can be defined for each Dataset individually.
In the CMS, the Datasets sheet has a new QuestionSetURL
column. The value is the url pointing to the QuestionSet
config sheet. A site-wide Question Set url can be added to the site config page under the key question_set_url
. A further system-wide fallback Question Set url can be set as an env var, FALLBACK_QUESTIONSET
(this is provided for backward-compatibility to smooth migration to the new system).
Datasets also have a new UpdateEvery
column. The value for each cell in this column should be a time interval as a string in the question: "Data should be updated every {{ time interval }}." E.g. year, month, 6 months, day, second Thursday, etc.
The new Question Set config sheet is like the Site
config. It has key
and value
columns. It expects a questions
key, the value is the url the questions sheet, and a question_set_schema
key, the value is the question set schema in json format (see the Question Set Schema section below).
The Dataset property Title
has been renamed Name
in the spreadsheet, to reflect the database fieldname used once loaded, and allow translations to work. Title
will continue to work as a fallback, but isn't translatable with Title@LC
.
Some question types require extra configuration. For example, the likert
question type has configuration to define the number of options, and description and value for each option. This will be used to setup the survey form. There is a column called 'Config' in the spreadsheet where a small snippet of json can be added. Below are example configurations for question types that require it:
[
{"description": "None", "value": "0"},
{"description": "Some", "value": "1"},
{"description": "All", "value": "2"}
]
Once loaded into the database, each Dataset
has a foreignKey to the QuestionSet
it uses. Questions
now have a foreignKey to the parent QuestionSet
(Question.questionsetid
). Questions
have altered their primaryKey to be (id, questionsetid). The QuestionSet.id
primaryKey is a hash of the site
+ qsurl
. This ensures that sites using a Question Set located at the same url will load into the database as distinct QuestionSet
instances.
Questions
now have a QuestionShort
column used as a very short version of the question text for open questions (scored questions). Values should be able to have 'It is' and 'It is not' prepended to them, e.g. Available free online.
A question set schema provides the structure and dependency graph for a set of Questions
.
This decouples Question
objects from information about the relationships and hierarchy between Questions
, leaving Questions
as stand-alone as possible.
Here is a question set schema for this example set of questions:
- Do you like apples?
- 1.2. Do you like bananas instead? (shown when 1 is 'No')
- Do you like RED apples? (enabled if 1 is 'Yes')
- Have you eaten a red apple today? (enabled if 2 is 'Yes')
- 3.1. Did it keep the doctor away? (shown when 3 is 'Yes')
[
{
// The question is "Do you like apples?"
"id": "like_apples",
"position": 1,
"defaultProperties": {
"visible": true,
"enabled": true,
"required": true
}
},
{
// The question is "Do you like bananas instead?"
"id": "bananas_instead",
"position": 1.1,
"defaultProperties": {
"visible": false,
"enabled": false,
"required": false
},
"ifProvider": [
{
"providerId": "like_apples",
"value": "No",
"properties": {
"visible": true,
"enabled": true,
"required": true
}
}
]
},
{
// The question is "Do you like RED apples?"
"id": "apple_colour",
"position": 2,
"defaultProperties": {
"visible": true,
"enabled": false,
"required": false
},
"ifProvider": [
{
"providerId": "like_apples",
"value": "Yes",
"properties": {
"enabled": true,
"required": true
}
}
]
},
{
// The question is "Have you eaten a red apple today?"
"id": "red_apple_today",
"position": 3,
"defaultProperties": {
"visible": true,
"enabled": false,
"required": false
},
"ifProvider": [
{
"providerId": "apple_colour",
"value": "Yes",
"properties": {
"enabled": true,
"required": true
}
}
]
},
{
// The question is "Did it keep the doctor away? (optional)"
"id": "doctor_away",
"position": 3.1,
"defaultProperties": {
"visible": false,
"enabled": false,
"required": false
},
"ifProvider": [
{
"providerId": "red_apple_today",
"value": "Yes",
"properties": {
"visible": true,
"enabled": true
}
}
]
}
]
The schema above must validate against the following JSON Schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"question": {
"title": "Question",
"description": "Determines the default and dependent properties for a Question.",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a question within a question set.",
"type": "string"
},
"position": {
"description": "The hierarchical position of the question within",
"type": "number"
},
"defaultProperties": {
"description": "An object containing the default properties for the visible state of the Question if no subsequent conditions are met.",
"type": "object",
"properties": {
"required": {"type": "boolean"},
"enabled": {"type": "boolean"},
"visible": {"type": "boolean"}
}
},
"ifProvider": {
"description": "An array of objects containing conditional logic to determine the state of Question properties dependent on the value of other 'Provider' Questions in the Question Set. First matching member takes presidents.",
"type": "array",
"items": {
"properties": {
"providerId": {
"description": "The ID of the Question on which this Question depends.",
"type": "string"
},
"value": {
"description": "The expected value that will trigger this condition.",
"type": ["string", "boolean", "number"]
},
"isNotEmpty": {
"description": "A boolean to determine whether an expected value is not an empty string, array or object, triggering the condition",
"type": "boolean"
},
"properties": {
"description": "The properties to set if the Question with dependentId returns the value.",
"type": "object",
"properties": {
"required": {"type": "boolean"},
"enabled": {"type": "boolean"},
"visible": {"type": "boolean"}
}
}
},
"oneOf": [
{"required": ["value", "providerId", "properties"]},
{"required": ["isNotEmpty", "providerId", "properties"]}
]
}
},
"score": {
"description": "An object defining the scoring characteristics for this question.",
"type": "object",
"properties": {
"weight": {"type": "number"}
}
}
},
"required": ["id"]
}
},
"title": "QuestionSet",
"description": "A set of Question objects.",
"type": "array",
"items": {"$ref": "#/definitions/question"}
}
Schema validity can be checked with online services, such as http://www.jsonschemavalidator.net/. And Question Set Schemas can be built with http://jeremydorn.com/json-editor/
The old templates directory has been renamed views_old
. New templates have been moved to views
. Both directories are referenced by the app as places where templates can be located, with the intention of eventually removing views_old
completely once migration is complete.
Static assets have been moved from public
to static
.
The Survey and the Index sites make use of the same templates. Differences between the two sites are managed by the is_index
flag.