@flowcore/cli 1.3.0 → 2.0.1
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 +16 -7
- package/README.md +9 -7
- package/dist/commands/stream.d.ts +4 -0
- package/dist/commands/stream.js +67 -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,32 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [1.3.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.2.0...v1.3.0) (2024-01-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* added timeout for destination as flag ([f731aac](https://github.com/flowcore-io/flowcore-cli/commit/f731aac025262179af1fd56e2ab2477a674635f9))
|
|
2
7
|
|
|
3
8
|
|
|
4
9
|
### Bug Fixes
|
|
5
10
|
|
|
6
|
-
*
|
|
11
|
+
* added description to start that includes week ([58687a7](https://github.com/flowcore-io/flowcore-cli/commit/58687a7bbb66aaa5d6da26af88e555cbb1e72467))
|
|
7
12
|
|
|
13
|
+
## [2.0.1](https://github.com/flowcore-io/flowcore-cli/compare/v2.0.0...v2.0.1) (2024-01-15)
|
|
8
14
|
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
### Bug Fixes
|
|
11
17
|
|
|
18
|
+
* set to use scan mode when no time buckets are found ([f1fac44](https://github.com/flowcore-io/flowcore-cli/commit/f1fac447a058eb7a61eeff5690208203d27c0a79))
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
## [2.0.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.3.0...v2.0.0) (2024-01-15)
|
|
14
21
|
|
|
15
|
-
* added timeout for destination as flag ([f731aac](https://github.com/flowcore-io/flowcore-cli/commit/f731aac025262179af1fd56e2ab2477a674635f9))
|
|
16
22
|
|
|
23
|
+
### ⚠ BREAKING CHANGES
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
* default mode now does not scan all time buckets but fetches populated ones from the index
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* 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
30
|
|
|
22
31
|
## [1.2.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.1.2...v1.2.0) (2024-01-09)
|
|
23
32
|
|
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.1 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.1/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.1/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.1/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.1/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.1/src/commands/whoami.ts)_
|
|
518
520
|
<!-- commandsstop -->
|
|
@@ -10,10 +10,14 @@ 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 calculateBuckets;
|
|
19
|
+
private calculateTimeDifferenceInHours;
|
|
20
|
+
private fetchTimeBuckets;
|
|
17
21
|
private processEvents;
|
|
18
22
|
private setTimeBucket;
|
|
19
23
|
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,55 @@ 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
|
+
calculateBuckets(timediff, startTimeBucket) {
|
|
101
|
+
return _.times(timediff + 1, (n) => dayjs(startTimeBucket).add(n, "hour").format(TIME_BUCKET_PATTERN));
|
|
102
|
+
}
|
|
103
|
+
calculateTimeDifferenceInHours(lastTimeBucket, startTimeBucket, firstTimeBucket) {
|
|
104
|
+
const timediff = dayjs(lastTimeBucket, TIME_BUCKET_HOUR_PATTERN).diff(startTimeBucket, "hour");
|
|
105
|
+
if (timediff < 0) {
|
|
106
|
+
ux.error(`Invalid time range, start time bucket (${ux.colorize("yellow", firstTimeBucket)}) is after last time bucket (${ux.colorize("yellow", lastTimeBucket)})`);
|
|
107
|
+
}
|
|
108
|
+
return timediff;
|
|
109
|
+
}
|
|
110
|
+
async fetchTimeBuckets(dataCoreId, aggregator, eventTypes, startTimeBucket, lastTimeBucket, graphqlClient) {
|
|
111
|
+
!this.flags.json && ux.action.start("Fetching time buckets");
|
|
112
|
+
let timeBuckets = [];
|
|
113
|
+
for (const eventType of eventTypes) {
|
|
114
|
+
let cursor = null;
|
|
115
|
+
do {
|
|
116
|
+
// eslint-disable-next-line no-await-in-loop
|
|
117
|
+
await graphqlClient.validateToken();
|
|
118
|
+
try {
|
|
119
|
+
// eslint-disable-next-line no-await-in-loop
|
|
120
|
+
const response = await graphqlClient.client.request(FETCH_CATALOG_INDEXES_QUERY, {
|
|
121
|
+
aggregator,
|
|
122
|
+
cursor: cursor || undefined,
|
|
123
|
+
dataCoreId,
|
|
124
|
+
eventType,
|
|
125
|
+
fromTimeBucket: startTimeBucket.format(TIME_BUCKET_PATTERN),
|
|
126
|
+
toTimeBucket: lastTimeBucket,
|
|
127
|
+
});
|
|
128
|
+
cursor = response.datacore.fetchIndexes.cursor;
|
|
129
|
+
timeBuckets.push(...response.datacore.fetchIndexes.timeBuckets);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// don't display error if token is expired
|
|
133
|
+
if (error instanceof Error && error.message.includes("Token is expired")) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
this.warn(`Error fetching events: ${error}`);
|
|
137
|
+
}
|
|
138
|
+
} while (cursor);
|
|
139
|
+
}
|
|
140
|
+
timeBuckets = _.uniq(timeBuckets);
|
|
141
|
+
!this.flags.json && ux.action.stop(`Found ${timeBuckets.length} time buckets`);
|
|
142
|
+
if (timeBuckets.length === 0) {
|
|
143
|
+
!this.flags.json && this.warn("No time buckets found, setting to scan based on requested time range");
|
|
144
|
+
const timediff = this.calculateTimeDifferenceInHours(lastTimeBucket, startTimeBucket, createTimebucket(dayjs()));
|
|
145
|
+
timeBuckets = this.calculateBuckets(timediff, startTimeBucket);
|
|
146
|
+
}
|
|
147
|
+
return timeBuckets;
|
|
148
|
+
}
|
|
94
149
|
async processEvents(observer, destination, output) {
|
|
95
150
|
let done = false;
|
|
96
151
|
let events = [];
|
|
@@ -181,13 +236,19 @@ export default class Stream extends BaseCommand {
|
|
|
181
236
|
}
|
|
182
237
|
}
|
|
183
238
|
async streamEvents(dataCoreId, aggregator, eventTypes, firstTimeBucket, lastTimeBucket, graphqlClient, observer, live) {
|
|
184
|
-
|
|
239
|
+
const startTimeBucket = dayjs(firstTimeBucket, TIME_BUCKET_HOUR_PATTERN);
|
|
185
240
|
const currentTime = dayjs.utc();
|
|
186
241
|
let liveMode = false;
|
|
187
242
|
let lastEventId = null;
|
|
243
|
+
const timediff = this.calculateTimeDifferenceInHours(lastTimeBucket, startTimeBucket, firstTimeBucket);
|
|
244
|
+
let timeBuckets = [];
|
|
245
|
+
timeBuckets = this.flags.scan ? this.calculateBuckets(timediff, startTimeBucket) : (await this.fetchTimeBuckets(dataCoreId, aggregator, eventTypes, startTimeBucket, lastTimeBucket, graphqlClient));
|
|
188
246
|
// eslint-disable-next-line no-constant-condition
|
|
189
247
|
while (true) {
|
|
190
|
-
const timeBucket =
|
|
248
|
+
const timeBucket = timeBuckets.shift();
|
|
249
|
+
if (!timeBucket) {
|
|
250
|
+
ux.error("No time bucket found");
|
|
251
|
+
}
|
|
191
252
|
if (!this.flags.json) {
|
|
192
253
|
ux.action.start(`Fetching events for ${startTimeBucket}`);
|
|
193
254
|
}
|
|
@@ -204,6 +265,7 @@ export default class Stream extends BaseCommand {
|
|
|
204
265
|
const response = await graphqlClient.client.request(FETCH_EVENTS_GQL_QUERY, {
|
|
205
266
|
afterEventId: lastEventId || undefined,
|
|
206
267
|
aggregator,
|
|
268
|
+
cursor: cursor || undefined,
|
|
207
269
|
dataCoreId,
|
|
208
270
|
eventTypes,
|
|
209
271
|
timeBucket,
|
|
@@ -223,10 +285,7 @@ export default class Stream extends BaseCommand {
|
|
|
223
285
|
this.warn(`Error fetching events: ${error}`);
|
|
224
286
|
}
|
|
225
287
|
} while (cursor);
|
|
226
|
-
if (
|
|
227
|
-
startTimeBucket = startTimeBucket.add(1, "hour");
|
|
228
|
-
}
|
|
229
|
-
if (timeBucket === lastTimeBucket) {
|
|
288
|
+
if (timeBuckets.length === 0) {
|
|
230
289
|
if (!live) {
|
|
231
290
|
observer.complete();
|
|
232
291
|
if (!this.flags.json) {
|
|
@@ -236,9 +295,9 @@ export default class Stream extends BaseCommand {
|
|
|
236
295
|
}
|
|
237
296
|
if (!liveMode) {
|
|
238
297
|
!this.flags.json && this.warn("Reached last time bucket, switching to live mode");
|
|
239
|
-
startTimeBucket = createTimebucketDayjs(currentTime);
|
|
240
298
|
liveMode = true;
|
|
241
299
|
}
|
|
300
|
+
timeBuckets.push(createTimebucketDayjs(currentTime).format(TIME_BUCKET_PATTERN));
|
|
242
301
|
}
|
|
243
302
|
if (liveMode && !found) {
|
|
244
303
|
// 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.1"
|
|
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.1",
|
|
90
90
|
"bugs": "https://github.com/flowcore-io/flowcore-cli/issues",
|
|
91
91
|
"keywords": [
|
|
92
92
|
"flowcore",
|