Skip to content
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

Feature: Online Survey with Auth and Case components; Case and Online Survey APIs; "Locked" Online Surveys with Links from Case Event Forms #3710

Closed
Closed
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1630bae
Add components for auth in online-survey-app
May 16, 2024
8971b53
Add online survey get and save form response from case event form id
May 23, 2024
d4a713c
Working version of online-survey login routing
May 29, 2024
bc72f19
Fix release online survey script copying of form directory
May 30, 2024
aaf3809
Add server-side APIs to create Case, Event and Form
May 30, 2024
9752ad5
Fix unfinished case-api function
May 31, 2024
a92d271
Fix bugs in case-api and add user-profile create api
May 31, 2024
69fce19
Add app config 'requireAccessCode' for users to lock access to online…
May 31, 2024
d852178
Reorder span items in release online survey component
Jun 3, 2024
1ef7c00
Include custom-scripts in online-survey releases
Jun 3, 2024
c4b6c68
Online Survey - save case variables to local storage
Jun 3, 2024
4d8c5b6
Add case module to online-survey-app
Jun 4, 2024
69a5108
Allow online survey link for case event form; online-survey custom-lo…
Jun 4, 2024
0b075e2
Ensure auth works for case in online surveys
Jun 4, 2024
c565cec
Editor event form list items get a copy link button
Jun 4, 2024
0360347
Add menu to event form list for survey link copy, qr, and open
Jun 5, 2024
1e0077c
Add getOnlineSurvey and getCaseEventFormSurveyLinks APIs
Jun 7, 2024
5837be6
Fix up case create api
Jun 8, 2024
1853c34
Update /case/createParticipant API
Jun 8, 2024
901ea4c
Add case service set context and caseSErvice window var for online su…
Jun 8, 2024
3640ef6
Fix bugs in case-api and case class
Jun 8, 2024
78c20ac
Add eventFormRedirect to online surveys
Jun 8, 2024
992c76f
case api getEventFormSurveyLinks outputs a JSON object with list of l…
Jun 10, 2024
dca0786
Fix setting event id in case creation api
Jun 11, 2024
eaa0bbb
Fix typo in content-set.md
Jun 11, 2024
26e197f
Event form link copy not allowed with http
Jun 11, 2024
e65a88d
Add case, event and form info to response
Jun 11, 2024
9f40001
Build custom-scripts when releasing online app
Jun 11, 2024
eaebe51
use surveyLinkUrl in event form list item
Jun 12, 2024
ac3d58b
Create participant event forms when adding participant through api
Jun 12, 2024
697a9f1
Set event and form name from definition in creation api
Jun 12, 2024
45ed8ee
Add helpLink config for online-survey to enable a custom link from an…
Jun 12, 2024
ed615fc
Fix creation of event forms for participant role in case api
Jun 12, 2024
175787e
Revert addition of all client/app-config.json; only set helpLink
Jun 12, 2024
8c2dbc6
Fix GROUP_ID in release online survey app
Jun 15, 2024
981d49b
Add timeout and logout to online survey
Jun 15, 2024
526f837
Add logout to editor app after warning
Jun 15, 2024
88071e5
Add process monitor dialogs to publish/unpublish online survey
Jun 15, 2024
59d05b7
Add expiry logout in online survey after warning
Jun 15, 2024
3eaa97a
Save-as-you-go in online surveys with case
Jun 15, 2024
a0011c2
Remove online survey getResponse API
Jun 15, 2024
21f922e
Add online-survey-app logout button and return to case url
Jun 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ export class SyncingService {
doc['items'][0]['inputs'].push({ name: 'tabletUserName', value: username });
// Redact any fields marked as private.
doc['items'].forEach(item => {
item['inputs'].forEach(input => {
item['inputs'].forEach(input => {
if (input.private) {
input.value = '';
}
9 changes: 5 additions & 4 deletions develop.sh
Original file line number Diff line number Diff line change
@@ -75,13 +75,13 @@ fi

if echo "$T_MODULES" | grep mysql; then
./mysql-create-dirs.sh
fi

if echo "$T_USE_MYSQL_CONTAINER" | grep "true"; then
if echo "$T_USE_MYSQL_CONTAINER" | grep "true"; then
./mysql-start-container.sh
echo "Waiting 60 seconds for mysql container to start..."
sleep 60
./mysql-setup.sh
sleep 60
./mysql-setup.sh
fi
fi

if echo "$T_MYSQL_PHPMYADMIN" | grep "TRUE"; then
@@ -230,6 +230,7 @@ OPTIONS="--link $T_COUCHDB_CONTAINER_NAME:couchdb \
--volume $(pwd)/editor/src:/tangerine/editor/src:delegated \
--volume $(pwd)/translations:/tangerine/translations:delegated \
--volume $(pwd)/online-survey-app/src:/tangerine/online-survey-app/src:delegated \
--volume $(pwd)/online-survey-app/dist:/tangerine/online-survey-app/dist:delegated \
--volume $(pwd)/tangy-form-editor:/tangerine/tangy-form-editor:delegated \
--volume $(pwd)/tangy-form:/tangerine/tangy-form:delegated \
tangerine/tangerine:local
2 changes: 1 addition & 1 deletion docs/editor/content-sets.md
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ Be sure to update any cron jobs to include the new build commands if they are us

``
git pull
npm rn install-server
npm run install-server
npm build
``

5 changes: 4 additions & 1 deletion editor/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -171,7 +171,10 @@ export class AppComponent implements OnInit, OnDestroy {
this.isConfirmDialogActive = false;
} else {
await this.logout();
}
}
} else if (Date.now() > expiryTimeInMs && this.isConfirmDialogActive) {
// the token expired, and we warned them. Time to log out.
await this.logout();
}
}

1 change: 1 addition & 0 deletions editor/src/app/case/classes/event-form-definition.class.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ export class EventFormDefinition {
allowDeleteIfFormNotStarted:string
onEventFormOpen?:string
onEventFormClose?:string
allowOnline?:boolean

constructor() {
}
Original file line number Diff line number Diff line change
@@ -5,6 +5,22 @@
<div [innerHTML]="renderedTemplateListItemPrimary|unsanitizeHtml"></div>
<div [innerHTML]="renderedTemplateListItemSecondary|unsanitizeHtml" secondary></div>
</div>
<span *ngIf="!eventFormArchived && !eventForm.formResponseId && canLinkToOnlineSurvey">
<button class="tangy-small-list-icon" [matMenuTriggerFor]="linkMenu" (click)="showLinkMenu(); $event.stopPropagation()">
<mwc-icon>share</mwc-icon>
</button>
<mat-menu #linkMenu="matMenu">
<button mat-menu-item tangy-small-list-icon (click)="onCopyLinkClick()">
<mwc-icon>link</mwc-icon>
</button>
<button mat-menu-item tangy-small-list-icon (click)="onQRCodeLinkClick()">
<mwc-icon>qr_code</mwc-icon>
</button>
<button mat-menu-item tangy-small-list-icon (click)="$event.stopPropagation()">
<a href="{{surveyLinkUrl}}" target="_new"><mwc-icon>open_in_new</mwc-icon></a>
</button>
</mat-menu>
</span>
<span *ngIf="canUserDeleteForms">
<button (click)="onDeleteFormClick(); $event.stopPropagation()" class="tangy-small-list-icon">
<mwc-icon>delete</mwc-icon>
Original file line number Diff line number Diff line change
@@ -13,8 +13,9 @@ import { TangyFormService } from 'src/app/tangy-forms/tangy-form.service';
import { CaseService } from '../../services/case.service';
import { AppConfigService } from 'src/app/shared/_services/app-config.service';
import { t } from 'tangy-form/util/t.js'


import { GroupsService } from 'src/app/groups/services/groups.service';
import { TangyErrorHandler } from 'src/app/shared/_services/tangy-error-handler.service';
import * as qrcode from 'qrcode-generator-es6';

@Component({
selector: 'app-event-form-list-item',
@@ -47,20 +48,37 @@ export class EventFormListItemComponent implements OnInit {
canUserDeleteForms: boolean;
groupId:string;
eventFormArchived: boolean = false;
canLinkToOnlineSurvey: boolean = false;
response:any
surveyLinkUrl:string;

constructor(
private formService: TangyFormService,
private ref: ChangeDetectorRef,
private router:Router,
private caseService: CaseService
private caseService: CaseService,
private groupsService: GroupsService,
private tangyErrorHandler: TangyErrorHandler
) {
ref.detach();
}

async ngOnInit() {
this.groupId = window.location.pathname.split('/')[2]

const group = await this.groupsService.getGroupInfo(this.groupId);
const groupOnlineSurveys = group?.onlineSurveys ?? [];

this.canLinkToOnlineSurvey = groupOnlineSurveys.some(survey =>
survey.published &&
survey.formId === this.eventFormDefinition.formId &&
this.eventFormDefinition.allowOnline
);

if (this.canLinkToOnlineSurvey) {
this.surveyLinkUrl = `/releases/prod/online-survey-apps/${this.groupId}/${this.eventFormDefinition.formId}/#/case/event/form/${this.case._id}/${this.caseEvent.id}/${this.eventForm.id}`;
}

this.canUserDeleteForms = ((this.eventFormDefinition.allowDeleteIfFormNotCompleted && !this.eventForm.complete)
|| (this.eventFormDefinition.allowDeleteIfFormNotStarted && !this.eventForm.formResponseId));
const response = await this.formService.getResponse(this.eventForm.formResponseId);
@@ -131,4 +149,35 @@ export class EventFormListItemComponent implements OnInit {
onUnarchiveFormClick() {
this.formUnarchivedEvent.emit(this.eventForm.id);
}

showLinkMenu() {
this.ref.detectChanges()
}

onCopyLinkClick() {
const url = `${window.location.origin}/${this.surveyLinkUrl}`;
try {
navigator.clipboard.writeText(url);

this.tangyErrorHandler.handleError('Online Survey link copied to clipboard');
} catch (err) {
let errMsg = 'Failed to copy link to clipboard';
if (window.location.origin.startsWith('http://')) {
errMsg = 'Copy to clipboard is not supported with http://.';
}
this.tangyErrorHandler.handleError(errMsg);
}
}

onQRCodeLinkClick() {
const url = `${window.location.origin}${this.surveyLinkUrl}`;

const qr = new qrcode.default(0, 'H')
qr.addData(`${url}`)
qr.make()
window['dialog'].innerHTML = `<div style="width:${Math.round((window.innerWidth > window.innerHeight ? window.innerHeight : window.innerWidth) *.6)}px" id="qr"></div>`
window['dialog'].open()
window['dialog'].querySelector('#qr').innerHTML = qr.createSvgTag({cellSize:500, margin:0,cellColor:(c, r) =>''})
}

}
4 changes: 2 additions & 2 deletions editor/src/app/core/auth/_components/login/login.component.ts
Original file line number Diff line number Diff line change
@@ -41,10 +41,10 @@ export class LoginComponent implements OnInit {
if (await this.authenticationService.login(this.user.username, this.user.password)) {
this.router.navigate(['/projects']);
} else {
this.errorMessage = _TRANSLATE('Login Unsuccesful');
this.errorMessage = _TRANSLATE('Login Unsuccessful');
}
} catch (error) {
this.errorMessage = _TRANSLATE('Login Unsuccesful');
this.errorMessage = _TRANSLATE('Login Unsuccessful');
console.error(error);
}
}
Original file line number Diff line number Diff line change
@@ -3,15 +3,7 @@
<h2>Instructions: </h2>

<div>
In the Unpublished Surveys section, click the
<button class="info-button">
<i class="material-icons tangy-location-list-icon">published_with_changes</i>
</button>
button to "Publish" an online survey. It will then be listed in the Published Surveys section.
</div>

<div>
In the Published Surveys section, use the link
In the <strong>Published Surveys</strong> section, use the link
<button class="info-button">
<i class="material-icons tangy-location-list-icon">link</i>
</button>
@@ -21,14 +13,40 @@ <h2>Instructions: </h2>
<i class="material-icons tangy-location-list-icon">unpublished</i>
</button>
button to "Un-publish" an online survey.
The
<button class="info-button">
<i class="material-icons tangy-location-list-icon">lock</i>
</button> button appears for surveys that require a Device User and Access Code.
</div>

<div>
In the <strong>Unpublish Surveys</strong> section, click the
<button class="info-button">
<i class="material-icons tangy-location-list-icon">published_with_changes</i>
</button>
button to "Publish" an online survey. It will then be listed in the Published Surveys section.
To 'Publish' a survey and require a Device User to provide an Access Code, click the
<button class="info-button">
<i class="material-icons tangy-location-list-icon">lock</i>
</button> button.
</div>

<div>

</div>

</div>
<h2 class="tangy-foreground-secondary">{{'Published Surveys'|translate}}</h2>
<mat-list class="drag-list">
<mat-list-item class="drag-item" *ngFor="let form of publishedSurveys; let index=index">
<span>{{index+1}}</span>
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span>&nbsp;&nbsp;</span>
<span class="tangy-spacer" [innerHTML]="form.title|unsanitizeHtml"></span>
<span *ngIf="form.locked">
<button mat-icon-button class="lock-button">
<i class="material-icons mat-32 tangy-location-list-icon">lock</i>
</button>
</span>
<span>{{form.updatedOn|date :'medium'}}
</span>

@@ -47,14 +65,15 @@ <h2 class="tangy-foreground-secondary">{{'Unpublished Surveys'|translate}}</h2>
<span>{{index+1}}</span>
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="tangy-spacer" [innerHTML]="form.title|unsanitizeHtml"></span>

<span >{{form.updatedOn|date :'medium'}}
<span *appHasAPermission="let i;group:group._id; permission:'can_manage_forms'">
<button mat-icon-button (click)="publishSurvey(form.id, form.title)">
<button mat-icon-button (click)="publishSurvey(form.id, form.title, false)">
<i class="material-icons mat-32 tangy-location-list-icon">published_with_changes</i>
</button>
<button mat-icon-button (click)="publishSurvey(form.id, form.title, true)">
<i class="material-icons mat-32 tangy-location-list-icon">lock</i>
</button>
</span>

<span >{{form.updatedOn|date :'medium'}}
</span>
</mat-list-item>
</mat-list>
Original file line number Diff line number Diff line change
@@ -5,6 +5,9 @@ import { TangyErrorHandler } from 'src/app/shared/_services/tangy-error-handler.
import { _TRANSLATE } from 'src/app/shared/_services/translation-marker';
import { GroupsService } from '../services/groups.service';
import { TangerineFormsService } from '../services/tangerine-forms.service';
import { ProcessMonitorService } from 'src/app/shared/_services/process-monitor.service';
import { ProcessMonitorDialogComponent } from 'src/app/shared/_components/process-monitor-dialog/process-monitor-dialog.component';
import { MatDialog } from '@angular/material/dialog';

@Component({
selector: 'app-release-online-survey',
@@ -19,10 +22,15 @@ export class ReleaseOnlineSurveyComponent implements OnInit {
group;
publishedSurveys;
unPublishedSurveys;
dialogRef:any

constructor(private route: ActivatedRoute,
private groupService: GroupsService,
private errorHandler: TangyErrorHandler,
private tangyFormService: TangerineFormsService) { }
private tangyFormService: TangerineFormsService,
private processMonitorService: ProcessMonitorService,
private dialog: MatDialog,
) { }

async ngOnInit() {
this.groupId = this.route.snapshot.paramMap.get('groupId');
@@ -32,6 +40,20 @@ export class ReleaseOnlineSurveyComponent implements OnInit {
url: 'onlineSurvey'
}
];
this.processMonitorService.change.subscribe((isDone) => {
if (this.processMonitorService.processes.length === 0) {
this.dialog.closeAll()
} else {
this.dialog.closeAll()
this.dialogRef = this.dialog.open(ProcessMonitorDialogComponent, {
data: {
messages: this.processMonitorService.processes.map(process => process.description).reverse()
},
disableClose: true
})
}
})

await this.getForms();
}

@@ -46,24 +68,32 @@ export class ReleaseOnlineSurveyComponent implements OnInit {
this.publishedSurveys = surveyData.filter(e => e.published);
this.unPublishedSurveys = surveyData.filter(e => !e.published);
}
async publishSurvey(formId, appName) {
async publishSurvey(formId, appName, locked) {
const process = this.processMonitorService.start('publishSurvey', 'Publishing Survey');

try {
await this.groupService.publishSurvey(this.groupId, formId, 'prod', appName);
await this.groupService.publishSurvey(this.groupId, formId, 'prod', appName, locked);
await this.getForms();
this.errorHandler.handleError(_TRANSLATE('Survey Published Successfully.'));
} catch (error) {
console.error(error);
this.errorHandler.handleError(_TRANSLATE('Could Not Contact Server.'));
} finally {
this.processMonitorService.stop(process.id);
}
}
async unPublishSurvey(formId) {
const process = this.processMonitorService.start('unpublishSurvey', 'Un-publishing Survey');

try {
await this.groupService.unPublishSurvey(this.groupId, formId);
await this.getForms();
this.errorHandler.handleError(_TRANSLATE('Survey UnPublished Successfully.'));
this.errorHandler.handleError(_TRANSLATE('Survey Un-published Successfully.'));
} catch (error) {
console.error(error);
this.errorHandler.handleError(_TRANSLATE('Could Not Contact Server.'));
} finally {
this.processMonitorService.stop(process.id);
}
}

7 changes: 4 additions & 3 deletions editor/src/app/groups/services/groups.service.ts
Original file line number Diff line number Diff line change
@@ -398,10 +398,11 @@ export class GroupsService {
}
}

async publishSurvey(groupId, formId, releaseType = 'prod', appName) {
async publishSurvey(groupId, formId, releaseType = 'prod', appName, locked=false) {
try {
const response = await this.httpClient.post(`/onlineSurvey/publish/${groupId}/${formId}`, {groupId, formId}, {observe: 'response'}).toPromise();
await this.httpClient.get(`/editor/release-online-survey-app/${groupId}/${formId}/${releaseType}/${appName}/${response.body['uploadKey']}`).toPromise()
const response = await this.httpClient.post(`/onlineSurvey/publish/${groupId}/${formId}`, {groupId, formId, locked}, {observe: 'response'}).toPromise();
const uploadKey = response.body['uploadKey']
await this.httpClient.post(`/editor/release-online-survey-app/${groupId}/${formId}/${releaseType}/${appName}`, {uploadKey, locked}).toPromise()
} catch (error) {
this.errorHandler.handleError(_TRANSLATE('Could Not Contact Server.'));
}
7 changes: 6 additions & 1 deletion online-survey-app/package.json
Original file line number Diff line number Diff line change
@@ -21,7 +21,12 @@
"@angular/platform-browser": "~10.1.0",
"@angular/platform-browser-dynamic": "~10.1.0",
"@angular/router": "~10.1.0",
"@ngx-translate/core": "^11.0.1",
"@ngx-translate/http-loader": "^4.0.0",
"@webcomponents/webcomponentsjs": "^2.4.4",
"axios": "^0.21.4",
"fast-json-patch": "^3.1.1",
"jwt-decode": "^4.0.0",
"material-design-icons-iconfont": "^6.1.0",
"redux": "^4.0.5",
"rxjs": "~6.6.0",
@@ -34,9 +39,9 @@
"@angular-devkit/build-angular": "~0.1001.0",
"@angular/cli": "~10.1.0",
"@angular/compiler-cli": "~10.1.0",
"@types/node": "^12.11.1",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
4 changes: 4 additions & 0 deletions online-survey-app/src/app/app-context.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum AppContext {
Editor = 'EDITOR',
Client = 'CLIENT'
}
Loading