From f8a487005c9044650b33d6185050249dcc2b5fb9 Mon Sep 17 00:00:00 2001 From: Lars Wikman Date: Tue, 10 Sep 2024 17:00:01 +0200 Subject: [PATCH] Add device count and estimated device count for Deployment views This allows seeing how big a deployment is in device count in listings and detail view. Beyond that the Edit form for Deployments will estimate the matching devices based on the conditions set. --- assets/css/_layout.scss | 18 ++++++++- lib/nerves_hub/deployments.ex | 38 +++++++++++++++++++ lib/nerves_hub_web/live/deployments/edit.ex | 35 ++++++++++++++++- .../live/deployments/edit.html.heex | 13 +++++-- lib/nerves_hub_web/live/deployments/index.ex | 2 + .../live/deployments/index.html.heex | 7 ++++ lib/nerves_hub_web/live/deployments/show.ex | 2 + .../live/deployments/show.html.heex | 4 ++ 8 files changed, 112 insertions(+), 7 deletions(-) diff --git a/assets/css/_layout.scss b/assets/css/_layout.scss index 56d3f32da..e38061359 100644 --- a/assets/css/_layout.scss +++ b/assets/css/_layout.scss @@ -375,6 +375,21 @@ html { } } + .x5-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + grid-column-gap: 2rem; + grid-row-gap: 2rem; + + @media (max-width: 860px) { + grid-template-columns: 1fr 1fr; + } + + @media (max-width: 600px) { + grid-template-columns: 1fr; + } + } + .x2-grid { display: grid; grid-template-columns: 1fr 1fr; @@ -411,5 +426,4 @@ html { .gr-2 { grid-row: 2; } -} - +} \ No newline at end of file diff --git a/lib/nerves_hub/deployments.ex b/lib/nerves_hub/deployments.ex index 7801f0652..a0a7ddc94 100644 --- a/lib/nerves_hub/deployments.ex +++ b/lib/nerves_hub/deployments.ex @@ -26,6 +26,24 @@ defmodule NervesHub.Deployments do |> Repo.all() end + @spec get_deployment_device_counts_by_product(integer()) :: %{integer() => integer()} + def get_deployment_device_counts_by_product(product_id) do + Device + |> select([d], {d.deployment_id, count(d.id)}) + |> where([d], d.product_id == ^product_id) + |> group_by([d], d.id) + |> Repo.all() + |> Map.new() + end + + @spec get_deployment_device_count(integer()) :: %{integer() => integer()} + def get_deployment_device_count(deployment_id) do + Device + |> select([d], count(d.id)) + |> where([d], d.deployment_id == ^deployment_id) + |> Repo.one() + end + @spec get_deployments_by_firmware(integer()) :: [Deployment.t()] def get_deployments_by_firmware(firmware_id) do from(d in Deployment, where: d.firmware_id == ^firmware_id) @@ -328,6 +346,11 @@ defmodule NervesHub.Deployments do :ok end + @spec change_deployment(Deployment.t(), map()) :: Changeset.t() + def change_deployment(deployment, params) do + Deployment.changeset(deployment, params) + end + @spec create_deployment(map) :: {:ok, Deployment.t()} | {:error, Changeset.t()} def create_deployment(params) do changeset = Deployment.creation_changeset(%Deployment{}, params) @@ -405,6 +428,21 @@ defmodule NervesHub.Deployments do ) end + @doc """ + Find all potential devices for a deployment + + Based on the product, firmware platform, firmware architecture, and device tags + """ + def estimate_devices_matched_by_conditions(product_id, platform, conditions) do + Device + |> where([dev], dev.product_id == ^product_id) + |> where([dev], fragment("d0.firmware_metadata ->> 'platform'") == ^platform) + |> where([dev], fragment("?::jsonb->'tags' <@ to_jsonb(?::text[])", ^conditions, dev.tags)) + |> Repo.all() + |> Enum.filter(&version_match?(&1, %{conditions: conditions})) + |> Enum.count() + end + @doc """ Check that a device version matches for a deployment's conditions diff --git a/lib/nerves_hub_web/live/deployments/edit.ex b/lib/nerves_hub_web/live/deployments/edit.ex index b146193fc..a916f967d 100644 --- a/lib/nerves_hub_web/live/deployments/edit.ex +++ b/lib/nerves_hub_web/live/deployments/edit.ex @@ -13,16 +13,28 @@ defmodule NervesHubWeb.Live.Deployments.Edit do %{"name" => name} = params %{product: product} = socket.assigns - deployment = Deployments.get_by_product_and_name!(product, name) + deployment = + Deployments.get_by_product_and_name!(product, name) |> NervesHub.Repo.preload(:firmware) + + current_device_count = Deployments.get_deployment_device_count(deployment.id) archives = Archives.all_by_product(deployment.product) firmwares = Firmwares.get_firmwares_for_deployment(deployment) changeset = Deployment.changeset(deployment, %{}) |> tags_to_string() + estimate_count = + Deployments.estimate_devices_matched_by_conditions( + deployment.product_id, + deployment.firmware.platform, + deployment.conditions + ) + socket |> assign(:archives, archives) |> assign(:deployment, deployment) + |> assign(:current_device_count, current_device_count) + |> assign(:estimate_count, estimate_count) |> assign(:firmware, deployment.firmware) |> assign(:firmwares, firmwares) |> assign(:form, to_form(changeset)) @@ -30,6 +42,27 @@ defmodule NervesHubWeb.Live.Deployments.Edit do end @impl Phoenix.LiveView + def handle_event("recalculate", %{"deployment" => params}, socket) do + params = inject_conditions_map(params) + + try do + count = + Deployments.estimate_devices_matched_by_conditions( + socket.assigns.deployment.product_id, + socket.assigns.deployment.firmware.platform, + params["conditions"] + ) + + changeset = Deployments.change_deployment(socket.assigns.deployment, params) + + {:noreply, assign(socket, estimate_count: count, form: to_form(tags_to_string(changeset)))} + rescue + _ -> + # Ignore version parsing errors + {:noreply, socket} + end + end + def handle_event("update-deployment", %{"deployment" => params}, socket) do %{org_user: org_user, org: org, product: product, user: user, deployment: deployment} = socket.assigns diff --git a/lib/nerves_hub_web/live/deployments/edit.html.heex b/lib/nerves_hub_web/live/deployments/edit.html.heex index aabfe786c..42cab47c0 100644 --- a/lib/nerves_hub_web/live/deployments/edit.html.heex +++ b/lib/nerves_hub_web/live/deployments/edit.html.heex @@ -4,7 +4,7 @@

Edit Deployment

Firmware version details
-
+
Product

<%= @product.name %>

@@ -26,11 +26,15 @@
Architecture

<%= @firmware.architecture %>

+
+
Device count
+

<%= @current_device_count %>

+
-<.form :let={f} for={@form} phx-submit="update-deployment"> +<.form :let={f} for={@form} phx-change="recalculate" phx-submit="update-deployment"> <%= hidden_input(f, :firmware_id, value: @firmware.id) %>
@@ -64,6 +68,7 @@

Conditions

Changing any conditions will reset any attached devices.

+

Estimated affected devices: <%= @estimate_count %>

@@ -78,7 +83,7 @@ id: "tags_input", value: @form.source - |> Ecto.Changeset.get_change(:conditions, %{}) + |> Ecto.Changeset.get_field(:conditions, %{}) |> Map.get("tags", "") ) %>
<%= error_tag(f, :tags) %>
@@ -97,7 +102,7 @@ id: "version_requirement", value: @form.source - |> Ecto.Changeset.get_change(:conditions, %{}) + |> Ecto.Changeset.get_field(:conditions, %{}) |> Map.get("version", "") ) %>
<%= error_tag(f, :version) %>
diff --git a/lib/nerves_hub_web/live/deployments/index.ex b/lib/nerves_hub_web/live/deployments/index.ex index a085dfc7d..ae0aefe57 100644 --- a/lib/nerves_hub_web/live/deployments/index.ex +++ b/lib/nerves_hub_web/live/deployments/index.ex @@ -8,6 +8,7 @@ defmodule NervesHubWeb.Live.Deployments.Index do @impl Phoenix.LiveView def mount(_params, _session, %{assigns: %{product: product}} = socket) do deployments = Deployments.get_deployments_by_product(product.id) + counts = Deployments.get_deployment_device_counts_by_product(product.id) deployments = deployments @@ -19,6 +20,7 @@ defmodule NervesHubWeb.Live.Deployments.Index do socket |> page_title("Deployments - #{product.name}") |> assign(:deployments, deployments) + |> assign(:counts, counts) |> ok() end diff --git a/lib/nerves_hub_web/live/deployments/index.html.heex b/lib/nerves_hub_web/live/deployments/index.html.heex index 30e796bb6..97a7ce632 100644 --- a/lib/nerves_hub_web/live/deployments/index.html.heex +++ b/lib/nerves_hub_web/live/deployments/index.html.heex @@ -26,6 +26,7 @@ Name State + Devices Firmware version Distributed to Version requirement @@ -44,6 +45,12 @@ <%= if deployment.is_active, do: "On", else: "Off" %>
+ +
Devices
+
+ <%= @counts[deployment.id] %> +
+
Firmware version
diff --git a/lib/nerves_hub_web/live/deployments/show.ex b/lib/nerves_hub_web/live/deployments/show.ex index 0df984539..8a15351a1 100644 --- a/lib/nerves_hub_web/live/deployments/show.ex +++ b/lib/nerves_hub_web/live/deployments/show.ex @@ -28,6 +28,7 @@ defmodule NervesHubWeb.Live.Deployments.Show do |> Map.put(:anchor, "latest-activity") inflight_updates = Devices.inflight_updates_for(deployment) + current_device_count = Deployments.get_deployment_device_count(deployment.id) socket |> page_title("Deployment - #{deployment.name} - #{product.name}") @@ -35,6 +36,7 @@ defmodule NervesHubWeb.Live.Deployments.Show do |> assign(:audit_logs, logs) |> assign(:inflight_updates, inflight_updates) |> assign(:firmware, deployment.firmware) + |> assign(:current_device_count, current_device_count) |> schedule_inflight_updates_updater() |> ok() end diff --git a/lib/nerves_hub_web/live/deployments/show.html.heex b/lib/nerves_hub_web/live/deployments/show.html.heex index 554e28fc9..bedf474ee 100644 --- a/lib/nerves_hub_web/live/deployments/show.html.heex +++ b/lib/nerves_hub_web/live/deployments/show.html.heex @@ -65,6 +65,10 @@
Version requirement

<%= version(@deployment) %>

+
+
Current device count
+

<%= @current_device_count %>

+