@learnpack/learnpack 5.0.11 → 5.0.13

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/README.md CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
21
21
  $ learnpack COMMAND
22
22
  running command...
23
23
  $ learnpack (-v|--version|version)
24
- @learnpack/learnpack/5.0.11 win32-x64 node-v20.16.0
24
+ @learnpack/learnpack/5.0.13 win32-x64 node-v20.16.0
25
25
  $ learnpack --help [COMMAND]
26
26
  USAGE
27
27
  $ learnpack COMMAND
@@ -75,7 +75,7 @@ DESCRIPTION
75
75
  12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
76
76
  ```
77
77
 
78
- _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\audit.ts)_
78
+ _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\audit.ts)_
79
79
 
80
80
  ## `learnpack clean`
81
81
 
@@ -90,7 +90,7 @@ DESCRIPTION
90
90
  Extra documentation goes here
91
91
  ```
92
92
 
93
- _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\clean.ts)_
93
+ _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\clean.ts)_
94
94
 
95
95
  ## `learnpack download [PACKAGE]`
96
96
 
@@ -108,7 +108,7 @@ DESCRIPTION
108
108
  Extra documentation goes here
109
109
  ```
110
110
 
111
- _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\download.ts)_
111
+ _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\download.ts)_
112
112
 
113
113
  ## `learnpack help [COMMAND]`
114
114
 
@@ -139,7 +139,7 @@ OPTIONS
139
139
  -h, --grading show CLI help
140
140
  ```
141
141
 
142
- _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\init.ts)_
142
+ _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\init.ts)_
143
143
 
144
144
  ## `learnpack login [PACKAGE]`
145
145
 
@@ -157,7 +157,7 @@ DESCRIPTION
157
157
  Extra documentation goes here
158
158
  ```
159
159
 
160
- _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\login.ts)_
160
+ _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\login.ts)_
161
161
 
162
162
  ## `learnpack logout [PACKAGE]`
163
163
 
@@ -175,7 +175,7 @@ DESCRIPTION
175
175
  Extra documentation goes here
176
176
  ```
177
177
 
178
- _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\logout.ts)_
178
+ _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\logout.ts)_
179
179
 
180
180
  ## `learnpack plugins`
181
181
 
@@ -306,7 +306,7 @@ OPTIONS
306
306
  -h, --help show CLI help
307
307
  ```
308
308
 
309
- _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\publish.ts)_
309
+ _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\publish.ts)_
310
310
 
311
311
  ## `learnpack start`
312
312
 
@@ -327,7 +327,7 @@ OPTIONS
327
327
  -w, --watch Watch for file changes
328
328
  ```
329
329
 
330
- _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\start.ts)_
330
+ _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\start.ts)_
331
331
 
332
332
  ## `learnpack test [EXERCISESLUG]`
333
333
 
@@ -341,7 +341,7 @@ ARGUMENTS
341
341
  EXERCISESLUG The name of the exercise to test
342
342
  ```
343
343
 
344
- _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\test.ts)_
344
+ _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\test.ts)_
345
345
 
346
346
  ## `learnpack translate`
347
347
 
@@ -352,7 +352,7 @@ USAGE
352
352
  $ learnpack translate
353
353
  ```
354
354
 
355
- _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.11/src\commands\translate.ts)_
355
+ _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.13/src\commands\translate.ts)_
356
356
  <!-- commandsstop -->
357
357
 
358
358
  > > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
@@ -69,6 +69,7 @@ class StartCommand extends SessionCommand_1.default {
69
69
  compilations: [],
70
70
  tests: [],
71
71
  is_testeable: e.graded || false,
72
+ quiz_submissions: [],
72
73
  }));
73
74
  if (path && steps.length > 0) {
74
75
  telemetry_1.default.start(agent, steps, path, tutorialSlug);
@@ -141,6 +142,10 @@ class StartCommand extends SessionCommand_1.default {
141
142
  telemetry: telemetry_1.default,
142
143
  });
143
144
  });
145
+ socket_1.default.on("quiz_submission", (data) => {
146
+ const { stepPosition, event, eventData } = data;
147
+ telemetry_1.default.registerStepEvent(stepPosition, event, eventData);
148
+ });
144
149
  socket_1.default.on("ai_interaction", (data) => {
145
150
  const { stepPosition, event, eventData } = data;
146
151
  telemetry_1.default.registerStepEvent(stepPosition, event, eventData);
@@ -134,6 +134,11 @@ async function default_1(app, configObject, configManager) {
134
134
  const payload = await session_1.default.getPayload();
135
135
  if (payload && payload.rigobot && payload.rigobot.key) {
136
136
  res.json({ rigoToken: payload.rigobot.key, payload: payload });
137
+ telemetry_1.default.setStudent({
138
+ user_id: payload.user_id,
139
+ email: payload.email,
140
+ token: payload.token,
141
+ });
137
142
  }
138
143
  else {
139
144
  res
@@ -49,8 +49,13 @@ const Session = {
49
49
  },
50
50
  setPayload: async function (value) {
51
51
  await this.initialize();
52
- await storage.setItem("bc-payload", Object.assign({ token: this.token }, value));
52
+ await storage.setItem("bc-payload", Object.assign({}, value));
53
53
  console_1.default.debug("Payload successfuly found and set for " + value.email);
54
+ // TelemetryManager.setStudent({
55
+ // user_id: value.user_id.toString(),
56
+ // email: value.email,
57
+ // token: value.token,
58
+ // })
54
59
  return true;
55
60
  },
56
61
  getPayload: async function () {
@@ -20,6 +20,16 @@ type TAIInteraction = {
20
20
  starting_at: number;
21
21
  ending_at: number;
22
22
  };
23
+ type TQuizSelection = {
24
+ question: string;
25
+ answer: string;
26
+ isCorrect: boolean;
27
+ };
28
+ type TQuizSubmission = {
29
+ quiz_hash: string;
30
+ selections: TQuizSelection[];
31
+ submitted_at: number;
32
+ };
23
33
  export type TStep = {
24
34
  slug: string;
25
35
  position: number;
@@ -30,6 +40,7 @@ export type TStep = {
30
40
  compilations: TCompilationAttempt[];
31
41
  tests: TTestAttempt[];
32
42
  ai_interactions: TAIInteraction[];
43
+ quiz_submissions: TQuizSubmission[];
33
44
  };
34
45
  type TWorkoutSession = {
35
46
  started_at: number;
@@ -45,20 +56,27 @@ export interface ITelemetryJSONSchema {
45
56
  user_id?: number | string;
46
57
  slug: string;
47
58
  agent?: string;
48
- tutorial_started_at?: number;
49
- last_interaction_at?: number;
59
+ tutorial_started_at: number;
60
+ last_interaction_at: number;
50
61
  steps: Array<TStep>;
51
62
  workout_session: TWorkoutSession[];
52
63
  }
53
- type TStepEvent = "compile" | "test" | "ai_interaction" | "open_step";
64
+ type TStepEvent = "compile" | "test" | "ai_interaction" | "open_step" | "quiz_submission";
54
65
  export type TTelemetryUrls = {
55
66
  streaming?: string;
56
67
  batch?: string;
57
68
  };
69
+ type TUser = {
70
+ token: string;
71
+ id: string;
72
+ email: string;
73
+ };
58
74
  interface ITelemetryManager {
59
75
  current: ITelemetryJSONSchema | null;
60
76
  configPath: string | null;
77
+ user: TUser;
61
78
  urls: TTelemetryUrls;
79
+ started: boolean;
62
80
  salute: (message: string) => void;
63
81
  start: (agent: string, steps: TStep[], path: string, tutorialSlug: string) => void;
64
82
  prevStep?: number;
@@ -13,7 +13,13 @@ function stringToBase64(input) {
13
13
  const TelemetryManager = {
14
14
  current: null,
15
15
  urls: {},
16
+ user: {
17
+ token: "",
18
+ id: "",
19
+ email: "",
20
+ },
16
21
  configPath: "",
22
+ started: false,
17
23
  salute: message => {
18
24
  console_1.default.info(message);
19
25
  },
@@ -33,6 +39,7 @@ const TelemetryManager = {
33
39
  slug: tutorialSlug,
34
40
  agent,
35
41
  tutorial_started_at: Date.now(),
42
+ last_interaction_at: Date.now(),
36
43
  steps,
37
44
  workout_session: [
38
45
  {
@@ -41,7 +48,16 @@ const TelemetryManager = {
41
48
  ],
42
49
  };
43
50
  }
51
+ if (this.current.user_id) {
52
+ this.user.id = this.current.user_id.toString();
53
+ }
44
54
  this.save();
55
+ this.started = true;
56
+ console_1.default.debug("Telemetry started successfully!");
57
+ if (!this.user.id) {
58
+ console_1.default.debug("No user ID found, impossible to submit telemetry at start");
59
+ return;
60
+ }
45
61
  this.submit();
46
62
  })
47
63
  .catch(error => {
@@ -52,18 +68,16 @@ const TelemetryManager = {
52
68
  });
53
69
  }
54
70
  },
55
- // verifyStudent: function () {
56
- // if (!this.current) {
57
- // return;
58
- // }
59
- // if (!this.current.user_id) {
60
- // }
61
- // },
62
71
  setStudent: function (student) {
63
72
  if (!this.current) {
73
+ console_1.default.debug("Telemetry has not been started");
64
74
  return;
65
75
  }
76
+ console_1.default.debug("Setting student", student);
66
77
  this.current.user_id = student.user_id;
78
+ this.user.id = student.user_id;
79
+ this.user.token = student.token;
80
+ this.user.email = student.email;
67
81
  this.save();
68
82
  this.submit();
69
83
  },
@@ -83,6 +97,7 @@ const TelemetryManager = {
83
97
  }
84
98
  },
85
99
  registerStepEvent: function (stepPosition, event, data) {
100
+ console_1.default.debug(`Registering Event ${event} for user ${this.user.id}`);
86
101
  if (!this.current) {
87
102
  // throw new Error("Telemetry has not been started");
88
103
  return;
@@ -129,6 +144,13 @@ const TelemetryManager = {
129
144
  }
130
145
  step.ai_interactions.push(data);
131
146
  break;
147
+ case "quiz_submission": {
148
+ if (!step.quiz_submissions) {
149
+ step.quiz_submissions = [];
150
+ }
151
+ step.quiz_submissions.push(data);
152
+ break;
153
+ }
132
154
  case "open_step": {
133
155
  const now = Date.now();
134
156
  if (!step.opened_at) {
@@ -178,13 +200,23 @@ const TelemetryManager = {
178
200
  },
179
201
  submit: async function () {
180
202
  console_1.default.debug("Submitting telemetry...");
181
- if (!this.current)
203
+ if (!this.current) {
204
+ console_1.default.debug("Telemetry has not been started");
205
+ return Promise.resolve();
206
+ }
207
+ if (!this.user.id) {
208
+ console_1.default.debug("User ID not found, skipping batch telemetry delivery");
182
209
  return Promise.resolve();
210
+ }
183
211
  const url = this.urls.batch;
184
212
  if (!url) {
185
- return;
213
+ console_1.default.debug("Batch URL not found, skipping batch telemetry delivery");
214
+ return Promise.resolve();
186
215
  }
187
216
  const body = this.current;
217
+ if (!body.user_id) {
218
+ body.user_id = this.user.id;
219
+ }
188
220
  api_1.default.sendBatchTelemetry(url, body);
189
221
  },
190
222
  save: function () {
@@ -1,6 +1,8 @@
1
1
  import { IConfig, IConfigObj } from "./config";
2
2
  export interface IPayload {
3
3
  email: string;
4
+ user_id: number;
5
+ token: string;
4
6
  }
5
7
  export interface IStartProps {
6
8
  token: string;
@@ -8,7 +8,7 @@ declare const _default: {
8
8
  lang?: string;
9
9
  slug?: string;
10
10
  }) => Promise<any>;
11
- sendBatchTelemetry: (url: string, body: object) => Promise<void>;
11
+ sendBatchTelemetry: (url: string, body: any) => Promise<void>;
12
12
  sendStreamTelemetry: (url: string, body: object) => Promise<void>;
13
13
  };
14
14
  export default _default;
package/lib/utils/api.js CHANGED
@@ -207,16 +207,21 @@ const sendBatchTelemetry = async function (url, body) {
207
207
  if (!session ||
208
208
  !Object.prototype.hasOwnProperty.call(session, "token") ||
209
209
  session.token === "") {
210
- console_1.default.debug("No token found, skipping stream telemetry delivery");
210
+ console_1.default.debug("No token found, skipping batch telemetry delivery");
211
+ return;
212
+ }
213
+ if (!session || !session.user_id || session.user_id === "") {
214
+ console_1.default.debug("No user_id found, skipping batch telemetry delivery");
211
215
  return;
212
216
  }
217
+ body.user_id = session.user_id;
213
218
  fetch(url, {
214
219
  method: "POST",
215
220
  body: JSON.stringify(body),
216
221
  }, false)
217
222
  .then(response => {
218
223
  console_1.default.debug("Telemetry sent successfully");
219
- return response.text();
224
+ return response;
220
225
  })
221
226
  .catch(error => {
222
227
  console_1.default.debug("Error while sending batch Telemetry", error);
@@ -160,6 +160,7 @@ const checkNotInstalledDependencies = async (neededPlugins) => {
160
160
  await installDependencies(jsPluginsDependencies, "npm");
161
161
  }
162
162
  if (pytestNeeded) {
163
+ await exec("pip install --upgrade pip");
163
164
  const { stdout, stderr } = await exec("pip list");
164
165
  if (stderr) {
165
166
  console_1.default.error(`Error executing pip list. Use debug for more info`);
@@ -1 +1 @@
1
- {"version":"5.0.11","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]}}}
1
+ {"version":"5.0.13","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]}}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.11",
4
+ "version": "5.0.13",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -157,6 +157,7 @@ export default class StartCommand extends SessionCommand {
157
157
  compilations: [],
158
158
  tests: [],
159
159
  is_testeable: e.graded || false,
160
+ quiz_submissions: [],
160
161
  })
161
162
  )
162
163
  if (path && steps.length > 0) {
@@ -248,6 +249,11 @@ export default class StartCommand extends SessionCommand {
248
249
  })
249
250
  })
250
251
 
252
+ socket.on("quiz_submission", (data: any) => {
253
+ const { stepPosition, event, eventData } = data
254
+ TelemetryManager.registerStepEvent(stepPosition, event, eventData)
255
+ })
256
+
251
257
  socket.on("ai_interaction", (data: any) => {
252
258
  const { stepPosition, event, eventData } = data
253
259
  TelemetryManager.registerStepEvent(stepPosition, event, eventData)
@@ -177,6 +177,12 @@ export default async function (
177
177
 
178
178
  if (payload && payload.rigobot && payload.rigobot.key) {
179
179
  res.json({ rigoToken: payload.rigobot.key, payload: payload })
180
+
181
+ TelemetryManager.setStudent({
182
+ user_id: payload.user_id,
183
+ email: payload.email,
184
+ token: payload.token,
185
+ })
180
186
  } else {
181
187
  res
182
188
  .status(400)
@@ -66,8 +66,15 @@ const Session: ISession = {
66
66
  },
67
67
  setPayload: async function (value: IPayload) {
68
68
  await this.initialize()
69
- await storage.setItem("bc-payload", { token: this.token, ...value })
69
+ await storage.setItem("bc-payload", { ...value })
70
70
  Console.debug("Payload successfuly found and set for " + value.email)
71
+
72
+ // TelemetryManager.setStudent({
73
+ // user_id: value.user_id.toString(),
74
+ // email: value.email,
75
+ // token: value.token,
76
+ // })
77
+
71
78
  return true
72
79
  },
73
80
  getPayload: async function () {
@@ -128,6 +135,7 @@ const Session: ISession = {
128
135
  const data = await api.login(email, password)
129
136
  if (data) {
130
137
  this.start({ token: data.token, payload: data })
138
+
131
139
  TelemetryManager.setStudent({
132
140
  user_id: data.user_id,
133
141
  email: data.email,
@@ -1,353 +1,423 @@
1
- import { IFile } from "../models/file"
2
- import API from "../utils/api"
3
- import Console from "../utils/console"
4
-
5
- const fs = require("fs")
6
-
7
- function createUUID(): string {
8
- return (
9
- Math.random().toString(36).slice(2, 10) +
10
- Math.random().toString(36).slice(2, 10)
11
- )
12
- }
13
-
14
- function stringToBase64(input: string): string {
15
- return Buffer.from(input).toString("base64")
16
- }
17
-
18
- type TCompilationAttempt = {
19
- source_code: string;
20
- stdout: string;
21
- exit_code: number;
22
- starting_at: number;
23
- ending_at: number;
24
- };
25
-
26
- type TTestAttempt = {
27
- source_code: string;
28
- stdout: string;
29
- exit_code: number;
30
- starting_at: number;
31
- ending_at: number;
32
- };
33
-
34
- type TAIInteraction = {
35
- student_message: string;
36
- source_code: string;
37
- ai_response: string;
38
- starting_at: number;
39
- ending_at: number;
40
- };
41
-
42
- export type TStep = {
43
- slug: string;
44
- position: number;
45
- files: IFile[];
46
- is_testeable: boolean;
47
- opened_at?: number; // The time when the step was opened
48
- completed_at?: number; // If the step has tests, the time when all the tests passed, else, the time when the user opens the next step
49
- compilations: TCompilationAttempt[]; // Everytime the user tries to compile the code
50
- tests: TTestAttempt[]; // Everytime the user tries to run the tests
51
- ai_interactions: TAIInteraction[]; // Everytime the user interacts with the AI
52
- };
53
-
54
- type TWorkoutSession = {
55
- started_at: number;
56
- ended_at?: number;
57
- };
58
-
59
- type TStudent = {
60
- token: string;
61
- user_id: string;
62
- email: string;
63
- };
64
-
65
- export interface ITelemetryJSONSchema {
66
- telemetry_id?: string;
67
- user_id?: number | string;
68
- slug: string;
69
- agent?: string;
70
- tutorial_started_at?: number;
71
- last_interaction_at?: number;
72
- steps: Array<TStep>; // The steps should be the same as the exercise
73
- workout_session: TWorkoutSession[]; // It start when the user starts Learnpack, if the last_interaction_at is available, it automatically fills with that
74
- // number and start another session
75
- }
76
-
77
- type TStepEvent = "compile" | "test" | "ai_interaction" | "open_step";
78
-
79
- export type TTelemetryUrls = {
80
- streaming?: string;
81
- batch?: string;
82
- };
83
-
84
- interface ITelemetryManager {
85
- current: ITelemetryJSONSchema | null;
86
- configPath: string | null;
87
- urls: TTelemetryUrls;
88
- salute: (message: string) => void;
89
- start: (
90
- agent: string,
91
- steps: TStep[],
92
- path: string,
93
- tutorialSlug: string
94
- ) => void;
95
- prevStep?: number;
96
- registerStepEvent: (
97
- stepPosition: number,
98
- event: TStepEvent,
99
- data: any
100
- ) => void;
101
- streamEvent: (stepPosition: number, event: string, data: any) => void;
102
- submit: () => Promise<void>;
103
- finishWorkoutSession: () => void;
104
- setStudent: (student: TStudent) => void;
105
- save: () => void;
106
- retrieve: () => Promise<ITelemetryJSONSchema | null>;
107
- }
108
-
109
- const TelemetryManager: ITelemetryManager = {
110
- current: null,
111
- urls: {},
112
- configPath: "",
113
- salute: message => {
114
- Console.info(message)
115
- },
116
-
117
- start: function (agent, steps, path, tutorialSlug) {
118
- this.configPath = path
119
- if (!this.current) {
120
- this.retrieve()
121
- .then(data => {
122
- const prevTelemetry = data
123
- if (prevTelemetry) {
124
- this.current = prevTelemetry
125
- this.finishWorkoutSession()
126
- } else {
127
- this.current = {
128
- telemetry_id: createUUID(),
129
- slug: tutorialSlug,
130
- agent,
131
- tutorial_started_at: Date.now(),
132
- steps,
133
- workout_session: [
134
- {
135
- started_at: Date.now(),
136
- },
137
- ],
138
- }
139
- }
140
-
141
- this.save()
142
- this.submit()
143
- })
144
- .catch(error => {
145
- Console.debug(error)
146
- // Delete the telemetry.json if it exists
147
- fs.unlinkSync(`${this.configPath}/telemetry.json`)
148
- throw new Error(
149
- "There was a problem starting, reload LearnPack\nRun\n$ learnpack start"
150
- )
151
- })
152
- }
153
- },
154
- // verifyStudent: function () {
155
- // if (!this.current) {
156
- // return;
157
- // }
158
-
159
- // if (!this.current.user_id) {
160
-
161
- // }
162
- // },
163
-
164
- setStudent: function (student) {
165
- if (!this.current) {
166
- return
167
- }
168
-
169
- this.current.user_id = student.user_id
170
- this.save()
171
- this.submit()
172
- },
173
- finishWorkoutSession: function () {
174
- if (!this.current) {
175
- return
176
- }
177
-
178
- const lastSession =
179
- this.current?.workout_session[this.current.workout_session.length - 1]
180
- if (
181
- lastSession &&
182
- !lastSession.ended_at &&
183
- this.current?.last_interaction_at
184
- ) {
185
- lastSession.ended_at = this.current.last_interaction_at
186
- this.current.workout_session.push({
187
- started_at: Date.now(),
188
- })
189
- }
190
- },
191
-
192
- registerStepEvent: function (stepPosition, event, data) {
193
- if (!this.current) {
194
- // throw new Error("Telemetry has not been started");
195
- return
196
- }
197
-
198
- const step = this.current.steps[stepPosition]
199
- if (!step) {
200
- return
201
- }
202
-
203
- if (data.source_code) {
204
- data.source_code = stringToBase64(data.source_code)
205
- }
206
-
207
- if (data.stdout) {
208
- data.stdout = stringToBase64(data.stdout)
209
- }
210
-
211
- if (data.stderr) {
212
- data.stderr = stringToBase64(data.stderr)
213
- }
214
-
215
- if (Object.prototype.hasOwnProperty.call(data, "exitCode")) {
216
- data.exit_code = data.exitCode
217
- data.exitCode = undefined
218
- }
219
-
220
- switch (event) {
221
- case "compile":
222
- if (!step.compilations) {
223
- step.compilations = []
224
- }
225
-
226
- step.compilations.push(data)
227
- this.current.steps[stepPosition] = step
228
- break
229
- case "test":
230
- if (!step.tests) {
231
- step.tests = []
232
- }
233
-
234
- // data.stdout =
235
- step.tests.push(data)
236
- if (data.exit_code === 0) {
237
- step.completed_at = Date.now()
238
- }
239
-
240
- this.current.steps[stepPosition] = step
241
- break
242
- case "ai_interaction":
243
- if (!step.ai_interactions) {
244
- step.ai_interactions = []
245
- }
246
-
247
- step.ai_interactions.push(data)
248
- break
249
- case "open_step": {
250
- const now = Date.now()
251
-
252
- if (!step.opened_at) {
253
- step.opened_at = now
254
- this.current.steps[stepPosition] = step
255
- }
256
-
257
- if (this.prevStep || this.prevStep === 0) {
258
- const prevStep = this.current.steps[this.prevStep]
259
- if (!prevStep.is_testeable && !prevStep.completed_at) {
260
- prevStep.completed_at = now
261
- this.current.steps[this.prevStep] = prevStep
262
- }
263
- }
264
-
265
- this.prevStep = stepPosition
266
-
267
- this.submit()
268
- break
269
- }
270
-
271
- default:
272
- throw new Error(`Event type ${event} is not supported`)
273
- }
274
-
275
- this.current.last_interaction_at = Date.now()
276
- this.streamEvent(stepPosition, event, data)
277
- this.save()
278
- },
279
- retrieve: function () {
280
- return new Promise((resolve, reject) => {
281
- fs.readFile(
282
- `${this.configPath}/telemetry.json`,
283
- "utf8",
284
- (err: any, data: any) => {
285
- if (err) {
286
- if (err.code === "ENOENT") {
287
- // File does not exist, resolve with undefined
288
- resolve(null)
289
- } else {
290
- reject(err)
291
- }
292
- } else {
293
- try {
294
- resolve(JSON.parse(data))
295
- } catch (error) {
296
- reject(error)
297
- }
298
- }
299
- }
300
- )
301
- })
302
- },
303
-
304
- submit: async function () {
305
- Console.debug("Submitting telemetry...")
306
-
307
- if (!this.current)
308
- return Promise.resolve()
309
- const url = this.urls.batch
310
- if (!url) {
311
- return
312
- }
313
-
314
- const body = this.current
315
-
316
- API.sendBatchTelemetry(url, body)
317
- },
318
- save: function () {
319
- fs.writeFile(
320
- `${this.configPath}/telemetry.json`,
321
- JSON.stringify(this.current),
322
- (err: any) => {
323
- if (err)
324
- throw err
325
- }
326
- )
327
- },
328
-
329
- streamEvent: async function (stepPosition, event, data) {
330
- if (!this.current)
331
- return
332
-
333
- const url = this.urls.streaming
334
- if (!url) {
335
- return
336
- }
337
-
338
- const stepSlug = this.current.steps[stepPosition].slug
339
-
340
- const body = {
341
- slug: stepSlug,
342
- telemetry_id: this.current.telemetry_id,
343
- user_id: this.current.user_id,
344
- step_position: stepPosition,
345
- event,
346
- data,
347
- }
348
-
349
- API.sendStreamTelemetry(url, body)
350
- },
351
- }
352
-
353
- export default TelemetryManager
1
+ import { IFile } from "../models/file"
2
+ import API from "../utils/api"
3
+ import Console from "../utils/console"
4
+
5
+ const fs = require("fs")
6
+
7
+ function createUUID(): string {
8
+ return (
9
+ Math.random().toString(36).slice(2, 10) +
10
+ Math.random().toString(36).slice(2, 10)
11
+ )
12
+ }
13
+
14
+ function stringToBase64(input: string): string {
15
+ return Buffer.from(input).toString("base64")
16
+ }
17
+
18
+ type TCompilationAttempt = {
19
+ source_code: string
20
+ stdout: string
21
+ exit_code: number
22
+ starting_at: number
23
+ ending_at: number
24
+ }
25
+
26
+ type TTestAttempt = {
27
+ source_code: string
28
+ stdout: string
29
+ exit_code: number
30
+ starting_at: number
31
+ ending_at: number
32
+ }
33
+
34
+ type TAIInteraction = {
35
+ student_message: string
36
+ source_code: string
37
+ ai_response: string
38
+ starting_at: number
39
+ ending_at: number
40
+ }
41
+
42
+ type TQuizSelection = {
43
+ question: string
44
+ answer: string
45
+ isCorrect: boolean
46
+ }
47
+
48
+ type TQuizSubmission = {
49
+ quiz_hash: string
50
+ selections: TQuizSelection[]
51
+ submitted_at: number
52
+ }
53
+
54
+ export type TStep = {
55
+ slug: string
56
+ position: number
57
+ files: IFile[]
58
+ is_testeable: boolean
59
+ opened_at?: number // The time when the step was opened
60
+ completed_at?: number // If the step has tests, the time when all the tests passed, else, the time when the user opens the next step
61
+ compilations: TCompilationAttempt[] // Everytime the user tries to compile the code
62
+ tests: TTestAttempt[] // Everytime the user tries to run the tests
63
+ ai_interactions: TAIInteraction[] // Everytime the user interacts with the AI
64
+ quiz_submissions: TQuizSubmission[] // Everytime the user submits a quiz
65
+ }
66
+
67
+ type TWorkoutSession = {
68
+ started_at: number
69
+ ended_at?: number
70
+ }
71
+
72
+ type TStudent = {
73
+ token: string
74
+ user_id: string
75
+ email: string
76
+ }
77
+
78
+ export interface ITelemetryJSONSchema {
79
+ telemetry_id?: string
80
+ user_id?: number | string
81
+ slug: string
82
+ agent?: string
83
+ tutorial_started_at: number
84
+ last_interaction_at: number
85
+ steps: Array<TStep> // The steps should be the same as the exercise
86
+ workout_session: TWorkoutSession[] // It start when the user starts Learnpack, if the last_interaction_at is available, it automatically fills with that
87
+ // number and start another session
88
+ }
89
+
90
+ type TStepEvent =
91
+ | "compile"
92
+ | "test"
93
+ | "ai_interaction"
94
+ | "open_step"
95
+ | "quiz_submission"
96
+
97
+ export type TTelemetryUrls = {
98
+ streaming?: string
99
+ batch?: string
100
+ }
101
+
102
+ type TUser = {
103
+ token: string
104
+ id: string
105
+ email: string
106
+ }
107
+
108
+ interface ITelemetryManager {
109
+ current: ITelemetryJSONSchema | null
110
+ configPath: string | null
111
+ user: TUser
112
+ urls: TTelemetryUrls
113
+ started: boolean
114
+ salute: (message: string) => void
115
+ start: (
116
+ agent: string,
117
+ steps: TStep[],
118
+ path: string,
119
+ tutorialSlug: string
120
+ ) => void
121
+ prevStep?: number
122
+ registerStepEvent: (
123
+ stepPosition: number,
124
+ event: TStepEvent,
125
+ data: any
126
+ ) => void
127
+ streamEvent: (stepPosition: number, event: string, data: any) => void
128
+ submit: () => Promise<void>
129
+ finishWorkoutSession: () => void
130
+ setStudent: (student: TStudent) => void
131
+ save: () => void
132
+ retrieve: () => Promise<ITelemetryJSONSchema | null>
133
+ }
134
+
135
+ const TelemetryManager: ITelemetryManager = {
136
+ current: null,
137
+ urls: {},
138
+ user: {
139
+ token: "",
140
+ id: "",
141
+ email: "",
142
+ },
143
+ configPath: "",
144
+ started: false,
145
+ salute: message => {
146
+ Console.info(message)
147
+ },
148
+
149
+ start: function (agent, steps, path, tutorialSlug) {
150
+ this.configPath = path
151
+ if (!this.current) {
152
+ this.retrieve()
153
+ .then(data => {
154
+ const prevTelemetry = data
155
+ if (prevTelemetry) {
156
+ this.current = prevTelemetry
157
+ this.finishWorkoutSession()
158
+ } else {
159
+ this.current = {
160
+ telemetry_id: createUUID(),
161
+ slug: tutorialSlug,
162
+ agent,
163
+ tutorial_started_at: Date.now(),
164
+ last_interaction_at: Date.now(),
165
+ steps,
166
+ workout_session: [
167
+ {
168
+ started_at: Date.now(),
169
+ },
170
+ ],
171
+ }
172
+ }
173
+
174
+ if (this.current.user_id) {
175
+ this.user.id = this.current.user_id.toString()
176
+ }
177
+
178
+ this.save()
179
+
180
+ this.started = true
181
+ Console.debug("Telemetry started successfully!")
182
+
183
+ if (!this.user.id) {
184
+ Console.debug(
185
+ "No user ID found, impossible to submit telemetry at start"
186
+ )
187
+ return
188
+ }
189
+
190
+ this.submit()
191
+ })
192
+ .catch(error => {
193
+ Console.debug(error)
194
+ // Delete the telemetry.json if it exists
195
+ fs.unlinkSync(`${this.configPath}/telemetry.json`)
196
+ throw new Error(
197
+ "There was a problem starting, reload LearnPack\nRun\n$ learnpack start"
198
+ )
199
+ })
200
+ }
201
+ },
202
+
203
+ setStudent: function (student) {
204
+ if (!this.current) {
205
+ Console.debug("Telemetry has not been started")
206
+
207
+ return
208
+ }
209
+
210
+ Console.debug("Setting student", student)
211
+
212
+ this.current.user_id = student.user_id
213
+ this.user.id = student.user_id
214
+ this.user.token = student.token
215
+ this.user.email = student.email
216
+ this.save()
217
+ this.submit()
218
+ },
219
+ finishWorkoutSession: function () {
220
+ if (!this.current) {
221
+ return
222
+ }
223
+
224
+ const lastSession =
225
+ this.current?.workout_session[this.current.workout_session.length - 1]
226
+ if (
227
+ lastSession &&
228
+ !lastSession.ended_at &&
229
+ this.current?.last_interaction_at
230
+ ) {
231
+ lastSession.ended_at = this.current.last_interaction_at
232
+ this.current.workout_session.push({
233
+ started_at: Date.now(),
234
+ })
235
+ }
236
+ },
237
+
238
+ registerStepEvent: function (stepPosition, event, data) {
239
+ Console.debug(`Registering Event ${event} for user ${this.user.id}`)
240
+
241
+ if (!this.current) {
242
+ // throw new Error("Telemetry has not been started");
243
+ return
244
+ }
245
+
246
+ const step = this.current.steps[stepPosition]
247
+ if (!step) {
248
+ return
249
+ }
250
+
251
+ if (data.source_code) {
252
+ data.source_code = stringToBase64(data.source_code)
253
+ }
254
+
255
+ if (data.stdout) {
256
+ data.stdout = stringToBase64(data.stdout)
257
+ }
258
+
259
+ if (data.stderr) {
260
+ data.stderr = stringToBase64(data.stderr)
261
+ }
262
+
263
+ if (Object.prototype.hasOwnProperty.call(data, "exitCode")) {
264
+ data.exit_code = data.exitCode
265
+ data.exitCode = undefined
266
+ }
267
+
268
+ switch (event) {
269
+ case "compile":
270
+ if (!step.compilations) {
271
+ step.compilations = []
272
+ }
273
+
274
+ step.compilations.push(data)
275
+ this.current.steps[stepPosition] = step
276
+ break
277
+ case "test":
278
+ if (!step.tests) {
279
+ step.tests = []
280
+ }
281
+
282
+ // data.stdout =
283
+ step.tests.push(data)
284
+ if (data.exit_code === 0) {
285
+ step.completed_at = Date.now()
286
+ }
287
+
288
+ this.current.steps[stepPosition] = step
289
+ break
290
+ case "ai_interaction":
291
+ if (!step.ai_interactions) {
292
+ step.ai_interactions = []
293
+ }
294
+
295
+ step.ai_interactions.push(data)
296
+ break
297
+
298
+ case "quiz_submission": {
299
+ if (!step.quiz_submissions) {
300
+ step.quiz_submissions = []
301
+ }
302
+
303
+ step.quiz_submissions.push(data)
304
+ break
305
+ }
306
+
307
+ case "open_step": {
308
+ const now = Date.now()
309
+
310
+ if (!step.opened_at) {
311
+ step.opened_at = now
312
+ this.current.steps[stepPosition] = step
313
+ }
314
+
315
+ if (this.prevStep || this.prevStep === 0) {
316
+ const prevStep = this.current.steps[this.prevStep]
317
+ if (!prevStep.is_testeable && !prevStep.completed_at) {
318
+ prevStep.completed_at = now
319
+ this.current.steps[this.prevStep] = prevStep
320
+ }
321
+ }
322
+
323
+ this.prevStep = stepPosition
324
+ this.submit()
325
+ break
326
+ }
327
+
328
+ default:
329
+ throw new Error(`Event type ${event} is not supported`)
330
+ }
331
+
332
+ this.current.last_interaction_at = Date.now()
333
+ this.streamEvent(stepPosition, event, data)
334
+ this.save()
335
+ },
336
+ retrieve: function () {
337
+ return new Promise((resolve, reject) => {
338
+ fs.readFile(
339
+ `${this.configPath}/telemetry.json`,
340
+ "utf8",
341
+ (err: any, data: any) => {
342
+ if (err) {
343
+ if (err.code === "ENOENT") {
344
+ // File does not exist, resolve with undefined
345
+ resolve(null)
346
+ } else {
347
+ reject(err)
348
+ }
349
+ } else {
350
+ try {
351
+ resolve(JSON.parse(data))
352
+ } catch (error) {
353
+ reject(error)
354
+ }
355
+ }
356
+ }
357
+ )
358
+ })
359
+ },
360
+
361
+ submit: async function () {
362
+ Console.debug("Submitting telemetry...")
363
+
364
+ if (!this.current) {
365
+ Console.debug("Telemetry has not been started")
366
+ return Promise.resolve()
367
+ }
368
+
369
+ if (!this.user.id) {
370
+ Console.debug("User ID not found, skipping batch telemetry delivery")
371
+ return Promise.resolve()
372
+ }
373
+
374
+ const url = this.urls.batch
375
+ if (!url) {
376
+ Console.debug("Batch URL not found, skipping batch telemetry delivery")
377
+ return Promise.resolve()
378
+ }
379
+
380
+ const body = this.current
381
+
382
+ if (!body.user_id) {
383
+ body.user_id = this.user.id
384
+ }
385
+
386
+ API.sendBatchTelemetry(url, body)
387
+ },
388
+ save: function () {
389
+ fs.writeFile(
390
+ `${this.configPath}/telemetry.json`,
391
+ JSON.stringify(this.current),
392
+ (err: any) => {
393
+ if (err)
394
+ throw err
395
+ }
396
+ )
397
+ },
398
+
399
+ streamEvent: async function (stepPosition, event, data) {
400
+ if (!this.current)
401
+ return
402
+
403
+ const url = this.urls.streaming
404
+ if (!url) {
405
+ return
406
+ }
407
+
408
+ const stepSlug = this.current.steps[stepPosition].slug
409
+
410
+ const body = {
411
+ slug: stepSlug,
412
+ telemetry_id: this.current.telemetry_id,
413
+ user_id: this.current.user_id,
414
+ step_position: stepPosition,
415
+ event,
416
+ data,
417
+ }
418
+
419
+ API.sendStreamTelemetry(url, body)
420
+ },
421
+ }
422
+
423
+ export default TelemetryManager
@@ -2,6 +2,8 @@ import { IConfig, IConfigObj } from "./config"
2
2
 
3
3
  export interface IPayload {
4
4
  email: string
5
+ user_id: number
6
+ token: string
5
7
  }
6
8
 
7
9
  export interface IStartProps {
package/src/utils/api.ts CHANGED
@@ -229,7 +229,7 @@ const APIError = (error: TypeError | string, code?: number) => {
229
229
  return _err
230
230
  }
231
231
 
232
- const sendBatchTelemetry = async function (url: string, body: object) {
232
+ const sendBatchTelemetry = async function (url: string, body: any) {
233
233
  if (!url) {
234
234
  return
235
235
  }
@@ -240,10 +240,17 @@ const sendBatchTelemetry = async function (url: string, body: object) {
240
240
  !Object.prototype.hasOwnProperty.call(session, "token") ||
241
241
  session.token === ""
242
242
  ) {
243
- Console.debug("No token found, skipping stream telemetry delivery")
243
+ Console.debug("No token found, skipping batch telemetry delivery")
244
+ return
245
+ }
246
+
247
+ if (!session || !session.user_id || session.user_id === "") {
248
+ Console.debug("No user_id found, skipping batch telemetry delivery")
244
249
  return
245
250
  }
246
251
 
252
+ body.user_id = session.user_id
253
+
247
254
  fetch(
248
255
  url,
249
256
  {
@@ -254,7 +261,7 @@ const sendBatchTelemetry = async function (url: string, body: object) {
254
261
  )
255
262
  .then(response => {
256
263
  Console.debug("Telemetry sent successfully")
257
- return response.text()
264
+ return response
258
265
  })
259
266
  .catch(error => {
260
267
  Console.debug("Error while sending batch Telemetry", error)
@@ -222,6 +222,8 @@ return true
222
222
  }
223
223
 
224
224
  if (pytestNeeded) {
225
+ await exec("pip install --upgrade pip")
226
+
225
227
  const { stdout, stderr } = await exec("pip list")
226
228
  if (stderr) {
227
229
  Console.error(`Error executing pip list. Use debug for more info`)