Skip to content

Commit 076656b

Browse files
fbriconevidolob
authored andcommitted
Restore telemetry without the vscode-commons dependency
1 parent c2c8863 commit 076656b

File tree

8 files changed

+489
-8
lines changed

8 files changed

+489
-8
lines changed

.vscode/launch.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
"stopOnEntry": false,
1212
"sourceMaps": true,
1313
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
14-
"preLaunchTask": "compile typescript"
14+
"preLaunchTask": "compile typescript",
15+
"env": {
16+
"VSCODE_REDHAT_TELEMETRY_DEBUG":"true"
17+
}
1518
},
1619
{
1720
"name": "Extension Tests",

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ e.g.
233233
}
234234
```
235235

236+
## Data and Telemetry
237+
238+
The `vscode-yaml` extension collects anonymous [usage data](USAGE_DATA.md) and sends it to Red Hat servers to help improve our products and services. Read our [privacy statement](https://developers.redhat.com/article/tool-data-collection) to learn more. This extension respects the `redhat.telemetry.enabled` setting which you can learn more about at https://github.com/redhat-developer/vscode-redhat-telemetry#how-to-disable-telemetry-reporting
239+
236240
## How to contribute
237241

238242
The instructions are available in the [contribution guide](CONTRIBUTING.md).

USAGE_DATA.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Data collection
22

3-
vscode-yaml has opt-in telemetry collection, provided by [vscode-commons](https://github.com/redhat-developer/vscode-commons).
3+
vscode-yaml has opt-in telemetry collection, provided by [vscode-redhat-telemetry](https://github.com/redhat-developer/vscode-redhat-telemetry).
44

55
## What's included in the vscode-yaml telemetry data
66

@@ -12,7 +12,7 @@ vscode-yaml has opt-in telemetry collection, provided by [vscode-commons](https:
1212
## What's included in the general telemetry data
1313

1414
Please see the
15-
[vscode-commons data collection information](https://github.com/redhat-developer/vscode-commons/blob/master/USAGE_DATA.md#other-extensions)
15+
[vscode-redhat-telemetry data collection information](https://github.com/redhat-developer/vscode-redhat-telemetry/blob/HEAD/USAGE_DATA.md)
1616
for information on what data it collects.
1717

1818
## How to opt in or out

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@
7474
],
7575
"configuration": {
7676
"properties": {
77+
"redhat.telemetry.enabled": {
78+
"type": "boolean",
79+
"default": null,
80+
"markdownDescription": "Enable usage data and errors to be sent to Red Hat servers. Read our [privacy statement](https://developers.redhat.com/article/tool-data-collection).",
81+
"scope": "window"
82+
},
7783
"yaml.trace.server": {
7884
"type": "string",
7985
"enum": [
@@ -210,6 +216,7 @@
210216
"vscode-test": "^1.4.0"
211217
},
212218
"dependencies": {
219+
"@redhat-developer/vscode-redhat-telemetry": "0.1.1",
213220
"fs-extra": "^9.1.0",
214221
"request-light": "^0.4.0",
215222
"vscode-languageclient": "7.0.0",

src/extension.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { joinPath } from './paths';
2323
import { getJsonSchemaContent, JSONSchemaDocumentContentProvider } from './json-schema-content-provider';
2424
import { JSONSchemaCache } from './json-schema-cache';
2525
import { getConflictingExtensions, showUninstallConflictsNotification } from './extensionConflicts';
26+
import { getRedHatService } from '@redhat-developer/vscode-redhat-telemetry';
27+
import { TelemetryErrorHandler, TelemetryOutputChannel } from './telemetry';
2628

2729
export interface ISchemaAssociations {
2830
[pattern: string]: string[];
@@ -80,6 +82,10 @@ let client: LanguageClient;
8082
const lsName = 'YAML Support';
8183

8284
export async function activate(context: ExtensionContext): Promise<SchemaExtensionAPI> {
85+
// Create Telemetry Service
86+
const telemetry = await (await getRedHatService(context)).getTelemetryService();
87+
telemetry.sendStartupEvent();
88+
8389
// The YAML language server is implemented in node
8490
const serverModule = context.asAbsolutePath(
8591
path.join('node_modules', 'yaml-language-server', 'out', 'server', 'src', 'server.js')
@@ -95,6 +101,8 @@ export async function activate(context: ExtensionContext): Promise<SchemaExtensi
95101
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
96102
};
97103

104+
const telemetryErrorHandler = new TelemetryErrorHandler(telemetry, lsName, 4);
105+
const outputChannel = window.createOutputChannel(lsName);
98106
// Options to control the language client
99107
const clientOptions: LanguageClientOptions = {
100108
// Register the server for on disk and newly created YAML documents
@@ -104,6 +112,8 @@ export async function activate(context: ExtensionContext): Promise<SchemaExtensi
104112
fileEvents: [workspace.createFileSystemWatcher('**/*.?(e)y?(a)ml'), workspace.createFileSystemWatcher('**/*.json')],
105113
},
106114
revealOutputChannelOn: RevealOutputChannelOn.Never,
115+
errorHandler: telemetryErrorHandler,
116+
outputChannel: new TelemetryOutputChannel(outputChannel, telemetry),
107117
};
108118

109119
// Create the language client and start it
@@ -124,6 +134,12 @@ export async function activate(context: ExtensionContext): Promise<SchemaExtensi
124134
)
125135
);
126136

137+
context.subscriptions.push(
138+
client.onTelemetry((e) => {
139+
telemetry.send(e);
140+
})
141+
);
142+
127143
findConflicts();
128144
client.onReady().then(() => {
129145
// Send a notification to the server with any YAML schema associations in all extensions
@@ -149,7 +165,7 @@ export async function activate(context: ExtensionContext): Promise<SchemaExtensi
149165
return getJsonSchemaContent(uri, schemaCache);
150166
});
151167

152-
// telemetry.send({ name: 'yaml.server.initialized' });
168+
telemetry.send({ name: 'yaml.server.initialized' });
153169
// Adapted from:
154170
// https://github.com/microsoft/vscode/blob/94c9ea46838a9a619aeafb7e8afd1170c967bb55/extensions/json-language-features/client/src/jsonClient.ts#L305-L318
155171
client.onNotification(ResultLimitReachedNotification.type, async (message) => {

src/telemetry.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { TelemetryService } from '@redhat-developer/vscode-redhat-telemetry';
7+
import { CloseAction, ErrorAction, ErrorHandler, Message } from 'vscode-languageclient/node';
8+
import * as vscode from 'vscode';
9+
10+
export class TelemetryErrorHandler implements ErrorHandler {
11+
private restarts: number[] = [];
12+
constructor(
13+
private readonly telemetry: TelemetryService,
14+
private readonly name: string,
15+
private readonly maxRestartCount: number
16+
) {}
17+
18+
error(error: Error, message: Message, count: number): ErrorAction {
19+
this.telemetry.send({ name: 'yaml.lsp.error', properties: { jsonrpc: message.jsonrpc, error: error.message } });
20+
if (count && count <= 3) {
21+
return ErrorAction.Continue;
22+
}
23+
return ErrorAction.Shutdown;
24+
}
25+
closed(): CloseAction {
26+
this.restarts.push(Date.now());
27+
if (this.restarts.length <= this.maxRestartCount) {
28+
return CloseAction.Restart;
29+
} else {
30+
const diff = this.restarts[this.restarts.length - 1] - this.restarts[0];
31+
if (diff <= 3 * 60 * 1000) {
32+
vscode.window.showErrorMessage(
33+
`The ${this.name} server crashed ${
34+
this.maxRestartCount + 1
35+
} times in the last 3 minutes. The server will not be restarted.`
36+
);
37+
return CloseAction.DoNotRestart;
38+
} else {
39+
this.restarts.shift();
40+
return CloseAction.Restart;
41+
}
42+
}
43+
}
44+
}
45+
46+
export class TelemetryOutputChannel implements vscode.OutputChannel {
47+
constructor(private readonly delegate: vscode.OutputChannel, private readonly telemetry: TelemetryService) {}
48+
49+
get name(): string {
50+
return this.delegate.name;
51+
}
52+
append(value: string): void {
53+
this.checkError(value);
54+
this.delegate.append(value);
55+
}
56+
appendLine(value: string): void {
57+
this.checkError(value);
58+
this.delegate.appendLine(value);
59+
}
60+
61+
private checkError(value: string): void {
62+
if (value.startsWith('[Error') || value.startsWith(' Message: Request')) {
63+
this.telemetry.send({ name: 'yaml.server.error', properties: { error: value } });
64+
}
65+
}
66+
clear(): void {
67+
this.delegate.clear();
68+
}
69+
show(preserveFocus?: boolean): void;
70+
show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
71+
show(column?: never, preserveFocus?: boolean): void {
72+
this.delegate.show(column, preserveFocus);
73+
}
74+
hide(): void {
75+
this.delegate.hide();
76+
}
77+
dispose(): void {
78+
this.delegate.dispose();
79+
}
80+
}

test/telemetry.test.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/* --------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
* ------------------------------------------------------------------------------------------ */
5+
import * as sinon from 'sinon';
6+
import * as sinonChai from 'sinon-chai';
7+
import * as chai from 'chai';
8+
import * as vscode from 'vscode';
9+
import { TelemetryErrorHandler, TelemetryOutputChannel } from '../src/telemetry';
10+
import { TelemetryEvent, TelemetryService } from '@redhat-developer/vscode-redhat-telemetry/lib/interfaces/telemetry';
11+
12+
const expect = chai.expect;
13+
chai.use(sinonChai);
14+
class TelemetryStub implements TelemetryService {
15+
sendStartupEvent(): Promise<void> {
16+
throw new Error('Method not implemented.');
17+
}
18+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
19+
send(event: TelemetryEvent): Promise<void> {
20+
throw new Error('Method not implemented.');
21+
}
22+
sendShutdownEvent(): Promise<void> {
23+
throw new Error('Method not implemented.');
24+
}
25+
flushQueue(): Promise<void> {
26+
throw new Error('Method not implemented.');
27+
}
28+
dispose(): Promise<void> {
29+
throw new Error('Method not implemented.');
30+
}
31+
}
32+
describe('Telemetry Test', () => {
33+
const sandbox = sinon.createSandbox();
34+
const testOutputChannel = vscode.window.createOutputChannel('YAML_TEST');
35+
afterEach(() => {
36+
sandbox.restore();
37+
});
38+
describe('TelemetryOutputChannel', () => {
39+
let telemetryChannel: TelemetryOutputChannel;
40+
let outputChannel: sinon.SinonStubbedInstance<vscode.OutputChannel>;
41+
let telemetry: sinon.SinonStubbedInstance<TelemetryService>;
42+
beforeEach(() => {
43+
outputChannel = sandbox.stub(testOutputChannel);
44+
telemetry = sandbox.stub(new TelemetryStub());
45+
telemetryChannel = new TelemetryOutputChannel(
46+
(outputChannel as unknown) as vscode.OutputChannel,
47+
(telemetry as unknown) as TelemetryService
48+
);
49+
});
50+
51+
it('should delegate "append" method', () => {
52+
telemetryChannel.append('Some');
53+
expect(outputChannel.append).calledOnceWith('Some');
54+
});
55+
56+
it('should delegate "appendLine" method', () => {
57+
telemetryChannel.appendLine('Some');
58+
expect(outputChannel.appendLine).calledOnceWith('Some');
59+
});
60+
61+
it('should delegate "clear" method', () => {
62+
telemetryChannel.clear();
63+
expect(outputChannel.clear).calledOnce;
64+
});
65+
66+
it('should delegate "dispose" method', () => {
67+
telemetryChannel.dispose();
68+
expect(outputChannel.dispose).calledOnce;
69+
});
70+
71+
it('should delegate "hide" method', () => {
72+
telemetryChannel.hide();
73+
expect(outputChannel.hide).calledOnce;
74+
});
75+
76+
it('should delegate "show" method', () => {
77+
telemetryChannel.show();
78+
expect(outputChannel.show).calledOnce;
79+
});
80+
81+
it('should send telemetry if log error in "append"', () => {
82+
telemetryChannel.append('[Error Some');
83+
expect(telemetry.send).calledOnceWith({ name: 'yaml.server.error', properties: { error: '[Error Some' } });
84+
});
85+
86+
it('should send telemetry if log error on "appendLine"', () => {
87+
telemetryChannel.appendLine('[Error Some');
88+
expect(telemetry.send).calledOnceWith({ name: 'yaml.server.error', properties: { error: '[Error Some' } });
89+
});
90+
});
91+
92+
describe('TelemetryErrorHandler', () => {
93+
let telemetry: sinon.SinonStubbedInstance<TelemetryService>;
94+
let errorHandler: TelemetryErrorHandler;
95+
96+
beforeEach(() => {
97+
telemetry = sandbox.stub(new TelemetryStub());
98+
errorHandler = new TelemetryErrorHandler(telemetry, 'YAML LS', 3);
99+
});
100+
101+
it('should log telemetry on error', () => {
102+
errorHandler.error(new Error('Some'), { jsonrpc: 'Error message' }, 3);
103+
expect(telemetry.send).calledOnceWith({
104+
name: 'yaml.lsp.error',
105+
properties: { jsonrpc: 'Error message', error: 'Some' },
106+
});
107+
});
108+
});
109+
});

0 commit comments

Comments
 (0)