@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 CHANGED
@@ -1,23 +1,32 @@
1
- ## [0.4.9](https://github.com/oclif/hello-world/compare/0.4.8...0.4.9) (2023-11-26)
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
- * **deps:** bump @oclif/plugin-plugins from 4.1.5 to 4.1.8 ([eb58fcf](https://github.com/oclif/hello-world/commit/eb58fcf7f6535189dc1acbec3628ed37702df7e5))
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
- ## [1.3.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.2.0...v1.3.0) (2024-01-09)
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
- ### Features
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
- ### Bug Fixes
25
+ * default mode now does not scan all time buckets but fetches populated ones from the index
19
26
 
20
- * added description to start that includes week ([58687a7](https://github.com/flowcore-io/flowcore-cli/commit/58687a7bbb66aaa5d6da26af88e555cbb1e72467))
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/1.3.0 linux-x64 node-v20.10.0
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/v1.3.0/src/commands/config/set.ts)_
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/v1.3.0/src/commands/config/show.ts)_
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/v1.3.0/src/commands/login.ts)_
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 <value>]
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/v1.3.0/src/commands/stream.ts)_
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/v1.3.0/src/commands/whoami.ts)_
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;
@@ -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 { TIME_BUCKET_HOUR_PATTERN, createTimebucket, createTimebucketDayjs } from "../utils/timebucket.util.js";
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
- let startTimeBucket = dayjs(firstTimeBucket, TIME_BUCKET_HOUR_PATTERN);
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 = createTimebucket(startTimeBucket);
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 (dayjs(timeBucket, TIME_BUCKET_HOUR_PATTERN).isBefore(currentTime, "hour")) {
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
+ `;
@@ -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": "1.3.0"
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": "1.3.0",
89
+ "version": "2.0.1",
90
90
  "bugs": "https://github.com/flowcore-io/flowcore-cli/issues",
91
91
  "keywords": [
92
92
  "flowcore",