Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/assignment/common/assignment.ts
5241 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Event } from '../../../base/common/event.js';
7
import * as platform from '../../../base/common/platform.js';
8
import type { IExperimentationFilterProvider } from 'tas-client';
9
10
export const ASSIGNMENT_STORAGE_KEY = 'VSCode.ABExp.FeatureData';
11
export const ASSIGNMENT_REFETCH_INTERVAL = 60 * 60 * 1000; // 1 hour
12
13
export interface IAssignmentService {
14
readonly _serviceBrand: undefined;
15
16
readonly onDidRefetchAssignments: Event<void>;
17
getTreatment<T extends string | number | boolean>(name: string): Promise<T | undefined>;
18
}
19
20
export enum TargetPopulation {
21
Insiders = 'insider',
22
Public = 'public',
23
Exploration = 'exploration'
24
}
25
26
/*
27
Based upon the official VSCode currently existing filters in the
28
ExP backend for the VSCode cluster.
29
https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster
30
"X-MSEdge-Market": "detection.market",
31
"X-FD-Corpnet": "detection.corpnet",
32
"X-VSCode-AppVersion": "appversion",
33
"X-VSCode-Build": "build",
34
"X-MSEdge-ClientId": "clientid",
35
"X-VSCode-ExtensionName": "extensionname",
36
"X-VSCode-ExtensionVersion": "extensionversion",
37
"X-VSCode-TargetPopulation": "targetpopulation",
38
"X-VSCode-Language": "language",
39
"X-VSCode-Platform": "platform",
40
"X-VSCode-ReleaseDate": "releasedate"
41
*/
42
export enum Filters {
43
/**
44
* The market in which the extension is distributed.
45
*/
46
Market = 'X-MSEdge-Market',
47
48
/**
49
* The corporation network.
50
*/
51
CorpNet = 'X-FD-Corpnet',
52
53
/**
54
* Version of the application which uses experimentation service.
55
*/
56
ApplicationVersion = 'X-VSCode-AppVersion',
57
58
/**
59
* Insiders vs Stable.
60
*/
61
Build = 'X-VSCode-Build',
62
63
/**
64
* Client Id which is used as primary unit for the experimentation.
65
*/
66
ClientId = 'X-MSEdge-ClientId',
67
68
/**
69
* Developer Device Id which can be used as an alternate unit for experimentation.
70
*/
71
DeveloperDeviceId = 'X-VSCode-DevDeviceId',
72
73
/**
74
* Extension header.
75
*/
76
ExtensionName = 'X-VSCode-ExtensionName',
77
78
/**
79
* The version of the extension.
80
*/
81
ExtensionVersion = 'X-VSCode-ExtensionVersion',
82
83
/**
84
* The language in use by VS Code
85
*/
86
Language = 'X-VSCode-Language',
87
88
/**
89
* The target population.
90
* This is used to separate internal, early preview, GA, etc.
91
*/
92
TargetPopulation = 'X-VSCode-TargetPopulation',
93
94
/**
95
* The platform (OS) on which VS Code is running.
96
*/
97
Platform = 'X-VSCode-Platform',
98
99
/**
100
* The release/build date of VS Code (UTC) in the format yyyymmddHH.
101
*/
102
ReleaseDate = 'X-VSCode-ReleaseDate',
103
}
104
105
export class AssignmentFilterProvider implements IExperimentationFilterProvider {
106
constructor(
107
private version: string,
108
private appName: string,
109
private machineId: string,
110
private devDeviceId: string,
111
private targetPopulation: TargetPopulation,
112
private releaseDate: string
113
) { }
114
115
/**
116
* Returns a version string that can be parsed by the TAS client.
117
* The tas client cannot handle suffixes lke "-insider"
118
* Ref: https://github.com/microsoft/tas-client/blob/30340d5e1da37c2789049fcf45928b954680606f/vscode-tas-client/src/vscode-tas-client/VSCodeFilterProvider.ts#L35
119
*
120
* @param version Version string to be trimmed.
121
*/
122
private static trimVersionSuffix(version: string): string {
123
const regex = /\-[a-zA-Z0-9]+$/;
124
const result = version.split(regex);
125
126
return result[0];
127
}
128
129
getFilterValue(filter: string): string | null {
130
switch (filter) {
131
case Filters.ApplicationVersion:
132
return AssignmentFilterProvider.trimVersionSuffix(this.version); // productService.version
133
case Filters.Build:
134
return this.appName; // productService.nameLong
135
case Filters.ClientId:
136
return this.machineId;
137
case Filters.DeveloperDeviceId:
138
return this.devDeviceId;
139
case Filters.Language:
140
return platform.language;
141
case Filters.ExtensionName:
142
return 'vscode-core'; // always return vscode-core for exp service
143
case Filters.ExtensionVersion:
144
return '999999.0'; // always return a very large number for cross-extension experimentation
145
case Filters.TargetPopulation:
146
return this.targetPopulation;
147
case Filters.Platform:
148
return platform.PlatformToString(platform.platform);
149
case Filters.ReleaseDate:
150
return AssignmentFilterProvider.formatReleaseDate(this.releaseDate);
151
default:
152
return '';
153
}
154
}
155
156
private static formatReleaseDate(iso: string): string {
157
// Expect ISO format, fall back to empty string if not provided
158
if (!iso) {
159
return '';
160
}
161
// Remove separators and milliseconds: YYYY-MM-DDTHH:MM:SS.sssZ -> YYYYMMDDHH
162
// Trimmed to 10 digits to fit within int32 bounds (ExP requirement)
163
const match = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})/.exec(iso);
164
if (!match) {
165
return '';
166
}
167
return match.slice(1, 5).join('');
168
}
169
170
getFilters(): Map<string, unknown> {
171
const filters: Map<string, unknown> = new Map<string, unknown>();
172
const filterValues = Object.values(Filters);
173
for (const value of filterValues) {
174
filters.set(value, this.getFilterValue(value));
175
}
176
177
return filters;
178
}
179
}
180
181