Skip to content

Commit 6ccaa05

Browse files
committed
chore: enrich and improve telemetry events
1 parent 14698d5 commit 6ccaa05

File tree

9 files changed

+377
-63
lines changed

9 files changed

+377
-63
lines changed

src/extension.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import statusBar from "./plugins/status-bar.ts";
1010
import { PluginManager } from "./plugins.ts";
1111
import { createContainerStatusTracker } from "./utils/container-status.ts";
1212
import { createLocalStackStatusTracker } from "./utils/localstack-status.ts";
13-
import { getOrCreateExtensionSessionId } from "./utils/manage.ts";
13+
import {
14+
getOrCreateExtensionSessionId,
15+
getOrCreateMachineId,
16+
} from "./utils/manage.ts";
1417
import { createSetupStatusTracker } from "./utils/setup-status.ts";
1518
import { createTelemetry } from "./utils/telemetry.ts";
1619
import { createTimeTracker } from "./utils/time-tracker.ts";
@@ -78,7 +81,8 @@ export async function activate(context: ExtensionContext) {
7881
const startTelemetry = Date.now();
7982
outputChannel.trace(`[telemetry]: Starting...`);
8083
const sessionId = await getOrCreateExtensionSessionId(context);
81-
const telemetry = createTelemetry(outputChannel, sessionId);
84+
const machineId = await getOrCreateMachineId(context);
85+
const telemetry = createTelemetry(outputChannel, sessionId, machineId);
8286
const endTelemetry = Date.now();
8387
outputChannel.trace(
8488
`[telemetry]: Completed in ${ms(endTelemetry - startTelemetry, {

src/plugins/authenticate.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import pMinDelay from "p-min-delay";
2+
import { commands, ProgressLocation, window } from "vscode";
3+
4+
import { createPlugin } from "../plugins.ts";
5+
import { requestAuthentication, saveAuthToken } from "../utils/authenticate.ts";
6+
7+
const MIN_TIME_BETWEEN_STEPS_MS = 1_000; // 1s
8+
9+
export default createPlugin(
10+
"authenticate",
11+
({ context, outputChannel, telemetry }) => {
12+
context.subscriptions.push(
13+
commands.registerCommand("localstack.authenticate", async () => {
14+
const startedAt = new Date().toISOString();
15+
const selection = await window.showInformationMessage(
16+
"Choose authentication method",
17+
"Sign in to LocalStack",
18+
"Enter auth token",
19+
);
20+
if (selection === "Sign in to LocalStack") {
21+
window.withProgress(
22+
{
23+
location: ProgressLocation.Notification,
24+
title: "Authenticate",
25+
cancellable: true,
26+
},
27+
async (progress, cancellationToken) => {
28+
/////////////////////////////////////////////////////////////////////
29+
progress.report({
30+
message:
31+
"Waiting for authentication response from the browser...",
32+
});
33+
const { authToken } = await pMinDelay(
34+
requestAuthentication(context, undefined),
35+
MIN_TIME_BETWEEN_STEPS_MS,
36+
);
37+
if (cancellationToken.isCancellationRequested) {
38+
telemetry.track({
39+
name: "auth_token_configured",
40+
payload: {
41+
namespace: "onboarding",
42+
origin: "manual_trigger",
43+
step_order: 2,
44+
started_at: startedAt,
45+
ended_at: new Date().toISOString(),
46+
status: "CANCELLED",
47+
},
48+
});
49+
return;
50+
}
51+
52+
/////////////////////////////////////////////////////////////////////
53+
progress.report({
54+
message: "Authenticating to file...",
55+
});
56+
await pMinDelay(
57+
saveAuthToken(authToken, outputChannel),
58+
MIN_TIME_BETWEEN_STEPS_MS,
59+
);
60+
61+
/////////////////////////////////////////////////////////////////////
62+
window.showInformationMessage("Authentication successful.");
63+
telemetry.track({
64+
name: "auth_token_configured",
65+
payload: {
66+
namespace: "onboarding",
67+
origin: "manual_trigger",
68+
step_order: 2,
69+
auth_token: authToken,
70+
started_at: startedAt,
71+
ended_at: new Date().toISOString(),
72+
status: "COMPLETED",
73+
},
74+
});
75+
},
76+
);
77+
} else if (selection === "Enter auth token") {
78+
const token = await window.showInputBox({
79+
prompt: "Enter your LocalStack Auth Token",
80+
placeHolder: "Paste your auth token here",
81+
ignoreFocusOut: true,
82+
});
83+
84+
if (!token) {
85+
telemetry.track({
86+
name: "auth_token_configured",
87+
payload: {
88+
namespace: "onboarding",
89+
origin: "manual_trigger",
90+
step_order: 2,
91+
started_at: startedAt,
92+
ended_at: new Date().toISOString(),
93+
status: "FAILED",
94+
errors: ["No token was provided."],
95+
},
96+
});
97+
return;
98+
}
99+
100+
if (!token.startsWith("ls-")) {
101+
const error_msg = 'The auth token should start with "ls-".';
102+
window.showErrorMessage(error_msg);
103+
104+
telemetry.track({
105+
name: "auth_token_configured",
106+
payload: {
107+
namespace: "onboarding",
108+
origin: "manual_trigger",
109+
step_order: 2,
110+
started_at: startedAt,
111+
ended_at: new Date().toISOString(),
112+
status: "FAILED",
113+
errors: [error_msg],
114+
},
115+
});
116+
return;
117+
}
118+
119+
await saveAuthToken(token, outputChannel);
120+
window.showInformationMessage("Authentication successful.");
121+
telemetry.track({
122+
name: "auth_token_configured",
123+
payload: {
124+
namespace: "onboarding",
125+
origin: "manual_trigger",
126+
step_order: 2,
127+
auth_token: token,
128+
started_at: startedAt,
129+
ended_at: new Date().toISOString(),
130+
status: "COMPLETED",
131+
},
132+
});
133+
} else if (selection === undefined) {
134+
telemetry.track({
135+
name: "auth_token_configured",
136+
payload: {
137+
namespace: "onboarding",
138+
origin: "manual_trigger",
139+
step_order: 2,
140+
started_at: startedAt,
141+
ended_at: new Date().toISOString(),
142+
status: "CANCELLED",
143+
},
144+
});
145+
}
146+
}),
147+
);
148+
},
149+
);

src/plugins/setup.ts

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
checkIsAuthenticated,
66
requestAuthentication,
77
saveAuthToken,
8+
readAuthToken,
89
} from "../utils/authenticate.ts";
910
import { configureAwsProfiles } from "../utils/configure-aws.ts";
1011
import { runInstallProcess } from "../utils/install.ts";
@@ -31,26 +32,6 @@ export default createPlugin(
3132
payload: {
3233
namespace: "onboarding",
3334
origin: origin_trigger,
34-
expected_steps: [
35-
{
36-
name: "emulator_installed",
37-
is_first_step: true,
38-
is_last_step: false,
39-
position: 1,
40-
},
41-
{
42-
name: "auth_token_configured",
43-
is_first_step: false,
44-
is_last_step: false,
45-
position: 2,
46-
},
47-
{
48-
name: "aws_profile_configured",
49-
is_first_step: false,
50-
is_last_step: true,
51-
position: 3,
52-
},
53-
],
5435
},
5536
});
5637

@@ -77,7 +58,7 @@ export default createPlugin(
7758
payload: {
7859
namespace: "onboarding",
7960
origin: origin_trigger,
80-
position: 1,
61+
step_order: 1,
8162
started_at: installationStartedAt,
8263
ended_at: new Date().toISOString(),
8364
status: "CANCELLED",
@@ -98,8 +79,38 @@ export default createPlugin(
9879
name: "setup_ended",
9980
payload: {
10081
namespace: "onboarding",
101-
steps: [1, 2, 3],
82+
steps: [
83+
{
84+
name: "emulator_installed",
85+
is_first_step: true,
86+
is_last_step: false,
87+
step_order: 1,
88+
status: "COMPLETED",
89+
},
90+
{
91+
name: "auth_token_configured",
92+
is_first_step: false,
93+
is_last_step: false,
94+
step_order: 2,
95+
status: "CANCELLED",
96+
},
97+
{
98+
name: "license_setup_ended",
99+
is_first_step: false,
100+
is_last_step: false,
101+
step_order: 3,
102+
status: "SKIPPED",
103+
},
104+
{
105+
name: "aws_profile_configured",
106+
is_first_step: false,
107+
is_last_step: true,
108+
step_order: 4,
109+
status: "SKIPPED",
110+
},
111+
],
102112
status: "CANCELLED",
113+
auth_token: await readAuthToken(),
103114
},
104115
});
105116
return;
@@ -113,7 +124,7 @@ export default createPlugin(
113124
payload: {
114125
namespace: "onboarding",
115126
origin: origin_trigger,
116-
position: 2,
127+
step_order: 2,
117128
started_at: authStartedAuthAt,
118129
ended_at: new Date().toISOString(),
119130
status: "SKIPPED",
@@ -135,7 +146,7 @@ export default createPlugin(
135146
payload: {
136147
namespace: "onboarding",
137148
origin: origin_trigger,
138-
position: 2,
149+
step_order: 2,
139150
auth_token: authToken,
140151
started_at: authStartedAuthAt,
141152
ended_at: new Date().toISOString(),
@@ -156,7 +167,7 @@ export default createPlugin(
156167
payload: {
157168
namespace: "onboarding",
158169
origin: origin_trigger,
159-
position: 2,
170+
step_order: 2,
160171
auth_token: authToken,
161172
started_at: authStartedAuthAt,
162173
ended_at: new Date().toISOString(),
@@ -175,6 +186,7 @@ export default createPlugin(
175186
// then there will be no license info to be reported by `localstack license info`.
176187
// Also, an expired license could be cached.
177188
// Activating the license pre-emptively to know its state during the setup process.
189+
const licenseCheckStartedAt = new Date().toISOString();
178190
const licenseIsValid = await minDelay(
179191
activateLicense(outputChannel).then(() =>
180192
checkIsLicenseValid(outputChannel),
@@ -191,15 +203,28 @@ export default createPlugin(
191203
await activateLicenseUntilValid(
192204
outputChannel,
193205
cancellationToken,
206+
telemetry,
207+
origin_trigger,
208+
licenseCheckStartedAt,
194209
);
195210
}
196211

197212
if (cancellationToken.isCancellationRequested) {
213+
telemetry.track({
214+
name: "license_setup_ended",
215+
payload: {
216+
namespace: "onboarding",
217+
step_order: 3,
218+
origin: origin_trigger,
219+
auth_token: await readAuthToken(),
220+
started_at: licenseCheckStartedAt,
221+
ended_at: new Date().toISOString(),
222+
status: "COMPLETED",
223+
},
224+
});
198225
return;
199226
}
200227

201-
//TODO add telemetry
202-
203228
/////////////////////////////////////////////////////////////////////
204229
progress.report({
205230
message: "Configuring AWS profiles...",
@@ -234,8 +259,38 @@ export default createPlugin(
234259
name: "setup_ended",
235260
payload: {
236261
namespace: "onboarding",
237-
steps: [1, 2, 3],
262+
steps: [
263+
{
264+
name: "emulator_installed",
265+
is_first_step: true,
266+
is_last_step: false,
267+
step_order: 1,
268+
status: "COMPLETED",
269+
},
270+
{
271+
name: "auth_token_configured",
272+
is_first_step: false,
273+
is_last_step: false,
274+
step_order: 2,
275+
status: "COMPLETED",
276+
},
277+
{
278+
name: "license_setup_ended",
279+
is_first_step: false,
280+
is_last_step: false,
281+
step_order: 3,
282+
status: "COMPLETED",
283+
},
284+
{
285+
name: "aws_profile_configured",
286+
is_first_step: false,
287+
is_last_step: true,
288+
step_order: 4,
289+
status: "COMPLETED",
290+
},
291+
],
238292
status: "COMPLETED",
293+
auth_token: await readAuthToken(),
239294
},
240295
});
241296
},

src/utils/authenticate.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export async function checkIsAuthenticated() {
131131
return false;
132132
}
133133
return true;
134-
} catch (error) {
134+
} catch {
135135
return false;
136136
}
137137
}
@@ -143,3 +143,21 @@ function isAuthTokenPresent(authObject: unknown) {
143143
AUTH_TOKEN_KEY in authObject
144144
);
145145
}
146+
147+
// Reads the auth token from the auth.json file for logging in the user
148+
export async function readAuthToken(): Promise<string> {
149+
try {
150+
const authJson = await fs.readFile(LOCALSTACK_AUTH_FILENAME, "utf-8");
151+
const authObject = JSON.parse(authJson) as unknown;
152+
if (!isAuthTokenPresent(authObject)) {
153+
return "";
154+
}
155+
const authToken = authObject[AUTH_TOKEN_KEY];
156+
if (typeof authToken !== "string") {
157+
return "";
158+
}
159+
return authToken;
160+
} catch {
161+
return "";
162+
}
163+
}

0 commit comments

Comments
 (0)