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

Add process monitoring to prevent navigating away from pages before ready #2825

Closed
wants to merge 11 commits into from
Closed
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ __Server upgrade instructions__

Reminder: Consider using the [Tangerine Upgrade Checklist](https://docs.tangerinecentral.org/system-administrator/upgrade-checklist.html) for making sure you test the upgrade safely.


```
cd tangerine
# Check the size of the data folder.
Expand All @@ -109,6 +110,32 @@ npm install -g couchdb-wedge
wedge pre-warm-views --target $T_COUCHDB_ENDPOINT
```

## v3.18.4

__Fixes__

- Backported a fix from the v3.19.0 branch for "Save the lastSequence number after each change is processed in the tangerine-mysql connector" Issue [#2772](https://github.com/Tangerine-Community/Tangerine/issues/2772)
- Address crashes when importing data using the mysql module [#2820](https://github.com/Tangerine-Community/Tangerine/issues/2820)


```
cd tangerine
# Check the size of the data folder.
du -sh data
# Check disk for free space. Ensure there is at least 10GB + size of the data folder amount of free space in order to perform the upgrade.
df -h
# Turn off tangerine and database.
docker stop tangerine couchdb
# Create a backup of the data folder.
cp -r data ../data-backup-$(date "+%F-%T")
# Fetch the updates.
git fetch origin
git checkout v3.18.4
./start.sh v3.18.4
# Remove Tangerine's previous version Docker Image.
docker rmi tangerine/tangerine:v3.18.3
```

## v3.18.3

__Fixes__
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ npm start

View the app at <http://localhost:4200>.

__Optional__: If you are also developing the form library Tangy Form at the same time, you can symlink that repository into `node_modules` folder. For example...
__Optional__: If you are also developing the form library Tangy Form at the same time, do the following:
- Symlink that repository into `node_modules` folder.
- Start Tangerine instance using ./develop-tangy-form-libs.sh , which exposes the source code for your tangy-form and tangy-form-editor libs to the container.

Symlink example:

```
rm -r node_modules/tangy-form
Expand Down
16 changes: 8 additions & 8 deletions RELEASE-INSTRUCTONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ After code freeze, a release branch is made and it's time to start creating some
Once the release candidate (rc) has passed testing, it's time to roll the stable release.

0. Try to merge master into the release branch. If commits are merged, then there may be released code that is missing from the RC that passed QA. Stop this release and tag a new RC for QA.
0. Merge release branch into master.
0. Checkout the release candidate tag and tag that commit with a stable version. ie. `git checkout v3.15.0-rc-21 && git tag v3.15.0 && git push origin v3.15.0`
0. Cancel the build on Docker Hub then pull the RC image, rename it, and push it. ie. `docker pull tangerine/tangerine:v3.15.0-rc-21 && docker tag tangerine/tangerine:v3.15.0-rc-21 tangerine/tangerine:v3.15.0 && docker push tangerine/tangerine:v3.15.0`
0. Make release on Github using the same tag pushed up to Github. Link to the appropriate release on ["What's New" page on docs.tangerinecentral.org](https://docs.tangerinecentral.org/whats-new/).
0. Merge master into next.
0. On master branch migrate changes in `CHANGELOG.md` to `./docs/whats-new.md`. This will trigger the docs
0. Publish a `tangerine-preview` release with `rm -rf tangerine && git clone [email protected]:tangerine-community/tangerine && cd tangerine && ./release-preview.sh <tag name>`.
0. Announce on Teams we have a new release.
1. Merge release branch into master.
2. Checkout the release candidate tag and tag that commit with a stable version. ie. `git checkout v3.15.0-rc-21 && git tag v3.15.0 && git push origin v3.15.0`
3. Pull the RC image, rename it, and push it. ie. `docker pull tangerine/tangerine:v3.15.0-rc-21 && docker tag tangerine/tangerine:v3.15.0-rc-21 tangerine/tangerine:v3.15.0 && docker push tangerine/tangerine:v3.15.0`
4. Make release on Github using the same tag pushed up to Github. Link to the appropriate release on ["What's New" page on docs.tangerinecentral.org](https://docs.tangerinecentral.org/whats-new/). Copy the release notes from the CHANGELOG to this release.
5. Cancel the build in GitHub Actions for this tag.
6. Merge master into next.
8. Publish a `tangerine-preview` release with `rm -rf tangerine && git clone [email protected]:tangerine-community/tangerine && cd tangerine && ./release-preview.sh <tag name>`.
9. Announce on Teams we have a new release.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"pouchdb-upsert": "^2.2.0",
"rxjs": "^6.5.5",
"rxjs-compat": "^6.5.5",
"tangy-form": "4.25.11",
"tangy-form": "4.25.13",
"translation-web-component": "0.0.3",
"tslib": "^1.10.0",
"underscore": "^1.9.1",
Expand Down
1 change: 1 addition & 0 deletions client/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
</mat-menu>

</mat-toolbar>
<div #loadingUi class="tangerine-app-content mat-typography loading-ui-container" [hidden]="true"><loading-ui></loading-ui></div>
<div class="tangerine-app-content mat-typography">
<router-outlet *ngIf="ready"></router-outlet>
</div>
19 changes: 15 additions & 4 deletions client/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { CaseService } from 'src/app/case/services/case.service';
import { VariableService } from './shared/_services/variable.service';
import { UpdateService, VAR_UPDATE_IS_RUNNING } from './shared/_services/update.service';
import { DeviceService } from './device/services/device.service';
import { Component, OnInit, QueryList, ViewChild } from '@angular/core';
import {Component, ElementRef, OnInit, QueryList, ViewChild} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { MatSidenav } from '@angular/material/sidenav';
import { Router } from '@angular/router';
Expand All @@ -27,6 +27,8 @@ import { AppConfigService } from './shared/_services/app-config.service';
import { SearchService } from './shared/_services/search.service';
import { Get } from 'tangy-form/helpers.js'
import { FIRST_SYNC_STATUS } from './device/components/device-sync/device-sync.component';
import {LoadingUiComponent} from "./core/loading-ui.component";
import {ProcessMonitorService} from "./shared/_services/process-monitor.service";

const sleep = (milliseconds) => new Promise((res) => setTimeout(() => res(true), milliseconds))

Expand All @@ -50,6 +52,7 @@ export class AppComponent implements OnInit {
languagePath:string
ready = false
@ViewChild(MatSidenav, {static: true}) sidenav: QueryList<MatSidenav>;
@ViewChild('loadingUi', { static: true }) loadingUi: ElementRef<LoadingUiComponent>;

constructor(
private userService: UserService,
Expand All @@ -73,7 +76,8 @@ export class AppComponent implements OnInit {
private dashboardService:DashboardService,
private variableService:VariableService,
private syncCouchdbService:SyncCouchdbService,
private translate: TranslateService
private translate: TranslateService,
private processMonitorService:ProcessMonitorService
) {
this.window = window;
this.window.PouchDB = PouchDB
Expand Down Expand Up @@ -102,7 +106,8 @@ export class AppComponent implements OnInit {
variable: variableService,
classForm: classFormService,
classDashboard: dashboardService,
translate: window['t']
translate: window['t'],
process:processMonitorService
}
}

Expand Down Expand Up @@ -186,8 +191,14 @@ export class AppComponent implements OnInit {
if (await this.variableService.get(VAR_UPDATE_IS_RUNNING)) {
this.router.navigate(['/update']);
}
this.processMonitorService.busy.subscribe((isBusy) => {
this.loadingUi.nativeElement.hidden = false
});
this.processMonitorService.done.subscribe((isDone) => {
this.loadingUi.nativeElement.hidden = true
});
}

async install() {
try {
const config =<any> await this.http.get('./assets/app-config.json').toPromise()
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ import { ClassModule } from './class/class.module';
import { AboutModule } from './core/about/about.module';
import { SearchModule } from './core/search/search.module';
import { NewFormResponseModule } from './core/new-form-response/new-form-response.module';
import './core/loading-ui.component'
import {AppInit} from './app-init';
import {AdminConfigurationModule} from "./core/admin-configuration/admin-configuration.module";
export { AppComponent }


export function initializeApp1(appInit: AppInit) {
return (): Promise<any> => {
return appInit.Init();
Expand Down
49 changes: 49 additions & 0 deletions client/src/app/core/loading-ui.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {LitElement, html, css} from 'lit-element';
import {customElement, property} from "lit-element/lib/decorators";
import '@polymer/paper-progress/paper-progress.js';

@customElement('loading-ui')
export class LoadingUiComponent extends LitElement {
static styles = css`
:host {
display: block;
}
.loading-text {
display: flex;
width: 50%;
height: 100px;
margin: auto;
margin-top: 200px;
border-radius: 10px;
border: 3px dashed #1c87c9;
align-items: center;
justify-content: center;
background: var(--primary-color);
color:white;
opacity: 100%;
font-size: x-large;
}
`;

render() {
return html`
<div class="loading-text" @click=${this.escape}>
<paper-progress indeterminate></paper-progress>
</div>
`;
}

escape() {
if (confirm(`${window['T'].translate('Please only leave this dialog if you believe there is an error in the application.')}`)) {
window['T'].process.clear()
}
}
}

declare global {
interface HTMLElementTagNameMap {
'loading-ui': LoadingUiComponent;
}
}


26 changes: 26 additions & 0 deletions client/src/app/shared/_guards/process-guard.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Injectable} from "@angular/core";
import {CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs";
import {ProcessMonitorService} from "../_services/process-monitor.service";
import {TangyFormsPlayerComponent} from "../../tangy-forms/tangy-forms-player/tangy-forms-player.component";

@Injectable()
export class ProcessGuard implements CanDeactivate<TangyFormsPlayerComponent> {
constructor(
private processMonitorService:ProcessMonitorService
) { }

canDeactivate(component: TangyFormsPlayerComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean {
// If no processes being busy
if (this.processMonitorService.processes.length > 0) {
return false;
} else {
return true;
}

}

}
58 changes: 58 additions & 0 deletions client/src/app/shared/_services/process-monitor.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Subject } from 'rxjs';
import { v4 as UUID } from 'uuid';
import { Injectable } from '@angular/core';

interface Process {
id:string
name:string
description:string
}

@Injectable({
providedIn: 'root'
})
class ProcessMonitorService {

// When number of processes go from 0 to 1, this Subject will emit true.
busy = new Subject()
// When number of processes go to 0, this Subject will emit true.
done = new Subject()
// A list of the active processes.
processes:Array<Process> = []

constructor() {
}

hasNoProcesses = this.processes.length === 0
? true
: false

start(name, description):Process {
const process = <Process>{
id: UUID(),
name,
description
}

this.processes.push(process)
if (this.hasNoProcesses) {
this.busy.next(true)
}
return process
}

stop(pid:string) {
this.processes = this.processes.filter(process => process.id !== pid)
if (this.processes.length === 0) {
this.done.next(true)
}
}

clear() {
this.processes = []
this.done.next(true)
}

}

export { ProcessMonitorService };
6 changes: 5 additions & 1 deletion client/src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { CreateProfileGuardService } from './_guards/create-profile-guard.servic
import { DEFAULT_USER_DOCS } from './_tokens/default-user-docs.token';
import { SearchService } from './_services/search.service';
import { FormTypesService } from './_services/form-types.service';
import { ProcessMonitorService } from './_services/process-monitor.service';
import {ProcessGuard} from "./_guards/process-guard.service";

@NgModule({
imports: [
Expand All @@ -49,7 +51,9 @@ import { FormTypesService } from './_services/form-types.service';
LoginGuard,
SearchService,
FormTypesService,
CreateProfileGuardService
ProcessMonitorService,
CreateProfileGuardService,
ProcessGuard
],
declarations: [
UnsanitizeHtmlPipe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TangyFormsInfoService } from 'src/app/tangy-forms/tangy-forms-info-serv
import { Component, ViewChild, ElementRef, Input } from '@angular/core';
import { _TRANSLATE } from '../../shared/translation-marker';
import { TangyFormService } from '../tangy-form.service';
import {ProcessMonitorService} from "../../shared/_services/process-monitor.service";
const sleep = (milliseconds) => new Promise((res) => setTimeout(() => res(true), milliseconds))


Expand Down Expand Up @@ -47,9 +48,12 @@ export class TangyFormsPlayerComponent {

window:any;
@ViewChild('container', {static: true}) container: ElementRef;
process: any;

constructor(
private tangyFormsInfoService:TangyFormsInfoService,
private tangyFormService: TangyFormService,
private processMonitorService: ProcessMonitorService,
) {
this.window = window
}
Expand Down Expand Up @@ -139,15 +143,21 @@ export class TangyFormsPlayerComponent {
this.throttledSaveResponse(response)
})
}
formEl.addEventListener('submit', (event) => {
if (this.preventSubmit) event.preventDefault()
formEl.addEventListener('before-submit', async (event) => {
if (this.preventSubmit) event.preventDefault()
this.process = this.processMonitorService.start('saving-a-tangy-form', 'Updating a form response.')
this.$submit.next(true)
})
formEl.addEventListener('submit', async (event) => {
if (this.preventSubmit) event.preventDefault()
this.$submit.next(true)
})
formEl.addEventListener('after-submit', async (event) => {
if (this.preventSubmit) event.preventDefault()
while (this.throttledSaveFiring === true) {
await sleep(1000)
}
this.processMonitorService.stop(this.process.id)
this.$afterSubmit.next(true)
})
formEl.addEventListener('resubmit', async (event) => {
Expand Down
Loading