File

src/app/test-controller/services/command.service.ts

Extends

WebsocketBackendService

Index

Properties
Methods

Constructor

constructor(isProductionMode: boolean, tcs: TestControllerService, serverUrl: string, http: HttpClient)
Parameters :
Name Type Optional
isProductionMode boolean No
tcs TestControllerService No
serverUrl string No
http HttpClient No

Methods

Private commandFromTerminal
commandFromTerminal(keyword: string, args: string[])
Parameters :
Name Type Optional
keyword string No
args string[] No
Returns : void
Private Static commandToString
commandToString(command: Command)
Parameters :
Name Type Optional
command Command No
Returns : string
ngOnDestroy
ngOnDestroy()
Inherited from WebsocketBackendService
Returns : void
Private setUpGlobalCommandsForDebug
setUpGlobalCommandsForDebug()
Returns : void
Private subscribeReceivedCommands
subscribeReceivedCommands()
Returns : void
Private subscribeTestStarted
subscribeTestStarted()
Returns : void
Private Static testStartedOrStopped
testStartedOrStopped(testStatus: TestControllerState)
Parameters :
Name Type Optional
testStatus TestControllerState No
cutConnection
cutConnection()
Inherited from WebsocketBackendService
Returns : void
Protected observeEndpointAndChannel
observeEndpointAndChannel()
Inherited from WebsocketBackendService
Returns : Observable<T>
Private pollNext
pollNext()
Inherited from WebsocketBackendService
Returns : void
Private scheduleNextPoll
scheduleNextPoll()
Inherited from WebsocketBackendService
Returns : void
Private subScribeToWsChannel
subScribeToWsChannel()
Inherited from WebsocketBackendService
Returns : void
Private unsubscribeFromWebsocket
unsubscribeFromWebsocket()
Inherited from WebsocketBackendService
Returns : void
Protected closeConnection
closeConnection()
Inherited from WebsocketService
Returns : void
connect
connect()
Inherited from WebsocketService
Returns : void
getChannel
getChannel(channelName: string)
Inherited from WebsocketService
Type parameters :
  • T
Parameters :
Name Type Optional
channelName string No
Returns : Observable<T>
send
send(event: string, data: any)
Inherited from WebsocketService
Parameters :
Name Type Optional
event string No
data any No
Returns : void

Properties

command$
Type : Subject<Command>
Default value : new Subject<Command>()
Private commandReceived$
Type : Subject<Command>
Default value : new Subject<Command>()
Private commandSubscription
Type : Subscription
Private executedCommandIds
Type : number[]
Default value : []
Protected initialData
Type : []
Default value : []
Inherited from WebsocketBackendService
Public isProductionMode
Type : boolean
Decorators :
@Inject('IS_PRODUCTION_MODE')
Protected pollingEndpoint
Type : string
Default value : ''
Inherited from WebsocketBackendService
Protected pollingInterval
Type : number
Default value : 5000
Inherited from WebsocketBackendService
Private testStartedSubscription
Type : Subscription
Protected wsChannelName
Type : string
Default value : 'commands'
Inherited from WebsocketBackendService
Protected connectionClosed
Default value : true
Inherited from WebsocketBackendService
connectionStatus$
Type : BehaviorSubject<ConnectionStatus>
Default value : new BehaviorSubject<ConnectionStatus>('initial')
Inherited from WebsocketBackendService
data$
Type : BehaviorSubject<T>
Inherited from WebsocketBackendService
Private pollingTimeoutId
Type : number
Default value : null
Inherited from WebsocketBackendService
Private wsConnectionStatusSubscription
Type : Subscription
Default value : null
Inherited from WebsocketBackendService
Private wsDataSubscription
Type : Subscription
Default value : null
Inherited from WebsocketBackendService
wsConnected$
Default value : new BehaviorSubject<boolean>(null)
Inherited from WebsocketService
Private wsSubject$
Type : WebSocketSubject<any>
Inherited from WebsocketService
Private wsSubscription
Type : Subscription
Inherited from WebsocketService
Protected wsUrl
Type : string
Default value : ''
Inherited from WebsocketService
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, commandKeywords, isKnownCommand, TestControllerState
} from '../interfaces/test-controller.interfaces';
import { TestControllerService } from './test-controller.service';
import { WebsocketBackendService } from '../../shared/websocket-backend.service';

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;
  private testStartedSubscription: Subscription;
  private executedCommandIds: number[] = [];

  constructor(
    @Inject('IS_PRODUCTION_MODE') public isProductionMode: boolean,
    private tcs: TestControllerService,
    @Inject('SERVER_URL') serverUrl: string,
    protected http: HttpClient
  ) {
    super(serverUrl, http);

    if (!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 {
    if ((testStatus === TestControllerState.RUNNING) || (testStatus === TestControllerState.PAUSED)) {
      return 'started';
    }
    if ((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 {
    if (this.commandSubscription) {
      this.commandSubscription.unsubscribe();
    }
    if (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() {
    if (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;
          if (this.pollingEndpoint) {
            return this.observeEndpointAndChannel();
          }
          this.cutConnection();
          return of([]);
        }),
        switchMap(commands => of(...commands))
      ).subscribe(this.commandReceived$);
  }

  private setUpGlobalCommandsForDebug() {
    // eslint-disable-next-line @typescript-eslint/dot-notation
    window['tc'] = {};
    commandKeywords.forEach((keyword: string) => {
      // eslint-disable-next-line @typescript-eslint/dot-notation
      window['tc'][keyword] = args => { this.commandFromTerminal(keyword, args); };
    });
  }

  private commandFromTerminal(keyword: string, args: string[]): void {
    if (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()
    };
    if (!isKnownCommand(keyword)) {
      return;
    }
    this.command$.next(command);
  }
}

results matching ""

    No results matching ""