blob: 69d932d493bca99c6ad5aa63a769f55555cbdad7 [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import m from 'mithril';
import type {Filter} from '../../components/widgets/datagrid/model';
import type {
EnumOption,
Setting as BigTraceSetting,
SettingFilter,
} from './settings_types';
// Routes SettingsPage reads/writes to the per-query snapshot instead of the
// global state modules. Used by the Query page's chip strip / +Add modal.
export interface SettingsBindings {
// Effective SettingFilter[] for data source + schema requests.
readonly getEffectiveSettings: () => ReadonlyArray<SettingFilter>;
// One setting's `values` by id. undefined = no snapshot entry; falls back to
// defaultValue.
readonly getSettingValue: (id: string) => readonly string[] | undefined;
readonly setSettingValue: (
id: string,
values: readonly string[],
category: string,
) => void;
readonly getTraceFilters: () => readonly Filter[];
readonly setTraceFilters: (filters: readonly Filter[]) => void;
// null = unchosen (defaultVisible); set accepts a concrete list or null.
readonly getTraceMetadataColumns: () => readonly string[] | null;
readonly setTraceMetadataColumns: (cols: readonly string[] | null) => void;
// Per-tab AIP-132 ordering (e.g. "size_bytes desc"), shipped as
// `trace_order_by` on the next Run. Empty defers to the default.
readonly getTraceOrderBy: () => string;
readonly setTraceOrderBy: (orderBy: string) => void;
// Per-tab enable/disable of one setting, independent of global state — a tab
// can toggle a filter without affecting /settings or other tabs. Effective
// settings drop per-tab-disabled entries.
readonly isSettingDisabled: (id: string) => boolean;
readonly setSettingDisabled: (id: string, disabled: boolean) => void;
// Called when the trace-list data source reports a fresh filteredTotalRows
// (traces the current filter selects). undefined = count not yet known.
readonly onTraceMatchCount?: (count: number | undefined) => void;
}
// Wraps a globally-registered Setting<T> so reads/writes route through per-tab
// bindings, inheriting the descriptor (type, schema, placeholder, options) so
// renderSetting() picks the right widget.
export class TabBoundSetting<T> implements BigTraceSetting<T> {
readonly id: string;
readonly name: string;
readonly description: string;
readonly type: BigTraceSetting<T>['type'];
readonly schema: BigTraceSetting<T>['schema'];
readonly defaultValue: T;
readonly category?: string;
readonly requiresReload?: boolean;
readonly options?: readonly (string | EnumOption)[];
readonly placeholder?: string;
readonly format?: 'sql';
readonly disabled: boolean;
constructor(
private readonly base: BigTraceSetting<T>,
private readonly bindings: SettingsBindings,
) {
this.id = base.id;
this.name = base.name;
this.description = base.description;
this.type = base.type;
this.schema = base.schema;
this.defaultValue = base.defaultValue;
this.category = base.category;
this.requiresReload = base.requiresReload;
this.options = base.options;
this.placeholder = base.placeholder;
this.format = base.format;
this.disabled = base.disabled ?? false;
}
get isDefault(): boolean {
return JSON.stringify(this.get()) === JSON.stringify(this.defaultValue);
}
get(): T {
const raw = this.bindings.getSettingValue(this.id);
if (raw === undefined) return this.base.get();
return convertFromWireValues<T>(raw, this.base) ?? this.defaultValue;
}
set(value: T): void {
const wire = Array.isArray(value) ? value.map(String) : [String(value)];
this.bindings.setSettingValue(this.id, wire, this.category ?? '');
m.redraw();
}
reset(): void {
this.set(this.defaultValue);
}
isDisabled(): boolean {
// Booleans have no enable/disable concept (the value is the on/off), so
// they're never disabled per-tab.
if (this.type === 'boolean') return false;
return this.bindings.isSettingDisabled(this.id);
}
setDisabled(disabled: boolean): void {
this.bindings.setSettingDisabled(this.id, disabled);
m.redraw();
}
[Symbol.dispose](): void {}
}
// Reads the wire-side `values` string array back into the setting's declared
// type. undefined when the entry doesn't match the type (e.g. "abc" for a
// number); callers fall back to defaultValue.
export function convertFromWireValues<T>(
raw: readonly string[],
setting: BigTraceSetting<T>,
): T | undefined {
switch (setting.type) {
case 'number': {
if (raw.length === 0) return undefined;
const n = parseFloat(raw[0]);
return Number.isFinite(n) ? (n as unknown as T) : undefined;
}
case 'boolean':
return (raw[0] === 'true') as unknown as T;
case 'string':
case 'enum':
return (raw[0] ?? '') as unknown as T;
case 'multi-select':
case 'string-array':
return [...raw] as unknown as T;
}
return undefined;
}