Requirements
The Covid scheduling SaaS application (Accommodate) needs a Terms and Conditions acceptance from users before scheduling Covid tests. The data should be captured and fed to warehouse for reporting. Note, its possible to use a deep link to get around the terms, but thats ok. The T&C page originally was only going to show once per user, but then that was changed to show each time the user goes to the application. Any netid holder should be able to use the application
Screenshot
Data reporting
View was created in postgres grouper with users and dates they agreed with terms:
create or replace
view penn_covid_accept_terms_v as
select
subject_id as penn_id,
to_timestamp(value_integer / 1000 ) as terms_accepted_timestamp
from
grouper_aval_asn_mship_v gaamv
where
gaamv.group_id = '3f70d4f1214f40f899cca9d707ddb9df'
and source_id = 'pennperson'
and gaamv.attribute_def_name_name = 'penn:isc:ait:apps:covidScheduling:service:attributes:covidAcceptTerms'
and subject_id is not null
and value_integer is not null
|
Data looks like
Create a table in oracle warehouse and index by penn_id
CREATE TABLE AUTHZADM.penn_covid_accept_terms
(
penn_id VARCHAR2( 20 CHAR) NOT NULL,
terms_accepted_timestamp TIMESTAMP( 6 ) NOT NULL
);
ALTER TABLE penn_covid_accept_terms ADD (
CONSTRAINT penn_covid_accept_terms_PK
PRIMARY KEY
(penn_id, terms_accepted_timestamp)
ENABLE VALIDATE);
CREATE INDEX AUTHZADM.PENN_COVID_AC_TRM_pkey_idx ON AUTHZADM.PENN_COVID_ACCEPT_TERMS
(PENN_ID);
--NOTE: grant to user that SELECTs for snowflake
|
Sync this data over a few times a day
grouper.client.properties
grouperClient.syncTable.covidTerms.databaseFrom=grouper
grouperClient.syncTable.covidTerms.databaseTo=warehouse
grouperClient.syncTable.covidTerms.tableFrom=penn_covid_accept_terms_v
grouperClient.syncTable.covidTerms.tableTo=penn_covid_accept_terms
grouperClient.syncTable.covidTerms.columns=penn_id, terms_accepted_timestamp
grouperClient.syncTable.covidTerms.primaryKeyColumns=penn_id, terms_accepted_timestamp
|
grouper-loader.properties
otherJob.covidTerms.quartzCron= 51 56 10 , 14 , 23 * * ?
otherJob.covidTerms. class =edu.internet2.middleware.grouper.app.tableSync.TableSyncOtherJob
otherJob.covidTerms.syncType=fullSyncFull
otherJob.covidTerms.grouperClientTableSyncConfigKey=covidTerms
|
See it work
Grouper design
- Grouper "custom ui" that anyone can use
- Show the terms, same for all users
- On click, run a GSH script that adds an attribute date to the membership of the user in the group
- Those attribute values can be retrieved via view → table sync to warehouse
See T&C acceptances in Grouper
Members of this group have accepted the terms
https://grouper.apps.upenn.edu/grouper/grouperUi/app/UiV2Main.index?operation=UiV2Group.viewGroup&groupId=3f70d4f1214f40f899cca9d707ddb9df
See attributes on the membership to get the specifics
Setup attributes
We need an attribute on an immediate membership that has a timestamp value type, and supports multi-valued assignments. We only need one attribute name
Allow everyone to use the screen
Note, the "opt in" just means they can hit the button. Being a member of the group doesnt mean anything if someone uses the UI to literally opt in to the group
Grouper "custom ui" configuration externalized text
This is configured in Grouper database configs in the UI: grouper.text.en.us.properties
penn_covid_header = <h1> 2020 Fall Semester: Who Must Be Tested</h1><ul><li>Students, faculty, postdoctoral trainees, blah blah blah Houses.</li><li>All residents blah blah blah sing (CHAS)</li></ul>
penn_covid_enrollmentLabel = <h1>Consent to Testing</h1>The University of Pennsylvania is asking members of blah blah blah related to COVID- 19 ;</li><li>To identify patterns of infection blah blah blah with respect to my test results.</li></ul>
penn_covid_enrollmentButtonText = I Agree
|
Grouper "custom ui" JSON configuration for text
There is externalized text and JSON attributes on the group used for custom ui
# no help link
{ "endIfMatches" : true , "customUiTextType" : "helpLink" , "index" : 0 , "text" : " " }
# this is the logo we put in the container
{ "endIfMatches" : true , "customUiTextType" : "logo" , "index" : 0 , "text" : "../../grouperExternal/public/penn/images/pennCares.png" }
# header text (who must get tested)
{ "endIfMatches" : true , "customUiTextType" : "header" , "index" : 10 , "defaultText" : true , "text" : "${textContainer.text['penn_covid_header']}" }
# body text (terms and conditions)
{ "endIfMatches" : true , "customUiTextType" : "enrollmentLabel" , "index" : 0 , "defaultText" : true , "text" : "${textContainer.text['penn_covid_enrollmentLabel']}" }
# button text
{ "endIfMatches" : true , "customUiTextType" : "enrollButtonText" , "index" : 0 , "defaultText" : true , "text" : "${textContainer.text['penn_covid_enrollmentButtonText']}" }
# show the button irrespective of membership in group (since if in group they still need to press button). The unenroll button isnt really used.
{ "endIfMatches" : true , "customUiTextType" : "unenrollButtonShow" , "index" : 0 , "defaultText" : true , "text" : "false" }
{ "endIfMatches" : true , "customUiTextType" : "enrollButtonShow" , "index" : 0 , "defaultText" : true , "text" : "true" }
|
Add logo into container
Start with the logo, put in the Grouper container in: slashRoot\opt\grouper\grouperWebapp\grouperExternal\public\penn\images\pennCares.png
Grouper "custom ui" configuration for redirect
This JSON tells the UI to redirect to the app after the user agrees to terms
{ "endIfMatches" : true , "customUiTextType" : "redirectToUrl" , "index" : 0 , "text" : "https://upen...ty.com/" , "script" : "${ cu_joinGroupButtonPressed }" }
|
This GSH script will make sure the user is in the group, and a new attribute value is assigned to that membership
{ "endIfMatches" : true , "customUiTextType" : "gshScript" , "index" : 0 , "text" : "${textContainer.text['penn_covid_gsh']}" , "script" : "${ cu_joinGroupButtonPressed }" }
Externalized text:
penn_covid_gsh = group.addMember(subject, false );$newline$Membership membership = MembershipFinder.findImmediateMembership(grouperSession, group, subject, true );$newline$membership.getAttributeValueDelegate().addValueTimestamp( "penn:isc:ait:apps:covidScheduling:service:attributes:covidAcceptTerms" , new java.sql.Timestamp(System.currentTimeMillis()));
Note, you put $newline$ in between lines so it is in one line in externalized text, so the GSH is actually:
group.addMember(subject, false );
Membership membership = MembershipFinder.findImmediateMembership(grouperSession, group, subject, true );
membership.getAttributeValueDelegate().addValueTimestamp( "penn:isc:ait:apps:covidScheduling:service:attributes:covidAcceptTerms" , new java.sql.Timestamp(System.currentTimeMillis()));
|