• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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