neiam
/apollos_crib
It's Apollo's Crib
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions failure · Badges · Packages
⭐
Log in to mark this repository.
Match the add-to buttons to the visions, and let a query be unpinned
395f406 · Gabriel Morell · 2026-08-11 17:57
Message
{commit_body(@commit)}
Files changed
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_query_live/show.ex
+25
−0
@@ -268,6 +268,31 @@ defmodule RoomSanctumWeb.QueryLive.Show do
| 268 | 268 | end |
| 269 | 269 | end |
| 270 | 270 | |
| 271 | + def handle_event("remove-from", %{"vision" => vision}, socket) do | |
| 272 | + Process.send_after(self(), :update_sec, 200) | |
| 273 | + | |
| 274 | + vision = Configuration.get_vision!(vision) | |
| 275 | + query_id = String.to_integer(socket.assigns.query_id) | |
| 276 | + | |
| 277 | + # Both halves again: drop every embed pointing at this query -- there may | |
| 278 | + # be more than one, since re-pinning used to duplicate them -- and the id. | |
| 279 | + queries = | |
| 280 | + vision.queries | |
| 281 | + |> Poison.encode!() | |
| 282 | + |> Poison.decode!() | |
| 283 | + |> Enum.reject(fn q -> to_string(get_in(q, ["data", "query"])) == to_string(query_id) end) | |
| 284 | + | |
| 285 | + query_ids = (vision.query_ids || []) -- [query_id] | |
| 286 | + | |
| 287 | + case Configuration.update_vision_ni(vision, %{queries: queries, query_ids: query_ids}) do | |
| 288 | + {:ok, _vision} -> | |
| 289 | + {:noreply, socket} | |
| 290 | + | |
| 291 | + {:error, _changeset} -> | |
| 292 | + {:noreply, put_flash(socket, :error, "Could not remove this from #{vision.name}")} | |
| 293 | + end | |
| 294 | + end | |
| 295 | + | |
| 271 | 296 | # A vision records its queries twice: an embedded list that renders, and an |
| 272 | 297 | # id array every "is this query in a vision" lookup reads. Both have to move. |
| 273 | 298 | defp pin_query(vision, socket) do |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_query_live/show.html.heex
+22
−8
@@ -151,18 +151,32 @@
| 151 | 151 | <% end %> |
| 152 | 152 | </h3> |
| 153 | 153 | <%= for v <- @visions do %> |
| 154 | − <%= live_patch to: Routes.vision_show_path(@socket, :show, v.id), class: "btn btn-primary gap-2 float-left mx-2" do %> | |
| 155 | − <%= v.name %> | |
| 156 | − <% end %> | |
| 154 | + <div class="flex gap-2 mx-2"> | |
| 155 | + <%= live_patch to: Routes.vision_show_path(@socket, :show, v.id), class: "btn btn-primary gap-2 grow" do %> | |
| 156 | + <%= v.name %> | |
| 157 | + <% end %> | |
| 158 | + <%!-- Only while the panel is open, so the usual view of "the | |
| 159 | + visions this query is in" stays a row of plain links. --%> | |
| 160 | + <button | |
| 161 | + :if={@avail_sel} | |
| 162 | + type="button" | |
| 163 | + class="btn btn-error btn-square shrink-0" | |
| 164 | + phx-click="remove-from" | |
| 165 | + phx-value-vision={v.id} | |
| 166 | + title={"Remove from #{v.name}"} | |
| 167 | + > | |
| 168 | + <i class="fa-solid fa-minus"></i> | |
| 169 | + </button> | |
| 170 | + </div> | |
| 157 | 171 | <% end%> |
| 158 | 172 | <%= if @avail_sel do %> |
| 159 | 173 | <%= if @avail_visions != [] do %> |
| 160 | 174 | <h4 class="text-xl text-accent font-bold">Add to</h4> |
| 161 | − <div class="flex flex-wrap gap-2"> | |
| 162 | − <%= for v <- @avail_visions do %> | |
| 163 | − <div class="btn btn-secondary gap-2" phx-click="add-to" phx-value-vision={v.id}><%= v.name %></div> | |
| 164 | − <% end %> | |
| 165 | − </div> | |
| 175 | + <%!-- Direct children of the card body, so they stretch the same | |
| 176 | + way the visions this query already belongs to do. --%> | |
| 177 | + <%= for v <- @avail_visions do %> | |
| 178 | + <div class="btn btn-secondary gap-2 float-left mx-2" phx-click="add-to" phx-value-vision={v.id}><%= v.name %></div> | |
| 179 | + <% end %> | |
| 166 | 180 | <% end %> |
| 167 | 181 | |
| 168 | 182 | <h4 class="text-xl text-accent font-bold mt-4">Or a new one</h4> |
modified
apps/room_sanctum/test/room_sanctum_web/live/add_to_vision_test.exs
+74
−0
@@ -180,6 +180,80 @@ defmodule RoomSanctumWeb.AddToVisionTest do
| 180 | 180 | end |
| 181 | 181 | end |
| 182 | 182 | |
| 183 | + describe "taking a query back out" do | |
| 184 | + defp remove(conn, query, vision) do | |
| 185 | + {:ok, live, _html} = live(conn, Routes.query_show_path(conn, :show, query)) | |
| 186 | + send(live.pid, :update_sec) | |
| 187 | + _ = render(live) | |
| 188 | + render_click(live, "remove-from", %{"vision" => to_string(vision.id)}) | |
| 189 | + Process.sleep(50) | |
| 190 | + Configuration.get_vision!(vision.id) | |
| 191 | + end | |
| 192 | + | |
| 193 | + test "drops the query from both halves", ctx do | |
| 194 | + q = query(ctx, "AAPL") | |
| 195 | + add(ctx.conn, q, ctx.vision) | |
| 196 | + | |
| 197 | + vision = remove(ctx.conn, q, ctx.vision) | |
| 198 | + | |
| 199 | + assert vision.query_ids == [] | |
| 200 | + assert vision.queries == [] | |
| 201 | + end | |
| 202 | + | |
| 203 | + test "the query page stops listing the vision", ctx do | |
| 204 | + q = query(ctx, "AAPL") | |
| 205 | + add(ctx.conn, q, ctx.vision) | |
| 206 | + assert length(Configuration.get_visions(:query, to_string(q.id))) == 1 | |
| 207 | + | |
| 208 | + remove(ctx.conn, q, ctx.vision) | |
| 209 | + | |
| 210 | + assert Configuration.get_visions(:query, to_string(q.id)) == [] | |
| 211 | + end | |
| 212 | + | |
| 213 | + test "the vision is offered again afterwards", ctx do | |
| 214 | + q = query(ctx, "AAPL") | |
| 215 | + add(ctx.conn, q, ctx.vision) | |
| 216 | + assert Configuration.get_visions_nv(:query, to_string(q.id)) == [] | |
| 217 | + | |
| 218 | + remove(ctx.conn, q, ctx.vision) | |
| 219 | + | |
| 220 | + assert length(Configuration.get_visions_nv(:query, to_string(q.id))) == 1 | |
| 221 | + end | |
| 222 | + | |
| 223 | + test "the other queries in that vision are left alone", ctx do | |
| 224 | + a = query(ctx, "AAPL") | |
| 225 | + b = query(ctx, "MSFT") | |
| 226 | + add(ctx.conn, a, ctx.vision) | |
| 227 | + add(ctx.conn, b, ctx.vision) | |
| 228 | + | |
| 229 | + vision = remove(ctx.conn, a, ctx.vision) | |
| 230 | + | |
| 231 | + assert vision.query_ids == [b.id] | |
| 232 | + assert length(vision.queries) == 1 | |
| 233 | + end | |
| 234 | + | |
| 235 | + test "removing something already gone is not an error", ctx do | |
| 236 | + q = query(ctx, "AAPL") | |
| 237 | + | |
| 238 | + vision = remove(ctx.conn, q, ctx.vision) | |
| 239 | + | |
| 240 | + assert vision.query_ids in [nil, []] | |
| 241 | + end | |
| 242 | + | |
| 243 | + test "the button only appears while the panel is open", ctx do | |
| 244 | + q = query(ctx, "AAPL") | |
| 245 | + add(ctx.conn, q, ctx.vision) | |
| 246 | + | |
| 247 | + {:ok, live, _html} = live(ctx.conn, Routes.query_show_path(ctx.conn, :show, q)) | |
| 248 | + send(live.pid, :update_sec) | |
| 249 | + closed = render(live) | |
| 250 | + refute closed =~ ~s(phx-click="remove-from") | |
| 251 | + | |
| 252 | + open = render_click(live, "toggle-sel", %{}) | |
| 253 | + assert open =~ ~s(phx-click="remove-from") | |
| 254 | + end | |
| 255 | + end | |
| 256 | + | |
| 183 | 257 | defp conn_for(ctx), do: ctx.conn |
| 184 | 258 | |
| 185 | 259 | test "a third query still lands", ctx do |
Parents: 8665053