Skip to content

Commit

Permalink
feat: switch and parallel running for DUP logic
Browse files Browse the repository at this point in the history
Introduces the concept of a "variant", which is an alternate candidate
generator for an app ID. Also introduces a variant for DUPs which will
be used to build and test the new departures logic. For now, this only
generates a set of placeholder elements.

Variants can be enabled via:

* Adding a `variant=NAME` query param to a screen URL. The resulting
  screen page will fetch and display data for only that variant.

* Enabling the "variant switcher" in the admin Inspector. This allows
  toggling between variants instantly without refreshing data; the data
  hook uses a special `all` value to request and hold onto the data for
  all variants at once.

Additionally, `ScreenData` functions have an option to run and serialize
all variants that exist for the app ID in parallel, in the background,
without waiting for or doing anything with the results. This means we
can have all existing screens constantly running the new logic at every
step of the implementation, giving us more confidence that the final
rollout would go smoothly. For now, we only use this option on "normal"
screen requests (not pending, not simulation).
  • Loading branch information
digitalcora committed Sep 13, 2024
1 parent 31621c8 commit 2f6eb78
Show file tree
Hide file tree
Showing 18 changed files with 539 additions and 121 deletions.
2 changes: 2 additions & 0 deletions assets/src/apps/v2/dup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { MappingContext } from "Components/v2/widget";
import NormalScreen, {
NormalSimulation,
} from "Components/v2/dup/normal_screen";
import Placeholder from "Components/v2/placeholder";
import NormalHeader from "Components/v2/dup/normal_header";
import Departures from "Components/v2/dup/departures";
import MultiScreenPage from "Components/v2/multi_screen_page";
Expand Down Expand Up @@ -53,6 +54,7 @@ const TYPE_TO_COMPONENT = {
body_split_zero: splitRotationFromPropNames(SplitBody, "zero"),
body_split_one: splitRotationFromPropNames(SplitBody, "one"),
body_split_two: splitRotationFromPropNames(SplitBody, "two"),
placeholder: Placeholder,
normal_header: NormalHeader,
departures: Departures,
evergreen_content: EvergreenContent,
Expand Down
119 changes: 95 additions & 24 deletions assets/src/components/admin/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const AUDIO_SCREEN_TYPES = new Set([
"pre_fare_v2",
]);

const SCREEN_TYPE_VARIANTS = { dup_v2: ["new_departures"] };

const Inspector: ComponentType = () => {
const [config, setConfig] = useState<Config | null>(null);

Expand All @@ -55,6 +57,7 @@ const Inspector: ComponentType = () => {
: null;

const [isSimulation, setIsSimulation] = useState(false);
const [isVariantEnabled, setIsVariantEnabled] = useState(false);

const frameRef = useRef<HTMLIFrameElement>(null);

Expand All @@ -79,13 +82,22 @@ const Inspector: ComponentType = () => {
screen={screen}
isSimulation={isSimulation}
setIsSimulation={setIsSimulation}
isVariantEnabled={isVariantEnabled}
setIsVariantEnabled={setIsVariantEnabled}
/>

{screen && (
<>
<ConfigControls screen={screen} />
<ViewControls zoom={zoom} setZoom={setZoom} />
<DataControls sendToFrame={sendToFrame} />
<DataControls
// Reset when the loaded screen changes, since the new screen
// will not be aware of previously-sent inspector messages
key={screen.id}
isVariantEnabled={isVariantEnabled}
screen={screen}
sendToFrame={sendToFrame}
/>
<AudioControls screen={screen} />
</>
)}
Expand All @@ -101,7 +113,11 @@ const Inspector: ComponentType = () => {
src={
screen
? new URL(
`/v2/screen/${screen.id}${isSimulation ? "/simulation" : ""}`,
[
`/v2/screen/${screen.id}`,
isSimulation ? "/simulation" : "",
isVariantEnabled ? "?variant=all" : "",
].join(""),
location.origin,
).toString()
: "about:blank"
Expand All @@ -117,7 +133,16 @@ const ScreenSelector: ComponentType<{
screen: ScreenWithId | null;
isSimulation: boolean;
setIsSimulation: (value: boolean) => void;
}> = ({ config, screen, isSimulation, setIsSimulation }) => {
isVariantEnabled: boolean;
setIsVariantEnabled: (value: boolean) => void;
}> = ({
config,
screen,
isSimulation,
setIsSimulation,
isVariantEnabled,
setIsVariantEnabled,
}) => {
const history = useHistory();
const { pathname, search } = useLocation();

Expand Down Expand Up @@ -165,7 +190,16 @@ const ScreenSelector: ComponentType<{
checked={isSimulation}
onChange={() => setIsSimulation(!isSimulation)}
/>
Screenplay Simulation
Screenplay simulation
</label>

<label>
<input
type="checkbox"
checked={isVariantEnabled}
onChange={() => setIsVariantEnabled(!isVariantEnabled)}
/>
Enable variant switcher
</label>
</fieldset>
);
Expand Down Expand Up @@ -253,11 +287,14 @@ const ViewControls: ComponentType<{
};

const DataControls: ComponentType<{
isVariantEnabled: boolean;
screen: ScreenWithId;
sendToFrame: (message: Message) => void;
}> = ({ sendToFrame }) => {
}> = ({ isVariantEnabled, screen, sendToFrame }) => {
const [dataTimestamp, setDataTimestamp] = useState<number | null>(null);
const [dataSecondsOld, setDataSecondsOld] = useState<number | null>(null);
const [isRefreshEnabled, setIsRefreshEnabled] = useState(true);
const [variant, setVariant] = useState<string | null>(null);

useReceiveMessage((message) => {
if (message.type == "data_refreshed") {
Expand All @@ -279,29 +316,63 @@ const DataControls: ComponentType<{
sendToFrame({ type: "set_refresh_rate", ms: isRefreshEnabled ? null : 0 });
}, [isRefreshEnabled]);

useEffect(() => {
sendToFrame({ type: "set_data_variant", variant: variant });
}, [variant]);

return (
<fieldset>
<legend>Data</legend>
<>
<fieldset>
<legend>Data</legend>

<div>
<button onClick={() => sendToFrame({ type: "refresh_data" })}>
Refresh
</button>
<div>
<button onClick={() => sendToFrame({ type: "refresh_data" })}>
Refresh
</button>

{isRefreshEnabled && dataSecondsOld != null && (
<span>⏱️ {dataSecondsOld} seconds ago</span>
)}
</div>
{isRefreshEnabled && dataSecondsOld != null && (
<span>⏱️ {dataSecondsOld} seconds ago</span>
)}
</div>

<label>
<input
type="checkbox"
checked={isRefreshEnabled}
onChange={() => setIsRefreshEnabled(!isRefreshEnabled)}
/>
Enable refresh interval
</label>
</fieldset>
<label>
<input
type="checkbox"
checked={isRefreshEnabled}
onChange={() => setIsRefreshEnabled(!isRefreshEnabled)}
/>
Enable refresh interval
</label>
</fieldset>

{isVariantEnabled && (
<fieldset>
<legend>Variants</legend>

<label>
<input
type="radio"
name="variant"
checked={variant === null}
onChange={() => setVariant(null)}
/>
Default
</label>

{(SCREEN_TYPE_VARIANTS[screen.config.app_id] ?? []).map((v) => (
<label key={v}>
<input
type="radio"
name="variant"
checked={variant === v}
onChange={() => setVariant(v)}
/>
<code>{v}</code>
</label>
))}
</fieldset>
)}
</>
);
};

Expand Down
Loading

0 comments on commit 2f6eb78

Please sign in to comment.