Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .envrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export API_V3_URL="https://api-v3.mbta.com"

export SECRET_KEY_BASE="local_secret_key_base_at_least_64_bytes_________________________________"

export SCREENS_API_CLIENT_KEY="local_screens_api_client_key"

## Postgres configuration: username and password for local server
# export DATABASE_USER=
# export DATABASE_PASSWORD=

## Feature flag. Setting to true uses the Postgres DB for screen configurations
export CONFIG_MIGRATION="false"
35 changes: 24 additions & 11 deletions assets/src/components/admin/admin_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,36 +44,49 @@ const AdminValidateControls = ({
};

const AdminConfirmControls = ({
confirmPath,
onConfirm,
configRef,
onCancel,
onError,
onSuccess,
}): JSX.Element => {
const confirmFn = () => {
const config = configRef.current.value;
const dataToSubmit = { config };
fetch.post(confirmPath, dataToSubmit).then((resultJson) => {
if (resultJson.success === true) {
const [isLoading, setIsLoading] = useState(false);

const confirmFn = async () => {
setIsLoading(true);
try {
const config = configRef.current.value;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No action required - Can we give onConfirm a type? It looks like it should be a React.RefObject<HTMLTextAreaElement | null> based on it's creation here, but here we treat it as if it'll never be null here. If it ever is null, this'll fail at runtime

const parsedConfig = JSON.parse(config);
const result = await onConfirm(parsedConfig);

if (result.success === true) {
onSuccess();
} else {
onError();
}
});
} catch (_error) {
onError();
} finally {
setIsLoading(false);
}
};

return (
<div>
<button onClick={onCancel}>Back</button>
<button onClick={confirmFn}>Confirm</button>
<button onClick={onCancel} disabled={isLoading}>
Back
</button>
<button onClick={confirmFn} disabled={isLoading}>
Confirm
</button>
</div>
);
};

const AdminForm = ({
fetchConfig,
validatePath,
confirmPath,
onConfirm,
onUpdated,
}): JSX.Element => {
const [editable, setEditable] = useState(true);
Expand Down Expand Up @@ -111,7 +124,7 @@ const AdminForm = ({
/>
) : (
<AdminConfirmControls
confirmPath={confirmPath}
onConfirm={onConfirm}
configRef={configRef}
onCancel={() => setEditable(true)}
onError={() => {
Expand Down
14 changes: 11 additions & 3 deletions assets/src/components/admin/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type AppId,
type Config,
SCREEN_APP_ENTRIES,
commitScreenConfigChanges,
fetch,
useModalDialog,
useResetKey,
Expand Down Expand Up @@ -45,6 +46,7 @@ const Editor = () => {
const [remoteConfig, setRemoteConfig] = useState<Config>(EMPTY_CONFIG);
const [appIdFilter, setAppIdFilter] = useState<AppId | null>(null);
const [selectedIDs, setSelectedIDs] = useState<Set<string>>(new Set());
const [configMigrationEnabled, setConfigMigrationEnabled] = useState(false);

const { dialog: navBlockDialog, ref: navBlockDialogRef } = useModalDialog();

Expand Down Expand Up @@ -116,6 +118,7 @@ const Editor = () => {
const config: Config = JSON.parse(response.config);
setLocalConfig(config);
setRemoteConfig(config);
setConfigMigrationEnabled(response.config_migration || false);
setSelectedIDs(new Set());
resetTableDataKey();
setIsCommitReady(false);
Expand All @@ -135,9 +138,14 @@ const Editor = () => {

const commitConfig = () => {
withInFlight(setIsLoading, async () => {
const { success } = await fetch.post("/api/admin/screens/confirm", {
config: JSON.stringify(localConfig),
});
const changedIds = Array.from(changedIDs);
const deletedIds = Array.from(deletedIDs);
const success = await commitScreenConfigChanges(
configMigrationEnabled,
changedIds,
deletedIds,
localConfig,
);

if (success) {
setRemoteConfig(localConfig);
Expand Down
29 changes: 27 additions & 2 deletions assets/src/components/admin/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { type AudioConfig } from "Components/screen_container";
import {
fetch,
withInFlight,
commitScreenConfigChanges,
SCREEN_APPS,
type Config,
type Screen,
Expand Down Expand Up @@ -49,6 +50,7 @@ const buildIframeUrl = (screen: ScreenWithId | null, isSimulation: boolean) => {

const Inspector: ComponentType = () => {
const [config, setConfig] = useState<Config | null>(null);
const [configMigrationEnabled, setConfigMigrationEnabled] = useState(false);
const [frameLoadedAt, setFrameLoadedAt] = useState(0);
const [isLoading, setIsLoading] = useState(false);

Expand All @@ -57,6 +59,7 @@ const Inspector: ComponentType = () => {
const response = await fetch.get("/api/admin");
const config: Config = JSON.parse(response.config);
setConfig(config);
setConfigMigrationEnabled(response.config_migration || false);
});
};

Expand Down Expand Up @@ -120,6 +123,8 @@ const Inspector: ComponentType = () => {
{screen && (
<>
<ConfigControls
config={config}
configMigrationEnabled={configMigrationEnabled}
screen={screen}
isLoading={isLoading}
onUpdated={(config) => onScreenUpdated(screen.id, config)}
Expand Down Expand Up @@ -231,10 +236,12 @@ const ScreenSelector: ComponentType<{
};

const ConfigControls: ComponentType<{
config: Config | null;
configMigrationEnabled: boolean;
screen: ScreenWithId;
isLoading: boolean;
onUpdated: (newConfig: Screen) => void;
}> = ({ screen, isLoading, onUpdated }) => {
}> = ({ config, configMigrationEnabled, screen, isLoading, onUpdated }) => {
const [editableConfig, setEditableConfig] = useState<Screen | null>(null);
const [isRequestingReload, setIsRequestingReload] = useState(false);
const dialogRef = useRef<HTMLDialogElement>(null);
Expand All @@ -246,6 +253,24 @@ const ConfigControls: ComponentType<{

const isDisabled = isLoading || isRequestingReload;

const handleConfirmEdit = async (editedConfig: Screen) => {
if (!config) return { success: false };

const updatedConfig: Config = {
...config,
screens: { ...config.screens, [screen.id]: editedConfig },
};

const success = await commitScreenConfigChanges(
configMigrationEnabled,
[screen.id],
[],
updatedConfig,
);

return { success };
};

return (
<fieldset>
<legend>Configuration</legend>
Expand Down Expand Up @@ -291,7 +316,7 @@ const ConfigControls: ComponentType<{
<AdminForm
fetchConfig={async () => editableConfig}
validatePath={`/api/admin/screens/validate/${screen.id}`}
confirmPath={`/api/admin/screens/confirm/${screen.id}`}
onConfirm={handleConfirmEdit}
onUpdated={(newConfig) => {
alert("Success. Allow 5 seconds for changes to propagate.");
closeDialog();
Expand Down
27 changes: 27 additions & 0 deletions assets/src/util/admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,30 @@ const doFetch = async (
throw error;
}
};

export const commitScreenConfigChanges = async (
configMigrationEnabled: boolean,
changedScreenIds: string[],
deletedScreenIds: string[],
localConfig: Config,
) => {
if (configMigrationEnabled) {
const changedConfigs = changedScreenIds.map((id) => ({
id,
config: localConfig.screens[id],
}));

const { success } = await fetch.post("/api/admin/screen_configs", {
screen_configs: changedConfigs,
deleted_screen_ids: deletedScreenIds,
});

return success;
} else {
const { success } = await fetch.post("/api/admin/screens/confirm", {
config: JSON.stringify(localConfig),
});

return success;
}
};
2 changes: 2 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# remember to add this file to your .gitignore.
import Config

config :screens, :config_migration, System.get_env("CONFIG_MIGRATION", "false") == "true"

config :screens, Screens.Repo,
username: System.fetch_env!("DATABASE_USER"),
password: System.get_env("DATABASE_PASSWORD"),
Expand Down
2 changes: 2 additions & 0 deletions lib/screens/config/screen_config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule Screens.Config.ScreenConfig do

import Ecto.Changeset

@derive {Jason.Encoder, except: [:__meta__]}

@type t() :: %__MODULE__{
id: String.t(),
config: Config.t()
Expand Down
Loading