• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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