@flowcore/cli 1.2.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,11 +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
+
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))
12
+
13
+ ## [2.0.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.3.0...v2.0.0) (2024-01-15)
7
14
 
8
15
 
16
+ ### ⚠ BREAKING CHANGES
17
+
18
+ * default mode now does not scan all time buckets but fetches populated ones from the index
19
+
20
+ ### Features
21
+
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))
9
23
 
10
24
  ## [1.2.0](https://github.com/flowcore-io/flowcore-cli/compare/v1.1.2...v1.2.0) (2024-01-09)
11
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.2.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.2.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.2.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.2.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,18 +449,21 @@ 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>]
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
461
463
  -o, --output=<option> [default: http] Output format
462
464
  <options: http|log>
463
- -s, --start=<value> Start time bucket to stream from, example: (1y, 1m, 1d, 1h)
465
+ -s, --start=<value> Start time bucket to stream from, example: (1y, 1m, 1w, 1d, 1h)
466
+ -t, --timeout=<value> [default: 5000] Timeout in milliseconds to wait for a response from the destination
464
467
  --profile=<value> Specify the configuration profile to use
465
468
 
466
469
  DESCRIPTION
@@ -476,7 +479,7 @@ EXAMPLES
476
479
  $ flowcore stream https://flowcore.io/<org>/<data core>/<flow type>/[<event type1>,<event type2>,<event type3>].stream -o log -s 3m
477
480
  ```
478
481
 
479
- _See code: [src/commands/stream.ts](https://github.com/flowcore-io/flowcore-cli/blob/v1.2.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)_
480
483
 
481
484
  ## `flowcore version`
482
485
 
@@ -513,5 +516,5 @@ DESCRIPTION
513
516
  Check what user you are logged in as
514
517
  ```
515
518
 
516
- _See code: [src/commands/whoami.ts](https://github.com/flowcore-io/flowcore-cli/blob/v1.2.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)_
517
520
  <!-- commandsstop -->
@@ -10,9 +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>;
15
+ timeout: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
14
16
  };
15
17
  run(): Promise<void>;
18
+ private fetchTimeBuckets;
16
19
  private processEvents;
17
20
  private setTimeBucket;
18
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,7 +31,9 @@ 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'] }),
32
- start: Flags.string({ char: 's', description: 'Start time bucket to stream from, example: (1y, 1m, 1d, 1h)' }),
34
+ scan: Flags.boolean({ char: 'c', default: false, description: 'Scan the full time range' }),
35
+ start: Flags.string({ char: 's', description: 'Start time bucket to stream from, example: (1y, 1m, 1w, 1d, 1h)' }),
36
+ timeout: Flags.integer({ char: 't', default: 5000, description: 'Timeout in milliseconds to wait for a response from the destination' }),
33
37
  };
34
38
  async run() {
35
39
  const config = this.cliConfiguration.getConfig();
@@ -61,6 +65,9 @@ export default class Stream extends BaseCommand {
61
65
  dataCore,
62
66
  organization: org,
63
67
  });
68
+ if (fetchDataCoreResponse.organization.datacores.length === 0) {
69
+ ux.error(`Data core ${dataCore} not found for organization ${org}`);
70
+ }
64
71
  const dataCoreId = fetchDataCoreResponse.organization.datacores[0].id;
65
72
  const eventRangeRequest = await graphqlClient.client.request(FETCH_EVENT_TYPE_RANGE_GQL_QUERY, {
66
73
  aggregator,
@@ -90,6 +97,40 @@ export default class Stream extends BaseCommand {
90
97
  void this.streamEvents(dataCoreId, aggregator, eventTypes, firstTimeBucket, lastTimeBucket, graphqlClient, observer, flags.live);
91
98
  await this.processEvents(observer, flags.destination, flags.output);
92
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
+ }
93
134
  async processEvents(observer, destination, output) {
94
135
  let done = false;
95
136
  let events = [];
@@ -130,7 +171,7 @@ export default class Stream extends BaseCommand {
130
171
  },
131
172
  method: "POST",
132
173
  }),
133
- new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Timeout waiting for response")), 5000); }),]);
174
+ new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Timeout waiting for response")), this.flags.timeout); }),]);
134
175
  if (!result.ok) {
135
176
  !this.flags.json && this.warn(`Error sending event to ${destination}: ${result.statusText}`);
136
177
  }
@@ -154,7 +195,7 @@ export default class Stream extends BaseCommand {
154
195
  }
155
196
  }
156
197
  setTimeBucket(date, start) {
157
- const parts = start.match(/(\d+)([dhmy])/i);
198
+ const parts = start.match(/(\d+)([dhmwy])/i);
158
199
  if (!parts) {
159
200
  ux.error(`Invalid start time bucket, generated from ${start}`);
160
201
  }
@@ -180,13 +221,22 @@ export default class Stream extends BaseCommand {
180
221
  }
181
222
  }
182
223
  async streamEvents(dataCoreId, aggregator, eventTypes, firstTimeBucket, lastTimeBucket, graphqlClient, observer, live) {
183
- let startTimeBucket = dayjs(firstTimeBucket, TIME_BUCKET_HOUR_PATTERN);
224
+ const startTimeBucket = dayjs(firstTimeBucket, TIME_BUCKET_HOUR_PATTERN);
184
225
  const currentTime = dayjs.utc();
185
226
  let liveMode = false;
186
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));
187
234
  // eslint-disable-next-line no-constant-condition
188
235
  while (true) {
189
- const timeBucket = createTimebucket(startTimeBucket);
236
+ const timeBucket = timeBuckets.shift();
237
+ if (!timeBucket) {
238
+ ux.error("No time bucket found");
239
+ }
190
240
  if (!this.flags.json) {
191
241
  ux.action.start(`Fetching events for ${startTimeBucket}`);
192
242
  }
@@ -203,6 +253,7 @@ export default class Stream extends BaseCommand {
203
253
  const response = await graphqlClient.client.request(FETCH_EVENTS_GQL_QUERY, {
204
254
  afterEventId: lastEventId || undefined,
205
255
  aggregator,
256
+ cursor: cursor || undefined,
206
257
  dataCoreId,
207
258
  eventTypes,
208
259
  timeBucket,
@@ -222,10 +273,7 @@ export default class Stream extends BaseCommand {
222
273
  this.warn(`Error fetching events: ${error}`);
223
274
  }
224
275
  } while (cursor);
225
- if (dayjs(timeBucket, TIME_BUCKET_HOUR_PATTERN).isBefore(currentTime, "hour")) {
226
- startTimeBucket = startTimeBucket.add(1, "hour");
227
- }
228
- if (timeBucket === lastTimeBucket) {
276
+ if (timeBuckets.length === 0) {
229
277
  if (!live) {
230
278
  observer.complete();
231
279
  if (!this.flags.json) {
@@ -235,9 +283,9 @@ export default class Stream extends BaseCommand {
235
283
  }
236
284
  if (!liveMode) {
237
285
  !this.flags.json && this.warn("Reached last time bucket, switching to live mode");
238
- startTimeBucket = createTimebucketDayjs(currentTime);
239
286
  liveMode = true;
240
287
  }
288
+ timeBuckets.push(createTimebucketDayjs(currentTime).format(TIME_BUCKET_PATTERN));
241
289
  }
242
290
  if (liveMode && !found) {
243
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,13 +101,29 @@
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
- "description": "Start time bucket to stream from, example: (1y, 1m, 1d, 1h)",
113
+ "description": "Start time bucket to stream from, example: (1y, 1m, 1w, 1d, 1h)",
107
114
  "name": "start",
108
115
  "hasDynamicHelp": false,
109
116
  "multiple": false,
110
117
  "type": "option"
118
+ },
119
+ "timeout": {
120
+ "char": "t",
121
+ "description": "Timeout in milliseconds to wait for a response from the destination",
122
+ "name": "timeout",
123
+ "default": 5000,
124
+ "hasDynamicHelp": false,
125
+ "multiple": false,
126
+ "type": "option"
111
127
  }
112
128
  },
113
129
  "hasDynamicHelp": false,
@@ -259,5 +275,5 @@
259
275
  ]
260
276
  }
261
277
  },
262
- "version": "1.2.0"
278
+ "version": "2.0.0"
263
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.2.0",
89
+ "version": "2.0.0",
90
90
  "bugs": "https://github.com/flowcore-io/flowcore-cli/issues",
91
91
  "keywords": [
92
92
  "flowcore",