Requirements
See BRIDGE-2485, BRIDGE-647, BRIDGE-769, BRIDGE-453, BRIDGE-2382, BRIDGE-2383
- The ability to select templates (like the email address verification email) by criteria such as the user's language, or the version of the app;
- The ability to clearly specify the template that should be used if the criteria filtering is ambiguous (unlike app configurations where we just take the first one);
- The ability to audit changes to templates (like study consents, which create immutable revisions... just record who created the revision);
- The possibility later to create custom templates that can be used with scheduling or notifications (right now, you have to manage these outside the system and can't schedule anything);
- The possibility later to add additional documents (the privacy document has long been considered, because it's required to submit an app to the App Store);
- Should replace the templates on the study object; could conceivably replace the study consent documents as well;
Not In Scope (up for discussion)
- Custom template implementation (just ensure the design can accommodate it);
- Additional document implementation (just ensure the design can accommodate it);
- Document inclusions or attachments (e.g. images for HTML documents... unless we really want this);
- Variable resolution (that's part of using the template);
- Publishing documents (that's part of the consent system).
Template Model
Also a description field and such... too lazy to update the image.
You can now add any number of templates for a specific type of template, differentiated by their guids, and selectable using Criteria.
As an example, there might be a VERIFY_EMAIL template for English and for French. The default specified in the Study object could be the English template. Each of these templates can be edited any number of times, creating a stream of TemplateRevision objects, and one of these would be the published revision, that is, the revision that will be returned to the caller when asking for this template. TemplateRevisions are not deletable.
STUDY
defaultTemplates: Map<TemplateType, String>
Where there are multiple templates for a given type, one can be specified as the default if the user doesn't send or doesn't have (or doesn't match) any template through the criteria matching system. Eventually remove all templates from the study object (see Migration below).
CREATE TABLE `Template` (
`guid` VARCHAR(60) NOT NULL,
`studyId` VARCHAR(255) NOT NULL,
`type` ENUM('ACCOUNT_EXISTS', 'ACCOUNT_EXISTS_SMS', 'APP_INSTALL_LINK', 'APP_INSTALL_LINK_SMS', 'EMAIL_SIGN_IN',
'PHONE_SIGN_IN_SMS', 'RESET_PASSWORD', 'RESET_PASSWORD_SMS', 'SIGNED_CONSENT', 'SIGNED_CONSENT_SMS',
'VERIFY_EMAIL', 'VERIFY_PHONE_SMS') NOT NULL, `name` VARCHAR(255) NULL,
`description` TEXT,
`createdOn` BIGINT UNSIGNED NULL,
`modifiedOn` BIGINT UNSIGNED NULL,
`publishedCreatedOn` BIGINT UNSIGNED NULL,
`deleted` BOOLEAN NOT NULL DEFAULT FALSE,
`version` INT UNSIGNED NOT NULL, /* optimistic locking */
PRIMARY KEY (`guid`),
INDEX `type_set_idx` (`studyId`, `type`)
) CHARACTER SET utf8 COLLATE utf8_unicode_ci;
CREATE TABLE `TemplateRevision` (
`templateGuid` VARCHAR(60) NOT NULL,
`createdOn` BIGINT UNSIGNED NULL,
`createdBy` VARCHAR(255) NOT NULL,
`storagePath` VARCHAR(255) NOT NULL,
`subject` VARCHAR(255) NULL,
`mimeType` ENUM('HTML', 'TEXT') NULL,
PRIMARY_KEY (`guid`, `createdOn`)
CONSTRAINT `Templates-Guid-Constraint`
FOREIGN KEY (`templateGuid`) REFERENCES `Templates` (`guid`) ON DELETE CASCADE) CHARACTER SET utf8 COLLATE utf8_unicode_ci;
REST API
All are accessible to developers, workers should be able to get published revision of whatever:
GET /v3/templates?type=<type>
get documents of type (type parameter is probably required)POST /v3/templates
create new document with a single default revision with default content. Pre-fill the first revision to help people get started. GET /v3/templates/<guid>
get specific templatePOST /v3/templates/<guid>
update specific template (not revisioned, but optimistically locked)DELETE /v3/templates/<guid>?physical=true
delete specific template (logical or physical); probably leaves TemplateRevision content on S3GET /v3/templates/<guid>/revisions
get the revisions for a template (createdOn DESC) for history of editsPOST /v3/templates/<guid>/revisions
create a new revision of the given template (cannot update an existing revision)GET /v3/templates/<guid>/revisions/<createdOn>
get a specific revision of a templatePOST /v3/templates/<guid>/revisions/<createdOn>/publish
publish this revision as the revision to use when the template is selected
Notes
Selection
1) get all the documents of the required type and filter using criteria (note that this might be challenging, we'll have to add criteria filtering to a number of places...we might want to move Criteria and CriteriaContext into the RequestContext ThreadLocal);
2) if 1 and only one is filtered, return published revision of that document;
3) otherwise return the published revision for the default template specified for that type in the Study object;
We should also move the signature block into the consent template so it can be localized... although I don't think there's been a single study that has sent out a consent document in the last year or two (or three?).
TemplateService
Sorry the formatting here is difficult to read, due to Jira.
public interface TemplateService {
/**
* Given a criteria context and a template type, return all the templates that match. If one is found through a
* criteria match, return it, otherwise, return the one indicated as the default in the study object's
* configuration.
*/
Template getTemplateForUser(CriteriaContext context, TemplateType type);
/** Get all the templates for a given type. (I am assuming this will not need to be paged). */
List<Template> getTemplatesForType(StudyIdentifier studyId, TemplateType type, boolean includeDeleted);
/** Get a specific template. */
Template getTemplate(StudyIdentifier studyId, String guid);
/** Create a new template. */
GuidVersionHolder createTemplate(StudyIdentifier studyId, TemplateService template);
/**
* Update a template. You can delete it logically, and change the published revision of the associated document with this call as well.
*/
GuidVersionHolder updateTemplate(StudyIdentifier studyId, TemplateService template);
/** Mark a template as deleted. */
void deleteTemplate(StudyIdentifier studyId, String guid);
/** Physically delete the template and all its revisions. */
void deleteTemplatePermanently(StudyIdentifier studyId, String guid);
/** Get a page of revisions from a SQL-type data store. These would not load the document contents from S3 or would not load the
* relevant column if we end up storing the body content in SQL.
*/
PagedResourceList<TemplateRevision> getTemplateRevisions(StudyIdentifier studyId, String guid,
int offsetBy, int pageSize);
/** Get a specific revision. */
TemplateRevision getTemplateRevision(StudyIdentifier studyId, String guid, long createdOn);
/** Create a new revision (this should fail if the createdOn timestamp for a given studyId and GUID already exists). */
CreatedOnHolder createTemplateRevision(StudyIdentifier studyId, TemplateRevision templateRevision);
}
Migration
Can be done in four deployments:
1) Add the new template system in parallel. When reading a template from the existing study object, defer first to the existing template system and use that if a template exists, falling back to the study object template. When writing templates to the study, write to both places, setting up a default template in the templates system if necessary.
2) Create templates for each study from the study object templates.
3) Remove migration bridges to the study object and use only the template system.
4) Delete the fields in the study object itself and remove the fields in the study object schema.