@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 CHANGED
@@ -1,23 +1,25 @@
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
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * **deps:** bump @oclif/plugin-plugins from 4.1.5 to 4.1.8 ([eb58fcf](https://github.com/oclif/hello-world/commit/eb58fcf7f6535189dc1acbec3628ed37702df7e5))
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
- ## [1.3.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.2.0...v1.3.0) (2024-01-09)
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
- * added timeout for destination as flag ([f731aac](https://github.com/flowcore-io/flowcore-cli/commit/f731aac025262179af1fd56e2ab2477a674635f9))
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
- ### Bug Fixes
20
+ ### Features
19
21
 
20
- * added description to start that includes week ([58687a7](https://github.com/flowcore-io/flowcore-cli/commit/58687a7bbb66aaa5d6da26af88e555cbb1e72467))
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/1.3.0 linux-x64 node-v20.10.0
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/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.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/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.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/v1.3.0/src/commands/login.ts)_
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 <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.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/v1.3.0/src/commands/whoami.ts)_
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;
@@ -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,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
- let startTimeBucket = dayjs(firstTimeBucket, TIME_BUCKET_HOUR_PATTERN);
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 = createTimebucket(startTimeBucket);
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 (dayjs(timeBucket, TIME_BUCKET_HOUR_PATTERN).isBefore(currentTime, "hour")) {
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
+ `;
@@ -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.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": "1.3.0",
89
+ "version": "2.0.0",
90
90
  "bugs": "https://github.com/flowcore-io/flowcore-cli/issues",
91
91
  "keywords": [
92
92
  "flowcore",