@flowcore/cli 1.3.0 → 2.0.0
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.
- package/CHANGELOG.md +10 -8
- package/README.md +9 -7
- package/dist/commands/stream.d.ts +2 -0
- package/dist/commands/stream.js +55 -8
- package/dist/utils/queries/fetch-indexes.gql.d.ts +18 -0
- package/dist/utils/queries/fetch-indexes.gql.js +18 -0
- package/oclif.manifest.json +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [1.3.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.2.0...v1.3.0) (2024-01-09)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Features
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* added timeout for destination as flag ([f731aac](https://github.com/flowcore-io/flowcore-cli/commit/f731aac025262179af1fd56e2ab2477a674635f9))
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
### Bug Fixes
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
* added description to start that includes week ([58687a7](https://github.com/flowcore-io/flowcore-cli/commit/58687a7bbb66aaa5d6da26af88e555cbb1e72467))
|
|
11
12
|
|
|
13
|
+
## [2.0.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.3.0...v2.0.0) (2024-01-15)
|
|
12
14
|
|
|
13
|
-
### Features
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
### ⚠ BREAKING CHANGES
|
|
16
17
|
|
|
18
|
+
* default mode now does not scan all time buckets but fetches populated ones from the index
|
|
17
19
|
|
|
18
|
-
###
|
|
20
|
+
### Features
|
|
19
21
|
|
|
20
|
-
* added
|
|
22
|
+
* added fetch of time bucket indexes to speed up fetching events, set --scan to query all time buckets within the requested range. ([650a7cd](https://github.com/flowcore-io/flowcore-cli/commit/650a7cd120ab372148abc79e3c105eee2687b466))
|
|
21
23
|
|
|
22
24
|
## [1.2.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.1.2...v1.2.0) (2024-01-09)
|
|
23
25
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ $ npm install -g @flowcore/cli
|
|
|
17
17
|
$ flowcore COMMAND
|
|
18
18
|
running command...
|
|
19
19
|
$ flowcore (--version)
|
|
20
|
-
@flowcore/cli/
|
|
20
|
+
@flowcore/cli/2.0.0 linux-x64 node-v20.10.0
|
|
21
21
|
$ flowcore --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ flowcore COMMAND
|
|
@@ -103,7 +103,7 @@ EXAMPLES
|
|
|
103
103
|
$ flowcore config set -l https://auth.flowcore.io/realms/flowcore/.well-known/openid-configuration -c my-client-id -p
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
_See code: [src/commands/config/set.ts](https://github.com/flowcore-io/flowcore-cli/blob/
|
|
106
|
+
_See code: [src/commands/config/set.ts](https://github.com/flowcore-io/flowcore-cli/blob/v2.0.0/src/commands/config/set.ts)_
|
|
107
107
|
|
|
108
108
|
## `flowcore config show`
|
|
109
109
|
|
|
@@ -123,7 +123,7 @@ EXAMPLES
|
|
|
123
123
|
$ flowcore config show
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
_See code: [src/commands/config/show.ts](https://github.com/flowcore-io/flowcore-cli/blob/
|
|
126
|
+
_See code: [src/commands/config/show.ts](https://github.com/flowcore-io/flowcore-cli/blob/v2.0.0/src/commands/config/show.ts)_
|
|
127
127
|
|
|
128
128
|
## `flowcore help [COMMANDS]`
|
|
129
129
|
|
|
@@ -166,7 +166,7 @@ EXAMPLES
|
|
|
166
166
|
$ flowcore login --port 8080
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
_See code: [src/commands/login.ts](https://github.com/flowcore-io/flowcore-cli/blob/
|
|
169
|
+
_See code: [src/commands/login.ts](https://github.com/flowcore-io/flowcore-cli/blob/v2.0.0/src/commands/login.ts)_
|
|
170
170
|
|
|
171
171
|
## `flowcore plugins`
|
|
172
172
|
|
|
@@ -449,12 +449,14 @@ Stream events from a datacore running on the Flowcore Platform
|
|
|
449
449
|
|
|
450
450
|
```
|
|
451
451
|
USAGE
|
|
452
|
-
$ flowcore stream STREAM [--profile <value>] [-d <value>] [-j] [-l] [-o http|log] [-s <value>] [-t
|
|
452
|
+
$ flowcore stream STREAM [--profile <value>] [-d <value>] [-j] [-l] [-o http|log] [-c] [-s <value>] [-t
|
|
453
|
+
<value>]
|
|
453
454
|
|
|
454
455
|
ARGUMENTS
|
|
455
456
|
STREAM stream url to connect to
|
|
456
457
|
|
|
457
458
|
FLAGS
|
|
459
|
+
-c, --scan Scan the full time range
|
|
458
460
|
-d, --destination=<value> [default: http://localhost:3000/transform] Destination to send events to
|
|
459
461
|
-j, --json Output json only
|
|
460
462
|
-l, --[no-]live Change to live mode when reaching last time bucket
|
|
@@ -477,7 +479,7 @@ EXAMPLES
|
|
|
477
479
|
$ flowcore stream https://flowcore.io/<org>/<data core>/<flow type>/[<event type1>,<event type2>,<event type3>].stream -o log -s 3m
|
|
478
480
|
```
|
|
479
481
|
|
|
480
|
-
_See code: [src/commands/stream.ts](https://github.com/flowcore-io/flowcore-cli/blob/
|
|
482
|
+
_See code: [src/commands/stream.ts](https://github.com/flowcore-io/flowcore-cli/blob/v2.0.0/src/commands/stream.ts)_
|
|
481
483
|
|
|
482
484
|
## `flowcore version`
|
|
483
485
|
|
|
@@ -514,5 +516,5 @@ DESCRIPTION
|
|
|
514
516
|
Check what user you are logged in as
|
|
515
517
|
```
|
|
516
518
|
|
|
517
|
-
_See code: [src/commands/whoami.ts](https://github.com/flowcore-io/flowcore-cli/blob/
|
|
519
|
+
_See code: [src/commands/whoami.ts](https://github.com/flowcore-io/flowcore-cli/blob/v2.0.0/src/commands/whoami.ts)_
|
|
518
520
|
<!-- commandsstop -->
|
|
@@ -10,10 +10,12 @@ export default class Stream extends BaseCommand<typeof Stream> {
|
|
|
10
10
|
json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
11
|
live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
12
|
output: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
scan: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
13
14
|
start: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
14
15
|
timeout: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
15
16
|
};
|
|
16
17
|
run(): Promise<void>;
|
|
18
|
+
private fetchTimeBuckets;
|
|
17
19
|
private processEvents;
|
|
18
20
|
private setTimeBucket;
|
|
19
21
|
private streamEvents;
|
package/dist/commands/stream.js
CHANGED
|
@@ -3,13 +3,15 @@ import { Args, Flags, ux } from '@oclif/core';
|
|
|
3
3
|
import dayjs from "dayjs";
|
|
4
4
|
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js";
|
|
5
5
|
import utc from "dayjs/plugin/utc.js";
|
|
6
|
+
import _ from "lodash";
|
|
6
7
|
import { Subject } from "rxjs";
|
|
7
8
|
import { BaseCommand } from "../base-command.js";
|
|
8
9
|
import { QueryGraphQL } from "../utils/graphql.util.js";
|
|
9
10
|
import { FETCH_DATA_CORE_GQL_QUERY } from "../utils/queries/fetch-data-core.gql.js";
|
|
10
11
|
import { FETCH_EVENT_TYPE_RANGE_GQL_QUERY } from "../utils/queries/fetch-event-type-range.gql.js";
|
|
11
12
|
import { FETCH_EVENTS_GQL_QUERY } from "../utils/queries/fetch-events.gql.js";
|
|
12
|
-
import {
|
|
13
|
+
import { FETCH_CATALOG_INDEXES_QUERY } from "../utils/queries/fetch-indexes.gql.js";
|
|
14
|
+
import { TIME_BUCKET_HOUR_PATTERN, TIME_BUCKET_PATTERN, createTimebucket, createTimebucketDayjs } from "../utils/timebucket.util.js";
|
|
13
15
|
import { LOGIN_CODES, ValidateLogin } from "../utils/validate-login.util.js";
|
|
14
16
|
dayjs.extend(utc);
|
|
15
17
|
dayjs.extend(isSameOrBefore);
|
|
@@ -29,6 +31,7 @@ export default class Stream extends BaseCommand {
|
|
|
29
31
|
json: Flags.boolean({ char: 'j', description: 'Output json only' }),
|
|
30
32
|
live: Flags.boolean({ allowNo: true, char: 'l', default: true, description: 'Change to live mode when reaching last time bucket' }),
|
|
31
33
|
output: Flags.string({ char: 'o', default: 'http', description: 'Output format', options: ['http', 'log'] }),
|
|
34
|
+
scan: Flags.boolean({ char: 'c', default: false, description: 'Scan the full time range' }),
|
|
32
35
|
start: Flags.string({ char: 's', description: 'Start time bucket to stream from, example: (1y, 1m, 1w, 1d, 1h)' }),
|
|
33
36
|
timeout: Flags.integer({ char: 't', default: 5000, description: 'Timeout in milliseconds to wait for a response from the destination' }),
|
|
34
37
|
};
|
|
@@ -62,6 +65,9 @@ export default class Stream extends BaseCommand {
|
|
|
62
65
|
dataCore,
|
|
63
66
|
organization: org,
|
|
64
67
|
});
|
|
68
|
+
if (fetchDataCoreResponse.organization.datacores.length === 0) {
|
|
69
|
+
ux.error(`Data core ${dataCore} not found for organization ${org}`);
|
|
70
|
+
}
|
|
65
71
|
const dataCoreId = fetchDataCoreResponse.organization.datacores[0].id;
|
|
66
72
|
const eventRangeRequest = await graphqlClient.client.request(FETCH_EVENT_TYPE_RANGE_GQL_QUERY, {
|
|
67
73
|
aggregator,
|
|
@@ -91,6 +97,40 @@ export default class Stream extends BaseCommand {
|
|
|
91
97
|
void this.streamEvents(dataCoreId, aggregator, eventTypes, firstTimeBucket, lastTimeBucket, graphqlClient, observer, flags.live);
|
|
92
98
|
await this.processEvents(observer, flags.destination, flags.output);
|
|
93
99
|
}
|
|
100
|
+
async fetchTimeBuckets(dataCoreId, aggregator, eventTypes, startTimeBucket, lastTimeBucket, graphqlClient) {
|
|
101
|
+
!this.flags.json && ux.action.start("Fetching time buckets");
|
|
102
|
+
let timeBuckets = [];
|
|
103
|
+
for (const eventType of eventTypes) {
|
|
104
|
+
let cursor = null;
|
|
105
|
+
do {
|
|
106
|
+
// eslint-disable-next-line no-await-in-loop
|
|
107
|
+
await graphqlClient.validateToken();
|
|
108
|
+
try {
|
|
109
|
+
// eslint-disable-next-line no-await-in-loop
|
|
110
|
+
const response = await graphqlClient.client.request(FETCH_CATALOG_INDEXES_QUERY, {
|
|
111
|
+
aggregator,
|
|
112
|
+
cursor: cursor || undefined,
|
|
113
|
+
dataCoreId,
|
|
114
|
+
eventType,
|
|
115
|
+
fromTimeBucket: startTimeBucket.format(TIME_BUCKET_PATTERN),
|
|
116
|
+
toTimeBucket: lastTimeBucket,
|
|
117
|
+
});
|
|
118
|
+
cursor = response.datacore.fetchIndexes.cursor;
|
|
119
|
+
timeBuckets.push(...response.datacore.fetchIndexes.timeBuckets);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// don't display error if token is expired
|
|
123
|
+
if (error instanceof Error && error.message.includes("Token is expired")) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
this.warn(`Error fetching events: ${error}`);
|
|
127
|
+
}
|
|
128
|
+
} while (cursor);
|
|
129
|
+
}
|
|
130
|
+
timeBuckets = _.uniq(timeBuckets);
|
|
131
|
+
!this.flags.json && ux.action.stop(`Found ${timeBuckets.length} time buckets`);
|
|
132
|
+
return timeBuckets;
|
|
133
|
+
}
|
|
94
134
|
async processEvents(observer, destination, output) {
|
|
95
135
|
let done = false;
|
|
96
136
|
let events = [];
|
|
@@ -181,13 +221,22 @@ export default class Stream extends BaseCommand {
|
|
|
181
221
|
}
|
|
182
222
|
}
|
|
183
223
|
async streamEvents(dataCoreId, aggregator, eventTypes, firstTimeBucket, lastTimeBucket, graphqlClient, observer, live) {
|
|
184
|
-
|
|
224
|
+
const startTimeBucket = dayjs(firstTimeBucket, TIME_BUCKET_HOUR_PATTERN);
|
|
185
225
|
const currentTime = dayjs.utc();
|
|
186
226
|
let liveMode = false;
|
|
187
227
|
let lastEventId = null;
|
|
228
|
+
const timediff = dayjs(lastTimeBucket, TIME_BUCKET_HOUR_PATTERN).diff(startTimeBucket, "hour");
|
|
229
|
+
if (timediff < 0) {
|
|
230
|
+
ux.error(`Invalid time range, start time bucket (${ux.colorize("yellow", firstTimeBucket)}) is after last time bucket (${ux.colorize("yellow", lastTimeBucket)})`);
|
|
231
|
+
}
|
|
232
|
+
let timeBuckets = [];
|
|
233
|
+
timeBuckets = this.flags.scan ? _.times(timediff + 1, (n) => dayjs(startTimeBucket).add(n, "hour").format(TIME_BUCKET_PATTERN)) : (await this.fetchTimeBuckets(dataCoreId, aggregator, eventTypes, startTimeBucket, lastTimeBucket, graphqlClient));
|
|
188
234
|
// eslint-disable-next-line no-constant-condition
|
|
189
235
|
while (true) {
|
|
190
|
-
const timeBucket =
|
|
236
|
+
const timeBucket = timeBuckets.shift();
|
|
237
|
+
if (!timeBucket) {
|
|
238
|
+
ux.error("No time bucket found");
|
|
239
|
+
}
|
|
191
240
|
if (!this.flags.json) {
|
|
192
241
|
ux.action.start(`Fetching events for ${startTimeBucket}`);
|
|
193
242
|
}
|
|
@@ -204,6 +253,7 @@ export default class Stream extends BaseCommand {
|
|
|
204
253
|
const response = await graphqlClient.client.request(FETCH_EVENTS_GQL_QUERY, {
|
|
205
254
|
afterEventId: lastEventId || undefined,
|
|
206
255
|
aggregator,
|
|
256
|
+
cursor: cursor || undefined,
|
|
207
257
|
dataCoreId,
|
|
208
258
|
eventTypes,
|
|
209
259
|
timeBucket,
|
|
@@ -223,10 +273,7 @@ export default class Stream extends BaseCommand {
|
|
|
223
273
|
this.warn(`Error fetching events: ${error}`);
|
|
224
274
|
}
|
|
225
275
|
} while (cursor);
|
|
226
|
-
if (
|
|
227
|
-
startTimeBucket = startTimeBucket.add(1, "hour");
|
|
228
|
-
}
|
|
229
|
-
if (timeBucket === lastTimeBucket) {
|
|
276
|
+
if (timeBuckets.length === 0) {
|
|
230
277
|
if (!live) {
|
|
231
278
|
observer.complete();
|
|
232
279
|
if (!this.flags.json) {
|
|
@@ -236,9 +283,9 @@ export default class Stream extends BaseCommand {
|
|
|
236
283
|
}
|
|
237
284
|
if (!liveMode) {
|
|
238
285
|
!this.flags.json && this.warn("Reached last time bucket, switching to live mode");
|
|
239
|
-
startTimeBucket = createTimebucketDayjs(currentTime);
|
|
240
286
|
liveMode = true;
|
|
241
287
|
}
|
|
288
|
+
timeBuckets.push(createTimebucketDayjs(currentTime).format(TIME_BUCKET_PATTERN));
|
|
242
289
|
}
|
|
243
290
|
if (liveMode && !found) {
|
|
244
291
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type FetchCatalogIndexesResponse = {
|
|
2
|
+
datacore: {
|
|
3
|
+
fetchIndexes: {
|
|
4
|
+
cursor: string;
|
|
5
|
+
timeBuckets: string[];
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export type FetchCatalogIndexesInput = {
|
|
10
|
+
aggregator: string;
|
|
11
|
+
cursor?: string;
|
|
12
|
+
dataCoreId: string;
|
|
13
|
+
eventType: string;
|
|
14
|
+
fromTimeBucket?: string;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
toTimeBucket?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare const FETCH_CATALOG_INDEXES_QUERY: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
export const FETCH_CATALOG_INDEXES_QUERY = gql `
|
|
3
|
+
query FLOWCORE_CLI_FETCH_CATALOG_INDEXES($dataCoreId: ID!, $aggregator: String!, $eventType: String!, $cursor: String, $fromTimeBucket: String, $toTimeBucket: String, $pageSize: Int) {
|
|
4
|
+
datacore(search: {id: $dataCoreId}) {
|
|
5
|
+
fetchIndexes(input: {
|
|
6
|
+
aggregator: $aggregator,
|
|
7
|
+
eventType: $eventType,
|
|
8
|
+
cursor: $cursor
|
|
9
|
+
fromTimeBucket: $fromTimeBucket
|
|
10
|
+
toTimeBucket: $toTimeBucket
|
|
11
|
+
pageSize: $pageSize
|
|
12
|
+
}) {
|
|
13
|
+
timeBuckets
|
|
14
|
+
cursor
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`;
|
package/oclif.manifest.json
CHANGED
|
@@ -101,6 +101,13 @@
|
|
|
101
101
|
],
|
|
102
102
|
"type": "option"
|
|
103
103
|
},
|
|
104
|
+
"scan": {
|
|
105
|
+
"char": "c",
|
|
106
|
+
"description": "Scan the full time range",
|
|
107
|
+
"name": "scan",
|
|
108
|
+
"allowNo": false,
|
|
109
|
+
"type": "boolean"
|
|
110
|
+
},
|
|
104
111
|
"start": {
|
|
105
112
|
"char": "s",
|
|
106
113
|
"description": "Start time bucket to stream from, example: (1y, 1m, 1w, 1d, 1h)",
|
|
@@ -268,5 +275,5 @@
|
|
|
268
275
|
]
|
|
269
276
|
}
|
|
270
277
|
},
|
|
271
|
-
"version": "
|
|
278
|
+
"version": "2.0.0"
|
|
272
279
|
}
|
package/package.json
CHANGED
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"prestart": "npm run build",
|
|
87
87
|
"update-schema": "rover graph introspect https://graph.api.staging.flowcore.io/graphql -o schema.gql"
|
|
88
88
|
},
|
|
89
|
-
"version": "
|
|
89
|
+
"version": "2.0.0",
|
|
90
90
|
"bugs": "https://github.com/flowcore-io/flowcore-cli/issues",
|
|
91
91
|
"keywords": [
|
|
92
92
|
"flowcore",
|