From 1b84dcb3fb090c873fb6641340cd3d28d4eb3553 Mon Sep 17 00:00:00 2001 From: Adrian Date: Tue, 17 Feb 2026 22:11:37 +0000 Subject: [PATCH] Fix dev seeding failures and surface seed errors --- server/lib/orcasite/radio/feed.ex | 2 + server/lib/orcasite/radio/seed.ex | 89 ++++++++++++++----------- server/lib/orcasite/radio/seed/utils.ex | 12 +++- server/priv/repo/seeds.exs | 4 +- ui/src/pages/seed.tsx | 25 ++++++- 5 files changed, 88 insertions(+), 44 deletions(-) diff --git a/server/lib/orcasite/radio/feed.ex b/server/lib/orcasite/radio/feed.ex index 0dc092462..35c85070d 100644 --- a/server/lib/orcasite/radio/feed.ex +++ b/server/lib/orcasite/radio/feed.ex @@ -225,6 +225,7 @@ defmodule Orcasite.Radio.Feed do :cloudfront_url, :dataplicity_id, :orcahello_id, + :maintainer_emails, :location_point, if(Orcasite.Config.seeding_enabled?(), do: :id) ] @@ -241,6 +242,7 @@ defmodule Orcasite.Radio.Feed do :cloudfront_url, :dataplicity_id, :orcahello_id, + :maintainer_emails, :location_point ] diff --git a/server/lib/orcasite/radio/seed.ex b/server/lib/orcasite/radio/seed.ex index 88a7d8a1c..375491596 100644 --- a/server/lib/orcasite/radio/seed.ex +++ b/server/lib/orcasite/radio/seed.ex @@ -61,26 +61,24 @@ defmodule Orcasite.Radio.Seed do default: fn -> DateTime.utc_now() |> DateTime.add(-2, :minute) end run fn %{arguments: %{start_time: start_time, end_time: end_time}}, _ -> - __MODULE__.feeds() - - feeds = Orcasite.Radio.Feed |> Ash.read!() - - seed_params = - for feed <- feeds, resource <- [:feed_segment, :detection, :audio_image, :bout] do - {feed, resource} - end - - seed_params - |> Stream.map(fn {feed, resource} -> - __MODULE__.resource!(%{ - resource: resource, - start_time: start_time, - end_time: end_time, - feed_id: feed.id - }) - end) - |> Enum.to_list() - |> then(&{:ok, &1}) + with {:ok, feeds} <- seed_and_load_feeds() do + seed_params = + for feed <- feeds, resource <- [:feed_segment, :detection, :audio_image, :bout] do + {feed, resource} + end + + seed_params + |> Stream.map(fn {feed, resource} -> + __MODULE__.resource!(%{ + resource: resource, + start_time: start_time, + end_time: end_time, + feed_id: feed.id + }) + end) + |> Enum.to_list() + |> then(&{:ok, &1}) + end end end @@ -90,25 +88,23 @@ defmodule Orcasite.Radio.Seed do argument :limit, :integer, allow_nil?: false, default: 100 run fn %{arguments: %{limit: limit}}, _ -> - __MODULE__.feeds() - - feeds = Orcasite.Radio.Feed |> Ash.read!() - - seed_params = - for feed <- feeds, resource <- [:detection, :audio_image, :bout] do - {feed, resource} - end - - seed_params - |> Stream.map(fn {feed, resource} -> - __MODULE__.latest_resource!(%{ - resource: resource, - limit: limit, - feed_id: feed.id - }) - end) - |> Enum.to_list() - |> then(&{:ok, &1}) + with {:ok, feeds} <- seed_and_load_feeds() do + seed_params = + for feed <- feeds, resource <- [:detection, :audio_image, :bout] do + {feed, resource} + end + + seed_params + |> Stream.map(fn {feed, resource} -> + __MODULE__.latest_resource!(%{ + resource: resource, + limit: limit, + feed_id: feed.id + }) + end) + |> Enum.to_list() + |> then(&{:ok, &1}) + end end end @@ -243,4 +239,19 @@ defmodule Orcasite.Radio.Seed do :feed_stream -> Orcasite.Radio.FeedStream end end + + defp seed_and_load_feeds do + with {:ok, _} <- __MODULE__.feeds(), + {:ok, feeds} <- Ash.read(Orcasite.Radio.Feed, authorize?: false) do + case feeds do + [] -> + {:error, + message: + "No feeds were seeded. Run `seedFeeds` first and inspect server logs for feed seeding errors."} + + _ -> + {:ok, feeds} + end + end + end end diff --git a/server/lib/orcasite/radio/seed/utils.ex b/server/lib/orcasite/radio/seed/utils.ex index ed13dd243..182c78961 100644 --- a/server/lib/orcasite/radio/seed/utils.ex +++ b/server/lib/orcasite/radio/seed/utils.ex @@ -12,7 +12,7 @@ defmodule Orcasite.Radio.Seed.Utils do attr = Absinthe.Adapter.Underscore.to_internal_name(key, []) cond do - writable_attr?(resource, attr) -> + writable_attr?(resource, attr) and include_attribute_input?(resource, attr, val) -> [{attr, val}] many_relationship?(resource, attr) -> @@ -30,6 +30,16 @@ defmodule Orcasite.Radio.Seed.Utils do |> Map.new() end + def include_attribute_input?(resource, key, value) do + case Ash.Resource.Info.attribute(resource, key) do + %{allow_nil?: false} when is_nil(value) -> + false + + _ -> + true + end + end + def writable_attr?(resource, key) do Ash.Resource.Info.attribute(resource, key) |> case do diff --git a/server/priv/repo/seeds.exs b/server/priv/repo/seeds.exs index 1fadfa585..0b70ada48 100644 --- a/server/priv/repo/seeds.exs +++ b/server/priv/repo/seeds.exs @@ -1,11 +1,11 @@ require Ash.Query -Orcasite.Radio.Seed.time_range(%{ +Orcasite.Radio.Seed.time_range!(%{ end_time: DateTime.utc_now(), start_time: DateTime.add(DateTime.utc_now(), -1, :hour) }) -Orcasite.Radio.Seed.latest() +Orcasite.Radio.Seed.latest!() # Create admin account strategy = AshAuthentication.Info.strategy!(Orcasite.Accounts.User, :password) diff --git a/ui/src/pages/seed.tsx b/ui/src/pages/seed.tsx index b7452c91b..f5331df09 100644 --- a/ui/src/pages/seed.tsx +++ b/ui/src/pages/seed.tsx @@ -55,6 +55,15 @@ const SeedPage: NextPageWithLayout = () => { message: "", }); + const onMutationError = (error: unknown) => { + setSeedForm((form) => ({ + ...form, + isSaving: false, + saved: true, + message: error instanceof Error ? error.message : "Seeding failed", + })); + }; + const onSuccess = (response: SeedFeedsResult | SeedResourceResult) => { const { result, errors } = response; if (errors && errors.length > 0) { @@ -83,11 +92,13 @@ const SeedPage: NextPageWithLayout = () => { onSuccess(seedFeeds); feedsQuery.refetch(); }, + onError: onMutationError, }); const seedResourceMutation = useSeedResourceMutation({ onSuccess: ({ seedResource }: { seedResource: SeedResourceResult }) => { onSuccess(seedResource); }, + onError: onMutationError, }); const seedAllMutation = useSeedAllMutation({ @@ -111,13 +122,18 @@ const SeedPage: NextPageWithLayout = () => { return `${count} ${lowerCaseResource(resource)}${count === 1 ? "" : "s"}`; }) .join(", "); + setSeedForm((form) => ({ ...form, isSaving: false, saved: true, - message: `Seeded ${countString}`, + message: + countString.length > 0 + ? `Seeded ${countString}` + : "No records were seeded. Check server logs for feed/seed errors.", })); }, + onError: onMutationError, }); const handleSubmit = () => { @@ -311,7 +327,12 @@ function toLocalISOString(date: Date) { SeedPage.getLayout = getSimpleLayout; export async function getStaticProps() { - const enableSeedFromProd = process.env.ENABLE_SEED_FROM_PROD === "true"; + const seedFlag = process.env.ENABLE_SEED_FROM_PROD; + const gqlEndpoint = process.env.NEXT_PUBLIC_GQL_ENDPOINT ?? ""; + // The local dev stack runs with a localhost GraphQL endpoint. + const isLocalDevStack = gqlEndpoint.includes("localhost"); + const enableSeedFromProd = seedFlag ? seedFlag === "true" : isLocalDevStack; + // Hide the seed page when `ENABLE_SEED_FROM_PROD` isn't enabled return !enableSeedFromProd ? { notFound: true } : { props: {} }; }