Revert "[M120 Migration]Fix for crash during chrome exit"
[platform/framework/web/chromium-efl.git] / tools / crbug / crbug.js
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 const process = require('child_process');
8 const https = require('https');
9
10 function log(msg) {
11   // console.log(msg);
12 }
13
14 class CrBugUser {
15   constructor(json) {
16     this.name_ = json.displayName;
17     this.id_ = json.name;
18     this.email_ = json.email;
19   }
20
21   get name() {
22     return this.name_;
23   }
24   get id() {
25     return this.id_;
26   }
27   get email() {
28     return this.email_;
29   }
30 };
31
32 class CrBugIssue {
33   constructor(json) {
34     this.number_ = json.name;
35     this.reporter_id_ = json.reporter;
36     this.owner_id_ = json.owner ? json.owner.user : undefined;
37     this.last_update_ = json.modifyTime;
38     this.close_ = json.closeTime ? new Date(json.closeTime) : undefined;
39
40     this.url_ = undefined;
41     const parts = this.number_.split('/');
42     if (parts[0] === 'projects' && parts[2] === 'issues') {
43       const project = parts[1];
44       const num = parts[3];
45       this.url_ =
46           `https://bugs.chromium.org/p/${project}/issues/detail?id=${num}`;
47     }
48   }
49
50   get number() {
51     return this.number_;
52   }
53   get owner_id() {
54     return this.owner_id_;
55   }
56   get reporter_id() {
57     return this.reporter_id_;
58   }
59   get url() {
60     return this.url_;
61   }
62 };
63
64 class CrBugComment {
65   constructor(json) {
66     this.user_id_ = json.commenter;
67     this.timestamp_ = new Date(json.createTime);
68     this.timestamp_.setSeconds(0);
69     this.content_ = json.content;
70     this.fields_ = json.amendments ?
71         json.amendments.map(m => m.fieldName.toLowerCase()) :
72         undefined;
73
74     this.json_ = JSON.stringify(json);
75   }
76
77   get user_id() {
78     return this.user_id_;
79   }
80   get timestamp() {
81     return this.timestamp_;
82   }
83   get content() {
84     return this.content_;
85   }
86   get updatedFields() {
87     return this.fields_;
88   }
89
90   isActivity() {
91     if (this.content)
92       return true;
93
94     const fields = this.updatedFields;
95
96     // If bug A gets merged into bug B, then ignore the update for bug A. There
97     // will also be an update for bug B, and that will be counted instead.
98     if (fields && fields.indexOf('mergedinto') >= 0) {
99       return false;
100     }
101
102     // If bug A is marked as blocked on bug B, then that triggers updates for
103     // both bugs. So only count 'blockedon', and ignore 'blocking'.
104     const allowedFields = [
105       'blockedon', 'cc', 'components', 'label', 'owner', 'priority', 'status',
106       'summary'
107     ];
108     if (fields && fields.some(f => allowedFields.indexOf(f) >= 0)) {
109       return true;
110     }
111     return false;
112   }
113 };
114
115 class CrBug {
116   constructor(project) {
117     this.token_ = this.getAuthToken_();
118     this.project_ = project;
119   }
120
121   getAuthToken_() {
122     const scope = 'https://www.googleapis.com/auth/userinfo.email';
123     const args = [
124       'luci-auth', 'token', '-use-id-token', '-audience',
125       'https://monorail-prod.appspot.com', '-scopes', scope, '-json-output', '-'
126     ];
127     const stdout = process.execSync(args.join(' ')).toString().trim();
128     const json = JSON.parse(stdout);
129     return json.token;
130   }
131
132   async fetchFromServer_(path, message) {
133     const hostname = 'api-dot-monorail-prod.appspot.com';
134     return new Promise((resolve, reject) => {
135       const postData = JSON.stringify(message);
136       const options = {
137         hostname: hostname,
138         method: 'POST',
139         path: path,
140         headers: {
141           'Content-Type': 'application/json',
142           'Accept': 'application/json',
143           'Authorization': `Bearer ${this.token_}`,
144         }
145       };
146
147       let data = '';
148       const req = https.request(options, (res) => {
149         log(`STATUS: ${res.statusCode}`);
150         log(`HEADERS: ${JSON.stringify(res.headers)}`);
151
152         res.setEncoding('utf8');
153         res.on('data', (chunk) => {
154           log(`BODY: ${chunk}`);
155           data += chunk;
156         });
157         res.on('end', () => {
158           if (data.startsWith(')]}\'')) {
159             resolve(JSON.parse(data.substr(4)));
160           } else {
161             resolve(data);
162           }
163         });
164       });
165
166       req.on('error', (e) => {
167         console.error(`problem with request: ${e.message}`);
168         reject(e.message);
169       });
170
171       // Write data to request body
172       log(`Writing ${postData}`);
173       req.write(postData);
174       req.end();
175     });
176   }
177
178   /**
179    * Calls SearchIssues with the given parameters.
180    *
181    * @param {string} query The query to use to search.
182    * @param {Number} pageSize The maximum issues to return.
183    * @param {string} pageToken The page token from the previous call.
184    *
185    * @return {JSON}
186    */
187   async searchIssuesPagination_(query, pageSize, pageToken) {
188     const message = {
189       'projects': [this.project_],
190       'query': query,
191       'pageToken': pageToken,
192     };
193     if (pageSize) {
194       message['pageSize'] = pageSize;
195     }
196     const url = '/prpc/monorail.v3.Issues/SearchIssues';
197     return this.fetchFromServer_(url, message);
198   }
199
200   /**
201    * Searches Monorail for issues using the given query.
202    * TODO(crbug.com/monorail/7143): SearchIssues only accepts one project.
203    *
204    * @param {string} query The query to use to search.
205    *
206    * @return {Array<CrBugIssue>}
207    */
208   async search(query) {
209     const pageSize = 100;
210     let pageToken;
211     let issues = [];
212     do {
213       const resp =
214           await this.searchIssuesPagination_(query, pageSize, pageToken);
215       if (resp.issues) {
216         issues = issues.concat(resp.issues.map(i => new CrBugIssue(i)));
217       }
218       pageToken = resp.nextPageToken;
219     } while (pageToken);
220     return issues;
221   }
222
223   /**
224    * Calls ListComments with the given parameters.
225    *
226    * @param {string} issueName Resource name of the issue.
227    * @param {string} filter The approval filter query.
228    * @param {Number} pageSize The maximum number of comments to return.
229    * @param {string} pageToken The page token from the previous request.
230    *
231    * @return {JSON}
232    */
233   async listCommentsPagination_(issueName, pageToken, pageSize) {
234     const message = {
235       'parent': issueName,
236       'pageToken': pageToken,
237       'filter': '',
238     };
239     if (pageSize) {
240       message['pageSize'] = pageSize;
241     }
242     const url = '/prpc/monorail.v3.Issues/ListComments';
243     return this.fetchFromServer_(url, message);
244   }
245
246   /**
247    * Returns all comments and previous/current descriptions of an issue.
248    *
249    * @param {CrBugIssue} issue The CrBugIssue instance.
250    *
251    * @return {Array<CrBugComment>}
252    */
253   async getComments(issue) {
254     let pageToken;
255     let comments = [];
256     do {
257       const resp = await this.listCommentsPagination_(issue.number, pageToken);
258       if (resp.comments) {
259         comments = comments.concat(resp.comments.map(c => new CrBugComment(c)));
260       }
261       pageToken = resp.nextPageToken;
262     } while (pageToken);
263     return comments;
264   }
265
266   /**
267    * Returns the user associated with 'username'.
268    *
269    * @param {string} username The username (e.g. linus@chromium.org).
270    *
271    * @return {CrBugUser}
272    */
273   async getUser(username) {
274     const url = '/prpc/monorail.v3.Users/GetUser';
275     const message = {
276       name: `users/${username}`,
277     };
278     return new CrBugUser(await this.fetchFromServer_(url, message));
279   }
280 };
281
282 module.exports = {
283   CrBug,
284 };