Browse Source

feat(admin-ui): Check for running jobs after each mutation

Michael Bromley 6 years ago
parent
commit
8b2b0dc8cb

+ 11 - 4
admin-ui/src/app/core/providers/job-queue/job-queue.service.ts

@@ -1,5 +1,5 @@
 import { Injectable, OnDestroy } from '@angular/core';
-import { interval, Observable, of, Subject, Subscription } from 'rxjs';
+import { interval, Observable, of, Subject, Subscription, timer } from 'rxjs';
 import { debounceTime, map, mapTo, scan, shareReplay, switchMap } from 'rxjs/operators';
 
 import { JobInfoFragment, JobState } from '../../../common/generated-types';
@@ -14,9 +14,7 @@ export class JobQueueService implements OnDestroy {
     private readonly subscription: Subscription;
 
     constructor(private dataService: DataService) {
-        const initialJobList$ = this.dataService.settings
-            .getRunningJobs()
-            .single$.subscribe(data => data.jobs.forEach(job => this.updateJob$.next(job)));
+        this.checkForJobs();
 
         this.activeJobs$ = this.updateJob$.pipe(
             scan<JobInfoFragment, Map<string, JobInfoFragment>>(
@@ -55,6 +53,15 @@ export class JobQueueService implements OnDestroy {
         }
     }
 
+    /**
+     * After a given delay, checks the server for any active jobs.
+     */
+    checkForJobs(delay: number = 1000) {
+        timer(delay)
+            .pipe(switchMap(() => this.dataService.settings.getRunningJobs().single$))
+            .subscribe(data => data.jobs.forEach(job => this.updateJob$.next(job)));
+    }
+
     addJob(jobId: string, onComplete?: (job: JobInfoFragment) => void) {
         this.dataService.settings.getJob(jobId).single$.subscribe(({ job }) => {
             if (job) {

+ 1 - 2
admin-ui/src/app/core/providers/notification/notification.service.ts

@@ -1,9 +1,8 @@
 import { ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core';
 
-import { OverlayHostService } from '../overlay-host/overlay-host.service';
-
 import { NotificationComponent } from '../../components/notification/notification.component';
 import { I18nService } from '../i18n/i18n.service';
+import { OverlayHostService } from '../overlay-host/overlay-host.service';
 
 export type NotificationType = 'info' | 'success' | 'error' | 'warning';
 export interface ToastConfig {

+ 38 - 0
admin-ui/src/app/data/check-jobs-link.ts

@@ -0,0 +1,38 @@
+import { Injector } from '@angular/core';
+import { ApolloLink, Operation } from 'apollo-link';
+
+import { JobQueueService } from '../core/providers/job-queue/job-queue.service';
+
+/**
+ * This link checks each operation and if it is a mutation, it tells the JobQueueService
+ * to poll for active jobs. This is because certain mutations trigger background jobs
+ * which should be made known in the UI.
+ */
+export class CheckJobsLink extends ApolloLink {
+    private _jobQueueService: JobQueueService;
+    get jobQueueService(): JobQueueService {
+        if (!this._jobQueueService) {
+            this._jobQueueService = this.injector.get(JobQueueService);
+        }
+        return this._jobQueueService;
+    }
+
+    /**
+     * We inject the Injector rather than the JobQueueService directly in order
+     * to avoid a circular dependency error.
+     */
+    constructor(private injector: Injector) {
+        super((operation, forward) => {
+            if (this.isMutation(operation)) {
+                this.jobQueueService.checkForJobs();
+            }
+            return forward ? forward(operation) : null;
+        });
+    }
+
+    private isMutation(operation: Operation): boolean {
+        return !!operation.query.definitions.find(
+            d => d.kind === 'OperationDefinition' && d.operation === 'mutation',
+        );
+    }
+}

+ 5 - 2
admin-ui/src/app/data/data.module.ts

@@ -1,5 +1,5 @@
 import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
-import { APP_INITIALIZER, NgModule } from '@angular/core';
+import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
 import { APOLLO_OPTIONS, ApolloModule } from 'apollo-angular';
 import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
 import { ApolloClientOptions } from 'apollo-client';
@@ -12,6 +12,7 @@ import { getAppConfig } from '../app.config';
 import introspectionResult from '../common/introspection-result';
 import { LocalStorageService } from '../core/providers/local-storage/local-storage.service';
 
+import { CheckJobsLink } from './check-jobs-link';
 import { clientDefaults } from './client-state/client-defaults';
 import { clientResolvers } from './client-state/client-resolvers';
 import { OmitTypenameLink } from './omit-typename-link';
@@ -24,6 +25,7 @@ import { initializeServerConfigService, ServerConfigService } from './server-con
 export function createApollo(
     localStorageService: LocalStorageService,
     fetchAdapter: FetchAdapter,
+    injector: Injector,
 ): ApolloClientOptions<any> {
     const { apiHost, apiPort, adminApiPath, tokenMethod } = getAppConfig();
     const host = apiHost === 'auto' ? `${location.protocol}//${location.hostname}` : apiHost;
@@ -44,6 +46,7 @@ export function createApollo(
     return {
         link: ApolloLink.from([
             new OmitTypenameLink(),
+            new CheckJobsLink(injector),
             setContext(() => {
                 const channelToken = localStorageService.get('activeChannelToken');
                 if (channelToken) {
@@ -92,7 +95,7 @@ export function createApollo(
         {
             provide: APOLLO_OPTIONS,
             useFactory: createApollo,
-            deps: [LocalStorageService, FetchAdapter],
+            deps: [LocalStorageService, FetchAdapter, Injector],
         },
         { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true },
         {