From 26934e23f0a95be8aa141246c66c7d74c0f559ef Mon Sep 17 00:00:00 2001 From: Jeroen Vloothuis Date: Fri, 27 Aug 2021 13:56:02 +0200 Subject: [PATCH] Added minimal support system (#135) --- .../lib/layouts/workspace/menu_builder.ex | 8 +- core/config/dev.exs | 4 +- core/lib/core/admin.ex | 6 +- core/lib/core/authorization.ex | 2 + core/lib/core/factories.ex | 16 +++- core/lib/core/helpdesk.ex | 35 +++++++++ core/lib/core/helpdesk/ticket.ex | 21 ++++++ core/lib/core_web/live/admin/routes.ex | 1 + .../core_web/live/admin/support_tickets.ex | 34 +++++++++ .../live/layouts/workspace/menu_builder.ex | 1 + core/lib/core_web/live/menu/items.ex | 1 + core/lib/core_web/live/routes.ex | 1 + core/lib/core_web/live/support.ex | 73 +++++++++++++++++++ .../gettext/en/LC_MESSAGES/erya-support.po | 12 +++ .../gettext/en/LC_MESSAGES/eyra-support.po | 47 ++++++++++++ core/priv/gettext/erya-support.pot | 11 +++ core/priv/gettext/eyra-support.pot | 46 ++++++++++++ .../gettext/nl/LC_MESSAGES/erya-support.po | 12 +++ .../gettext/nl/LC_MESSAGES/eyra-support.po | 47 ++++++++++++ ...20210816074801_create_helpdesk_tickets.exs | 17 +++++ core/test/core/helpdesk_test.exs | 57 +++++++++++++++ .../live/admin/support_tickets_test.exs | 49 +++++++++++++ core/test/core_web/live/support_test.exs | 22 ++++++ core/test/support/helpers.ex | 13 ++++ core/test/test_helper.exs | 9 +++ 25 files changed, 540 insertions(+), 5 deletions(-) create mode 100644 core/lib/core/helpdesk.ex create mode 100644 core/lib/core/helpdesk/ticket.ex create mode 100644 core/lib/core_web/live/admin/support_tickets.ex create mode 100644 core/lib/core_web/live/support.ex create mode 100644 core/priv/gettext/en/LC_MESSAGES/erya-support.po create mode 100644 core/priv/gettext/en/LC_MESSAGES/eyra-support.po create mode 100644 core/priv/gettext/erya-support.pot create mode 100644 core/priv/gettext/eyra-support.pot create mode 100644 core/priv/gettext/nl/LC_MESSAGES/erya-support.po create mode 100644 core/priv/gettext/nl/LC_MESSAGES/eyra-support.po create mode 100644 core/priv/repo/migrations/20210816074801_create_helpdesk_tickets.exs create mode 100644 core/test/core/helpdesk_test.exs create mode 100644 core/test/core_web/live/admin/support_tickets_test.exs create mode 100644 core/test/core_web/live/support_test.exs diff --git a/core/bundles/link/lib/layouts/workspace/menu_builder.ex b/core/bundles/link/lib/layouts/workspace/menu_builder.ex index 7c4f5768d..e984e9961 100644 --- a/core/bundles/link/lib/layouts/workspace/menu_builder.ex +++ b/core/bundles/link/lib/layouts/workspace/menu_builder.ex @@ -39,7 +39,10 @@ defmodule Link.Layouts.Workspace.MenuBuilder do [] |> append(live_item(socket, :dashboard, active_item), can_access?(user_state, Link.Dashboard)) - |> append(live_item(socket, :surveys, active_item), can_access?(user_state, CoreWeb.Study.New)) + |> append( + live_item(socket, :surveys, active_item), + can_access?(user_state, CoreWeb.Study.New) + ) |> append(live_item(socket, :studentpool, active_item), user_state.coordinator) |> append(live_item(socket, :marketplace, active_item)) |> append(live_item(socket, :todo, active_item, true, next_action_count)) @@ -48,9 +51,10 @@ defmodule Link.Layouts.Workspace.MenuBuilder do defp build_menu_second_part(socket, active_item, page_id) do [ language_switch_item(socket, page_id), + live_item(socket, :support, active_item), live_item(socket, :settings, active_item), live_item(socket, :profile, active_item), - user_session_item(socket, :signout, active_item), + user_session_item(socket, :signout, active_item) ] |> append(live_item(socket, :debug, active_item), feature_enabled?(:debug)) end diff --git a/core/config/dev.exs b/core/config/dev.exs index 959bb933d..06a79310c 100644 --- a/core/config/dev.exs +++ b/core/config/dev.exs @@ -68,5 +68,7 @@ config :core, try do import_config "dev.secret.exs" rescue - _ -> IO.puts("Continueing without `dev.secret.exs` file..") + File.Error -> + # Continuing without `dev.secret.exs` file... + nil end diff --git a/core/lib/core/admin.ex b/core/lib/core/admin.ex index 7aa152256..44678dda1 100644 --- a/core/lib/core/admin.ex +++ b/core/lib/core/admin.ex @@ -1,8 +1,12 @@ defmodule Core.Admin do + def emails do + Application.get_env(:core, :admins, MapSet.new()) + end + def admin?(email) when is_nil(email), do: false def admin?(email) when is_binary(email) do - Application.get_env(:core, :admins, MapSet.new()) |> MapSet.member?(email) + MapSet.member?(emails(), email) end def admin?(%{email: email}) do diff --git a/core/lib/core/authorization.ex b/core/lib/core/authorization.ex index 130c17d4d..16457f980 100644 --- a/core/lib/core/authorization.ex +++ b/core/lib/core/authorization.ex @@ -20,7 +20,9 @@ defmodule Core.Authorization do grant_access(CoreWeb.Admin.Login, [:visitor, :member]) grant_access(CoreWeb.Admin.CoordinatorManagement, [:admin]) + grant_access(CoreWeb.Admin.SupportTickets, [:admin]) grant_access(CoreWeb.Index, [:visitor, :member]) + grant_access(CoreWeb.Support, [:member]) grant_access(CoreWeb.Dashboard, [:researcher]) grant_access(CoreWeb.Marketplace, [:member]) grant_access(CoreWeb.Todo, [:member]) diff --git a/core/lib/core/factories.ex b/core/lib/core/factories.ex index 4f77d013c..9abd043f7 100644 --- a/core/lib/core/factories.ex +++ b/core/lib/core/factories.ex @@ -14,7 +14,8 @@ defmodule Core.Factories do Authorization, DataDonation, NotificationCenter, - WebPush + WebPush, + Helpdesk } alias Core.Repo @@ -40,6 +41,11 @@ defmodule Core.Factories do }) end + def build(:admin) do + :member + |> build(%{email: Core.Admin.emails() |> Enum.random()}) + end + def build(:student) do :member |> build(%{student: true}) @@ -72,6 +78,14 @@ defmodule Core.Factories do } end + def build(:helpdesk_ticket) do + %Helpdesk.Ticket{ + title: Faker.Lorem.sentence(), + description: Faker.Lorem.paragraph(), + user: build(:member) + } + end + def build(:author) do %Studies.Author{ fullname: Faker.Person.name(), diff --git a/core/lib/core/helpdesk.ex b/core/lib/core/helpdesk.ex new file mode 100644 index 000000000..0534953f6 --- /dev/null +++ b/core/lib/core/helpdesk.ex @@ -0,0 +1,35 @@ +defmodule Core.Helpdesk do + @moduledoc """ + The Helpdesk context. + """ + + import Ecto.Query, warn: false + alias Core.Repo + + alias Core.Helpdesk.Ticket + + def list_open_tickets do + from(t in Ticket, + where: is_nil(t.completed_at), + order_by: {:asc, :inserted_at}, + preload: :user + ) + |> Repo.all() + end + + def close_ticket_by_id(id) do + from(t in Ticket, where: t.id == ^id) + |> Repo.update_all(set: [completed_at: DateTime.utc_now()]) + end + + def create_ticket(user, attrs \\ %{}) do + %Ticket{} + |> Ticket.changeset(attrs) + |> Ecto.Changeset.put_assoc(:user, user) + |> Repo.insert() + end + + def new_ticket_changeset(attrs \\ %{}) do + Ticket.changeset(%Ticket{}, attrs) + end +end diff --git a/core/lib/core/helpdesk/ticket.ex b/core/lib/core/helpdesk/ticket.ex new file mode 100644 index 000000000..5340092ab --- /dev/null +++ b/core/lib/core/helpdesk/ticket.ex @@ -0,0 +1,21 @@ +defmodule Core.Helpdesk.Ticket do + use Ecto.Schema + import Ecto.Changeset + alias Core.Accounts.User + + schema "helpdesk_tickets" do + belongs_to(:user, User) + field(:description, :string) + field(:title, :string) + field(:completed_at, :utc_datetime) + + timestamps() + end + + @doc false + def changeset(ticket, attrs) do + ticket + |> cast(attrs, [:title, :description, :completed_at]) + |> validate_required([:title, :description]) + end +end diff --git a/core/lib/core_web/live/admin/routes.ex b/core/lib/core_web/live/admin/routes.ex index 5f58544d2..3a79be2dc 100644 --- a/core/lib/core_web/live/admin/routes.ex +++ b/core/lib/core_web/live/admin/routes.ex @@ -6,6 +6,7 @@ defmodule CoreWeb.Live.Admin.Routes do live("/login", Login) live("/coordinator-management", CoordinatorManagement) + live("/support-tickets", SupportTickets) end end end diff --git a/core/lib/core_web/live/admin/support_tickets.ex b/core/lib/core_web/live/admin/support_tickets.ex new file mode 100644 index 000000000..506d1d962 --- /dev/null +++ b/core/lib/core_web/live/admin/support_tickets.ex @@ -0,0 +1,34 @@ +defmodule CoreWeb.Admin.SupportTickets do + use CoreWeb, :live_view + alias Core.Helpdesk + + data(tickets, :any) + + def mount(_params, _session, socket) do + { + :ok, + socket |> assign(:tickets, Helpdesk.list_open_tickets()) + } + end + + def handle_event("close_ticket", %{"id" => id}, socket) do + Helpdesk.close_ticket_by_id(id) + {:noreply, socket |> assign(:tickets, Helpdesk.list_open_tickets())} + end + + def render(assigns) do + ~H""" +
+ Admin +
+

{{ticket.title}}

+

{{ticket.description}}

+

+ Contact {{ticket.user.email}} +

+ +
+
+ """ + end +end diff --git a/core/lib/core_web/live/layouts/workspace/menu_builder.ex b/core/lib/core_web/live/layouts/workspace/menu_builder.ex index 3789eb036..6b895fe88 100644 --- a/core/lib/core_web/live/layouts/workspace/menu_builder.ex +++ b/core/lib/core_web/live/layouts/workspace/menu_builder.ex @@ -44,6 +44,7 @@ defmodule CoreWeb.Layouts.Workspace.MenuBuilder do language_switch_item(socket, page_id), live_item(socket, :settings, active_item), live_item(socket, :profile, active_item), + live_item(socket, :support, active_item), user_session_item(socket, :signout, active_item) ] end diff --git a/core/lib/core_web/live/menu/items.ex b/core/lib/core_web/live/menu/items.ex index c6fa45bba..0599fe585 100644 --- a/core/lib/core_web/live/menu/items.ex +++ b/core/lib/core_web/live/menu/items.ex @@ -9,6 +9,7 @@ defmodule CoreWeb.Menu.Items do marketplace: %{target: CoreWeb.Marketplace, domain: "eyra-ui"}, todo: %{target: CoreWeb.Todo, domain: "eyra-ui"}, payments: %{target: CoreWeb.Dashboard, domain: "eyra-ui"}, + support: %{target: CoreWeb.Support, domain: "eyra-ui"}, settings: %{target: CoreWeb.User.Settings, domain: "eyra-ui"}, profile: %{target: CoreWeb.User.Profile, domain: "eyra-ui"}, signout: %{target: :delete, domain: "eyra-ui"}, diff --git a/core/lib/core_web/live/routes.ex b/core/lib/core_web/live/routes.ex index bd569547c..66944c132 100644 --- a/core/lib/core_web/live/routes.ex +++ b/core/lib/core_web/live/routes.ex @@ -30,6 +30,7 @@ defmodule CoreWeb.Live.Routes do live("/marketplace", Marketplace) live("/todo", Todo) live("/notifications", Notifications) + live("/support", Support) end if Mix.env() in [:dev, :test] do diff --git a/core/lib/core_web/live/support.ex b/core/lib/core_web/live/support.ex new file mode 100644 index 000000000..9e54732b7 --- /dev/null +++ b/core/lib/core_web/live/support.ex @@ -0,0 +1,73 @@ +defmodule CoreWeb.Support do + use CoreWeb, :live_view + use CoreWeb.Layouts.Workspace.Component, :support + + alias Surface.Components.Form + alias EyraUI.Text.{Title3, BodyLarge} + alias EyraUI.Spacing + alias CoreWeb.Layouts.Workspace.Component, as: Workspace + alias EyraUI.Form.{TextArea, TextInput} + alias EyraUI.Text.{Title3} + alias EyraUI.Button.SubmitButton + alias Core.Helpdesk + + data(focus, :any, default: nil) + data(data, :any, default: {}) + + def mount(_params, _session, socket) do + {:ok, + socket + |> assign(:changeset, Helpdesk.new_ticket_changeset()) + |> update_menus()} + end + + def handle_event( + "create_ticket", + %{"ticket" => data}, + %{assigns: %{current_user: user}} = socket + ) do + case Helpdesk.create_ticket(user, data) do + {:ok, _} -> + {:noreply, + socket + |> put_flash(:info, dgettext("eyra-support", "ticket_created.info.flash")) + |> push_redirect(to: Routes.live_path(socket, CoreWeb.Marketplace))} + + {:error, changeset} -> + {:noreply, assign(socket, :changeset, changeset)} + end + end + + def handle_event("focus", %{"field" => field}, socket) do + {:noreply, assign(socket, :focus, field)} + end + + def handle_event("store_state", %{"ticket" => ticket}, socket) do + {:noreply, assign(socket, :changeset, Helpdesk.new_ticket_changeset(ticket))} + end + + def render(assigns) do + ~H""" + + + {{dgettext("eyra-support", "form.title")}} + {{dgettext("eyra-support", "form.description")}} + +
+
+ + +