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/client issue improvements 2 #3427

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion client/src/app/case/components/issue/issue.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h1>{{issue.label}}</h1>
</div>
<div class="card-actions">
<paper-button routerLink="form-revision"><mwc-icon>call_split</mwc-icon>{{'Propose'|translate}}</paper-button>
<!-- Not showing this on client for now paper-button *ngIf="hasProposedChange" (click)="onMergeClick()"><mwc-icon>call_merge</mwc-icon>Merge</paper-button -->
<paper-button *ngIf="hasProposedChange && hasMergeChangePermission" (click)="onMergeClick()"><mwc-icon>call_merge</mwc-icon>Commit</paper-button>
</div>
</paper-card>

Expand Down
3 changes: 3 additions & 0 deletions client/src/app/case/components/issue/issue.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class IssueComponent implements OnInit {
numberOfChanges:number
numberOfRevisions:number
canMergeProposedChange = false
hasMergeChangePermission = false
hasProposedChange = false
isOpen = false
diffMarkup:string
Expand Down Expand Up @@ -91,6 +92,8 @@ export class IssueComponent implements OnInit {
this.isOpen = this.issue.status === IssueStatus.Open ? true : false
this.canMergeProposedChange = await this.caseService.canMergeProposedChange(this.issue._id)
this.hasProposedChange = await this.caseService.hasProposedChange(this.issue._id)
this.hasMergeChangePermission = await this.caseService.hasMergeChangePermission(this.issue._id)

this.eventInfos = this.issue.events.map(event => {
return <EventInfo>{
id: event.id,
Expand Down
37 changes: 33 additions & 4 deletions client/src/app/case/services/case.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ class CaseService {
hasEventFormPermission(operation:EventFormOperation, eventFormDefinition:EventFormDefinition, eventForm?:EventForm) {
if (
(
eventForm && !eventForm.inactive &&
eventForm && !eventForm.inactive && eventForm.permissions &&
eventForm.permissions[operation].filter(op => this.userService.roles.includes(op)).length > 0
) ||
(
Expand Down Expand Up @@ -848,7 +848,8 @@ class CaseService {
async createIssuesInQueue() {
const userProfile = await this.userService.getUserProfile()
for (let queuedIssue of this.queuedIssuesForCreation) {
await this.createIssue(queuedIssue.label, queuedIssue.comment, this.case._id, this.getCurrentCaseEventId(), this.getCurrentEventFormId(), userProfile._id, this.userService.getCurrentUser(), false, '')
const device = await this.deviceService.getDevice()
await this.createIssue(queuedIssue.label, queuedIssue.comment, this.case._id, this.getCurrentCaseEventId(), this.getCurrentEventFormId(), userProfile._id, this.userService.getCurrentUser(), false, device._id)
}
this.queuedIssuesForCreation = []
}
Expand Down Expand Up @@ -1055,7 +1056,9 @@ class CaseService {

async hasProposedChange(issueId:string) {
const issue = new Issue(await this.tangyFormService.getResponse(issueId))
return !!issue.events.find(event => event.type === IssueEventType.ProposedChange)
const baseEvent = [...issue.events].reverse().find(event => event.type === IssueEventType.Open || event.type === IssueEventType.Rebase)
const indexOfBaseEvent = issue.events.findIndex(event => event.id === baseEvent.id)
return !!issue.events.find((event, i) => event.type === IssueEventType.ProposedChange && i > indexOfBaseEvent)
}

async canMergeProposedChange(issueId:string) {
Expand All @@ -1068,6 +1071,32 @@ class CaseService {
return currentFormResponse._rev === eventBase.data.response._rev && currentCaseInstance._rev === eventBase.data.caseInstance._rev ? true : false
}

async hasMergeChangePermission(issueId:string) {
var allowed = false
const issue = new Issue(await this.tangyFormService.getResponse(issueId))
if (issue && issue.caseId && issue.eventId) {
const caseEvent = this.events.find(event => event.id === issue.eventId)
const caseEventDefinition = this.caseDefinition.eventDefinitions.find(eventDefinition => eventDefinition.id === caseEvent.caseEventDefinitionId)

const eventForm = caseEvent.eventForms.find(form => form.id === issue.eventFormId)
const eventFormDefinition = caseEventDefinition.eventFormDefinitions.find(formDefinition => formDefinition.id === eventForm.eventFormDefinitionId)

const caseEventUpdatePermission = this.hasCaseEventPermission(CaseEventOperation.UPDATE, caseEventDefinition)

const eventFormUpdatePermission = this.hasEventFormPermission(EventFormOperation.UPDATE, eventFormDefinition)

const appConfig = await this.appConfigService.getAppConfig()

if (caseEventUpdatePermission && eventFormUpdatePermission) {
allowed = true
} else if (appConfig.allowMergeOfIssues) {
allowed = true
}
}

return allowed
}

async issueDiff(issueId) {
const issue = new Issue(await this.tangyFormService.getResponse(issueId))
const firstOpenEvent = issue.events.find(event => event.type === IssueEventType.Open)
Expand Down Expand Up @@ -1109,7 +1138,7 @@ class CaseService {
}

isIssueContext() {
return window.location.hash.includes('/issues/')
return window.location.hash.includes('/issues/') || window.location.hash.includes('/issue/')
? true
: false
}
Expand Down
1 change: 1 addition & 0 deletions client/src/app/device/classes/device.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class Device {
version:string
syncLocations:Array<LocationConfig> = []
assignedLocation:LocationConfig
assignedFormResponseIds:Array<string> = []
}

export interface LocationConfig {
Expand Down
4 changes: 4 additions & 0 deletions client/src/app/shared/_classes/app-config.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ export class AppConfig {
barcodeSearchMapFunction:string
// Determines if a "Create Issue" button appears when viewing submitted Event Forms.
allowCreationOfIssues:boolean
// Determines if a "Merge Issue" button appears in the Issues Revision page.
allowMergeOfIssues:boolean
filterCaseEventScheduleByDeviceAssignedLocation:boolean = false
pullFormsModifiedOnServer:boolean = false


//
// Tangerine Coach configuration.
Expand Down
114 changes: 103 additions & 11 deletions client/src/app/sync/sync-couchdb.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {TangyFormService} from "../tangy-forms/tangy-form.service";
import { SyncDirection } from './sync-direction.enum';
import { UserService } from '../shared/_services/user.service';
import { DeviceService } from '../device/services/device.service';

const sleep = (milliseconds) => new Promise((res) => setTimeout(() => res(true), milliseconds))
const retryDelay = 5*1000

Expand Down Expand Up @@ -232,9 +233,10 @@ export class SyncCouchdbService {
let pullReplicationStatus
let hadPullSuccess = false
this.retryCount = 1
let prePullLastSeq = await this.variableService.get('sync-pull-last_seq')
while (!hadPullSuccess && !this.cancelling) {
try {
pullReplicationStatus = await this.pull(userDb, remoteDb, appConfig, syncDetails, batchSize);
pullReplicationStatus = await this.pull(userDb, remoteDb, appConfig, syncDetails, batchSize, prePullLastSeq);
if (!pullReplicationStatus.pullError) {
await this.variableService.set('sync-pull-last_seq', pullReplicationStatus.info.last_seq)
hadPullSuccess = true
Expand All @@ -254,9 +256,30 @@ export class SyncCouchdbService {
replicationStatus = {...replicationStatus, ...pullReplicationStatus}
this.syncMessage$.next(replicationStatus);

// Whatever we pulled, even if there was an error, we don't need to push so set last push sequence again.
const localSequenceAfterPull = (await userDb.changes({descending: true, limit: 1})).last_seq
await this.variableService.set('sync-push-last_seq', localSequenceAfterPull)
// Pull Form Response Ids Assigned to the Device
// get the list of docs assigned to the device from the server device database
const deviceDoc = await this.deviceService.getRemoteDeviceInfo(syncDetails.deviceId, syncDetails.deviceToken)
const assignedFormResponseIds = deviceDoc?.assignedFormResponseIds ? deviceDoc.assignedFormResponseIds : []

let pullIssueFormsReplicationStatus
let hadPullIssueFormsSuccess = false
while (!hadPullIssueFormsSuccess && !this.cancelling) {
try {
pullIssueFormsReplicationStatus = await this.pullFormResponses(userDb, remoteDb, assignedFormResponseIds, appConfig, syncDetails, batchSize, prePullLastSeq);
if (!pullIssueFormsReplicationStatus.pullError) {
hadPullIssueFormsSuccess = true
pullIssueFormsReplicationStatus.hadPullSuccess = true
} else {
await sleep(retryDelay)
++this.retryCount
}
} catch (e) {
console.error(e)
await sleep(retryDelay)
}
}
replicationStatus = {...replicationStatus, ...pullIssueFormsReplicationStatus}
this.syncMessage$.next(replicationStatus);

if (this.cancelling) {
this.finishCancelling(replicationStatus)
Expand Down Expand Up @@ -507,7 +530,7 @@ export class SyncCouchdbService {
})
}

async pull(userDb, remoteDb, appConfig, syncDetails, batchSize): Promise<ReplicationStatus> {
async pull(userDb, remoteDb, appConfig, syncDetails, batchSize, prePullLastSeq): Promise<ReplicationStatus> {
let status = <ReplicationStatus>{
pulled: 0,
pullError: '',
Expand All @@ -516,12 +539,11 @@ export class SyncCouchdbService {
remaining: 0,
direction: 'pull'
};
let pull_last_seq = await this.variableService.get('sync-pull-last_seq')
if (typeof pull_last_seq === 'undefined') {
pull_last_seq = 0;
if (typeof prePullLastSeq === 'undefined') {
prePullLastSeq = 0;
}
if (this.fullSync && this.fullSync === 'pull') {
pull_last_seq = 0;
prePullLastSeq = 0;
}
const pullSelector = this.getPullSelector(syncDetails);
let progress = {
Expand All @@ -540,7 +562,7 @@ export class SyncCouchdbService {
* are kept in memory at a time, so the maximum docs in memory at once would equal batch_size × batches_limit."
*/
let syncOptions = {
...syncDetails.usePouchDbLastSequenceTracking ? { } : { "since": pull_last_seq },
...syncDetails.usePouchDbLastSequenceTracking ? { } : { "since": prePullLastSeq },
"batch_size": batchSize,
"write_batch_size": this.writeBatchSize,
"batches_limit": 1,
Expand All @@ -567,7 +589,7 @@ export class SyncCouchdbService {
error = e
}

status.initialPullLastSeq = pull_last_seq
status.initialPullLastSeq = prePullLastSeq
status.currentPushLastSeq = status.info.last_seq
status.batchSize = batchSize

Expand Down Expand Up @@ -643,5 +665,75 @@ export class SyncCouchdbService {
}
return pullSelector;
}

async pullFormResponses(userDb, remoteDb, docIds, appConfig, syncDetails, batchSize, prePullLastSeq): Promise<ReplicationStatus> {
let status = <ReplicationStatus>{
pulled: 0,
pullError: '',
pullConflicts: [],
info: '',
remaining: 0,
direction: 'pull'
};
if (typeof prePullLastSeq === 'undefined') {
prePullLastSeq = await this.variableService.get('sync-pull-last_seq')
}
if (this.fullSync && this.fullSync === 'pull') {
prePullLastSeq = await this.variableService.get('sync-pull-last_seq')
}

let progress = {
'direction': 'pull',
'message': 'Received data from remote server.'
}
this.syncMessage$.next(progress)
let failureDetected = false
let error;
let pulled = 0

/**
* The sync option batches_limit is set to 1 in order to reduce the memory load on the tablet.
* From the pouchdb API doc:
* "Number of batches to process at a time. Defaults to 10. This (along wtih batch_size) controls how many docs
* are kept in memory at a time, so the maximum docs in memory at once would equal batch_size × batches_limit."
*/
let syncOptions = {
...syncDetails.usePouchDbLastSequenceTracking ? { } : { "since": prePullLastSeq },
"batch_size": batchSize,
"write_batch_size": this.writeBatchSize,
"batches_limit": 1,
"pulled": pulled,
"doc_ids": docIds,
"checkpoint": 'target',
"changes_batch_size": this.changesBatchSize
}
syncOptions = this.pullSyncOptions ? this.pullSyncOptions : syncOptions

try {
status = <ReplicationStatus>await this._pull(userDb, remoteDb, syncOptions);
if (typeof status.pulled !== 'undefined') {
pulled = pulled + status.pulled
status.pulled = pulled
} else {
status.pulled = pulled
}
this.syncMessage$.next(status)
} catch (e) {
console.log("Error: " + e)
failureDetected = true
error = e
}

status.initialPullLastSeq = prePullLastSeq
status.currentPushLastSeq = status.info.last_seq
status.batchSize = batchSize

if (failureDetected) {
status.pullError = `${error.message || error}. ${window['t']('Trying again')}: ${window['t']('Retry ')}${this.retryCount}.`
}

this.syncMessage$.next(status)

return status;
}
}
1 change: 1 addition & 0 deletions editor/src/app/case/classes/case.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Case extends TangyFormResponseModel {
notifications: Array<Notification> = []
type:string = 'case'
archived:boolean
groupId:string

constructor(data?:any) {
super()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@
right: 15px;
color: #fff;
}

.open-issue-button {
position: absolute;
top: 74px;
right: 15px;
color: #fff;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[caseEventId]="caseEvent.id"
[eventFormId]="eventForm.id"
></app-case-breadcrumb>
<paper-button *ngIf="loaded" class="new-issue-button" routerLink="/new-issue/{{caseId}}/{{caseEvent.id}}/{{eventForm.id}}"><mwc-icon>playlist_add</mwc-icon> new issue</paper-button>
<paper-button *ngIf="loaded && !issueExistsForFormResponse" class="new-issue-button" routerLink="/new-issue/{{caseId}}/{{caseEvent.id}}/{{eventForm.id}}"><mwc-icon>playlist_add</mwc-icon> new issue</paper-button>
<paper-button *ngIf="loaded && issueExistsForFormResponse" class="open-issue-button" routerLink="/issue/{{caseId}}/{{caseEvent.id}}/{{eventForm.id}}"><mwc-icon>flag</mwc-icon> open issues</paper-button>
<app-tangy-forms-player #formPlayer></app-tangy-forms-player>

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class EventFormComponent implements OnInit, OnDestroy {
caseId:string

loaded = false
issueExistsForFormResponse = false

window:any

Expand All @@ -38,7 +39,7 @@ export class EventFormComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private hostElementRef: ElementRef,
private router: Router,
private caseService: CaseService,
private caseService: CaseService
) {
this.window = window
}
Expand Down Expand Up @@ -86,10 +87,13 @@ export class EventFormComponent implements OnInit, OnDestroy {

// After render of the player, it will have created a new form response if one was not assigned.
// Make sure to save that new form response ID into the EventForm.
// If it exists, check for issues
this.formPlayer.$rendered.subscribe(async () => {
if (!this.formResponseId) {
this.eventForm.formResponseId = this.formPlayer.formResponseId
await this.caseService.save()
} else {
this.issueExistsForFormResponse = !!await this.caseService.findIssuesByFormResponseId(this.caseId, this.formResponseId)
}
})
this.formPlayer.$submit.subscribe(async () => {
Expand Down
Loading