Browse Source

feat(admin-ui): Add support for job cancellation

Relates to #614
Michael Bromley 5 years ago
parent
commit
c6004c1ceb

+ 24 - 1
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -311,6 +311,7 @@ export type Mutation = {
   assignRoleToAdministrator: Administrator;
   /** Authenticates the user using a named authentication strategy */
   authenticate: AuthenticationResult;
+  cancelJob: Job;
   cancelOrder: CancelOrderResult;
   /** Create a new Administrator */
   createAdministrator: Administrator;
@@ -536,6 +537,11 @@ export type MutationAuthenticateArgs = {
 };
 
 
+export type MutationCancelJobArgs = {
+  jobId: Scalars['ID'];
+};
+
+
 export type MutationCancelOrderArgs = {
   input: CancelOrderInput;
 };
@@ -1392,7 +1398,8 @@ export enum JobState {
   RUNNING = 'RUNNING',
   COMPLETED = 'COMPLETED',
   RETRYING = 'RETRYING',
-  FAILED = 'FAILED'
+  FAILED = 'FAILED',
+  CANCELLED = 'CANCELLED'
 }
 
 export type JobList = PaginatedList & {
@@ -7400,6 +7407,16 @@ export type GetJobQueueListQuery = { jobQueues: Array<(
     & Pick<JobQueue, 'name' | 'running'>
   )> };
 
+export type CancelJobMutationVariables = Exact<{
+  id: Scalars['ID'];
+}>;
+
+
+export type CancelJobMutation = { cancelJob: (
+    { __typename?: 'Job' }
+    & JobInfoFragment
+  ) };
+
 export type ReindexMutationVariables = Exact<{ [key: string]: never; }>;
 
 
@@ -8949,6 +8966,12 @@ export namespace GetJobQueueList {
   export type JobQueues = NonNullable<(NonNullable<GetJobQueueListQuery['jobQueues']>)[number]>;
 }
 
+export namespace CancelJob {
+  export type Variables = CancelJobMutationVariables;
+  export type Mutation = CancelJobMutation;
+  export type CancelJob = (NonNullable<CancelJobMutation['cancelJob']>);
+}
+
 export namespace Reindex {
   export type Variables = ReindexMutationVariables;
   export type Mutation = ReindexMutation;

+ 9 - 0
packages/admin-ui/src/lib/core/src/data/definitions/settings-definitions.ts

@@ -681,6 +681,15 @@ export const GET_JOB_QUEUE_LIST = gql`
     }
 `;
 
+export const CANCEL_JOB = gql`
+    mutation CancelJob($id: ID!) {
+        cancelJob(jobId: $id) {
+            ...JobInfo
+        }
+    }
+    ${JOB_INFO_FRAGMENT}
+`;
+
 export const REINDEX = gql`
     mutation Reindex {
         reindex {

+ 8 - 0
packages/admin-ui/src/lib/core/src/data/providers/settings-data.service.ts

@@ -3,6 +3,7 @@ import { pick } from '@vendure/common/lib/pick';
 
 import {
     AddMembersToZone,
+    CancelJob,
     CreateChannel,
     CreateChannelInput,
     CreateCountry,
@@ -57,6 +58,7 @@ import {
 } from '../../common/generated-types';
 import {
     ADD_MEMBERS_TO_ZONE,
+    CANCEL_JOB,
     CREATE_CHANNEL,
     CREATE_COUNTRY,
     CREATE_TAX_CATEGORY,
@@ -372,4 +374,10 @@ export class SettingsDataService {
             },
         });
     }
+
+    cancelJob(id: string) {
+        return this.baseDataService.mutate<CancelJob.Mutation, CancelJob.Variables>(CANCEL_JOB, {
+            id,
+        });
+    }
 }

+ 9 - 8
packages/admin-ui/src/lib/core/src/providers/job-queue/job-queue.service.ts

@@ -23,14 +23,14 @@ export class JobQueueService implements OnDestroy {
                 (jobMap, job) => this.handleJob(jobMap, job),
                 new Map<string, JobInfoFragment>(),
             ),
-            map((jobMap) => Array.from(jobMap.values())),
+            map(jobMap => Array.from(jobMap.values())),
             debounceTime(500),
             shareReplay(1),
         );
 
         this.subscription = this.activeJobs$
             .pipe(
-                switchMap((jobs) => {
+                switchMap(jobs => {
                     if (jobs.length) {
                         return interval(2500).pipe(mapTo(jobs));
                     } else {
@@ -38,10 +38,10 @@ export class JobQueueService implements OnDestroy {
                     }
                 }),
             )
-            .subscribe((jobs) => {
+            .subscribe(jobs => {
                 if (jobs.length) {
-                    this.dataService.settings.pollJobs(jobs.map((j) => j.id)).single$.subscribe((data) => {
-                        data.jobsById.forEach((job) => {
+                    this.dataService.settings.pollJobs(jobs.map(j => j.id)).single$.subscribe(data => {
+                        data.jobsById.forEach(job => {
                             this.updateJob$.next(job);
                         });
                     });
@@ -61,8 +61,8 @@ export class JobQueueService implements OnDestroy {
     checkForJobs(delay: number = 1000) {
         timer(delay)
             .pipe(
-                switchMap(() => this.dataService.client.userStatus().mapSingle((data) => data.userStatus)),
-                switchMap((userStatus) => {
+                switchMap(() => this.dataService.client.userStatus().mapSingle(data => data.userStatus)),
+                switchMap(userStatus => {
                     if (userStatus.permissions.includes(Permission.ReadSettings) && userStatus.isLoggedIn) {
                         return this.dataService.settings.getRunningJobs().single$;
                     } else {
@@ -70,7 +70,7 @@ export class JobQueueService implements OnDestroy {
                     }
                 }),
             )
-            .subscribe((data) => data.jobs.items.forEach((job) => this.updateJob$.next(job)));
+            .subscribe(data => data.jobs.items.forEach(job => this.updateJob$.next(job)));
     }
 
     addJob(jobId: string, onComplete?: (job: JobInfoFragment) => void) {
@@ -92,6 +92,7 @@ export class JobQueueService implements OnDestroy {
                 break;
             case JobState.COMPLETED:
             case JobState.FAILED:
+            case JobState.CANCELLED:
                 jobMap.delete(job.id);
                 const handler = this.onCompleteHandlers.get(job.id);
                 if (handler) {

+ 20 - 0
packages/admin-ui/src/lib/system/src/components/job-list/job-list.component.html

@@ -57,6 +57,7 @@
     <vdr-dt-column>{{ 'system.job-state' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'system.job-duration' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'system.job-result' | translate }}</vdr-dt-column>
+    <vdr-dt-column></vdr-dt-column>
     <ng-template let-job="item">
         <td class="left align-middle">
             <vdr-entity-info [entity]="job"></vdr-entity-info>
@@ -108,5 +109,24 @@
                 </vdr-dropdown-menu>
             </vdr-dropdown>
         </td>
+        <td class="right align-middle">
+            <vdr-dropdown *ngIf="!job.isSettled && job.state !== 'FAILED'">
+                <button class="icon-button" vdrDropdownTrigger>
+                    <clr-icon shape="ellipsis-vertical"></clr-icon>
+                </button>
+                <vdr-dropdown-menu vdrPosition="bottom-right">
+                    <button
+                        type="button"
+                        class="delete-button"
+                        (click)="cancelJob(job.id)"
+                        [disabled]="!('DeleteSettings' | hasPermission)"
+                        vdrDropdownItem
+                    >
+                        <clr-icon shape="ban" class="is-danger"></clr-icon>
+                        {{ 'common.cancel' | translate }}
+                    </button>
+                </vdr-dropdown-menu>
+            </vdr-dropdown>
+        </td>
     </ng-template>
 </vdr-data-table>

+ 6 - 1
packages/admin-ui/src/lib/system/src/components/job-list/job-list.component.ts

@@ -20,7 +20,8 @@ import { filter, map, takeUntil } from 'rxjs/operators';
     styleUrls: ['./job-list.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class JobListComponent extends BaseListComponent<GetAllJobs.Query, GetAllJobs.Items>
+export class JobListComponent
+    extends BaseListComponent<GetAllJobs.Query, GetAllJobs.Items>
     implements OnInit {
     queues$: Observable<GetJobQueueList.JobQueues[]>;
     liveUpdate = new FormControl(true);
@@ -89,4 +90,8 @@ export class JobListComponent extends BaseListComponent<GetAllJobs.Query, GetAll
         }
         return true;
     }
+
+    cancelJob(id: string) {
+        this.dataService.settings.cancelJob(id).subscribe(() => this.refresh());
+    }
 }

+ 3 - 0
packages/admin-ui/src/lib/system/src/components/job-state-label/job-state-label.component.ts

@@ -17,6 +17,8 @@ export class JobStateLabelComponent {
                 return 'check-circle';
             case JobState.FAILED:
                 return 'exclamation-circle';
+            case JobState.CANCELLED:
+                return 'ban';
             case JobState.PENDING:
             case JobState.RETRYING:
                 return 'hourglass';
@@ -30,6 +32,7 @@ export class JobStateLabelComponent {
             case JobState.COMPLETED:
                 return 'success';
             case JobState.FAILED:
+            case JobState.CANCELLED:
                 return 'error';
             case JobState.PENDING:
             case JobState.RETRYING: