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

network-indicator: issue #3, show progress spinner on network activity #16

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,8 @@
</button>
</div>

<div style="position: absolute; bottom: 2px; right: 2px; z-index: 10000;"
*ngIf="progressService.httpRequestInProgress | async"
>
<mat-spinner diameter="20" ></mat-spinner>
</div>
2 changes: 2 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { from, of } from 'rxjs';
import { xapianLoadedSubject } from './xapian/xapianwebloader';
import { SwPush } from '@angular/service-worker';
import { exportKeysFromJWK } from './webpush/vapid.tools';
import { ProgressService } from './http/progress.service';

const LOCAL_STORAGE_SETTING_MAILVIEWER_ON_RIGHT_SIDE_IF_MOBILE = 'mailViewerOnRightSideIfMobile';

Expand Down Expand Up @@ -131,6 +132,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
public snackBar: MatSnackBar,
public dialog: MatDialog,
private router: Router,
public progressService: ProgressService,
private mdIconRegistry: MatIconRegistry,
private http: Http,
private sanitizer: DomSanitizer,
Expand Down
88 changes: 88 additions & 0 deletions src/app/http/progress.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// --------- BEGIN RUNBOX LICENSE ---------
// Copyright (C) 2016-2018 Runbox Solutions AS (runbox.com).
//
// This file is part of Runbox 7.
//
// Runbox 7 is free software: You can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// Runbox 7 is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Runbox 7. If not, see <https://www.gnu.org/licenses/>.
// ---------- END RUNBOX LICENSE ----------

import { TestBed, getTestBed, async } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { MatSnackBarModule, MatDialogModule } from '@angular/material';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RunboxWebmailAPI } from '../rmmapi/rbwebmail';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { RMMHttpInterceptorService } from '../rmmapi/rmmhttpinterceptor.service';
import { ProgressService } from './progress.service';
import { RouterTestingModule } from '@angular/router/testing';
import { filter, take } from 'rxjs/operators';

describe('ProgressService', () => {
let injector: TestBed;
let rmmapiservice: RunboxWebmailAPI;
let progressService: ProgressService;
let httpMock: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
MatSnackBarModule,
MatDialogModule,
NoopAnimationsModule,
RouterTestingModule
],
providers: [
RunboxWebmailAPI,
ProgressService,
{ provide: HTTP_INTERCEPTORS, useClass: RMMHttpInterceptorService, multi: true}
]
});
injector = getTestBed();
rmmapiservice = injector.get(RunboxWebmailAPI);
progressService = injector.get(ProgressService);
httpMock = injector.get(HttpTestingController);
});

it('should indicate http request activity', async( async() => {

let httpProgressSeen = false;
progressService.httpRequestInProgress.pipe(
filter(c => c === true ? true : false),
take(1)
).subscribe(() => httpProgressSeen = true);

const req = httpMock.expectOne(`/rest/v1/me`);

req.flush({
'result': {'uid': '11', 'last_name': 'testuser'},
'status': 'success'}, {status: 200, statusText: 'OK'});

const last_on_req = httpMock.expectOne(`/rest/v1/last_on`);
last_on_req.flush(200);

const me = await rmmapiservice.me.toPromise();
expect(me.last_name).toBe('testuser');

expect(httpProgressSeen).toBeTruthy();

const hasCurrentHttpActivity = await progressService.httpRequestInProgress.pipe(
take(1)
).toPromise();

expect(hasCurrentHttpActivity).toBeFalsy();
expect(rmmapiservice.last_on_interval).toBeTruthy();
clearInterval(rmmapiservice.last_on_interval);
}));
});
3 changes: 2 additions & 1 deletion src/app/http/progress.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@
import {Injectable} from '@angular/core';
import {BrowserXhr} from '@angular/http';

import {Subject} from 'rxjs';
import {Subject, BehaviorSubject} from 'rxjs';

@Injectable()
export class ProgressService {
downloadProgress: Subject<ProgressEvent> = new Subject();
uploadProgress: Subject<ProgressEvent> = new Subject();
httpRequestInProgress: Subject<Boolean> = new BehaviorSubject(false);
}

@Injectable()
Expand Down
1 change: 1 addition & 0 deletions src/app/login/login.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ <h4>Security question</h4>
</div>
</form>

<mat-progress-bar mode="indeterminate" *ngIf="progressService.httpRequestInProgress | async"></mat-progress-bar>
</div>
<div class="loginSection" style="height: 20%; background-image: linear-gradient(170deg, #014f89, #001e35);">
<div id="loginFooter">
Expand Down
4 changes: 3 additions & 1 deletion src/app/login/login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Http, Response, URLSearchParams, Headers } from '@angular/http';
import { Subject, Observable } from 'rxjs';
import { RMMAuthGuardService } from '../rmmapi/rmmauthguard.service';
import { map } from 'rxjs/operators';
import { ProgressService } from '../http/progress.service';

@Component({
// tslint:disable-next-line:component-selector
Expand All @@ -41,7 +42,8 @@ export class LoginComponent {

constructor(private http: Http,
private router: Router,
private authservice: RMMAuthGuardService
private authservice: RMMAuthGuardService,
public progressService: ProgressService
) {

}
Expand Down
14 changes: 11 additions & 3 deletions src/app/rmmapi/rbwebmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// along with Runbox 7. If not, see <https://www.gnu.org/licenses/>.
// ---------- END RUNBOX LICENSE ----------

import { Injectable } from '@angular/core';
import { Injectable, NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { Observable , of, from , Subject , AsyncSubject } from 'rxjs';
import { MessageInfo, MailAddressInfo } from '../xapian/messageinfo';
Expand Down Expand Up @@ -152,12 +152,15 @@ export class RunboxWebmailAPI {
public me: AsyncSubject<RunboxMe> = new AsyncSubject();
public rblocale: any;

public last_on_interval;

messageContentsCache: { [messageId: number]: Observable<MessageContents> } = {};

constructor(
public snackBar: MatSnackBar,
private http: HttpClient,
private dialog: MatDialog
private dialog: MatDialog,
private ngZone: NgZone
) {
this.rblocale = new RunboxLocale();
this.http.get('/rest/v1/me')
Expand All @@ -172,7 +175,12 @@ export class RunboxWebmailAPI {
this.me.next(me);
this.me.complete();

setInterval(() => this.updateLastOn().subscribe(), 5 * 60 * 1000);
this.ngZone.runOutsideAngular(() =>
this.last_on_interval = setInterval(() => this.ngZone.run(() => {
this.updateLastOn().subscribe();
}), 5 * 60 * 1000)
);

this.updateLastOn().subscribe();
});
}
Expand Down
24 changes: 22 additions & 2 deletions src/app/rmmapi/rmmhttpinterceptor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,48 @@
// ---------- END RUNBOX LICENSE ----------

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpErrorResponse, HttpClient } from '@angular/common/http';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpClient } from '@angular/common/http';
import { Observable , throwError as _throw } from 'rxjs';
import { catchError, filter, tap, map } from 'rxjs/operators';
import { Router } from '@angular/router';
import { ProgressService } from '../http/progress.service';

@Injectable()
export class RMMHttpInterceptorService implements HttpInterceptor {

httpRequestCount = 0;

constructor(
private httpClient: HttpClient,
private router: Router
private router: Router,
private progressService: ProgressService
) {

}

decrementHttpRequestCount() {
this.httpRequestCount--;
if (this.httpRequestCount === 0) {
this.progressService.httpRequestInProgress.next(false);
}
// console.log('decrement', this.httpRequestCount);
}

intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {

if (this.httpRequestCount === 0) {
this.progressService.httpRequestInProgress.next(true);
}
this.httpRequestCount ++;
// console.log('increment', this.httpRequestCount);
return next.handle(req).pipe(
map((evt: HttpEvent<any>) => {
if (evt instanceof HttpResponse) {
const r = evt as HttpResponse<any>;
this.decrementHttpRequestCount();
if (r.body.status === 'error' && r.body.errors[0].indexOf('login') > 0) {
this.router.navigate(['/login'], {skipLocationChange: true});
throw(r.body);
Expand All @@ -49,6 +68,7 @@ export class RMMHttpInterceptorService implements HttpInterceptor {
return evt;
}),
catchError((e) => {
this.decrementHttpRequestCount();
if (e.status === 403) {
console.log('Forbidden');
this.httpClient.get('/rest/v1/me')
Expand Down