airbyte-source-gitlab 3.0.0.dev202403072311__py3-none-any.whl → 4.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,613 @@
1
+ version: 0.78.1
2
+ type: DeclarativeSource
3
+
4
+ definitions:
5
+ # Authenticators
6
+ bearer_authenticator:
7
+ type: BearerAuthenticator
8
+ api_token: "{{ config['credentials']['access_token'] }}"
9
+ oauth_authenticator:
10
+ type: OAuthAuthenticator
11
+ client_id: "{{ config['credentials']['client_id'] }}"
12
+ client_secret: "{{ config['credentials']['client_secret'] }}"
13
+ refresh_token: "{{ config['credentials']['refresh_token'] }}"
14
+ token_refresh_endpoint: "https://{{ config.get('api_url', 'gitlab.com') }}/oauth/token"
15
+ refresh_token_updater:
16
+ refresh_token_error_status_codes: [400, 401]
17
+ refresh_token_error_key: "error"
18
+ refresh_token_error_values: ["invalid_grant"]
19
+ authenticator:
20
+ type: SelectiveAuthenticator
21
+ authenticator_selection_path: ["credentials", "auth_type"]
22
+ authenticators:
23
+ oauth2.0: "#/definitions/oauth_authenticator"
24
+ access_token: "#/definitions/bearer_authenticator"
25
+
26
+ # Requester
27
+ requester:
28
+ type: HttpRequester
29
+ url_base: >-
30
+ {% set parts = config.get('api_url', 'gitlab.com').split('://') %}
31
+ {{ parts[0] if parts|length > 1 else 'https' }}://{{ parts[1] if parts[1] else parts[0] }}/api/v4/
32
+ http_method: GET
33
+ authenticator: "#/definitions/authenticator"
34
+ use_cache: true
35
+ error_handler:
36
+ type: DefaultErrorHandler
37
+ response_filters:
38
+ - type: HttpResponseFilter
39
+ action: IGNORE
40
+ http_codes: [403]
41
+ - type: HttpResponseFilter
42
+ action: FAIL
43
+ http_codes: [401]
44
+ error_message: Unable to refresh the `access_token`, please re-authenticate in Sources > Settings.
45
+ - type: HttpResponseFilter
46
+ action: RETRY
47
+ http_codes: [500]
48
+ - type: HttpResponseFilter
49
+ action: FAIL
50
+ http_codes: [404]
51
+ error_message: Groups and/or projects that you provide are invalid or you don't have permission to view it.
52
+ request_parameters: "{{ parameters.get('request_parameters', {}) }}"
53
+
54
+ # Selector
55
+ selector:
56
+ type: RecordSelector
57
+ extractor:
58
+ type: DpathExtractor
59
+ field_path: []
60
+
61
+ # Paginator
62
+ paginator:
63
+ type: DefaultPaginator
64
+ pagination_strategy:
65
+ type: PageIncrement
66
+ page_size: 50
67
+ start_from_page: 1
68
+ inject_on_first_request: false
69
+ page_size_option:
70
+ type: RequestOption
71
+ field_name: per_page
72
+ inject_into: request_parameter
73
+ page_token_option:
74
+ type: RequestOption
75
+ field_name: page
76
+ inject_into: request_parameter
77
+
78
+ # Retrievers
79
+ retriever:
80
+ type: SimpleRetriever
81
+ record_selector: "#/definitions/selector"
82
+ requester: "#/definitions/requester"
83
+ paginator: "#/definitions/paginator"
84
+
85
+ group_streams_retriever:
86
+ $ref: "#/definitions/retriever"
87
+ partition_router:
88
+ type: CustomPartitionRouter
89
+ class_name: source_gitlab.components.partition_routers.GroupStreamsPartitionRouter
90
+ parent_stream_configs:
91
+ - type: ParentStreamConfig
92
+ parent_key: "id"
93
+ stream: "#/definitions/groups_list_stream"
94
+ partition_field: "id"
95
+ - type: ParentStreamConfig
96
+ parent_key: "id"
97
+ stream: "#/definitions/include_descendant_groups_stream"
98
+ partition_field: "id"
99
+
100
+ group_child_streams_retriever:
101
+ $ref: "#/definitions/retriever"
102
+ partition_router:
103
+ type: SubstreamPartitionRouter
104
+ parent_stream_configs:
105
+ - type: ParentStreamConfig
106
+ parent_key: "id"
107
+ stream: "#/definitions/groups_stream"
108
+ partition_field: "id"
109
+
110
+ projects_streams_retriever:
111
+ $ref: "#/definitions/retriever"
112
+ partition_router:
113
+ type: CustomPartitionRouter
114
+ class_name: source_gitlab.components.partition_routers.ProjectStreamsPartitionRouter
115
+ parent_stream_configs:
116
+ - type: ParentStreamConfig
117
+ parent_key: "id"
118
+ stream: "#/definitions/groups_stream"
119
+ partition_field: "id"
120
+
121
+ projects_child_streams_retriever:
122
+ $ref: "#/definitions/retriever"
123
+ partition_router:
124
+ type: SubstreamPartitionRouter
125
+ parent_stream_configs:
126
+ - type: ParentStreamConfig
127
+ parent_key: "id"
128
+ stream: "#/definitions/projects_stream"
129
+ partition_field: "id"
130
+
131
+ pipelines_child_streams_retriever:
132
+ $ref: "#/definitions/retriever"
133
+ partition_router:
134
+ type: SubstreamPartitionRouter
135
+ parent_stream_configs:
136
+ - type: ParentStreamConfig
137
+ parent_key: "id"
138
+ stream: "#/definitions/pipelines_stream"
139
+ partition_field: "id"
140
+
141
+ # Transformations
142
+ add_project_id_field:
143
+ type: AddFields
144
+ fields:
145
+ - type: AddedFieldDefinition
146
+ path: ["project_id"]
147
+ value: "{{ stream_slice.id }}"
148
+
149
+ add_group_id_field:
150
+ type: AddFields
151
+ fields:
152
+ - type: AddedFieldDefinition
153
+ path: ["group_id"]
154
+ value: "{{ stream_slice.id }}"
155
+
156
+ # Service streams
157
+ base_full_refresh_stream:
158
+ type: DeclarativeStream
159
+ primary_key: "id"
160
+
161
+ base_groups_child_stream:
162
+ $ref: "#/definitions/base_full_refresh_stream"
163
+ retriever: "#/definitions/group_child_streams_retriever"
164
+
165
+ base_projects_child_stream:
166
+ $ref: "#/definitions/base_full_refresh_stream"
167
+ retriever: "#/definitions/projects_child_streams_retriever"
168
+
169
+ base_projects_incremental_child_stream:
170
+ $ref: "#/definitions/base_projects_child_stream"
171
+ incremental_sync:
172
+ type: DatetimeBasedCursor
173
+ cursor_field: "{{ parameters.get('cursor_field', 'updated_at') }}"
174
+ start_datetime: "{{ config.get('start_date', '2014-01-01T00:00:00Z') }}"
175
+ datetime_format: "%Y-%m-%dT%H:%M:%SZ"
176
+ cursor_datetime_formats:
177
+ - "%Y-%m-%dT%H:%M:%S.%f%z"
178
+ - "%Y-%m-%dT%H:%M:%S.%fZ"
179
+ - "%Y-%m-%dT%H:%M:%S%z"
180
+ - "%Y-%m-%dT%H:%M:%SZ"
181
+ cursor_granularity: "PT1S"
182
+ step: "P180DT1S"
183
+ start_time_option:
184
+ type: RequestOption
185
+ field_name: "{{ parameters.get('lower_bound_filter', 'updated_after') }}"
186
+ inject_into: request_parameter
187
+ end_time_option:
188
+ type: RequestOption
189
+ field_name: "{{ parameters.get('upper_bound_filter', 'updated_before') }}"
190
+ inject_into: request_parameter
191
+
192
+ groups_list_stream:
193
+ name: "groups_list"
194
+ $ref: "#/definitions/base_full_refresh_stream"
195
+ retriever: "#/definitions/retriever"
196
+ $parameters:
197
+ path: "groups"
198
+ request_parameters: {}
199
+
200
+ include_descendant_groups_stream:
201
+ name: "include_descendant_groups"
202
+ $ref: "#/definitions/base_full_refresh_stream"
203
+ retriever:
204
+ $ref: "#/definitions/retriever"
205
+ partition_router:
206
+ type: ListPartitionRouter
207
+ cursor_field: "slice"
208
+ values: |
209
+ {% set slices = [] %}
210
+ {% for group_id in config.get('groups_list', []) %}
211
+ {% set _ = slices.append({'path': 'groups/' + group_id}) %}
212
+ {% set _ = slices.append({'path': 'groups/' + group_id + '/descendant_groups'}) %}
213
+ {% endfor %}
214
+ {{ slices }}
215
+ $parameters:
216
+ path: "{{ stream_slice.slice.path }}"
217
+ request_parameters: {}
218
+
219
+ # Full refresh streams
220
+ ## Groups-based streams
221
+ groups_stream:
222
+ name: "groups"
223
+ $ref: "#/definitions/base_full_refresh_stream"
224
+ retriever: "#/definitions/group_streams_retriever"
225
+ transformations:
226
+ - type: AddFields
227
+ fields:
228
+ - type: AddedFieldDefinition
229
+ path:
230
+ - projects
231
+ value: |
232
+ {% set projects = [] %}
233
+ {% for project in (record.get('projects') or []): %}
234
+ {% set _ = projects.append({'id': project['id'], 'path_with_namespace': project['path_with_namespace']}) %}
235
+ {% endfor %}
236
+ {{ projects }}
237
+ $parameters:
238
+ path: "groups/{{ stream_slice.id }}"
239
+ request_parameters: {}
240
+
241
+ group_milestones_stream:
242
+ name: "group_milestones"
243
+ $ref: "#/definitions/base_groups_child_stream"
244
+ $parameters:
245
+ path: "groups/{{ stream_slice.id }}/milestones"
246
+
247
+ group_members_stream:
248
+ name: "group_members"
249
+ $ref: "#/definitions/base_groups_child_stream"
250
+ primary_key: ["group_id", "id"]
251
+ transformations:
252
+ - "#/definitions/add_group_id_field"
253
+ $parameters:
254
+ path: "groups/{{ stream_slice.id }}/members"
255
+
256
+ group_labels_stream:
257
+ name: "group_labels"
258
+ $ref: "#/definitions/base_groups_child_stream"
259
+ primary_key: ["group_id", "id"]
260
+ transformations:
261
+ - "#/definitions/add_group_id_field"
262
+ $parameters:
263
+ path: "groups/{{ stream_slice.id }}/labels"
264
+
265
+ group_issue_boards_stream:
266
+ name: "group_issue_boards"
267
+ $ref: "#/definitions/base_groups_child_stream"
268
+ transformations:
269
+ - "#/definitions/add_group_id_field"
270
+ $parameters:
271
+ path: "groups/{{ stream_slice.id }}/boards"
272
+
273
+ epics_stream:
274
+ name: "epics"
275
+ $ref: "#/definitions/base_groups_child_stream"
276
+ transformations:
277
+ - type: AddFields
278
+ fields:
279
+ - type: AddedFieldDefinition
280
+ path: ["author_id"]
281
+ value: "{{ (record.get('author') or {}).get('id') }}"
282
+ primary_key: "iid"
283
+ $parameters:
284
+ path: "groups/{{ stream_slice.id }}/epics"
285
+
286
+ epic_issues_stream:
287
+ name: "epic_issues"
288
+ $ref: "#/definitions/base_groups_child_stream"
289
+ primary_key: "iid"
290
+ transformations:
291
+ - type: AddFields
292
+ fields:
293
+ - type: AddedFieldDefinition
294
+ path: ["milestone_id"]
295
+ value: "{{ (record.get('milestone') or {}).get('id') }}"
296
+ - type: AddedFieldDefinition
297
+ path: ["assignee_id"]
298
+ value: "{{ (record.get('assignee') or {}).get('id') }}"
299
+ - type: AddedFieldDefinition
300
+ path: ["author_id"]
301
+ value: "{{ (record.get('author') or {}).get('id') }}"
302
+ - type: AddedFieldDefinition
303
+ path: ["assignees"]
304
+ value: |
305
+ {% set ids = [] %}
306
+ {% for data in (record.get('assignees') or []) %}
307
+ {% set _ = ids.append(data.get('id')) %}
308
+ {% endfor %}
309
+ {{ ids }}
310
+ retriever:
311
+ $ref: "#/definitions/retriever"
312
+ partition_router:
313
+ type: SubstreamPartitionRouter
314
+ parent_stream_configs:
315
+ - type: ParentStreamConfig
316
+ parent_key: "iid"
317
+ stream: "#/definitions/epics_stream"
318
+ partition_field: "iid"
319
+ $parameters:
320
+ path: "groups/{{ stream_slice.parent_slice.id }}/epics/{{ stream_slice.iid }}/issues"
321
+
322
+ ## Projects-based streams
323
+ projects_stream:
324
+ name: "projects"
325
+ $ref: "#/definitions/base_full_refresh_stream"
326
+ retriever: "#/definitions/projects_streams_retriever"
327
+ $parameters:
328
+ path: "projects/{{ stream_slice.id }}"
329
+ request_parameters:
330
+ statistics: 1
331
+
332
+ project_milestones_stream:
333
+ name: "project_milestones"
334
+ $ref: "#/definitions/base_projects_child_stream"
335
+ $parameters:
336
+ path: "projects/{{ stream_slice.id }}/milestones"
337
+
338
+ project_members_stream:
339
+ name: "project_members"
340
+ $ref: "#/definitions/base_projects_child_stream"
341
+ primary_key: ["project_id", "id"]
342
+ transformations:
343
+ - "#/definitions/add_project_id_field"
344
+ $parameters:
345
+ path: "projects/{{ stream_slice.id }}/members"
346
+
347
+ project_labels_stream:
348
+ name: "project_labels"
349
+ $ref: "#/definitions/base_projects_child_stream"
350
+ primary_key: ["project_id", "id"]
351
+ transformations:
352
+ - "#/definitions/add_project_id_field"
353
+ $parameters:
354
+ path: "projects/{{ stream_slice.id }}/labels"
355
+
356
+ branches_stream:
357
+ name: "branches"
358
+ $ref: "#/definitions/base_projects_child_stream"
359
+ transformations:
360
+ - "#/definitions/add_project_id_field"
361
+ - type: AddFields
362
+ fields:
363
+ - type: AddedFieldDefinition
364
+ path: ["commit_id"]
365
+ value: "{{ (record.get('commit') or {}).get('id') }}"
366
+ primary_key: ["project_id", "name"]
367
+ $parameters:
368
+ path: "projects/{{ stream_slice.id }}/repository/branches"
369
+
370
+ releases_stream:
371
+ name: "releases"
372
+ $ref: "#/definitions/base_projects_child_stream"
373
+ primary_key: "name"
374
+ transformations:
375
+ - "#/definitions/add_project_id_field"
376
+ - type: AddFields
377
+ fields:
378
+ - type: AddedFieldDefinition
379
+ path: ["author_id"]
380
+ value: "{{ (record.get('author') or {}).get('id') }}"
381
+ - type: AddedFieldDefinition
382
+ path: ["commit_id"]
383
+ value: "{{ (record.get('commit') or {}).get('id') }}"
384
+ - type: AddedFieldDefinition
385
+ path: ["milestones"]
386
+ value: |
387
+ {% set ids = [] %}
388
+ {% for data in record.get('milestones', []) %}
389
+ {% set _ = ids.append(data.get('id')) %}
390
+ {% endfor %}
391
+ {{ ids }}
392
+ $parameters:
393
+ path: "projects/{{ stream_slice.id }}/releases"
394
+
395
+ tags_stream:
396
+ name: "tags"
397
+ $ref: "#/definitions/base_projects_child_stream"
398
+ primary_key: ["project_id", "name"]
399
+ transformations:
400
+ - "#/definitions/add_project_id_field"
401
+ - type: AddFields
402
+ fields:
403
+ - type: AddedFieldDefinition
404
+ path: ["commit_id"]
405
+ value: "{{ (record.get('commit') or {}).get('id') }}"
406
+ $parameters:
407
+ path: "projects/{{ stream_slice.id }}/repository/tags"
408
+
409
+ users_stream:
410
+ name: "users"
411
+ $ref: "#/definitions/base_projects_child_stream"
412
+ primary_key: "name"
413
+ $parameters:
414
+ path: "projects/{{ stream_slice.id }}/users"
415
+
416
+ deployments_stream:
417
+ name: "deployments"
418
+ $ref: "#/definitions/base_projects_child_stream"
419
+ transformations:
420
+ - "#/definitions/add_project_id_field"
421
+ - type: AddFields
422
+ fields:
423
+ - type: AddedFieldDefinition
424
+ path: ["user_username"]
425
+ value: "{{ (record.get('user') or {}).get('username') }}"
426
+ - type: AddedFieldDefinition
427
+ path: ["user_full_name"]
428
+ value: "{{ (record.get('user') or {}).get('name') }}"
429
+ - type: AddedFieldDefinition
430
+ path: ["environment_name"]
431
+ value: "{{ (record.get('environment') or {}).get('name') }}"
432
+ - type: AddedFieldDefinition
433
+ path: ["user_id"]
434
+ value: "{{ (record.get('user') or {}).get('id') }}"
435
+ - type: AddedFieldDefinition
436
+ path: ["environment_id"]
437
+ value: "{{ (record.get('environment') or {}).get('id') }}"
438
+ $parameters:
439
+ path: "projects/{{ stream_slice.id }}/deployments"
440
+
441
+ merge_request_commits_stream:
442
+ name: "merge_request_commits"
443
+ $ref: "#/definitions/base_projects_child_stream"
444
+ retriever:
445
+ $ref: "#/definitions/retriever"
446
+ partition_router:
447
+ type: SubstreamPartitionRouter
448
+ parent_stream_configs:
449
+ - type: ParentStreamConfig
450
+ parent_key: "iid"
451
+ stream: "#/definitions/merge_requests_stream"
452
+ partition_field: "iid"
453
+ transformations:
454
+ - type: AddFields
455
+ fields:
456
+ - type: AddedFieldDefinition
457
+ path: ["project_id"]
458
+ value: "{{ stream_slice.parent_slice.id }}"
459
+ - type: AddedFieldDefinition
460
+ path: ["merge_request_iid"]
461
+ value: "{{ stream_slice.iid }}"
462
+ $parameters:
463
+ path: "projects/{{ stream_slice.parent_slice.id }}/merge_requests/{{ stream_slice.iid }}/commits"
464
+
465
+ pipelines_extended_stream:
466
+ name: "pipelines_extended"
467
+ $ref: "#/definitions/base_projects_child_stream"
468
+ retriever: "#/definitions/pipelines_child_streams_retriever"
469
+ $parameters:
470
+ path: "projects/{{ stream_slice.parent_slice.id }}/pipelines/{{ stream_slice.id }}"
471
+
472
+ jobs_stream:
473
+ name: "jobs"
474
+ $ref: "#/definitions/base_projects_child_stream"
475
+ retriever: "#/definitions/pipelines_child_streams_retriever"
476
+ transformations:
477
+ - type: AddFields
478
+ fields:
479
+ - type: AddedFieldDefinition
480
+ path: ["project_id"]
481
+ value: "{{ stream_slice.parent_slice.id }}"
482
+ - type: AddedFieldDefinition
483
+ path: ["user_id"]
484
+ value: "{{ (record.get('user') or {}).get('id') }}"
485
+ - type: AddedFieldDefinition
486
+ path: ["pipeline_id"]
487
+ value: "{{ (record.get('pipeline') or {}).get('id') }}"
488
+ - type: AddedFieldDefinition
489
+ path: ["runner_id"]
490
+ value: "{{ (record.get('runner') or {}).get('id') }}"
491
+ - type: AddedFieldDefinition
492
+ path: ["commit_id"]
493
+ value: "{{ (record.get('commit') or {}).get('id') }}"
494
+ $parameters:
495
+ path: "projects/{{ stream_slice.parent_slice.id }}/pipelines/{{ stream_slice.id }}/jobs"
496
+
497
+ # Incremental streams
498
+ commits_stream:
499
+ name: "commits"
500
+ $ref: "#/definitions/base_projects_incremental_child_stream"
501
+ transformations:
502
+ - "#/definitions/add_project_id_field"
503
+ $parameters:
504
+ path: "projects/{{ stream_slice.id }}/repository/commits"
505
+ cursor_field: "created_at"
506
+ lower_bound_filter: "since"
507
+ upper_bound_filter: "until"
508
+ request_parameters:
509
+ with_stats: "true"
510
+
511
+ issues_stream:
512
+ name: "issues"
513
+ $ref: "#/definitions/base_projects_incremental_child_stream"
514
+ transformations:
515
+ - type: AddFields
516
+ fields:
517
+ - type: AddedFieldDefinition
518
+ path: ["author_id"]
519
+ value: "{{ (record.get('author') or {}).get('id') }}"
520
+ - type: AddedFieldDefinition
521
+ path: ["assignee_id"]
522
+ value: "{{ (record.get('assignee') or {}).get('id') }}"
523
+ - type: AddedFieldDefinition
524
+ path: ["closed_by_id"]
525
+ value: "{{ (record.get('closed_by') or {}).get('id') }}"
526
+ - type: AddedFieldDefinition
527
+ path: ["milestone_id"]
528
+ value: "{{ (record.get('milestone') or {}).get('id') }}"
529
+ - type: AddedFieldDefinition
530
+ path: ["assignees"]
531
+ value: |
532
+ {% set ids = [] %}
533
+ {% for data in record.get('assignees', []) %}
534
+ {% set _ = ids.append(data.get('id')) %}
535
+ {% endfor %}
536
+ {{ ids }}
537
+ $parameters:
538
+ path: "projects/{{ stream_slice.id }}/issues"
539
+ request_parameters:
540
+ scope: "all"
541
+
542
+ merge_requests_stream:
543
+ name: "merge_requests"
544
+ $ref: "#/definitions/base_projects_incremental_child_stream"
545
+ transformations:
546
+ - type: AddFields
547
+ fields:
548
+ - type: AddedFieldDefinition
549
+ path: ["author_id"]
550
+ value: "{{ (record.get('author') or {}).get('id') }}"
551
+ - type: AddedFieldDefinition
552
+ path: ["assignee_id"]
553
+ value: "{{ (record.get('assignee') or {}).get('id') }}"
554
+ - type: AddedFieldDefinition
555
+ path: ["closed_by_id"]
556
+ value: "{{ (record.get('closed_by') or {}).get('id') }}"
557
+ - type: AddedFieldDefinition
558
+ path: ["milestone_id"]
559
+ value: "{{ (record.get('milestone') or {}).get('id') }}"
560
+ - type: AddedFieldDefinition
561
+ path: ["merged_by_id"]
562
+ value: "{{ (record.get('merged_by') or {}).get('id') }}"
563
+ - type: AddedFieldDefinition
564
+ path: ["assignees"]
565
+ value: |
566
+ {% set ids = [] %}
567
+ {% for data in record.get('assignees', []) %}
568
+ {% set _ = ids.append(data.get('id')) %}
569
+ {% endfor %}
570
+ {{ ids }}
571
+ $parameters:
572
+ path: "projects/{{ stream_slice.id }}/merge_requests"
573
+ request_parameters:
574
+ scope: "all"
575
+
576
+ pipelines_stream:
577
+ name: "pipelines"
578
+ $ref: "#/definitions/base_projects_incremental_child_stream"
579
+ $parameters:
580
+ path: "projects/{{ stream_slice.id }}/pipelines"
581
+
582
+ streams:
583
+ # Groups-based streams
584
+ - "#/definitions/groups_stream"
585
+ - "#/definitions/group_milestones_stream"
586
+ - "#/definitions/group_members_stream"
587
+ - "#/definitions/group_labels_stream"
588
+ - "#/definitions/group_issue_boards_stream"
589
+ - "#/definitions/epics_stream"
590
+ - "#/definitions/epic_issues_stream"
591
+
592
+ # Projects-based streams
593
+ - "#/definitions/projects_stream"
594
+ - "#/definitions/project_milestones_stream"
595
+ - "#/definitions/project_members_stream"
596
+ - "#/definitions/project_labels_stream"
597
+ - "#/definitions/branches_stream"
598
+ - "#/definitions/releases_stream"
599
+ - "#/definitions/tags_stream"
600
+ - "#/definitions/users_stream"
601
+ - "#/definitions/deployments_stream"
602
+ - "#/definitions/commits_stream"
603
+ - "#/definitions/issues_stream"
604
+ - "#/definitions/merge_requests_stream"
605
+ - "#/definitions/merge_request_commits_stream"
606
+ - "#/definitions/pipelines_stream"
607
+ - "#/definitions/pipelines_extended_stream"
608
+ - "#/definitions/jobs_stream"
609
+
610
+ check:
611
+ type: CheckStream
612
+ stream_names:
613
+ - projects