From d2b8d2a25fe89989930c148b03b93e1db7e27971 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 1 Nov 2023 14:54:33 +0200 Subject: [PATCH] Add a dashboard for approving twitter handles --- .formatter.exs | 3 +- assets/js/app.js | 26 ++++- .../monitored_twitter_handle.ex | 23 +++-- .../components/layouts/app.html.heex | 1 - .../components/layouts/root.html.heex | 5 +- .../controllers/custom_admin_controller.ex | 13 ++- .../types/monitored_twitter_handle_types.ex | 5 + .../monitored_twitter_handle_live.ex | 96 +++++++++++++++++++ lib/sanbase_web/router.ex | 2 + lib/sanbase_web/sanbase_web.ex | 2 +- ...extend_monitored_twitter_handles_table.exs | 9 ++ priv/repo/structure.sql | 4 +- 12 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 lib/sanbase_web/live/monitored_twitter_handle/monitored_twitter_handle_live.ex create mode 100644 priv/repo/migrations/20231101104145_extend_monitored_twitter_handles_table.exs diff --git a/.formatter.exs b/.formatter.exs index e750e3f7c0..27607cac3e 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -9,5 +9,6 @@ "mix.exs", ".formatter.exs" ], - plugins: [Phoenix.LiveView.HTMLFormatter] + # plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"] ] diff --git a/assets/js/app.js b/assets/js/app.js index a2faf92f58..0b4c60e640 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,6 +1,24 @@ -// js +import '../css/app.css'; + import "phoenix_html" -import "./socket.js" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() -// css -import '../css/app.css'; \ No newline at end of file +// expose liveSocket on window for web console debug logs and latency simulation: +>> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket \ No newline at end of file diff --git a/lib/sanbase/monitored_twitter_handle/monitored_twitter_handle.ex b/lib/sanbase/monitored_twitter_handle/monitored_twitter_handle.ex index 119b2dc280..c472b7f00e 100644 --- a/lib/sanbase/monitored_twitter_handle/monitored_twitter_handle.ex +++ b/lib/sanbase/monitored_twitter_handle/monitored_twitter_handle.ex @@ -16,6 +16,7 @@ defmodule Sanbase.MonitoredTwitterHandle do origin: String.t(), # One of approved/declined/pending_approval status: String.t(), + comment: String.t(), inserted_at: DateTime.t(), updated_at: DateTime.t() } @@ -25,6 +26,8 @@ defmodule Sanbase.MonitoredTwitterHandle do field(:notes, :string) field(:origin, :string) field(:status, :string) + # moderator/admin comment when approving/declining + field(:comment, :string) belongs_to(:user, User) @@ -62,14 +65,16 @@ defmodule Sanbase.MonitoredTwitterHandle do {:ok, Repo.all(query)} end - @doc false - def update_status(record_id, status) + # @doc false + def update_status(record_id, status, comment \\ nil) when status in ["approved", "declined", "pending_approval"] do # The status is updated from an admin panel - Repo.get!(__MODULE__, record_id) - |> change(%{status: status}) - |> Repo.update() - |> case do + result = + Repo.get!(__MODULE__, record_id) + |> change(%{status: status, comment: comment}) + |> Repo.update() + + case result do {:ok, %__MODULE__{user_id: user_id}} = result when status == "approved" -> maybe_add_user_promo_code(user_id) result @@ -79,6 +84,12 @@ defmodule Sanbase.MonitoredTwitterHandle do end end + def list_all_submissions() do + query = from(m in __MODULE__, where: m.origin == "graphql_api") + + Repo.all(query) + end + # Private functions defp count_user_approved_submissions(user_id) do diff --git a/lib/sanbase_web/components/layouts/app.html.heex b/lib/sanbase_web/components/layouts/app.html.heex index f7203441c9..0afbd61bd6 100644 --- a/lib/sanbase_web/components/layouts/app.html.heex +++ b/lib/sanbase_web/components/layouts/app.html.heex @@ -1,5 +1,4 @@
-
diff --git a/lib/sanbase_web/components/layouts/root.html.heex b/lib/sanbase_web/components/layouts/root.html.heex index 2027e2ccc1..20390991b3 100644 --- a/lib/sanbase_web/components/layouts/root.html.heex +++ b/lib/sanbase_web/components/layouts/root.html.heex @@ -4,15 +4,14 @@ - <.live_title> - <%= assigns[:page_title] || "Sanbase" %> + <.live_title suffix=" ยท Phoenix Framework"> + <%= assigns[:page_title] || "Pento" %> - <%= @inner_content %> diff --git a/lib/sanbase_web/controllers/custom_admin_controller.ex b/lib/sanbase_web/controllers/custom_admin_controller.ex index c1de393adb..baedd722b0 100644 --- a/lib/sanbase_web/controllers/custom_admin_controller.ex +++ b/lib/sanbase_web/controllers/custom_admin_controller.ex @@ -1,17 +1,16 @@ defmodule SanbaseWeb.CustomAdminController do use SanbaseWeb, :controller - alias SanbaseWeb.Router.Helpers, as: Routes - def index(conn, _params) do render(conn, "index.html", search_value: "", routes: [ - {"Users", Routes.user_path(conn, :index)}, - {"Webinars", Routes.webinar_path(conn, :index)}, - {"Sheets templates", Routes.sheets_template_path(conn, :index)}, - {"Reports", Routes.report_path(conn, :index)}, - {"Custom plans", Routes.custom_plan_path(conn, :index)} + {"Users", ~p"/admin2/users"}, + {"Webinars", ~p"/admin2/webinars"}, + {"Sheets templates", ~p"/admin2/sheets_templates/"}, + {"Reports", ~p"/admin2/reports"}, + {"Custom plans", ~p"/admin2/custom_plans"}, + {"Monitored Twitter Handles", ~p"/admin2/monitored_twitter_handle_live"} ] ) end diff --git a/lib/sanbase_web/graphql/schema/types/monitored_twitter_handle_types.ex b/lib/sanbase_web/graphql/schema/types/monitored_twitter_handle_types.ex index 5c40484b09..985e1363ab 100644 --- a/lib/sanbase_web/graphql/schema/types/monitored_twitter_handle_types.ex +++ b/lib/sanbase_web/graphql/schema/types/monitored_twitter_handle_types.ex @@ -6,6 +6,11 @@ defmodule SanbaseWeb.Graphql.MonitoredTwitterHandleTypes do field(:notes, :string) field(:status, :string) + @desc ~s""" + Comment submitted by a moderator when approving or declining a handle. + """ + field(:comment, :string) + field(:inserted_at, non_null(:datetime)) field(:updated_at, non_null(:datetime)) end diff --git a/lib/sanbase_web/live/monitored_twitter_handle/monitored_twitter_handle_live.ex b/lib/sanbase_web/live/monitored_twitter_handle/monitored_twitter_handle_live.ex new file mode 100644 index 0000000000..74d16bddce --- /dev/null +++ b/lib/sanbase_web/live/monitored_twitter_handle/monitored_twitter_handle_live.ex @@ -0,0 +1,96 @@ +defmodule SanbaseWeb.MonitoredTwitterHandleLive do + use SanbaseWeb, :live_view + + alias Sanbase.MonitoredTwitterHandle + + @impl true + def render(assigns) do + ~H""" +
+
+ <.table id="monitored_twitter_handles" rows={@handles}> + <:col :let={row} label="Twitter Handle"><%= row.handle %> + <:col :let={row} label="Status"><%= row.status %> + <:col :let={row} label="Notes"><%= row.notes %> + <:col :let={row} label="Moderator comment"><%= row.comment %> + <:action :let={row}> + <.form :let={f} for={@form} phx-submit="update_status"> + <.input type="text" field={@form[:comment]} placeholder="Comment..." /> + + <.button name="status" value="approved">Approve + <.button name="status" value="declined">Decline + + + +
+
+ """ + end + + @impl true + def mount(_params, _session, socket) do + {:ok, + socket + |> assign(:handles, list_handles()) + |> assign(:form, to_form(%{}))} + end + + def handle_event( + "update_status", + %{"status" => status, "record_id" => record_id} = params, + socket + ) + when status in ["approved", "declined"] do + record_id = String.to_integer(record_id) + comment = if params["comment"] == "", do: nil, else: params["comment"] + MonitoredTwitterHandle.update_status(record_id, status, comment) + handles = update_assigns_handle(socket.assigns.handles, record_id, status, comment) + + {:noreply, assign(socket, :handles, handles)} + end + + defp update_assigns_handle(handles, record_id, status, comment \\ nil) do + handles + |> Enum.map(fn + %{id: ^record_id} = record -> + comment = comment || record.comment + + record + |> Map.put(:status, status) + |> Map.put(:comment, comment) + + record -> + record + end) + |> order_records() + end + + defp list_handles() do + Sanbase.MonitoredTwitterHandle.list_all_submissions() + |> Enum.map(fn struct -> + %{ + id: struct.id, + status: struct.status, + handle: struct.handle, + notes: struct.notes, + comment: struct.comment, + inserted_at: struct.inserted_at + } + end) + |> order_records() + end + + defp order_records(handles) do + handles + |> Enum.sort_by( + fn record -> + case record.status do + "pending_approval" -> 1 + "approved" -> 2 + "declined" -> 3 + end + end, + :asc + ) + end +end diff --git a/lib/sanbase_web/router.ex b/lib/sanbase_web/router.ex index 9ef22cc66c..005584e412 100644 --- a/lib/sanbase_web/router.ex +++ b/lib/sanbase_web/router.ex @@ -10,6 +10,7 @@ defmodule SanbaseWeb.Router do plug(:accepts, ["html"]) plug(:fetch_session) plug(:fetch_live_flash) + plug(:put_root_layout, {SanbaseWeb.Layouts, :root}) plug(:protect_from_forgery) plug(:put_secure_browser_headers) end @@ -57,6 +58,7 @@ defmodule SanbaseWeb.Router do import Phoenix.LiveDashboard.Router live_dashboard("/dashboard", metrics: SanbaseWeb.Telemetry, ecto_repos: [Sanbase.Repo]) + live("/monitored_twitter_handle_live", MonitoredTwitterHandleLive) post("/users", UserController, :search) diff --git a/lib/sanbase_web/sanbase_web.ex b/lib/sanbase_web/sanbase_web.ex index 1bbbc74dd8..0068b784a4 100644 --- a/lib/sanbase_web/sanbase_web.ex +++ b/lib/sanbase_web/sanbase_web.ex @@ -55,7 +55,7 @@ defmodule SanbaseWeb do def live_view do quote do use Phoenix.LiveView, - layout: {PentoWeb.Layouts, :app} + layout: {SanbaseWeb.Layouts, :app} unquote(html_helpers()) end diff --git a/priv/repo/migrations/20231101104145_extend_monitored_twitter_handles_table.exs b/priv/repo/migrations/20231101104145_extend_monitored_twitter_handles_table.exs new file mode 100644 index 0000000000..cd0ee0842b --- /dev/null +++ b/priv/repo/migrations/20231101104145_extend_monitored_twitter_handles_table.exs @@ -0,0 +1,9 @@ +defmodule Sanbase.Repo.Migrations.ExtendMonitoredTwitterHandlesTable do + use Ecto.Migration + + def change do + alter table(:monitored_twitter_handles) do + add(:comment, :text) + end + end +end diff --git a/priv/repo/structure.sql b/priv/repo/structure.sql index b613559d2d..11db23dd05 100644 --- a/priv/repo/structure.sql +++ b/priv/repo/structure.sql @@ -2048,7 +2048,8 @@ CREATE TABLE public.monitored_twitter_handles ( user_id bigint, inserted_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, - status character varying(255) DEFAULT 'pending_approval'::character varying + status character varying(255) DEFAULT 'pending_approval'::character varying, + comment text ); @@ -8924,3 +8925,4 @@ INSERT INTO public."schema_migrations" (version) VALUES (20231012130814); INSERT INTO public."schema_migrations" (version) VALUES (20231019111320); INSERT INTO public."schema_migrations" (version) VALUES (20231023123140); INSERT INTO public."schema_migrations" (version) VALUES (20231026084628); +INSERT INTO public."schema_migrations" (version) VALUES (20231101104145);