Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | 1x | import { Inject, Injectable, OnDestroy } from '@angular/core'; import { of, Subject, Subscription, timer } from 'rxjs'; import { concatMap, distinctUntilChanged, filter, ignoreElements, map, mergeMap, startWith, switchMap, tap } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; import { Command, CommandKeyword, commandKeywords, isKnownCommand, TestControllerState } from '../interfaces/test-controller.interfaces'; import { TestControllerService } from './test-controller.service'; import { WebsocketBackendService } from '../../shared/shared.module'; type TestStartedOrStopped = 'started' | 'terminated' | ''; @Injectable({ providedIn: 'root' }) export class CommandService extends WebsocketBackendService<Command[]> implements OnDestroy { command$: Subject<Command> = new Subject<Command>(); protected initialData = []; protected pollingEndpoint = ''; protected pollingInterval = 5000; protected wsChannelName = 'commands'; private commandReceived$: Subject<Command> = new Subject<Command>(); private commandSubscription: Subscription | null = null; private testStartedSubscription: Subscription | null = null; private executedCommandIds: number[] = []; constructor( @Inject('IS_PRODUCTION_MODE') public isProductionMode: boolean, private tcs: TestControllerService, @Inject('BACKEND_URL') serverUrl: string, protected http: HttpClient ) { super(serverUrl, http); Iif (!this.isProductionMode) { this.setUpGlobalCommandsForDebug(); } // as services don't have a OnInit Hook (see: https://v9.angular.io/api/core/OnInit) we subscribe here this.subscribeReceivedCommands(); this.subscribeTestStarted(); } private static commandToString(command: Command): string { return `[${command.id}] ${command.keyword} ${command.arguments.join(' ')}`; } private static testStartedOrStopped(testStatus: TestControllerState): TestStartedOrStopped { Iif ((testStatus === TestControllerState.RUNNING) || (testStatus === TestControllerState.PAUSED)) { return 'started'; } Iif ((testStatus === TestControllerState.FINISHED) || (testStatus === TestControllerState.ERROR)) { return 'terminated'; } return ''; } // services are normally meant to live forever, so unsubscription *should* be unnecessary // this unsubscriptions are only for the case, the project's architecture will be changed dramatically once // while not having a OnInit-hook services *do have* an OnDestroy-hook (see: https://v9.angular.io/api/core/OnDestroy) ngOnDestroy(): void { Iif (this.commandSubscription) { this.commandSubscription.unsubscribe(); } Iif (this.testStartedSubscription) { this.testStartedSubscription.unsubscribe(); } } private subscribeReceivedCommands() { this.commandSubscription = this.commandReceived$ .pipe( filter((command: Command) => (this.executedCommandIds.indexOf(command.id) < 0)), // min delay between items concatMap((command: Command) => timer(1000).pipe(ignoreElements(), startWith(command))), mergeMap((command: Command) => // eslint-disable-next-line this.http.patch(`${this.serverUrl}test/${this.tcs.testId}/command/${command.id}/executed`, {}) .pipe( map(() => command), tap(cmd => this.executedCommandIds.push(cmd.id)) )) ).subscribe(command => this.command$.next(command)); } private subscribeTestStarted() { Iif (typeof this.testStartedSubscription !== 'undefined') { this.testStartedSubscription?.unsubscribe(); } this.testStartedSubscription = this.tcs.testStatus$ .pipe( distinctUntilChanged(), map(CommandService.testStartedOrStopped), filter(testStartedOrStopped => testStartedOrStopped !== ''), map(testStartedOrStopped => (((testStartedOrStopped === 'started') && (this.tcs.testMode.receiveRemoteCommands)) ? `test/${this.tcs.testId}/commands` : '')), filter(newPollingEndpoint => newPollingEndpoint !== this.pollingEndpoint), switchMap((pollingEndpoint: string) => { this.pollingEndpoint = pollingEndpoint; Iif (this.pollingEndpoint) { return this.observeEndpointAndChannel(); } this.cutConnection(); return of([]); }), switchMap(commands => of(...commands)) ).subscribe(this.commandReceived$); } private setUpGlobalCommandsForDebug() { (window as any).tc = commandKeywords .reduce((acc, keyword) => { acc[keyword] = args => { this.commandFromTerminal(keyword, args); }; return acc; }, <{ [key in CommandKeyword]: (arr: string[]) => void; } & object>{}); } private commandFromTerminal(keyword: string, args: string[]): void { Iif (this.isProductionMode) { return; } const newArgs = (typeof args === 'undefined') ? [] : args; const id = Math.round(Math.random() * -10000000); const command = { keyword, arguments: newArgs, id, timestamp: Date.now() }; Iif (!isKnownCommand(keyword)) { return; } this.command$.next(command); } } |