blob: 05af3a078bfe964e4e3fb29a8ed790e71fde3fc2 [file] [log] [blame]
// Copyright (C) 2020 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 {channelChanged, getNextChannel, setChannel} from '../common/channels';
import {featureFlags, Flag, OverrideState} from '../common/feature_flags';
import {globals} from './globals';
import {createPage} from './pages';
const RELEASE_PROCESS_URL =
'https://perfetto.dev/docs/visualization/perfetto-ui-release-process';
interface FlagOption {
id: string;
name: string;
}
interface SelectWidgetAttrs {
label: string;
description: m.Children;
options: FlagOption[];
selected: string;
onSelect: (id: string) => void;
}
class SelectWidget implements m.ClassComponent<SelectWidgetAttrs> {
view(vnode: m.Vnode<SelectWidgetAttrs>) {
const attrs = vnode.attrs;
return m(
'.flag-widget',
m('label', attrs.label),
m(
'select',
{
onchange: (e: InputEvent) => {
const value = (e.target as HTMLSelectElement).value;
attrs.onSelect(value);
globals.rafScheduler.scheduleFullRedraw();
},
},
attrs.options.map((o) => {
const selected = o.id === attrs.selected;
return m('option', {value: o.id, selected}, o.name);
}),
),
m('.description', attrs.description),
);
}
}
interface FlagWidgetAttrs {
flag: Flag;
}
class FlagWidget implements m.ClassComponent<FlagWidgetAttrs> {
view(vnode: m.Vnode<FlagWidgetAttrs>) {
const flag = vnode.attrs.flag;
const defaultState = flag.defaultValue ? 'Enabled' : 'Disabled';
return m(SelectWidget, {
label: flag.name,
description: flag.description,
options: [
{id: OverrideState.DEFAULT, name: `Default (${defaultState})`},
{id: OverrideState.TRUE, name: 'Enabled'},
{id: OverrideState.FALSE, name: 'Disabled'},
],
selected: flag.overriddenState(),
onSelect: (value: string) => {
switch (value) {
case OverrideState.TRUE:
flag.set(true);
break;
case OverrideState.FALSE:
flag.set(false);
break;
default:
case OverrideState.DEFAULT:
flag.reset();
break;
}
},
});
}
}
export const FlagsPage = createPage({
view() {
const needsReload = channelChanged();
return m(
'.flags-page',
m(
'.flags-content',
m('h1', 'Feature flags'),
needsReload &&
[
m('h2', 'Please reload for your changes to take effect'),
],
m(SelectWidget, {
label: 'Release channel',
description: [
'Which release channel of the UI to use. See ',
m('a',
{
href: RELEASE_PROCESS_URL,
},
'Release Process'),
' for more information.',
],
options: [
{id: 'stable', name: 'Stable (default)'},
{id: 'canary', name: 'Canary'},
{id: 'autopush', name: 'Autopush'},
],
selected: getNextChannel(),
onSelect: (id) => setChannel(id),
}),
m('button',
{
onclick: () => {
featureFlags.resetAll();
globals.rafScheduler.scheduleFullRedraw();
},
},
'Reset all below'),
featureFlags.allFlags().map((flag) => m(FlagWidget, {flag})),
));
},
});