// 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 * as 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 { view(vnode: m.Vnode) { 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 { view(vnode: m.Vnode) { 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})), )); } });