1/* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {Inject, Injectable, NgZone} from '@angular/core'; 18import {MatSnackBar} from '@angular/material/snack-bar'; 19import {TRACE_INFO} from 'app/trace_info'; 20import {UserNotificationListener} from 'interfaces/user_notification_listener'; 21import {ParserError, ParserErrorType} from 'parsers/parser_factory'; 22import {SnackBarComponent} from './snack_bar_component'; 23 24@Injectable({providedIn: 'root'}) 25export class SnackBarOpener implements UserNotificationListener { 26 constructor( 27 @Inject(NgZone) private ngZone: NgZone, 28 @Inject(MatSnackBar) private snackBar: MatSnackBar 29 ) {} 30 31 onParserErrors(errors: ParserError[]) { 32 const messages = this.convertErrorsToMessages(errors); 33 34 if (messages.length === 0) { 35 return; 36 } 37 38 this.ngZone.run(() => { 39 // The snackbar needs to be opened within ngZone, 40 // otherwise it will first display on the left and then will jump to the center 41 this.snackBar.openFromComponent(SnackBarComponent, { 42 data: messages, 43 duration: 10000, 44 }); 45 }); 46 } 47 48 private convertErrorsToMessages(errors: ParserError[]): string[] { 49 const messages: string[] = []; 50 const groups = this.groupErrorsByType(errors); 51 52 for (const [type, groupedErrors] of groups) { 53 const CROP_THRESHOLD = 5; 54 const countUsed = Math.min(groupedErrors.length, CROP_THRESHOLD); 55 const countCropped = groupedErrors.length - countUsed; 56 57 groupedErrors.slice(0, countUsed).forEach((error) => { 58 messages.push(this.convertErrorToMessage(error)); 59 }); 60 61 if (countCropped > 0) { 62 messages.push(this.makeCroppedMessage(type, countCropped)); 63 } 64 } 65 66 return messages; 67 } 68 69 private convertErrorToMessage(error: ParserError): string { 70 const fileName = error.trace !== undefined ? error.trace : '<no file name>'; 71 const traceTypeName = 72 error.traceType !== undefined ? TRACE_INFO[error.traceType].name : '<unknown>'; 73 74 switch (error.type) { 75 case ParserErrorType.NO_INPUT_FILES: 76 return 'No input files'; 77 case ParserErrorType.UNSUPPORTED_FORMAT: 78 return `${fileName}: unsupported file format`; 79 case ParserErrorType.OVERRIDE: { 80 return `${fileName}: overridden by another trace of type ${traceTypeName}`; 81 } 82 default: 83 return `${fileName}: unknown error occurred`; 84 } 85 } 86 87 private makeCroppedMessage(type: ParserErrorType, count: number): string { 88 switch (type) { 89 case ParserErrorType.OVERRIDE: 90 return `... (cropped ${count} overridden trace messages)`; 91 case ParserErrorType.UNSUPPORTED_FORMAT: 92 return `... (cropped ${count} unsupported file format messages)`; 93 default: 94 return `... (cropped ${count} unknown error messages)`; 95 } 96 } 97 98 private groupErrorsByType(errors: ParserError[]): Map<ParserErrorType, ParserError[]> { 99 const groups = new Map<ParserErrorType, ParserError[]>(); 100 101 errors.forEach((error) => { 102 if (groups.get(error.type) === undefined) { 103 groups.set(error.type, []); 104 } 105 groups.get(error.type)!.push(error); 106 }); 107 108 return groups; 109 } 110} 111