@google-cloud/nodejs-common 0.9.4-beta3 → 0.9.9-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,11 +67,13 @@ class Spreadsheets {
67
67
  /**
68
68
  * Init Spreadsheets API client.
69
69
  * @param {string} spreadsheetId
70
+ * @param {!Object<string,string>=} env The environment object to hold env
71
+ * variables.
70
72
  */
71
- constructor(spreadsheetId) {
73
+ constructor(spreadsheetId, env = process.env) {
72
74
  /** @const {string} */
73
75
  this.spreadsheetId = spreadsheetId;
74
- const authClient = new AuthClient(API_SCOPES);
76
+ const authClient = new AuthClient(API_SCOPES, env);
75
77
  const auth = authClient.getDefaultAuth();
76
78
  /** @const {!!google.sheets} */
77
79
  this.instance = google.sheets({
@@ -108,7 +110,6 @@ class Spreadsheets {
108
110
  * see:
109
111
  * https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear
110
112
  * @param {string} sheetName Name of the Sheet.
111
- * @return {!Promise<boolean>} Whether the operation succeeded.
112
113
  */
113
114
  async clearSheet(sheetName) {
114
115
  const request = {
@@ -119,10 +120,9 @@ class Spreadsheets {
119
120
  const response = await this.instance.spreadsheets.values.clear(request);
120
121
  const data = response.data;
121
122
  this.logger.debug(`Clear sheet[${sheetName}}]: `, data);
122
- return true;
123
123
  } catch (error) {
124
- this.logger.error(error);
125
- return false;
124
+ this.logger.error(error.toString());
125
+ throw error;
126
126
  }
127
127
  }
128
128
 
@@ -162,7 +162,6 @@ class Spreadsheets {
162
162
  * @param {string} sheetName Name of the Sheet.
163
163
  * @param {number} targetRows Loaded data rows number.
164
164
  * @param {number} targetColumns Loaded data columns number.
165
- * @return {!Promise<boolean>} Whether the operation succeeded.
166
165
  */
167
166
  async reshape(sheetName, targetRows, targetColumns) {
168
167
  const request = /** @type{Params$Resource$Spreadsheets$Get} */ {
@@ -194,13 +193,12 @@ class Spreadsheets {
194
193
  if (requests.resource.requests.length > 0) {
195
194
  const {data} = await this.instance.spreadsheets.batchUpdate(requests);
196
195
  this.logger.debug(`Reshape Sheet [${sheetName}]: `, data);
197
- return true;
196
+ } else {
197
+ this.logger.debug('No need to reshape.');
198
198
  }
199
- this.logger.debug('No need to reshape.');
200
- return true;
201
199
  } catch (error) {
202
- console.error(error);
203
- return false;
200
+ this.logger.error(error.toString());
201
+ throw error;
204
202
  }
205
203
  }
206
204
 
@@ -218,7 +216,9 @@ class Spreadsheets {
218
216
  spreadsheetId: this.spreadsheetId,
219
217
  resource: {requests: [{pasteData}]},
220
218
  };
221
- /** @type {BatchResult} */ const batchResult = {};
219
+ /** @type {BatchResult} */ const batchResult = {
220
+ numberOfLines: data.trim().split('\n').length,
221
+ };
222
222
  try {
223
223
  const response = await this.instance.spreadsheets.batchUpdate(request);
224
224
  const data = response.data;
@@ -0,0 +1,356 @@
1
+ // Copyright 2021 Google Inc.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ /**
16
+ * @fileoverview Youtube API Client Library.
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const {google} = require('googleapis');
22
+ const {
23
+ Schema$Channel,
24
+ Schema$Video,
25
+ Schema$CommentThread,
26
+ Schema$Playlist,
27
+ Schema$Search,
28
+ } = google.youtube;
29
+ const AuthClient = require('./auth_client.js');
30
+ const {getLogger} = require('../components/utils.js');
31
+
32
+ const API_SCOPES = Object.freeze([
33
+ 'https://www.googleapis.com/auth/youtube.force-ssl'
34
+ ]);
35
+ const API_VERSION = 'v3';
36
+
37
+ /**
38
+ * Configuration for listing youtube channels.
39
+ * @see https://developers.google.com/youtube/v3/docs/channels/list
40
+ * @typedef {{
41
+ * part: Array<string>,
42
+ * categoryId: (string|undefined),
43
+ * forUsername: (string|undefined),
44
+ * id: (string|undefined),
45
+ * managedByMe: (boolean|undefined),
46
+ * mine: (boolean|undefined),
47
+ * hl: (string|undefined),
48
+ * onBehalfOfContentOwner: (string|undefined),
49
+ * pageToken: (string|undefined),
50
+ * }}
51
+ */
52
+ let ListChannelsConfig;
53
+
54
+ /**
55
+ * Configuration for listing youtube videos.
56
+ * @see https://developers.google.com/youtube/v3/docs/videos/list
57
+ * @typedef {{
58
+ * part: Array<string>,
59
+ * id: (string|undefined),
60
+ * chart: (string|undefined),
61
+ * hl: (string|undefined),
62
+ * maxHeight: (unsigned integer|undefined),
63
+ * maxResults: (unsigned integer|undefined),
64
+ * maxWidth: (unsigned integer|undefined),
65
+ * onBehalfOfContentOwner: (string|undefined),
66
+ * pageToken: (string|undefined),
67
+ * regionCode: (string|undefined),
68
+ * }}
69
+ */
70
+ let ListVideosConfig;
71
+
72
+ /**
73
+ * Configuration for listing youtube comment threads by the given id.
74
+ * @see https://developers.google.com/youtube/v3/docs/commentThreads/list
75
+ * @typedef {{
76
+ * part: Array<string>,
77
+ * id: (string|undefined),
78
+ * allThreadsRelatedToChannelId: (string|undefined),
79
+ * channelId: (string|undefined),
80
+ * videoId: (string|undefined),
81
+ * maxResults: (unsigned integer|undefined),
82
+ * moderationStatus: (string|undefined),
83
+ * order: (string|undefined),
84
+ * pageToken: (string|undefined),
85
+ * searchTerms: (string|undefined),
86
+ * textFormat: (string|undefined),
87
+ * limit: (unsigned integer|undefined),
88
+ * }}
89
+ */
90
+ let ListCommentThreadsConfig;
91
+
92
+ /**
93
+ * Configuration for listing youtube play list.
94
+ * @see https://developers.google.com/youtube/v3/docs/Playlists/list
95
+ * @typedef {{
96
+ * part: Array<string>,
97
+ * channelId: (string|undefined),
98
+ * id: (string|undefined),
99
+ * mine: (boolean|undefined),
100
+ * hl: (string|undefined),
101
+ * maxResults: (unsigned integer|undefined),
102
+ * onBehalfOfContentOwner: (string|undefined),
103
+ * onBehalfOfContentOwnerChannel: (string|undefined),
104
+ * pageToken: (string|undefined)
105
+ * }}
106
+ */
107
+ let ListPlaylistConfig;
108
+
109
+ /**
110
+ * Configuration for listing youtube search results.
111
+ * @see https://developers.google.com/youtube/v3/docs/search/list
112
+ * @typedef {{
113
+ * part: Array<string>,
114
+ * forContentOwner: (boolean|undefined),
115
+ * forDeveloper: (boolean|undefined),
116
+ * forMine: (boolean|undefined),
117
+ * relatedToVideoId: (string|undefined),
118
+ * channelId: (string|undefined),
119
+ * channelType: (string|undefined),
120
+ * eventType: (string|undefined),
121
+ * location: (string|undefined),
122
+ * locationRadius: (string|undefined),
123
+ * maxResults: (unsigned integer|undefined),
124
+ * onBehalfOfContentOwner: (string|undefined),
125
+ * order: (string|undefined),
126
+ * pageToken: (string|undefined),
127
+ * publishedAfter: (datetime|undefined),
128
+ * publishedBefore: (datetime|undefined),
129
+ * q: (string|undefined),
130
+ * regionCode: (string|undefined),
131
+ * relevanceLanguage: (string|undefined),
132
+ * safeSearch: (string|undefined),
133
+ * topicId: (string|undefined),
134
+ * type: (string|undefined),
135
+ * videoCaption: (string|undefined),
136
+ * videoCategoryId: (string|undefined),
137
+ * videoDefinition: (string|undefined),
138
+ * videoDimension: (string|undefined),
139
+ * videoDuration: (string|undefined),
140
+ * videoEmbeddable: (string|undefined),
141
+ * videoLicense: (string|undefined),
142
+ * videoSyndicated: (string|undefined),
143
+ * videoType: (string|undefined)
144
+ * }}
145
+ */
146
+ let ListSearchConfig;
147
+
148
+ /**
149
+ * Youtube API v3 stub.
150
+ * See: https://developers.google.com/youtube/v3/docs
151
+ * Channel list type definition, see:
152
+ * https://developers.google.com/youtube/v3/docs/channels/list
153
+ * Video list type definition, see:
154
+ * https://developers.google.com/youtube/v3/docs/videos/list
155
+ * CommentThread list type definition, see:
156
+ * https://developers.google.com/youtube/v3/docs/commentThreads/list
157
+ * Playlist list type definition, see:
158
+ * https://developers.google.com/youtube/v3/docs/playlists/list
159
+ * Search list type definition, see:
160
+ * https://developers.google.com/youtube/v3/docs/search/list
161
+ */
162
+ class YouTube {
163
+ /**
164
+ * @constructor
165
+ * @param {!Object<string,string>=} env The environment object to hold env
166
+ * variables.
167
+ */
168
+ constructor(env = process.env) {
169
+ const authClient = new AuthClient(API_SCOPES, env);
170
+ this.auth = authClient.getDefaultAuth();
171
+ /** @const {!google.youtube} */
172
+ this.instance = google.youtube({
173
+ version: API_VERSION,
174
+ });
175
+ /**
176
+ * Logger object from 'log4js' package where this type is not exported.
177
+ */
178
+ this.logger = getLogger('API.YT');
179
+ }
180
+
181
+ /**
182
+ * Returns a collection of zero or more channel resources that match the
183
+ * request criteria.
184
+ * @see https://developers.google.com/youtube/v3/docs/channels/list
185
+ * @param {!ListChannelsConfig} config List channels configuration.
186
+ * @return {!Promise<Array<Schema$Channel>>}
187
+ */
188
+ async listChannels(config) {
189
+ const channelListRequest = Object.assign({
190
+ auth: this.auth,
191
+ }, config);
192
+ channelListRequest.part = channelListRequest.part.join(',')
193
+ try {
194
+ const response = await this.instance.channels.list(channelListRequest);
195
+ this.logger.debug('Response: ', response);
196
+ return response.data.items;
197
+ } catch (error) {
198
+ const errorMsg =
199
+ `Fail to list channels: ${JSON.stringify(channelListRequest)}`;
200
+ this.logger.error('YouTube list channels failed.', error.message);
201
+ this.logger.debug('Errors in response:', error);
202
+ throw new Error(errorMsg);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Returns a list of videos that match the API request parameters.
208
+ * @see https://developers.google.com/youtube/v3/docs/videos/list
209
+ * @param {!ListVideosConfig} config List videos configuration.
210
+ * @return {!Promise<Array<Schema$Video>>}
211
+ */
212
+ async listVideos(config) {
213
+ const videoListRequest = Object.assign({
214
+ auth: this.auth,
215
+ }, config);
216
+ videoListRequest.part = videoListRequest.part.join(',')
217
+ try {
218
+ const response = await this.instance.videos.list(videoListRequest);
219
+ this.logger.debug('Response: ', response);
220
+ return response.data.items;
221
+ } catch (error) {
222
+ const errorMsg =
223
+ `Fail to list videos: ${JSON.stringify(videoListRequest)}`;
224
+ this.logger.error('YouTube list videos failed.', error.message);
225
+ this.logger.debug('Errors in response:', error);
226
+ throw new Error(errorMsg);
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Returns a list of comment threads that match the API request parameters.
232
+ * @see https://developers.google.com/youtube/v3/docs/videos/list
233
+ * @param {!ListCommentThreadsConfig} config List comment threads
234
+ * configuration.
235
+ * @return {!Promise<Array<Schema$CommentThread>>}
236
+ */
237
+ async listCommentThreads(config) {
238
+ const commentThreadsRequest = Object.assign({
239
+ auth: this.auth,
240
+ }, config);
241
+ commentThreadsRequest.part = commentThreadsRequest.part.join(',')
242
+ try {
243
+ const response = await this.instance.commentThreads.list(
244
+ commentThreadsRequest);
245
+ this.logger.debug('Response: ', response.data);
246
+ return response.data.items;
247
+ } catch (error) {
248
+ const errorMsg =
249
+ `Fail to list comment threads: ${JSON.stringify(commentThreadsRequest)}`;
250
+ this.logger.error('YouTube list comment threads failed.', error.message);
251
+ this.logger.debug('Errors in response:', error);
252
+ throw new Error(errorMsg);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Returns a collection of playlists that match the API request parameters.
258
+ * @see https://developers.google.com/youtube/v3/docs/playlists/list
259
+ * @param {!ListPlaylistConfig} config List playlist configuration.
260
+ * @param {number} resultLimit The limit of the number of results.
261
+ * @param {string} pageToken Token to identify a specific page in the result.
262
+ * @return {!Promise<Array<Schema$Playlist>>}
263
+ */
264
+ async listPlaylists(config, resultLimit = 1000, pageToken = null) {
265
+ if (resultLimit <= 0) return [];
266
+
267
+ const playlistsRequest = Object.assign({
268
+ auth: this.auth,
269
+ }, config, {
270
+ pageToken
271
+ });
272
+
273
+ if (Array.isArray(playlistsRequest.part)) {
274
+ playlistsRequest.part = playlistsRequest.part.join(',');
275
+ }
276
+
277
+ try {
278
+ const response = await this.instance.playlists.list(
279
+ playlistsRequest);
280
+ this.logger.debug('Response: ', response.data);
281
+ if (response.data.nextPageToken) {
282
+ this.logger.debug(
283
+ 'Call youtube playlist:list API agian with Token: ',
284
+ response.data.nextPageToken);
285
+ const nextResponse = await this.listPlaylists(
286
+ config,
287
+ resultLimit - response.data.items.length,
288
+ response.data.nextPageToken);
289
+ return response.data.items.concat(nextResponse);
290
+ }
291
+ return response.data.items;
292
+ } catch (error) {
293
+ const errorMsg =
294
+ `Fail to list playlists: ${JSON.stringify(playlistsRequest)}`;
295
+ this.logger.error('YouTube list playlists failed.', error.message);
296
+ this.logger.debug('Errors in response:', error);
297
+ throw new Error(errorMsg);
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Returns a collection of search results that match the query parameters
303
+ * specified in the API request.
304
+ * @see https://developers.google.com/youtube/v3/docs/search/list
305
+ * @param {!ListSearchConfig} config List search result configuration.
306
+ * @param {number} resultLimit The limit of the number of results.
307
+ * @param {string} pageToken Token to identify a specific page in the result.
308
+ * @return {!Promise<Array<Schema$Search>>}
309
+ */
310
+ async listSearchResults(config, resultLimit = 1000, pageToken = null) {
311
+ if (resultLimit <= 0) return [];
312
+
313
+ const searchRequest = Object.assign({
314
+ auth: this.auth,
315
+ }, config, {
316
+ pageToken
317
+ });
318
+
319
+ if (Array.isArray(searchRequest.part)) {
320
+ searchRequest.part = searchRequest.part.join(',');
321
+ }
322
+
323
+ try {
324
+ const response = await this.instance.search.list(searchRequest);
325
+ this.logger.debug('Response: ', response.data);
326
+ if (response.data.nextPageToken) {
327
+ this.logger.debug(
328
+ 'Call youtube search:list API agian with Token: ',
329
+ response.data.nextPageToken);
330
+ const nextResponse = await this.listSearchResults(
331
+ config,
332
+ resultLimit - response.data.items.length,
333
+ response.data.nextPageToken);
334
+ return response.data.items.concat(nextResponse);
335
+ }
336
+ return response.data.items;
337
+ } catch (error) {
338
+ const errorMsg =
339
+ `Fail to list search results: ${JSON.stringify(searchRequest)}`;
340
+ this.logger.error('YouTube list search results failed.', error.message);
341
+ this.logger.debug('Errors in response:', error);
342
+ throw new Error(errorMsg);
343
+ }
344
+ }
345
+ }
346
+
347
+ module.exports = {
348
+ YouTube,
349
+ ListChannelsConfig,
350
+ ListVideosConfig,
351
+ ListCommentThreadsConfig,
352
+ ListPlaylistConfig,
353
+ ListSearchConfig,
354
+ API_VERSION,
355
+ API_SCOPES,
356
+ };
@@ -26,14 +26,17 @@ const {CloudPlatformApis} = require('../apis/cloud_platform_apis.js');
26
26
  /**
27
27
  * The result of a batch of data sent to target API. The batch here means the
28
28
  * data that will be sent out in one single request.
29
+ * Some APIs allows partial failure: it will take those correct data and
30
+ * response with reasons for those failed ones. 'groupedFailed' uses error
31
+ * message as the key, and tthe array of related failed lines(records) as value.
29
32
  * Some APIs upload whole file. In this case, there will be not 'numberOfLines'
30
- * or 'failedLines'.
33
+ * or 'failedLines', etc.
31
34
  * @typedef {{
32
35
  * result: boolean,
33
36
  * numberOfLines: (number|undefined),
34
37
  * failedLines: (Array<string>|undefined),
35
38
  * errors: (Array<string>|undefined),
36
- * output: (Array<string>|undefined),
39
+ * groupedFailed: (Object<string,Array<string>>|undefined),
37
40
  * }}
38
41
  */
39
42
  let BatchResult;
@@ -177,6 +180,27 @@ const splitArray = (records, splitSize) => {
177
180
  return results;
178
181
  };
179
182
 
183
+ /**
184
+ * Merges an object of 'groupedFailed into the object 'mergedResult'
185
+ * @param {!BatchResult} mergedResult
186
+ * @param {!BatchResult} groupedFailed
187
+ * @private
188
+ */
189
+ const mergeGroupedFailed_ = (mergedResult, groupedFailed) => {
190
+ if (groupedFailed) {
191
+ const mergedKeys = Object.keys(mergedResult.groupedFailed);
192
+ mergedKeys.forEach((key) => {
193
+ mergedResult.groupedFailed[key] =
194
+ mergedResult.groupedFailed[key].concat(groupedFailed[key]);
195
+ });
196
+ Object.keys(groupedFailed)
197
+ .filter((key) => mergedKeys.indexOf(key) < 0)
198
+ .forEach((key) => {
199
+ mergedResult.groupedFailed[key] = groupedFailed[key];
200
+ });
201
+ }
202
+ }
203
+
180
204
  /**
181
205
  * Merges an array of API results (BatchResult) in to a single one.
182
206
  *
@@ -191,11 +215,17 @@ const mergeBatchResults = (batchResults, batchPrefix) => {
191
215
  numberOfLines: 0,
192
216
  failedLines: [],
193
217
  errors: [],
194
- output: []
218
+ groupedFailed: {},
195
219
  };
196
220
  batchResults.forEach((batchResult, index) => {
197
221
  const batchId = `${batchPrefix}-${index}`;
198
- const {result, numberOfLines, failedLines, errors, output} = batchResult;
222
+ const {
223
+ result,
224
+ numberOfLines,
225
+ failedLines = [],
226
+ errors = [],
227
+ groupedFailed,
228
+ } = batchResult;
199
229
  if (logger.isDebugEnabled()) {
200
230
  logger.debug(
201
231
  ` Task [${batchId}] has ${numberOfLines} lines: ${result
@@ -207,10 +237,13 @@ const mergeBatchResults = (batchResults, batchPrefix) => {
207
237
  }
208
238
  mergedResult.result = mergedResult.result && result;
209
239
  mergedResult.numberOfLines += numberOfLines;
210
- mergedResult.failedLines = mergedResult.failedLines.concat(
211
- failedLines || []);
212
- mergedResult.errors = mergedResult.errors.concat(errors || []);
213
- mergedResult.output = mergedResult.output.concat(output || []);
240
+ mergedResult.failedLines = mergedResult.failedLines.concat(failedLines);
241
+ errors.forEach((error) => {
242
+ if (mergedResult.errors.indexOf(error) === -1) {
243
+ mergedResult.errors.push(error);
244
+ }
245
+ });
246
+ mergeGroupedFailed_(mergedResult, groupedFailed);
214
247
  });
215
248
  return mergedResult;
216
249
  };
@@ -289,12 +322,13 @@ const apiSpeedControl = (recordSize = 1, numberOfThreads = 1, qps = 1,
289
322
  const records = Array.isArray(data) ?
290
323
  data :
291
324
  data.split('\n').filter((line) => line.trim() !== '');
292
- if (maxRecords < data.length) {
325
+ if (maxRecords < records.length) {
293
326
  const error = `Predicted timeout: ${records.length} records with config: `
294
327
  + `${recordSize} records/request and ${qps} QPS in ${timeout} sec.`;
295
328
  logger.error(error);
296
329
  return {
297
330
  result: false,
331
+ numberOfLines: records.length,
298
332
  errors: [error],
299
333
  };
300
334
  }
@@ -523,6 +557,7 @@ module.exports = {
523
557
  wait,
524
558
  BatchResult,
525
559
  SendSingleBatch,
560
+ mergeBatchResults,
526
561
  sendSingleRound,
527
562
  apiSpeedControl,
528
563
  splitArray,