mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat(voice): rework screen share delivery behind an experiment (#2859)
This commit is contained in:
@@ -15,6 +15,10 @@ import {
|
||||
GatewayRolloutConfigResponse,
|
||||
GatewayRolloutConfigUpdateRequest,
|
||||
} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas';
|
||||
import {
|
||||
ScreenShareDeliveryConfigResponse,
|
||||
ScreenShareDeliveryConfigUpdateRequest,
|
||||
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
|
||||
import {
|
||||
VoiceNoiseSuppressionConfigResponse,
|
||||
VoiceNoiseSuppressionConfigUpdateRequest,
|
||||
@@ -644,6 +648,7 @@ export const InstanceConfigResponse = z.object({
|
||||
sso: SsoConfigResponse,
|
||||
gateway_rollout: GatewayRolloutConfigResponse,
|
||||
voice_noise_suppression: VoiceNoiseSuppressionConfigResponse,
|
||||
screen_share_delivery: ScreenShareDeliveryConfigResponse,
|
||||
experiment_delivery: ExperimentDeliveryConfigResponse,
|
||||
registration: InstanceRegistrationResponse,
|
||||
self_hosted: z.boolean(),
|
||||
@@ -680,6 +685,7 @@ const InstancePolicyUpdateSchema = z.object({
|
||||
export const InstanceConfigUpdateRequest = z.object({
|
||||
gateway_rollout: GatewayRolloutConfigUpdateRequest.nullish(),
|
||||
voice_noise_suppression: VoiceNoiseSuppressionConfigUpdateRequest.nullish(),
|
||||
screen_share_delivery: ScreenShareDeliveryConfigUpdateRequest.nullish(),
|
||||
experiment_delivery: ExperimentDeliveryConfigUpdateRequest.nullish(),
|
||||
registration: z
|
||||
.object({
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
|
||||
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
|
||||
resolveScreenShareDeliveryAssignment,
|
||||
type ScreenShareDeliveryConfig,
|
||||
ScreenShareDeliveryConfigSchema,
|
||||
ScreenShareDeliveryConfigUpdateRequest,
|
||||
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
|
||||
import {experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
const TARGETED_USER_ID = '1000000000000000001';
|
||||
const OTHER_USER_ID = '1000000000000000002';
|
||||
|
||||
function createConfig(overrides: Partial<ScreenShareDeliveryConfig> = {}): ScreenShareDeliveryConfig {
|
||||
return {
|
||||
...DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG,
|
||||
included_user_ids: [],
|
||||
excluded_user_ids: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function syntheticUserIds(count: number): Array<string> {
|
||||
const ids: Array<string> = [];
|
||||
for (let index = 0; index < count; index++) {
|
||||
ids.push((1400000000000000000n + BigInt(index)).toString());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function targetedUserIds(config: ScreenShareDeliveryConfig, userIds: ReadonlyArray<string>): Set<string> {
|
||||
const targeted = new Set<string>();
|
||||
for (const userId of userIds) {
|
||||
if (resolveScreenShareDeliveryAssignment(config, userId).enabled) {
|
||||
targeted.add(userId);
|
||||
}
|
||||
}
|
||||
return targeted;
|
||||
}
|
||||
|
||||
describe('screen share delivery configuration', () => {
|
||||
test('derives defaults from the schema with independently owned arrays', () => {
|
||||
const first = ScreenShareDeliveryConfigSchema.parse({});
|
||||
const second = ScreenShareDeliveryConfigSchema.parse({});
|
||||
expect(first).toEqual(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG);
|
||||
first.included_user_ids.push(TARGETED_USER_ID);
|
||||
first.excluded_user_ids.push(OTHER_USER_ID);
|
||||
expect(second).toEqual(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG);
|
||||
});
|
||||
|
||||
test('defaults to disabled with an empty rollout', () => {
|
||||
expect(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG.enabled).toBe(false);
|
||||
expect(DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG.rollout_basis_points).toBe(0);
|
||||
});
|
||||
|
||||
test.each([{}, {enabled: false}, {enabled: undefined}, {rollout_basis_points: 2500}])(
|
||||
'keeps partial updates free of configuration defaults: %j',
|
||||
(patch) => {
|
||||
expect(ScreenShareDeliveryConfigUpdateRequest.parse(patch)).toEqual(patch);
|
||||
},
|
||||
);
|
||||
|
||||
test('does not accept a client-provided configuration version', () => {
|
||||
expect(ScreenShareDeliveryConfigUpdateRequest.parse({config_version: 12})).toEqual({});
|
||||
});
|
||||
|
||||
test.each([
|
||||
{rollout_basis_points: -1},
|
||||
{rollout_basis_points: 10001},
|
||||
{rollout_salt: ' '},
|
||||
{included_user_ids: ['not-an-id']},
|
||||
{excluded_user_ids: ['not-an-id']},
|
||||
])('applies the same validation to stored configuration and updates: %j', (value) => {
|
||||
expect(ScreenShareDeliveryConfigSchema.safeParse(value).success).toBe(false);
|
||||
expect(ScreenShareDeliveryConfigUpdateRequest.safeParse(value).success).toBe(false);
|
||||
});
|
||||
|
||||
test('rejects more than a thousand targeted user ids', () => {
|
||||
const ids = syntheticUserIds(1001);
|
||||
expect(ScreenShareDeliveryConfigSchema.safeParse({included_user_ids: ids}).success).toBe(false);
|
||||
expect(ScreenShareDeliveryConfigSchema.safeParse({included_user_ids: ids.slice(0, 1000)}).success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveScreenShareDeliveryAssignment', () => {
|
||||
test('the inert assignment is disabled', () => {
|
||||
expect(INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('returns the inert assignment when the master switch is off', () => {
|
||||
const config = createConfig({
|
||||
enabled: false,
|
||||
rollout_basis_points: 10000,
|
||||
included_user_ids: [TARGETED_USER_ID],
|
||||
});
|
||||
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID)).toEqual(
|
||||
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns the inert assignment for the default config', () => {
|
||||
expect(resolveScreenShareDeliveryAssignment(createConfig(), TARGETED_USER_ID)).toEqual(
|
||||
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
|
||||
);
|
||||
});
|
||||
|
||||
test('never hands back the shared inert object', () => {
|
||||
const assignment = resolveScreenShareDeliveryAssignment(createConfig(), TARGETED_USER_ID);
|
||||
expect(assignment).not.toBe(INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT);
|
||||
});
|
||||
|
||||
test('denylist beats allowlist', () => {
|
||||
const config = createConfig({
|
||||
enabled: true,
|
||||
included_user_ids: [TARGETED_USER_ID],
|
||||
excluded_user_ids: [TARGETED_USER_ID],
|
||||
});
|
||||
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID).enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('denylist beats the bucket', () => {
|
||||
const config = createConfig({
|
||||
enabled: true,
|
||||
rollout_basis_points: 10000,
|
||||
excluded_user_ids: [TARGETED_USER_ID],
|
||||
});
|
||||
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID).enabled).toBe(false);
|
||||
expect(resolveScreenShareDeliveryAssignment(config, OTHER_USER_ID).enabled).toBe(true);
|
||||
});
|
||||
|
||||
test('allowlist beats the bucket', () => {
|
||||
const config = createConfig({
|
||||
enabled: true,
|
||||
rollout_basis_points: 0,
|
||||
included_user_ids: [TARGETED_USER_ID],
|
||||
});
|
||||
expect(resolveScreenShareDeliveryAssignment(config, TARGETED_USER_ID).enabled).toBe(true);
|
||||
expect(resolveScreenShareDeliveryAssignment(config, OTHER_USER_ID).enabled).toBe(false);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{basisPoints: 0, enabled: false},
|
||||
{basisPoints: 10000, enabled: true},
|
||||
])('a rollout of $basisPoints basis points targets $enabled', ({basisPoints, enabled}) => {
|
||||
const config = createConfig({enabled: true, rollout_basis_points: basisPoints});
|
||||
for (const userId of syntheticUserIds(200)) {
|
||||
expect(resolveScreenShareDeliveryAssignment(config, userId).enabled).toBe(enabled);
|
||||
}
|
||||
});
|
||||
|
||||
test('the bucket boundary is exclusive at the low end and inclusive one point above', () => {
|
||||
const salt = DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG.rollout_salt;
|
||||
const bucket = experimentBucket(TARGETED_USER_ID, salt);
|
||||
expect(
|
||||
resolveScreenShareDeliveryAssignment(
|
||||
createConfig({enabled: true, rollout_basis_points: bucket}),
|
||||
TARGETED_USER_ID,
|
||||
).enabled,
|
||||
).toBe(false);
|
||||
expect(
|
||||
resolveScreenShareDeliveryAssignment(
|
||||
createConfig({enabled: true, rollout_basis_points: bucket + 1}),
|
||||
TARGETED_USER_ID,
|
||||
).enabled,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('raising the rollout basis points only ever adds users', () => {
|
||||
const userIds = syntheticUserIds(2000);
|
||||
const atOneThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 1000}), userIds);
|
||||
const atTwoThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 2000}), userIds);
|
||||
expect(atOneThousand.size).toBeGreaterThan(0);
|
||||
for (const userId of atOneThousand) {
|
||||
expect(atTwoThousand.has(userId)).toBe(true);
|
||||
}
|
||||
expect(atTwoThousand.size).toBeGreaterThan(atOneThousand.size);
|
||||
});
|
||||
|
||||
test('the targeted set follows the salt', () => {
|
||||
const userIds = syntheticUserIds(2000);
|
||||
const first = targetedUserIds(
|
||||
createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'screen-share-delivery-v1'}),
|
||||
userIds,
|
||||
);
|
||||
const second = targetedUserIds(
|
||||
createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'screen-share-delivery-v2'}),
|
||||
userIds,
|
||||
);
|
||||
expect(first).not.toEqual(second);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {EXPERIMENT_BUCKET_RESOLUTION, experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
|
||||
import {z} from 'zod';
|
||||
|
||||
const SCREEN_SHARE_DELIVERY_ROLLOUT_BASIS_POINTS_MAX = EXPERIMENT_BUCKET_RESOLUTION;
|
||||
const SCREEN_SHARE_DELIVERY_MAX_TARGETED_USERS = 1000;
|
||||
const DEFAULT_SCREEN_SHARE_DELIVERY_SALT = 'screen-share-delivery-v1';
|
||||
|
||||
const ScreenShareDeliveryTargetIdSchema = z.string().regex(/^\d{1,20}$/u);
|
||||
const ScreenShareDeliveryTargetedUserIdsSchema = z
|
||||
.array(ScreenShareDeliveryTargetIdSchema)
|
||||
.max(SCREEN_SHARE_DELIVERY_MAX_TARGETED_USERS);
|
||||
|
||||
const screenShareDeliveryConfigFields = {
|
||||
enabled: z.boolean(),
|
||||
config_version: z.number().int().min(0),
|
||||
rollout_basis_points: z.number().int().min(0).max(SCREEN_SHARE_DELIVERY_ROLLOUT_BASIS_POINTS_MAX),
|
||||
rollout_salt: z.string().trim().min(1).max(64),
|
||||
included_user_ids: ScreenShareDeliveryTargetedUserIdsSchema,
|
||||
excluded_user_ids: ScreenShareDeliveryTargetedUserIdsSchema,
|
||||
};
|
||||
|
||||
export const ScreenShareDeliveryConfigSchema = z.object({
|
||||
enabled: screenShareDeliveryConfigFields.enabled.default(false),
|
||||
config_version: screenShareDeliveryConfigFields.config_version.default(0),
|
||||
rollout_basis_points: screenShareDeliveryConfigFields.rollout_basis_points.default(0),
|
||||
rollout_salt: screenShareDeliveryConfigFields.rollout_salt.default(DEFAULT_SCREEN_SHARE_DELIVERY_SALT),
|
||||
included_user_ids: screenShareDeliveryConfigFields.included_user_ids.default([]),
|
||||
excluded_user_ids: screenShareDeliveryConfigFields.excluded_user_ids.default([]),
|
||||
});
|
||||
|
||||
export type ScreenShareDeliveryConfig = z.infer<typeof ScreenShareDeliveryConfigSchema>;
|
||||
|
||||
export const DEFAULT_SCREEN_SHARE_DELIVERY_CONFIG: ScreenShareDeliveryConfig = ScreenShareDeliveryConfigSchema.parse(
|
||||
{},
|
||||
);
|
||||
|
||||
export const ScreenShareDeliveryConfigUpdateRequest = z
|
||||
.object(screenShareDeliveryConfigFields)
|
||||
.omit({config_version: true})
|
||||
.partial();
|
||||
|
||||
export type ScreenShareDeliveryConfigUpdateRequest = z.infer<typeof ScreenShareDeliveryConfigUpdateRequest>;
|
||||
|
||||
export const ScreenShareDeliveryConfigResponse = ScreenShareDeliveryConfigSchema;
|
||||
|
||||
export type ScreenShareDeliveryConfigResponse = z.infer<typeof ScreenShareDeliveryConfigResponse>;
|
||||
|
||||
export const ScreenShareDeliveryAssignmentResponse = z.object({
|
||||
enabled: screenShareDeliveryConfigFields.enabled,
|
||||
});
|
||||
|
||||
export type ScreenShareDeliveryAssignmentResponse = z.infer<typeof ScreenShareDeliveryAssignmentResponse>;
|
||||
|
||||
export const INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT: ScreenShareDeliveryAssignmentResponse = {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
export function resolveScreenShareDeliveryAssignment(
|
||||
config: ScreenShareDeliveryConfig,
|
||||
userId: string,
|
||||
): ScreenShareDeliveryAssignmentResponse {
|
||||
if (!config.enabled) return {...INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT};
|
||||
if (config.excluded_user_ids.includes(userId)) return {...INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT};
|
||||
if (config.included_user_ids.includes(userId)) return {enabled: true};
|
||||
return {enabled: experimentBucket(userId, config.rollout_salt) < config.rollout_basis_points};
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT,
|
||||
ScreenShareDeliveryAssignmentResponse,
|
||||
} from '@fluxer/schema/src/domains/admin/ScreenShareDeliverySchemas';
|
||||
import {
|
||||
INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
|
||||
VoiceNoiseSuppressionAssignmentResponse,
|
||||
@@ -42,6 +46,7 @@ export type ExperimentDeliveryConfigResponse = z.infer<typeof ExperimentDelivery
|
||||
|
||||
const ExperimentAssignmentsSchema = z.object({
|
||||
voice_noise_suppression: VoiceNoiseSuppressionAssignmentResponse.optional(),
|
||||
screen_share_delivery: ScreenShareDeliveryAssignmentResponse.optional(),
|
||||
});
|
||||
|
||||
export const ExperimentAssignmentsResponse = z.object({
|
||||
@@ -63,3 +68,9 @@ export function readVoiceNoiseSuppressionAssignment(
|
||||
): VoiceNoiseSuppressionAssignmentResponse {
|
||||
return response.assignments.voice_noise_suppression ?? INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT;
|
||||
}
|
||||
|
||||
export function readScreenShareDeliveryAssignment(
|
||||
response: ExperimentAssignmentsResponse,
|
||||
): ScreenShareDeliveryAssignmentResponse {
|
||||
return response.assignments.screen_share_delivery ?? INERT_SCREEN_SHARE_DELIVERY_ASSIGNMENT;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user