| /** |
| * Copyright 2019 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import {LiveAnnouncer} from '@angular/cdk/a11y'; |
| import {Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core'; |
| import {ReplaySubject} from 'rxjs'; |
| import {finalize, takeUntil} from 'rxjs/operators'; |
| |
| import {FileService} from '../services/file_service'; |
| import {initTestRunConfig, RerunContext, ShardingMode, Test, TestRunConfig} from '../services/mtt_models'; |
| import {Notifier} from '../services/notifier'; |
| import {FormChangeTracker} from '../shared/can_deactivate'; |
| |
| import {assertRequiredInput, buildApiErrorMessage, noAwait} from './util'; |
| |
| /** |
| * A form that display test run config info. |
| * |
| * After clicking new test run, this is the first component that stepper is |
| * going to render. |
| */ |
| @Component({ |
| selector: 'test-run-config-form', |
| styleUrls: ['test_run_config_form.css'], |
| templateUrl: './test_run_config_form.ng.html', |
| providers: [{provide: FormChangeTracker, useExisting: TestRunConfigForm}] |
| }) |
| export class TestRunConfigForm extends FormChangeTracker implements OnInit, |
| OnChanges, |
| OnDestroy { |
| readonly SHARDING_MODES = Object.values(ShardingMode); |
| |
| @Input() testMap!: {[id: string]: Test}; |
| @Input() testRunConfig!: Partial<TestRunConfig>; |
| /** True to allow users to select previous test runs to resume. */ |
| @Input() enableRerun = true; |
| /** Local previous test run ID. */ |
| @Input() prevTestRunId?: string; |
| /** Test plan to override when initializing a test run config. */ |
| @Input() testPlanToOverride?: string; |
| |
| /** Emits the updated config for two-way binding with parent */ |
| @Output() |
| readonly testRunConfigChange = new EventEmitter<Partial<TestRunConfig>>(); |
| /** Emits the latest rerun parameters. */ |
| @Output() readonly rerunContext = new EventEmitter<RerunContext>(); |
| |
| /** Notified when the component is destroyed. */ |
| private readonly destroy = new ReplaySubject<void>(); |
| |
| /** ID of the selected test */ |
| testId = ''; |
| /** True if the current run will resume another run. */ |
| isRerun = false; |
| /** True if resuming a remote test run (from another instance). */ |
| isRemoteRerun = false; |
| /** Test results filename/URL of the previous remote run. */ |
| testResultsFilename?: string; |
| private testResultsFileUrl?: string; |
| /** True if currently uploading a results file for a remote rerun. */ |
| isUploading = false; |
| /** File upload completion percentage (0 to 100). */ |
| uploadProgress = 0; |
| /** Show advanced settings. */ |
| showAdvancedSettings = false; |
| |
| constructor( |
| private readonly fs: FileService, |
| private readonly liveAnnouncer: LiveAnnouncer, |
| private readonly notifier: Notifier) { |
| super(); |
| } |
| |
| ngOnInit() { |
| assertRequiredInput(this.testMap, 'testMap', 'test-run-config-form'); |
| assertRequiredInput( |
| this.testRunConfig, 'testRunConfig', 'test-run-config-form'); |
| this.updateRerunContext(); |
| } |
| |
| ngOnChanges(changes: SimpleChanges) { |
| if (changes['testRunConfig']) { |
| this.testId = this.testRunConfig.test_id || ''; |
| } |
| } |
| |
| ngOnDestroy() { |
| this.destroy.next(); |
| this.liveAnnouncer.clear(); |
| } |
| |
| loadTest(testId: string) { |
| if (!this.testMap[testId]) { |
| return; |
| } |
| this.testRunConfig = |
| initTestRunConfig(this.testMap[testId], this.testPlanToOverride); |
| this.testRunConfigChange.emit(this.testRunConfig); |
| } |
| |
| /** |
| * Upload a results file for a remote rerun. |
| * @param file file to upload |
| */ |
| async uploadResultsFile(file?: File) { |
| if (!file) { |
| return; |
| } |
| |
| this.isUploading = true; |
| noAwait(this.liveAnnouncer.announce(`Uploading ${file.name}`, 'polite')); |
| this.fs.uploadFile(file, `tmp/${file.name}`) |
| .pipe( |
| takeUntil(this.destroy), |
| finalize(() => { |
| this.isUploading = false; |
| this.uploadProgress = 0; |
| }), |
| ) |
| .subscribe( |
| event => { |
| this.uploadProgress = event.progress; |
| if (event.done) { |
| this.testResultsFilename = file.name; |
| this.testResultsFileUrl = |
| this.fs.getFileUrl('', `tmp/${file.name}`); |
| this.updateRerunContext(); |
| this.liveAnnouncer.announce( |
| `${file.name} uploaded`, 'assertive'); |
| } |
| }, |
| error => { |
| this.notifier.showError( |
| `Failed to upload '${file.name}'.`, |
| buildApiErrorMessage(error)); |
| }); |
| } |
| |
| /** Updates the rerun context based on the current parameters. */ |
| updateRerunContext() { |
| if (!this.enableRerun) { |
| this.isRerun = false; |
| return; |
| } |
| if (this.isRemoteRerun) { |
| this.isRerun = !!this.testResultsFilename; |
| this.rerunContext.emit({ |
| context_filename: this.testResultsFilename, |
| context_file_url: this.testResultsFileUrl |
| }); |
| } else { |
| this.isRerun = !!this.prevTestRunId; |
| this.rerunContext.emit({test_run_id: this.prevTestRunId}); |
| } |
| } |
| |
| /** Validates the form contents and returns error message if any. */ |
| validateContents(): string[] { |
| const errors: string[] = []; |
| if (this.prevTestRunId) { |
| if (this.testRunConfig.sharding_mode === ShardingMode.MODULE && |
| this.prevTestRunId.trim()) { |
| errors.push( |
| 'Cannot select Sharding Mode MODULE when the Previous Test Run ' + |
| 'ID is specified.'); |
| } |
| } |
| return errors; |
| } |
| } |