Path: blob/main/src/vs/platform/assignment/common/assignment.ts
5241 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Event } from '../../../base/common/event.js';6import * as platform from '../../../base/common/platform.js';7import type { IExperimentationFilterProvider } from 'tas-client';89export const ASSIGNMENT_STORAGE_KEY = 'VSCode.ABExp.FeatureData';10export const ASSIGNMENT_REFETCH_INTERVAL = 60 * 60 * 1000; // 1 hour1112export interface IAssignmentService {13readonly _serviceBrand: undefined;1415readonly onDidRefetchAssignments: Event<void>;16getTreatment<T extends string | number | boolean>(name: string): Promise<T | undefined>;17}1819export enum TargetPopulation {20Insiders = 'insider',21Public = 'public',22Exploration = 'exploration'23}2425/*26Based upon the official VSCode currently existing filters in the27ExP backend for the VSCode cluster.28https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster29"X-MSEdge-Market": "detection.market",30"X-FD-Corpnet": "detection.corpnet",31"X-VSCode-AppVersion": "appversion",32"X-VSCode-Build": "build",33"X-MSEdge-ClientId": "clientid",34"X-VSCode-ExtensionName": "extensionname",35"X-VSCode-ExtensionVersion": "extensionversion",36"X-VSCode-TargetPopulation": "targetpopulation",37"X-VSCode-Language": "language",38"X-VSCode-Platform": "platform",39"X-VSCode-ReleaseDate": "releasedate"40*/41export enum Filters {42/**43* The market in which the extension is distributed.44*/45Market = 'X-MSEdge-Market',4647/**48* The corporation network.49*/50CorpNet = 'X-FD-Corpnet',5152/**53* Version of the application which uses experimentation service.54*/55ApplicationVersion = 'X-VSCode-AppVersion',5657/**58* Insiders vs Stable.59*/60Build = 'X-VSCode-Build',6162/**63* Client Id which is used as primary unit for the experimentation.64*/65ClientId = 'X-MSEdge-ClientId',6667/**68* Developer Device Id which can be used as an alternate unit for experimentation.69*/70DeveloperDeviceId = 'X-VSCode-DevDeviceId',7172/**73* Extension header.74*/75ExtensionName = 'X-VSCode-ExtensionName',7677/**78* The version of the extension.79*/80ExtensionVersion = 'X-VSCode-ExtensionVersion',8182/**83* The language in use by VS Code84*/85Language = 'X-VSCode-Language',8687/**88* The target population.89* This is used to separate internal, early preview, GA, etc.90*/91TargetPopulation = 'X-VSCode-TargetPopulation',9293/**94* The platform (OS) on which VS Code is running.95*/96Platform = 'X-VSCode-Platform',9798/**99* The release/build date of VS Code (UTC) in the format yyyymmddHH.100*/101ReleaseDate = 'X-VSCode-ReleaseDate',102}103104export class AssignmentFilterProvider implements IExperimentationFilterProvider {105constructor(106private version: string,107private appName: string,108private machineId: string,109private devDeviceId: string,110private targetPopulation: TargetPopulation,111private releaseDate: string112) { }113114/**115* Returns a version string that can be parsed by the TAS client.116* The tas client cannot handle suffixes lke "-insider"117* Ref: https://github.com/microsoft/tas-client/blob/30340d5e1da37c2789049fcf45928b954680606f/vscode-tas-client/src/vscode-tas-client/VSCodeFilterProvider.ts#L35118*119* @param version Version string to be trimmed.120*/121private static trimVersionSuffix(version: string): string {122const regex = /\-[a-zA-Z0-9]+$/;123const result = version.split(regex);124125return result[0];126}127128getFilterValue(filter: string): string | null {129switch (filter) {130case Filters.ApplicationVersion:131return AssignmentFilterProvider.trimVersionSuffix(this.version); // productService.version132case Filters.Build:133return this.appName; // productService.nameLong134case Filters.ClientId:135return this.machineId;136case Filters.DeveloperDeviceId:137return this.devDeviceId;138case Filters.Language:139return platform.language;140case Filters.ExtensionName:141return 'vscode-core'; // always return vscode-core for exp service142case Filters.ExtensionVersion:143return '999999.0'; // always return a very large number for cross-extension experimentation144case Filters.TargetPopulation:145return this.targetPopulation;146case Filters.Platform:147return platform.PlatformToString(platform.platform);148case Filters.ReleaseDate:149return AssignmentFilterProvider.formatReleaseDate(this.releaseDate);150default:151return '';152}153}154155private static formatReleaseDate(iso: string): string {156// Expect ISO format, fall back to empty string if not provided157if (!iso) {158return '';159}160// Remove separators and milliseconds: YYYY-MM-DDTHH:MM:SS.sssZ -> YYYYMMDDHH161// Trimmed to 10 digits to fit within int32 bounds (ExP requirement)162const match = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})/.exec(iso);163if (!match) {164return '';165}166return match.slice(1, 5).join('');167}168169getFilters(): Map<string, unknown> {170const filters: Map<string, unknown> = new Map<string, unknown>();171const filterValues = Object.values(Filters);172for (const value of filterValues) {173filters.set(value, this.getFilterValue(value));174}175176return filters;177}178}179180181