Skip to content
Open
Show file tree
Hide file tree
Changes from all 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