1// Copyright (C) 2020 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import * as m from 'mithril'; 16 17import {channelChanged, getNextChannel, setChannel} from '../common/channels'; 18import {featureFlags, Flag, OverrideState} from '../common/feature_flags'; 19 20import {globals} from './globals'; 21import {createPage} from './pages'; 22 23const RELEASE_PROCESS_URL = 24 'https://perfetto.dev/docs/visualization/perfetto-ui-release-process'; 25 26interface FlagOption { 27 id: string; 28 name: string; 29} 30 31interface SelectWidgetAttrs { 32 label: string; 33 description: m.Children; 34 options: FlagOption[]; 35 selected: string; 36 onSelect: (id: string) => void; 37} 38 39class SelectWidget implements m.ClassComponent<SelectWidgetAttrs> { 40 view(vnode: m.Vnode<SelectWidgetAttrs>) { 41 const attrs = vnode.attrs; 42 return m( 43 '.flag-widget', 44 m('label', attrs.label), 45 m( 46 'select', 47 { 48 onchange: (e: InputEvent) => { 49 const value = (e.target as HTMLSelectElement).value; 50 attrs.onSelect(value); 51 globals.rafScheduler.scheduleFullRedraw(); 52 }, 53 }, 54 attrs.options.map(o => { 55 const selected = o.id === attrs.selected; 56 return m('option', {value: o.id, selected}, o.name); 57 }), 58 ), 59 m('.description', attrs.description), 60 ); 61 } 62} 63 64interface FlagWidgetAttrs { 65 flag: Flag; 66} 67 68class FlagWidget implements m.ClassComponent<FlagWidgetAttrs> { 69 view(vnode: m.Vnode<FlagWidgetAttrs>) { 70 const flag = vnode.attrs.flag; 71 const defaultState = flag.defaultValue ? 'Enabled' : 'Disabled'; 72 return m(SelectWidget, { 73 label: flag.name, 74 description: flag.description, 75 options: [ 76 {id: OverrideState.DEFAULT, name: `Default (${defaultState})`}, 77 {id: OverrideState.TRUE, name: 'Enabled'}, 78 {id: OverrideState.FALSE, name: 'Disabled'}, 79 ], 80 selected: flag.overriddenState(), 81 onSelect: (value: string) => { 82 switch (value) { 83 case OverrideState.TRUE: 84 flag.set(true); 85 break; 86 case OverrideState.FALSE: 87 flag.set(false); 88 break; 89 default: 90 case OverrideState.DEFAULT: 91 flag.reset(); 92 break; 93 } 94 } 95 }); 96 } 97} 98 99export const FlagsPage = createPage({ 100 view() { 101 const needsReload = channelChanged(); 102 return m( 103 '.flags-page', 104 m( 105 '.flags-content', 106 m('h1', 'Feature flags'), 107 needsReload && 108 [ 109 m('h2', 'Please reload for your changes to take effect'), 110 ], 111 m(SelectWidget, { 112 label: 'Release channel', 113 description: [ 114 'Which release channel of the UI to use. See ', 115 m('a', 116 { 117 href: RELEASE_PROCESS_URL, 118 }, 119 'Release Process'), 120 ' for more information.' 121 ], 122 options: [ 123 {id: 'stable', name: 'Stable (default)'}, 124 {id: 'canary', name: 'Canary'}, 125 {id: 'autopush', name: 'Autopush'}, 126 ], 127 selected: getNextChannel(), 128 onSelect: id => setChannel(id), 129 }), 130 m('button', 131 { 132 onclick: () => { 133 featureFlags.resetAll(); 134 globals.rafScheduler.scheduleFullRedraw(); 135 }, 136 }, 137 'Reset all below'), 138 139 featureFlags.allFlags().map(flag => m(FlagWidget, {flag})), 140 )); 141 } 142}); 143