forked from aws/aws-toolkit-vscode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtelemetryOptIn.ts
More file actions
171 lines (151 loc) · 6.85 KB
/
telemetryOptIn.ts
File metadata and controls
171 lines (151 loc) · 6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { ExtensionContext, env, Uri, window } from 'vscode'
import { CloudFormationTelemetrySettings } from './extensionConfig'
import { commandKey } from './utils'
import { isAutomation } from '../../shared/vscode/env'
import { getLogger } from '../../shared/logger/logger'
import globals from '../../shared/extensionGlobals'
enum TelemetryChoice {
Allow = 'Yes, Allow',
Later = 'Not Now',
Never = 'Never',
LearnMore = 'Learn More',
}
const telemetryKeys = {
hasResponded: commandKey('telemetry.hasResponded'),
lastPromptDate: commandKey('telemetry.lastPromptDate'),
unpersistedResponse: commandKey('telemetry.unpersistedResponse'),
} as const
const telemetrySettings = {
enabled: 'enabled',
} as const
const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000
const promptTimeoutMs = 2500
const telemetryDocsUrl = 'https://github.com/aws-cloudformation/cloudformation-languageserver/tree/main/src/telemetry'
/* eslint-disable aws-toolkits/no-banned-usages */
export async function handleTelemetryOptIn(
context: ExtensionContext,
cfnTelemetrySettings: CloudFormationTelemetrySettings
): Promise<boolean> {
// If previous choice failed to persist, persist it now and return
const unpersistedResponse = (await context.globalState.get(telemetryKeys.unpersistedResponse)) as string
const hasResponded = context.globalState.get<boolean>(telemetryKeys.hasResponded)
const lastPromptDate = context.globalState.get<number>(telemetryKeys.lastPromptDate)
if (unpersistedResponse) {
// May still raise popup if user lacks permission or file is corrupted
const didSave = await saveTelemetryResponse(unpersistedResponse, cfnTelemetrySettings)
await context.globalState.update(telemetryKeys.unpersistedResponse, undefined)
// If we still couldn't save, clear everything so they get asked again until the file/perms is fixed
if (!didSave) {
getLogger().warn(
'CloudFormation telemetry choice was not saved successfully after restart. Clearing related globalState keys for next restart'
)
await context.globalState.update(telemetryKeys.hasResponded, undefined)
await context.globalState.update(telemetryKeys.lastPromptDate, undefined)
}
return logAndReturnTelemetryChoice(
unpersistedResponse === TelemetryChoice.Allow.toString(),
hasResponded,
lastPromptDate
)
}
// Never throws because we provide a default
const telemetryEnabled = cfnTelemetrySettings.get(telemetrySettings.enabled, false)
if (isAutomation()) {
return logAndReturnTelemetryChoice(telemetryEnabled)
}
// If user has permanently responded, use their choice
if (hasResponded) {
return logAndReturnTelemetryChoice(telemetryEnabled, hasResponded)
}
// Check if we should show reminder (30 days since last prompt)
const shouldPrompt = lastPromptDate === undefined || globals.clock.Date.now() - lastPromptDate >= thirtyDaysMs
if (!shouldPrompt) {
return logAndReturnTelemetryChoice(telemetryEnabled, hasResponded, lastPromptDate)
}
// Show prompt but set false if timeout
const promptPromise = promptTelemetryOptIn(context, cfnTelemetrySettings)
const timeoutPromise = new Promise<false>((resolve) =>
globals.clock.setTimeout(() => resolve(false), promptTimeoutMs)
)
const result = await Promise.race([promptPromise, timeoutPromise])
// Keep prompt alive in background
void promptPromise
return logAndReturnTelemetryChoice(result)
}
/**
* Updates the telemetry setting. In case of error, the update calls do not throw.
* They instead raise a popup and return false.
*
* @returns boolean whether the save/update was successful
*/
/* eslint-disable aws-toolkits/no-banned-usages */
async function saveTelemetryResponse(
response: string | undefined,
cfnTelemetrySettings: CloudFormationTelemetrySettings
): Promise<boolean> {
if (response === TelemetryChoice.Allow) {
return await cfnTelemetrySettings.update(telemetrySettings.enabled, true)
} else if (response === TelemetryChoice.Never) {
return await cfnTelemetrySettings.update(telemetrySettings.enabled, false)
} else if (response === TelemetryChoice.Later) {
return await cfnTelemetrySettings.update(telemetrySettings.enabled, false)
}
return false
}
function logAndReturnTelemetryChoice(choice: boolean, hasResponded?: boolean, lastPromptDate?: number): boolean {
getLogger().info(
'CloudFormation telemetry: choice=%s, hasResponded=%s, lastPromptDate=%s',
choice,
hasResponded,
lastPromptDate
)
return choice
}
/* eslint-disable aws-toolkits/no-banned-usages */
async function promptTelemetryOptIn(
context: ExtensionContext,
cfnTelemetrySettings: CloudFormationTelemetrySettings
): Promise<boolean> {
const message =
'Help us improve the AWS CloudFormation Language Server by sharing anonymous telemetry data with AWS. You can change this preference at any time in aws.cloudformation Settings.'
const response = await window.showInformationMessage(
message,
TelemetryChoice.Allow,
TelemetryChoice.Later,
TelemetryChoice.Never,
TelemetryChoice.LearnMore
)
if (response === TelemetryChoice.LearnMore) {
await env.openExternal(Uri.parse(telemetryDocsUrl))
return promptTelemetryOptIn(context, cfnTelemetrySettings)
}
const now = globals.clock.Date.now()
await context.globalState.update(telemetryKeys.lastPromptDate, now)
// There's a chance our settings aren't registered yet from package.json, so we
// see if we can persist to settings first
try {
// Throws (with no popup) if setting is not registered
cfnTelemetrySettings.get(telemetrySettings.enabled)
} catch (err) {
getLogger().warn(err as Error)
// Save the choice in globalState and save to settings next time handleTelemetryOptIn is called
await context.globalState.update(telemetryKeys.unpersistedResponse, response)
if (response === TelemetryChoice.Allow) {
await context.globalState.update(telemetryKeys.hasResponded, true)
return true
} else if (response === TelemetryChoice.Never) {
await context.globalState.update(telemetryKeys.hasResponded, true)
return false
} else if (response === TelemetryChoice.Later) {
return false
}
}
// At this point should be able to save and get successfully
await saveTelemetryResponse(response, cfnTelemetrySettings)
await context.globalState.update(telemetryKeys.hasResponded, response !== TelemetryChoice.Later)
return cfnTelemetrySettings.get(telemetrySettings.enabled, false)
}