Skip to content

feat: Screen Config Postgres Client APIs and Admin Logic - #3231

Open
robbie-sundstrom wants to merge 3 commits into
mainfrom
rs/scm/api-initial
Open

feat: Screen Config Postgres Client APIs and Admin Logic#3231
robbie-sundstrom wants to merge 3 commits into
mainfrom
rs/scm/api-initial

Conversation

@robbie-sundstrom

Copy link
Copy Markdown
Contributor

Asana task: Define Screen Config shared APIs

  • Adds APIs for fetching all configs and editing a list of configs that will be called by Screenplay. These are protected behind API authorization]
    • Note that I’ve tested calling the API to fetch all configs from Screenplay locally and it’s in working order
  • Modifies Screens Admin. This was initially a separate task, and is split into a separate commit with this work. Including this work allowed me to better understand how I should design use of the feature flag, and what could be shared between the two.
    • The existing index Screens Admin endpoint now calls a different function that handles the feature flag logic
    • There is a new screen_configs endpoint that handles modifying/deleting screen configs in JSON or Postgres based on the feature flag

Frontend Changes

  • Fetching all configs
    • The structure of the JSON we pass could be simplified, but after playing around with it, I think this would be easiest after the migration. Keeping the same nested format for now
  • Making updates to configs
    • Since this currently passes back the full JSON to make changes, the FE now passes different data based on the feature flag it receives from the BE. This is handled by the new function commitScreenConfigChanges.
    • editor.tsx calls this directly
    • inspector.tsx now calls this within the AdminForm component.

@robbie-sundstrom
robbie-sundstrom requested a review from a team as a code owner August 11, 2026 15:32
@robbie-sundstrom robbie-sundstrom changed the title Rs/scm/api initial feat: Screen Config Postgres Client APIs and Admin Logic Aug 11, 2026
Comment thread lib/screens_web/router.ex
get "/screens_by_alert", ScreensByAlertController, :index
end

scope "/api", ScreensWeb do

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.

Non-blocking nit - From the perspective of someone who is trying to hit these endpoints for the first time, they'd probably have to start digging through the code to figure out why they'd getting 401's back. It might be worth putting in the README that you need to set an authorization header in requests to these endpoints

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

Comment thread lib/screens/screen_configs.ex
Comment thread lib/screens/screen_configs.ex Outdated
Comment thread lib/screens/screen_configs.ex Outdated
end)
end

@doc """

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.

For my own understanding, we have a mix of @doc comments and blocks of comments (e.g.). How do we determine what we want to doc with @doc vs not?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I do need to be consistent with this 😅 I kind of like the @doc comments because of how they integrate with tools like ElixirLS in VSCode. But I do generally think the @doc comments are moreso for API consumers, which is why we generally don't have them throughout our codebase. I'll move towards consistency here, which I'd like to be @doc comments for this new file at least

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.

The Elixir convention as far as I've seen is @doc for public functions and # blocks for private functions, since private functions are treated as "hidden" anyway and there's no way to access docs attached to them beyond looking at the code. Technically the main purpose of @doc is in generating ExDocs which are made available on Hex for published libraries; in an application codebase, it's mostly useful for editor LS integration. I usually include them if there's something important for callers to know that isn't covered by the function name, argument names, and typespec, which is all also readily accessible via LSP. (This is not just "for API consumers" in the sense of "consumers of some API external to the whole application", but if you take an expansive view of an "API" as "the set of public functions exported by a module", then that is accurate!)

Comment thread lib/screens/screen_configs.ex Outdated
Comment on lines +119 to +122
@spec perform_deletes([String.t()]) :: :ok | {:error, any()}
defp perform_deletes(deletes) do
Enum.reduce_while(deletes, :ok, fn id, _acc ->
Repo.delete_all(from s in ScreenConfig, where: s.id == ^id)

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.

Does this fn ever return {:error, any()}? I deleted {:error, any()} from the return type, and mix dialyzer passed. Maybe something to do with the reduce_while and delete_all I'm not seeing?

This may be tied to the first question, but for my own understanding, why do we need to use reduce_while here? Wouldn't Enum.each/2 suffice here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Enum.each did originally suffice here, since we're not short circuiting early like in update_to_postgres in the event that we do encounter an error with deletion. I'm making some changes to error handling based on your other comments to add more detailed error codes and details, so I have moved this back to a reduce_while

Comment thread lib/screens/screen_configs.ex Outdated
"""
@spec commit_updates([%{:id => String.t(), :config => map()}], [String.t()]) ::
:ok | {:error, any()}
def commit_updates(updates, deletes \\ []) do

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.

I'm not entirely sure we should combine updates + deletes in a single API call. Maybe I'm being too much of a purist/this reminds me a little bit of SOAP calls though. Is there an advantage of combining these other than one less round trip from the API to the server?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is just recreating existing behavior, which calls a single API point for both updates and deletes at the same time. I agree in principle though, and I could update this so that the frontend make two separate API calls

def index(conn, _params) do
screen_configs =
ScreenConfigs.list_screen_configs()
|> Enum.map(fn screen_config ->

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.

Do we need this Enum.map call? Does the ScreenConfig list returned by list_screen_configs() have everything we need here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It actually has more info than is needed, since it includes the timestamp fields (created_at, updated_at). Since you pointed this out, I do think we could filter these out at the data access layer though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants