From 7195c7fba610df9ea4a4ad9c89887fc1f96b3352 Mon Sep 17 00:00:00 2001 From: shibao Date: Thu, 24 Nov 2022 14:31:16 -0500 Subject: [PATCH] work on pipelines --- lib/memex/pipelines.ex | 165 +++++++++++++++--- lib/memex/pipelines/pipeline.ex | 57 +++++- .../components/pipelines_table_component.ex | 135 ++++++++++++++ .../live/pipeline_live/form_component.ex | 51 ++++-- .../pipeline_live/form_component.html.heex | 45 +++-- lib/memex_web/live/pipeline_live/index.ex | 74 +++++--- .../live/pipeline_live/index.html.heex | 111 ++++++------ lib/memex_web/live/pipeline_live/show.ex | 30 +++- .../live/pipeline_live/show.html.heex | 72 ++++---- lib/memex_web/router.ex | 3 +- priv/gettext/actions.pot | 21 +-- priv/gettext/default.pot | 43 +++++ priv/gettext/prompts.pot | 3 +- .../20220726001146_create_pipelines.exs | 21 +++ test/memex/pipelines_test.exs | 124 +++++++++---- test/memex_web/live/pipeline_live_test.exs | 64 ++++--- test/support/fixtures/pipelines_fixtures.ex | 10 +- 17 files changed, 787 insertions(+), 242 deletions(-) create mode 100644 lib/memex_web/components/pipelines_table_component.ex diff --git a/lib/memex/pipelines.ex b/lib/memex/pipelines.ex index 7300755..995f3ba 100644 --- a/lib/memex/pipelines.ex +++ b/lib/memex/pipelines.ex @@ -4,21 +4,88 @@ defmodule Memex.Pipelines do """ import Ecto.Query, warn: false - alias Memex.Repo - - alias Memex.Pipelines.Pipeline + alias Ecto.Changeset + alias Memex.{Accounts.User, Pipelines.Pipeline, Repo} @doc """ Returns the list of pipelines. ## Examples - iex> list_pipelines() + iex> list_pipelines(%User{id: 123}) [%Pipeline{}, ...] + iex> list_pipelines("my pipeline", %User{id: 123}) + [%Pipeline{title: "my pipeline"}, ...] + """ - def list_pipelines do - Repo.all(Pipeline) + @spec list_pipelines(User.t()) :: [Pipeline.t()] + @spec list_pipelines(search :: String.t() | nil, User.t()) :: [Pipeline.t()] + def list_pipelines(search \\ nil, user) + + def list_pipelines(search, %{id: user_id}) when search |> is_nil() or search == "" do + Repo.all(from p in Pipeline, where: p.user_id == ^user_id, order_by: p.title) + end + + def list_pipelines(search, %{id: user_id}) when search |> is_binary() do + trimmed_search = String.trim(search) + + Repo.all( + from p in Pipeline, + where: p.user_id == ^user_id, + where: + fragment( + "search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')", + ^trimmed_search + ), + order_by: { + :desc, + fragment( + "ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)", + ^trimmed_search + ) + } + ) + end + + @doc """ + Returns the list of public pipelines for viewing + + ## Examples + + iex> list_public_pipelines() + [%Pipeline{}, ...] + + iex> list_public_pipelines("my pipeline") + [%Pipeline{title: "my pipeline"}, ...] + """ + @spec list_public_pipelines() :: [Pipeline.t()] + @spec list_public_pipelines(search :: String.t() | nil) :: [Pipeline.t()] + def list_public_pipelines(search \\ nil) + + def list_public_pipelines(search) when search |> is_nil() or search == "" do + Repo.all(from p in Pipeline, where: p.visibility == :public, order_by: p.title) + end + + def list_public_pipelines(search) when search |> is_binary() do + trimmed_search = String.trim(search) + + Repo.all( + from p in Pipeline, + where: p.visibility == :public, + where: + fragment( + "search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')", + ^trimmed_search + ), + order_by: { + :desc, + fragment( + "ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)", + ^trimmed_search + ) + } + ) end @doc """ @@ -28,31 +95,47 @@ defmodule Memex.Pipelines do ## Examples - iex> get_pipeline!(123) + iex> get_pipeline!(123, %User{id: 123}) %Pipeline{} - iex> get_pipeline!(456) + iex> get_pipeline!(456, %User{id: 123}) ** (Ecto.NoResultsError) """ - def get_pipeline!(id), do: Repo.get!(Pipeline, id) + @spec get_pipeline!(Pipeline.id(), User.t()) :: Pipeline.t() + def get_pipeline!(id, %{id: user_id}) do + Repo.one!( + from p in Pipeline, + where: p.id == ^id, + where: p.user_id == ^user_id or p.visibility in [:public, :unlisted] + ) + end + + def get_pipeline!(id, _invalid_user) do + Repo.one!( + from p in Pipeline, + where: p.id == ^id, + where: p.visibility in [:public, :unlisted] + ) + end @doc """ Creates a pipeline. ## Examples - iex> create_pipeline(%{field: value}) + iex> create_pipeline(%{field: value}, %User{id: 123}) {:ok, %Pipeline{}} - iex> create_pipeline(%{field: bad_value}) + iex> create_pipeline(%{field: bad_value}, %User{id: 123}) {:error, %Ecto.Changeset{}} """ - def create_pipeline(attrs \\ %{}) do - %Pipeline{} - |> Pipeline.changeset(attrs) - |> Repo.insert() + @spec create_pipeline(User.t()) :: {:ok, Pipeline.t()} | {:error, Pipeline.changeset()} + @spec create_pipeline(attrs :: map(), User.t()) :: + {:ok, Pipeline.t()} | {:error, Pipeline.changeset()} + def create_pipeline(attrs \\ %{}, user) do + Pipeline.create_changeset(attrs, user) |> Repo.insert() end @doc """ @@ -60,16 +143,18 @@ defmodule Memex.Pipelines do ## Examples - iex> update_pipeline(pipeline, %{field: new_value}) + iex> update_pipeline(pipeline, %{field: new_value}, %User{id: 123}) {:ok, %Pipeline{}} - iex> update_pipeline(pipeline, %{field: bad_value}) + iex> update_pipeline(pipeline, %{field: bad_value}, %User{id: 123}) {:error, %Ecto.Changeset{}} """ - def update_pipeline(%Pipeline{} = pipeline, attrs) do + @spec update_pipeline(Pipeline.t(), attrs :: map(), User.t()) :: + {:ok, Pipeline.t()} | {:error, Pipeline.changeset()} + def update_pipeline(%Pipeline{} = pipeline, attrs, user) do pipeline - |> Pipeline.changeset(attrs) + |> Pipeline.update_changeset(attrs, user) |> Repo.update() end @@ -78,15 +163,24 @@ defmodule Memex.Pipelines do ## Examples - iex> delete_pipeline(pipeline) + iex> delete_pipeline(%Pipeline{user_id: 123}, %User{id: 123}) {:ok, %Pipeline{}} - iex> delete_pipeline(pipeline) + iex> delete_pipeline(%Pipeline{}, %User{role: :admin}) + {:ok, %Pipeline{}} + + iex> delete_pipeline(%Pipeline{}, %User{id: 123}) {:error, %Ecto.Changeset{}} """ - def delete_pipeline(%Pipeline{} = pipeline) do - Repo.delete(pipeline) + @spec delete_pipeline(Pipeline.t(), User.t()) :: + {:ok, Pipeline.t()} | {:error, Pipeline.changeset()} + def delete_pipeline(%Pipeline{user_id: user_id} = pipeline, %{id: user_id}) do + pipeline |> Repo.delete() + end + + def delete_pipeline(%Pipeline{} = pipeline, %{role: :admin}) do + pipeline |> Repo.delete() end @doc """ @@ -94,11 +188,30 @@ defmodule Memex.Pipelines do ## Examples - iex> change_pipeline(pipeline) + iex> change_pipeline(pipeline, %User{id: 123}) + %Ecto.Changeset{data: %Pipeline{}} + + iex> change_pipeline(pipeline, %{title: "new title"}, %User{id: 123}) %Ecto.Changeset{data: %Pipeline{}} """ - def change_pipeline(%Pipeline{} = pipeline, attrs \\ %{}) do - Pipeline.changeset(pipeline, attrs) + @spec change_pipeline(Pipeline.t(), User.t()) :: Pipeline.changeset() + @spec change_pipeline(Pipeline.t(), attrs :: map(), User.t()) :: Pipeline.changeset() + def change_pipeline(%Pipeline{} = pipeline, attrs \\ %{}, user) do + pipeline |> Pipeline.update_changeset(attrs, user) + end + + @doc """ + Gets a canonical string representation of the `:tags` field for a Pipeline + """ + @spec get_tags_string(Pipeline.t() | Pipeline.changeset() | [String.t()] | nil) :: String.t() + def get_tags_string(nil), do: "" + def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",") + def get_tags_string(%Pipeline{tags: tags}), do: tags |> get_tags_string() + + def get_tags_string(%Changeset{} = changeset) do + changeset + |> Changeset.get_field(:tags) + |> get_tags_string() end end diff --git a/lib/memex/pipelines/pipeline.ex b/lib/memex/pipelines/pipeline.ex index 37bae09..23c0989 100644 --- a/lib/memex/pipelines/pipeline.ex +++ b/lib/memex/pipelines/pipeline.ex @@ -1,21 +1,68 @@ defmodule Memex.Pipelines.Pipeline do + @moduledoc """ + Represents a chain of considerations to take to accomplish a task + """ use Ecto.Schema import Ecto.Changeset + alias Ecto.{Changeset, UUID} + alias Memex.Accounts.User @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "pipelines" do - field :description, :string field :title, :string + field :description, :string + field :tags, {:array, :string} + field :tags_string, :string, virtual: true field :visibility, Ecto.Enum, values: [:public, :private, :unlisted] + belongs_to :user, User + timestamps() end + @type t :: %__MODULE__{ + title: String.t(), + description: String.t(), + tags: [String.t()] | nil, + tags_string: String.t(), + visibility: :public | :private | :unlisted, + user: User.t() | Ecto.Association.NotLoaded.t(), + user_id: User.id(), + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + @type id :: UUID.t() + @type changeset :: Changeset.t(t()) + @doc false - def changeset(pipeline, attrs) do - pipeline - |> cast(attrs, [:title, :description, :visibility]) - |> validate_required([:title, :description, :visibility]) + @spec create_changeset(attrs :: map(), User.t()) :: changeset() + def create_changeset(attrs, %User{id: user_id}) do + %__MODULE__{} + |> cast(attrs, [:title, :description, :tags, :visibility]) + |> change(user_id: user_id) + |> cast_tags_string(attrs) + |> validate_required([:title, :user_id, :visibility]) end + + @spec update_changeset(t(), attrs :: map(), User.t()) :: changeset() + def update_changeset(%{user_id: user_id} = pipeline, attrs, %User{id: user_id}) do + pipeline + |> cast(attrs, [:title, :description, :tags, :visibility]) + |> cast_tags_string(attrs) + |> validate_required([:title, :visibility]) + end + + defp cast_tags_string(changeset, %{"tags_string" => tags_string}) + when tags_string |> is_binary() do + tags = + tags_string + |> String.split(",", trim: true) + |> Enum.map(fn str -> str |> String.trim() end) + |> Enum.sort() + + changeset |> change(tags: tags) + end + + defp cast_tags_string(changeset, _attrs), do: changeset end diff --git a/lib/memex_web/components/pipelines_table_component.ex b/lib/memex_web/components/pipelines_table_component.ex new file mode 100644 index 0000000..9f49a6d --- /dev/null +++ b/lib/memex_web/components/pipelines_table_component.ex @@ -0,0 +1,135 @@ +defmodule MemexWeb.Components.PipelinesTableComponent do + @moduledoc """ + A component that displays a list of pipelines + """ + use MemexWeb, :live_component + alias Ecto.UUID + alias Memex.{Accounts.User, Pipelines, Pipelines.Pipeline} + alias Phoenix.LiveView.{Rendered, Socket} + + @impl true + @spec update( + %{ + required(:id) => UUID.t(), + required(:current_user) => User.t(), + required(:pipelines) => [Pipeline.t()], + optional(any()) => any() + }, + Socket.t() + ) :: {:ok, Socket.t()} + def update(%{id: _id, pipelines: _pipelines, current_user: _current_user} = assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:actions, fn -> [] end) + |> display_pipelines() + + {:ok, socket} + end + + defp display_pipelines( + %{ + assigns: %{ + pipelines: pipelines, + current_user: current_user, + actions: actions + } + } = socket + ) do + columns = + if actions == [] or current_user |> is_nil() do + [] + else + [%{label: nil, key: :actions, sortable: false}] + end + + columns = [ + %{label: gettext("title"), key: :title}, + %{label: gettext("description"), key: :description}, + %{label: gettext("tags"), key: :tags}, + %{label: gettext("visibility"), key: :visibility} + | columns + ] + + rows = + pipelines + |> Enum.map(fn pipeline -> + pipeline + |> get_row_data_for_pipeline(%{ + columns: columns, + current_user: current_user, + actions: actions + }) + end) + + socket |> assign(columns: columns, rows: rows) + end + + @impl true + def render(assigns) do + ~H""" +
+ <.live_component + module={MemexWeb.Components.TableComponent} + id={@id} + columns={@columns} + rows={@rows} + /> +
+ """ + end + + @spec get_row_data_for_pipeline(Pipeline.t(), additional_data :: map()) :: map() + defp get_row_data_for_pipeline(pipeline, %{columns: columns} = additional_data) do + columns + |> Map.new(fn %{key: key} -> + {key, get_value_for_key(key, pipeline, additional_data)} + end) + end + + @spec get_value_for_key(atom(), Pipeline.t(), additional_data :: map()) :: + any() | {any(), Rendered.t()} + defp get_value_for_key(:title, %{id: id, title: title}, _additional_data) do + assigns = %{id: id, title: title} + + title_block = ~H""" + <.link + navigate={Routes.pipeline_show_path(Endpoint, :show, @id)} + class="link" + data-qa={"pipeline-show-#{@id}"} + > + <%= @title %> + + """ + + {title, title_block} + end + + defp get_value_for_key(:description, %{description: description}, _additional_data) do + assigns = %{description: description} + + description_block = ~H""" +
+ <%= @description %> +
+ """ + + {description, description_block} + end + + defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do + tags |> Pipelines.get_tags_string() + end + + defp get_value_for_key(:actions, pipeline, %{actions: actions}) do + assigns = %{actions: actions, pipeline: pipeline} + + ~H""" +
+ <%= render_slot(@actions, @pipeline) %> +
+ """ + end + + defp get_value_for_key(key, pipeline, _additional_data), do: pipeline |> Map.get(key) +end diff --git a/lib/memex_web/live/pipeline_live/form_component.ex b/lib/memex_web/live/pipeline_live/form_component.ex index 9b3201a..b55f00d 100644 --- a/lib/memex_web/live/pipeline_live/form_component.ex +++ b/lib/memex_web/live/pipeline_live/form_component.ex @@ -4,8 +4,8 @@ defmodule MemexWeb.PipelineLive.FormComponent do alias Memex.Pipelines @impl true - def update(%{pipeline: pipeline} = assigns, socket) do - changeset = Pipelines.change_pipeline(pipeline) + def update(%{pipeline: pipeline, current_user: current_user} = assigns, socket) do + changeset = Pipelines.change_pipeline(pipeline, current_user) {:ok, socket @@ -14,39 +14,56 @@ defmodule MemexWeb.PipelineLive.FormComponent do end @impl true - def handle_event("validate", %{"pipeline" => pipeline_params}, socket) do + def handle_event( + "validate", + %{"pipeline" => pipeline_params}, + %{assigns: %{pipeline: pipeline, current_user: current_user}} = socket + ) do changeset = - socket.assigns.pipeline - |> Pipelines.change_pipeline(pipeline_params) + pipeline + |> Pipelines.change_pipeline(pipeline_params, current_user) |> Map.put(:action, :validate) {:noreply, assign(socket, :changeset, changeset)} end - def handle_event("save", %{"pipeline" => pipeline_params}, socket) do - save_pipeline(socket, socket.assigns.action, pipeline_params) + def handle_event( + "save", + %{"pipeline" => pipeline_params}, + %{assigns: %{action: action}} = socket + ) do + save_pipeline(socket, action, pipeline_params) end - defp save_pipeline(socket, :edit, pipeline_params) do - case Pipelines.update_pipeline(socket.assigns.pipeline, pipeline_params) do - {:ok, _pipeline} -> + defp save_pipeline( + %{assigns: %{pipeline: pipeline, return_to: return_to, current_user: current_user}} = + socket, + :edit, + pipeline_params + ) do + case Pipelines.update_pipeline(pipeline, pipeline_params, current_user) do + {:ok, %{title: title}} -> {:noreply, socket - |> put_flash(:info, "pipeline updated successfully") - |> push_navigate(to: socket.assigns.return_to)} + |> put_flash(:info, gettext("%{title} saved", title: title)) + |> push_navigate(to: return_to)} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, :changeset, changeset)} end end - defp save_pipeline(socket, :new, pipeline_params) do - case Pipelines.create_pipeline(pipeline_params) do - {:ok, _pipeline} -> + defp save_pipeline( + %{assigns: %{return_to: return_to, current_user: current_user}} = socket, + :new, + pipeline_params + ) do + case Pipelines.create_pipeline(pipeline_params, current_user) do + {:ok, %{title: title}} -> {:noreply, socket - |> put_flash(:info, "pipeline created successfully") - |> push_navigate(to: socket.assigns.return_to)} + |> put_flash(:info, gettext("%{title} created", title: title)) + |> push_navigate(to: return_to)} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, changeset: changeset)} diff --git a/lib/memex_web/live/pipeline_live/form_component.html.heex b/lib/memex_web/live/pipeline_live/form_component.html.heex index 7826b42..ad1e510 100644 --- a/lib/memex_web/live/pipeline_live/form_component.html.heex +++ b/lib/memex_web/live/pipeline_live/form_component.html.heex @@ -1,6 +1,4 @@ -
-

<%= @title %>

- +
<.form :let={f} for={@changeset} @@ -8,23 +6,44 @@ phx-target={@myself} phx-change="validate" phx-submit="save" + phx-debounce="300" + class="flex flex-col justify-start items-stretch space-y-4" > - <%= label(f, :title) %> - <%= text_input(f, :title) %> + <%= text_input(f, :title, + class: "input input-primary", + placeholder: gettext("title") + ) %> <%= error_tag(f, :title) %> - <%= label(f, :description) %> - <%= textarea(f, :description) %> + <%= textarea(f, :description, + id: "pipeline-form-description", + class: "input input-primary h-64 min-h-64", + phx_hook: "MaintainAttrs", + phx_update: "ignore", + placeholder: gettext("description") + ) %> <%= error_tag(f, :description) %> - <%= label(f, :visibility) %> - <%= select(f, :visibility, Ecto.Enum.values(Memex.Pipelines.Pipeline, :visibility), - prompt: "Choose a value" + <%= text_input(f, :tags_string, + id: "tags-input", + class: "input input-primary", + placeholder: gettext("tag1,tag2"), + phx_update: "ignore", + value: Pipelines.get_tags_string(@changeset) ) %> - <%= error_tag(f, :visibility) %> + <%= error_tag(f, :tags_string) %> -
- <%= submit("Save", phx_disable_with: "Saving...") %> +
+ <%= select(f, :visibility, Ecto.Enum.values(Memex.Pipelines.Pipeline, :visibility), + class: "grow input input-primary", + prompt: gettext("select privacy") + ) %> + + <%= submit(dgettext("actions", "save"), + phx_disable_with: gettext("saving..."), + class: "mx-auto btn btn-primary" + ) %>
+ <%= error_tag(f, :visibility) %>
diff --git a/lib/memex_web/live/pipeline_live/index.ex b/lib/memex_web/live/pipeline_live/index.ex index 03319f5..2ee1b35 100644 --- a/lib/memex_web/live/pipeline_live/index.ex +++ b/lib/memex_web/live/pipeline_live/index.ex @@ -1,46 +1,80 @@ defmodule MemexWeb.PipelineLive.Index do use MemexWeb, :live_view - - alias Memex.Pipelines - alias Memex.Pipelines.Pipeline + alias Memex.{Pipelines, Pipelines.Pipeline} @impl true + def mount(%{"search" => search}, _session, socket) do + {:ok, socket |> assign(search: search) |> display_pipelines()} + end + def mount(_params, _session, socket) do - {:ok, assign(socket, :pipelines, list_pipelines())} + {:ok, socket |> assign(search: nil) |> display_pipelines()} end @impl true - def handle_params(params, _url, socket) do - {:noreply, apply_action(socket, socket.assigns.live_action, params)} + def handle_params(params, _url, %{assigns: %{live_action: live_action}} = socket) do + {:noreply, apply_action(socket, live_action, params)} end - defp apply_action(socket, :edit, %{"id" => id}) do + defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do + %{title: title} = pipeline = Pipelines.get_pipeline!(id, current_user) + socket - |> assign(:page_title, "edit pipeline") - |> assign(:pipeline, Pipelines.get_pipeline!(id)) + |> assign(page_title: gettext("edit %{title}", title: title)) + |> assign(pipeline: pipeline) end - defp apply_action(socket, :new, _params) do + defp apply_action(%{assigns: %{current_user: %{id: current_user_id}}} = socket, :new, _params) do socket - |> assign(:page_title, "new Pipeline") - |> assign(:pipeline, %Pipeline{}) + |> assign(page_title: gettext("new pipeline")) + |> assign(pipeline: %Pipeline{user_id: current_user_id}) end defp apply_action(socket, :index, _params) do socket - |> assign(:page_title, "listing pipelines") - |> assign(:pipeline, nil) + |> assign(page_title: gettext("pipelines")) + |> assign(search: nil) + |> assign(pipeline: nil) + |> display_pipelines() + end + + defp apply_action(socket, :search, %{"search" => search}) do + socket + |> assign(page_title: gettext("pipelines")) + |> assign(search: search) + |> assign(pipeline: nil) + |> display_pipelines() end @impl true - def handle_event("delete", %{"id" => id}, socket) do - pipeline = Pipelines.get_pipeline!(id) - {:ok, _} = Pipelines.delete_pipeline(pipeline) + def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do + pipeline = Pipelines.get_pipeline!(id, current_user) + {:ok, %{title: title}} = Pipelines.delete_pipeline(pipeline, current_user) - {:noreply, assign(socket, :pipelines, list_pipelines())} + socket = + socket + |> assign(pipelines: Pipelines.list_pipelines(current_user)) + |> put_flash(:info, gettext("%{title} deleted", title: title)) + + {:noreply, socket} end - defp list_pipelines do - Pipelines.list_pipelines() + @impl true + def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do + {:noreply, socket |> push_patch(to: Routes.pipeline_index_path(Endpoint, :index))} + end + + def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do + {:noreply, + socket |> push_patch(to: Routes.pipeline_index_path(Endpoint, :search, search_term))} + end + + defp display_pipelines(%{assigns: %{current_user: current_user, search: search}} = socket) + when not (current_user |> is_nil()) do + socket |> assign(pipelines: Pipelines.list_pipelines(search, current_user)) + end + + defp display_pipelines(%{assigns: %{search: search}} = socket) do + socket |> assign(pipelines: Pipelines.list_public_pipelines(search)) end end diff --git a/lib/memex_web/live/pipeline_live/index.html.heex b/lib/memex_web/live/pipeline_live/index.html.heex index 729fe6f..ec04bb9 100644 --- a/lib/memex_web/live/pipeline_live/index.html.heex +++ b/lib/memex_web/live/pipeline_live/index.html.heex @@ -1,10 +1,69 @@ -

listing pipelines

+
+

+ <%= gettext("pipelines") %> +

+ + <.form + :let={f} + for={:search} + phx-change="search" + phx-submit="search" + class="self-stretch flex flex-col items-stretch" + > + <%= text_input(f, :search_term, + class: "input input-primary", + value: @search, + phx_debounce: 300, + placeholder: gettext("search") + ) %> + + + <%= if @pipelines |> Enum.empty?() do %> +

+ <%= gettext("no pipelines found") %> +

+ <% else %> + <.live_component + module={MemexWeb.Components.PipelinesTableComponent} + id="pipelines-index-table" + current_user={@current_user} + pipelines={@pipelines} + > + <:actions :let={pipeline}> + <%= if @current_user do %> + <.link + patch={Routes.pipeline_index_path(@socket, :edit, pipeline)} + data-qa={"pipeline-edit-#{pipeline.id}"} + > + <%= dgettext("actions", "edit") %> + + <.link + href="#" + phx-click="delete" + phx-value-id={pipeline.id} + data-confirm={dgettext("prompts", "are you sure?")} + data-qa={"delete-pipeline-#{pipeline.id}"} + > + <%= dgettext("actions", "delete") %> + + <% end %> + + + <% end %> + + <%= if @current_user do %> + <.link patch={Routes.pipeline_index_path(@socket, :new)} class="self-end btn btn-primary"> + <%= dgettext("actions", "new pipeline") %> + + <% end %> +
<%= if @live_action in [:new, :edit] do %> <.modal return_to={Routes.pipeline_index_path(@socket, :index)}> <.live_component module={MemexWeb.PipelineLive.FormComponent} id={@pipeline.id || :new} + current_user={@current_user} title={@page_title} action={@live_action} pipeline={@pipeline} @@ -12,53 +71,3 @@ /> <% end %> - - - - - - - - - - - - - <%= for pipeline <- @pipelines do %> - - - - - - - - <% end %> - -
TitleDescriptionVisibility
<%= pipeline.title %><%= pipeline.description %><%= pipeline.visibility %> - - <.link navigate={Routes.pipeline_show_path(@socket, :show, pipeline)}> - <%= dgettext("actions", "show") %> - - - - <.link patch={Routes.pipeline_index_path(@socket, :edit, pipeline)}> - <%= dgettext("actions", "edit") %> - - - - <.link - href="#" - phx-click="delete" - phx-value-id={pipeline.id} - data-confirm={dgettext("prompts", "are you sure?")} - > - <%= dgettext("actions", "delete") %> - - -
- - - <.link patch={Routes.pipeline_index_path(@socket, :new)}> - <%= dgettext("actions", "new pipeline") %> - - diff --git a/lib/memex_web/live/pipeline_live/show.ex b/lib/memex_web/live/pipeline_live/show.ex index a819dc5..e6a548d 100644 --- a/lib/memex_web/live/pipeline_live/show.ex +++ b/lib/memex_web/live/pipeline_live/show.ex @@ -9,13 +9,33 @@ defmodule MemexWeb.PipelineLive.Show do end @impl true - def handle_params(%{"id" => id}, _, socket) do + def handle_params( + %{"id" => id}, + _, + %{assigns: %{live_action: live_action, current_user: current_user}} = socket + ) do {:noreply, socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:pipeline, Pipelines.get_pipeline!(id))} + |> assign(:page_title, page_title(live_action)) + |> assign(:pipeline, Pipelines.get_pipeline!(id, current_user))} end - defp page_title(:show), do: "show pipeline" - defp page_title(:edit), do: "edit pipeline" + @impl true + def handle_event( + "delete", + _params, + %{assigns: %{pipeline: pipeline, current_user: current_user}} = socket + ) do + {:ok, %{title: title}} = Pipelines.delete_pipeline(pipeline, current_user) + + socket = + socket + |> put_flash(:info, gettext("%{title} deleted", title: title)) + |> push_navigate(to: Routes.pipeline_index_path(Endpoint, :index)) + + {:noreply, socket} + end + + defp page_title(:show), do: gettext("show pipeline") + defp page_title(:edit), do: gettext("edit pipeline") end diff --git a/lib/memex_web/live/pipeline_live/show.html.heex b/lib/memex_web/live/pipeline_live/show.html.heex index 0d4196c..eea830d 100644 --- a/lib/memex_web/live/pipeline_live/show.html.heex +++ b/lib/memex_web/live/pipeline_live/show.html.heex @@ -1,10 +1,51 @@ -

show pipeline

+
+

+ <%= @pipeline.title %> +

+ +

<%= if @pipeline.tags, do: @pipeline.tags |> Enum.join(", ") %>

+ + + +

+ <%= gettext("Visibility: %{visibility}", visibility: @pipeline.visibility) %> +

+ +
+ <.link class="btn btn-primary" patch={Routes.pipeline_index_path(@socket, :index)}> + <%= dgettext("actions", "back") %> + + <%= if @current_user do %> + <.link class="btn btn-primary" patch={Routes.pipeline_show_path(@socket, :edit, @pipeline)}> + <%= dgettext("actions", "edit") %> + + + + <% end %> +
+
<%= if @live_action in [:edit] do %> <.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline)}> <.live_component module={MemexWeb.PipelineLive.FormComponent} id={@pipeline.id} + current_user={@current_user} title={@page_title} action={@live_action} pipeline={@pipeline} @@ -12,32 +53,3 @@ /> <% end %> - -
    -
  • - Title: - <%= @pipeline.title %> -
  • - -
  • - Description: - <%= @pipeline.description %> -
  • - -
  • - Visibility: - <%= @pipeline.visibility %> -
  • -
- - - <.link patch={Routes.pipeline_show_path(@socket, :edit, @pipeline)} class="button"> - <%= dgettext("actions", "edit") %> - - -| - - <.link patch={Routes.pipeline_index_path(@socket, :index)}> - <%= dgettext("actions", "Back") %> - - diff --git a/lib/memex_web/router.ex b/lib/memex_web/router.ex index 5ce90ce..092d626 100644 --- a/lib/memex_web/router.ex +++ b/lib/memex_web/router.ex @@ -87,7 +87,8 @@ defmodule MemexWeb.Router do live "/context/:id", ContextLive.Show, :show live "/pipelines", PipelineLive.Index, :index - live "/pipelines/:id", PipelineLive.Show, :show + live "/pipelines/:search", PipelineLive.Index, :search + live "/pipeline/:id", PipelineLive.Show, :show end end diff --git a/priv/gettext/actions.pot b/priv/gettext/actions.pot index bc02bce..fc6c651 100644 --- a/priv/gettext/actions.pot +++ b/priv/gettext/actions.pot @@ -10,11 +10,6 @@ msgid "" msgstr "" -#: lib/memex_web/live/pipeline_live/show.html.heex:41 -#, elixir-autogen, elixir-format -msgid "Back" -msgstr "" - #: lib/memex_web/live/invite_live/index.html.heex:30 #, elixir-autogen, elixir-format msgid "Copy to clipboard" @@ -76,7 +71,8 @@ msgstr "" #: lib/memex_web/live/context_live/show.html.heex:37 #: lib/memex_web/live/note_live/index.html.heex:47 #: lib/memex_web/live/note_live/show.html.heex:37 -#: lib/memex_web/live/pipeline_live/index.html.heex:51 +#: lib/memex_web/live/pipeline_live/index.html.heex:47 +#: lib/memex_web/live/pipeline_live/show.html.heex:37 #, elixir-autogen, elixir-format msgid "delete" msgstr "" @@ -90,8 +86,8 @@ msgstr "" #: lib/memex_web/live/context_live/show.html.heex:27 #: lib/memex_web/live/note_live/index.html.heex:38 #: lib/memex_web/live/note_live/show.html.heex:27 -#: lib/memex_web/live/pipeline_live/index.html.heex:41 -#: lib/memex_web/live/pipeline_live/show.html.heex:35 +#: lib/memex_web/live/pipeline_live/index.html.heex:38 +#: lib/memex_web/live/pipeline_live/show.html.heex:27 #, elixir-autogen, elixir-format msgid "edit" msgstr "" @@ -122,7 +118,7 @@ msgstr "" msgid "new note" msgstr "" -#: lib/memex_web/live/pipeline_live/index.html.heex:62 +#: lib/memex_web/live/pipeline_live/index.html.heex:56 #, elixir-autogen, elixir-format msgid "new pipeline" msgstr "" @@ -140,17 +136,14 @@ msgstr "" #: lib/memex_web/live/context_live/form_component.html.heex:42 #: lib/memex_web/live/note_live/form_component.html.heex:42 +#: lib/memex_web/live/pipeline_live/form_component.html.heex:42 #, elixir-autogen, elixir-format msgid "save" msgstr "" -#: lib/memex_web/live/pipeline_live/index.html.heex:36 -#, elixir-autogen, elixir-format -msgid "show" -msgstr "" - #: lib/memex_web/live/context_live/show.html.heex:23 #: lib/memex_web/live/note_live/show.html.heex:23 +#: lib/memex_web/live/pipeline_live/show.html.heex:23 #, elixir-autogen, elixir-format msgid "back" msgstr "" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 7c719a4..09bd55b 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -12,6 +12,7 @@ msgstr "" #: lib/memex_web/live/context_live/form_component.ex:61 #: lib/memex_web/live/note_live/form_component.ex:60 +#: lib/memex_web/live/pipeline_live/form_component.ex:65 #, elixir-autogen, elixir-format msgid "%{title} created" msgstr "" @@ -20,12 +21,15 @@ msgstr "" #: lib/memex_web/live/context_live/show.ex:33 #: lib/memex_web/live/note_live/index.ex:57 #: lib/memex_web/live/note_live/show.ex:33 +#: lib/memex_web/live/pipeline_live/index.ex:57 +#: lib/memex_web/live/pipeline_live/show.ex:33 #, elixir-autogen, elixir-format msgid "%{title} deleted" msgstr "" #: lib/memex_web/live/context_live/form_component.ex:44 #: lib/memex_web/live/note_live/form_component.ex:43 +#: lib/memex_web/live/pipeline_live/form_component.ex:48 #, elixir-autogen, elixir-format msgid "%{title} saved" msgstr "" @@ -107,6 +111,7 @@ msgstr "" #: lib/memex_web/live/context_live/show.html.heex:18 #: lib/memex_web/live/note_live/show.html.heex:18 +#: lib/memex_web/live/pipeline_live/show.html.heex:18 #, elixir-autogen, elixir-format msgid "Visibility: %{visibility}" msgstr "" @@ -180,6 +185,7 @@ msgstr "" #: lib/memex_web/live/context_live/index.ex:23 #: lib/memex_web/live/note_live/index.ex:23 +#: lib/memex_web/live/pipeline_live/index.ex:23 #, elixir-autogen, elixir-format msgid "edit %{title}" msgstr "" @@ -298,6 +304,9 @@ msgid "notes:" msgstr "" #: lib/memex_web/components/topbar.ex:61 +#: lib/memex_web/live/pipeline_live/index.ex:35 +#: lib/memex_web/live/pipeline_live/index.ex:43 +#: lib/memex_web/live/pipeline_live/index.html.heex:3 #, elixir-autogen, elixir-format msgid "pipelines" msgstr "" @@ -344,12 +353,14 @@ msgstr "" #: lib/memex_web/live/context_live/form_component.html.heex:43 #: lib/memex_web/live/note_live/form_component.html.heex:43 +#: lib/memex_web/live/pipeline_live/form_component.html.heex:43 #, elixir-autogen, elixir-format msgid "saving..." msgstr "" #: lib/memex_web/live/context_live/form_component.html.heex:39 #: lib/memex_web/live/note_live/form_component.html.heex:39 +#: lib/memex_web/live/pipeline_live/form_component.html.heex:39 #, elixir-autogen, elixir-format msgid "select privacy" msgstr "" @@ -366,20 +377,24 @@ msgstr "" #: lib/memex_web/live/context_live/form_component.html.heex:30 #: lib/memex_web/live/note_live/form_component.html.heex:30 +#: lib/memex_web/live/pipeline_live/form_component.html.heex:30 #, elixir-autogen, elixir-format msgid "tag1,tag2" msgstr "" #: lib/memex_web/components/contexts_table_component.ex:49 #: lib/memex_web/components/notes_table_component.ex:49 +#: lib/memex_web/components/pipelines_table_component.ex:49 #, elixir-autogen, elixir-format msgid "tags" msgstr "" #: lib/memex_web/components/contexts_table_component.ex:47 #: lib/memex_web/components/notes_table_component.ex:47 +#: lib/memex_web/components/pipelines_table_component.ex:47 #: lib/memex_web/live/context_live/form_component.html.heex:14 #: lib/memex_web/live/note_live/form_component.html.heex:14 +#: lib/memex_web/live/pipeline_live/form_component.html.heex:14 #, elixir-autogen, elixir-format msgid "title" msgstr "" @@ -411,6 +426,7 @@ msgstr "" #: lib/memex_web/components/contexts_table_component.ex:50 #: lib/memex_web/components/notes_table_component.ex:50 +#: lib/memex_web/components/pipelines_table_component.ex:50 #, elixir-autogen, elixir-format msgid "visibility" msgstr "" @@ -432,6 +448,7 @@ msgstr "" #: lib/memex_web/live/context_live/index.html.heex:17 #: lib/memex_web/live/note_live/index.html.heex:17 +#: lib/memex_web/live/pipeline_live/index.html.heex:17 #, elixir-autogen, elixir-format msgid "search" msgstr "" @@ -460,3 +477,29 @@ msgstr "" #, elixir-autogen, elixir-format msgid "show context" msgstr "" + +#: lib/memex_web/components/pipelines_table_component.ex:48 +#: lib/memex_web/live/pipeline_live/form_component.html.heex:23 +#, elixir-autogen, elixir-format +msgid "description" +msgstr "" + +#: lib/memex_web/live/pipeline_live/show.ex:40 +#, elixir-autogen, elixir-format +msgid "edit pipeline" +msgstr "" + +#: lib/memex_web/live/pipeline_live/index.ex:29 +#, elixir-autogen, elixir-format +msgid "new pipeline" +msgstr "" + +#: lib/memex_web/live/pipeline_live/index.html.heex:23 +#, elixir-autogen, elixir-format +msgid "no pipelines found" +msgstr "" + +#: lib/memex_web/live/pipeline_live/show.ex:39 +#, elixir-autogen, elixir-format +msgid "show pipeline" +msgstr "" diff --git a/priv/gettext/prompts.pot b/priv/gettext/prompts.pot index a6ec60f..a56690a 100644 --- a/priv/gettext/prompts.pot +++ b/priv/gettext/prompts.pot @@ -145,7 +145,8 @@ msgstr "" #: lib/memex_web/live/context_live/show.html.heex:34 #: lib/memex_web/live/note_live/index.html.heex:44 #: lib/memex_web/live/note_live/show.html.heex:34 -#: lib/memex_web/live/pipeline_live/index.html.heex:49 +#: lib/memex_web/live/pipeline_live/index.html.heex:44 +#: lib/memex_web/live/pipeline_live/show.html.heex:34 #, elixir-autogen, elixir-format msgid "are you sure?" msgstr "" diff --git a/priv/repo/migrations/20220726001146_create_pipelines.exs b/priv/repo/migrations/20220726001146_create_pipelines.exs index ca21f31..24416e4 100644 --- a/priv/repo/migrations/20220726001146_create_pipelines.exs +++ b/priv/repo/migrations/20220726001146_create_pipelines.exs @@ -6,9 +6,30 @@ defmodule Memex.Repo.Migrations.CreatePipelines do add :id, :binary_id, primary_key: true add :title, :string add :description, :text + add :tags, {:array, :citext} add :visibility, :string + add :user_id, references(:users, on_delete: :delete_all, type: :binary_id) + timestamps() end + + flush() + + execute """ + ALTER TABLE pipelines + ADD COLUMN search tsvector + GENERATED ALWAYS AS ( + setweight(to_tsvector('english', coalesce(title, '')), 'A') || + setweight(to_tsvector('english', coalesce(immutable_array_to_string(tags, ' '), '')), 'B') || + setweight(to_tsvector('english', coalesce(description, '')), 'C') + ) STORED + """ + + execute("CREATE INDEX pipelines_trgm_idx ON pipelines USING GIN (search)") + end + + def down do + drop table(:pipelines) end end diff --git a/test/memex/pipelines_test.exs b/test/memex/pipelines_test.exs index 939928c..8746d6c 100644 --- a/test/memex/pipelines_test.exs +++ b/test/memex/pipelines_test.exs @@ -1,68 +1,120 @@ defmodule Memex.PipelinesTest do use Memex.DataCase - - alias Memex.Pipelines + import Memex.PipelinesFixtures + alias Memex.{Pipelines, Pipelines.Pipeline} + @moduletag :pipelines_test + @invalid_attrs %{description: nil, tag: nil, title: nil, visibility: nil} describe "pipelines" do - alias Memex.Pipelines.Pipeline - - import Memex.PipelinesFixtures - - @invalid_attrs %{description: nil, title: nil, visibility: nil} - - test "list_pipelines/0 returns all pipelines" do - pipeline = pipeline_fixture() - assert Pipelines.list_pipelines() == [pipeline] + setup do + [user: user_fixture()] end - test "get_pipeline!/1 returns the pipeline with given id" do - pipeline = pipeline_fixture() - assert Pipelines.get_pipeline!(pipeline.id) == pipeline + test "list_pipelines/1 returns all pipelines for a user", %{user: user} do + pipeline_a = pipeline_fixture(%{title: "a", visibility: :public}, user) + pipeline_b = pipeline_fixture(%{title: "b", visibility: :unlisted}, user) + pipeline_c = pipeline_fixture(%{title: "c", visibility: :private}, user) + assert Pipelines.list_pipelines(user) == [pipeline_a, pipeline_b, pipeline_c] end - test "create_pipeline/1 with valid data creates a pipeline" do - valid_attrs = %{description: "some description", title: "some title", visibility: :public} + test "list_public_pipelines/0 returns public pipelines", %{user: user} do + public_pipeline = pipeline_fixture(%{visibility: :public}, user) + pipeline_fixture(%{visibility: :unlisted}, user) + pipeline_fixture(%{visibility: :private}, user) + assert Pipelines.list_public_pipelines() == [public_pipeline] + end - assert {:ok, %Pipeline{} = pipeline} = Pipelines.create_pipeline(valid_attrs) + test "get_pipeline!/1 returns the pipeline with given id", %{user: user} do + pipeline = pipeline_fixture(%{visibility: :public}, user) + assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline + + pipeline = pipeline_fixture(%{visibility: :unlisted}, user) + assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline + + pipeline = pipeline_fixture(%{visibility: :private}, user) + assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline + end + + test "get_pipeline!/1 only returns unlisted or public pipelines for other users", %{ + user: user + } do + another_user = user_fixture() + pipeline = pipeline_fixture(%{visibility: :public}, another_user) + assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline + + pipeline = pipeline_fixture(%{visibility: :unlisted}, another_user) + assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline + + pipeline = pipeline_fixture(%{visibility: :private}, another_user) + + assert_raise Ecto.NoResultsError, fn -> + Pipelines.get_pipeline!(pipeline.id, user) + end + end + + test "create_pipeline/1 with valid data creates a pipeline", %{user: user} do + valid_attrs = %{ + "description" => "some description", + "tags_string" => "tag1,tag2", + "title" => "some title", + "visibility" => :public + } + + assert {:ok, %Pipeline{} = pipeline} = Pipelines.create_pipeline(valid_attrs, user) assert pipeline.description == "some description" + assert pipeline.tags == ["tag1", "tag2"] assert pipeline.title == "some title" assert pipeline.visibility == :public end - test "create_pipeline/1 with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = Pipelines.create_pipeline(@invalid_attrs) + test "create_pipeline/1 with invalid data returns error changeset", %{user: user} do + assert {:error, %Ecto.Changeset{}} = Pipelines.create_pipeline(@invalid_attrs, user) end - test "update_pipeline/2 with valid data updates the pipeline" do - pipeline = pipeline_fixture() + test "update_pipeline/2 with valid data updates the pipeline", %{user: user} do + pipeline = pipeline_fixture(user) update_attrs = %{ - description: "some updated description", - title: "some updated title", - visibility: :private + "description" => "some updated description", + "tags_string" => "tag1,tag2", + "title" => "some updated title", + "visibility" => :private } - assert {:ok, %Pipeline{} = pipeline} = Pipelines.update_pipeline(pipeline, update_attrs) + assert {:ok, %Pipeline{} = pipeline} = + Pipelines.update_pipeline(pipeline, update_attrs, user) + assert pipeline.description == "some updated description" + assert pipeline.tags == ["tag1", "tag2"] assert pipeline.title == "some updated title" assert pipeline.visibility == :private end - test "update_pipeline/2 with invalid data returns error changeset" do - pipeline = pipeline_fixture() - assert {:error, %Ecto.Changeset{}} = Pipelines.update_pipeline(pipeline, @invalid_attrs) - assert pipeline == Pipelines.get_pipeline!(pipeline.id) + test "update_pipeline/2 with invalid data returns error changeset", %{user: user} do + pipeline = pipeline_fixture(user) + + assert {:error, %Ecto.Changeset{}} = + Pipelines.update_pipeline(pipeline, @invalid_attrs, user) + + assert pipeline == Pipelines.get_pipeline!(pipeline.id, user) end - test "delete_pipeline/1 deletes the pipeline" do - pipeline = pipeline_fixture() - assert {:ok, %Pipeline{}} = Pipelines.delete_pipeline(pipeline) - assert_raise Ecto.NoResultsError, fn -> Pipelines.get_pipeline!(pipeline.id) end + test "delete_pipeline/1 deletes the pipeline", %{user: user} do + pipeline = pipeline_fixture(user) + assert {:ok, %Pipeline{}} = Pipelines.delete_pipeline(pipeline, user) + assert_raise Ecto.NoResultsError, fn -> Pipelines.get_pipeline!(pipeline.id, user) end end - test "change_pipeline/1 returns a pipeline changeset" do - pipeline = pipeline_fixture() - assert %Ecto.Changeset{} = Pipelines.change_pipeline(pipeline) + test "delete_pipeline/1 deletes the pipeline for an admin user", %{user: user} do + admin_user = admin_fixture() + pipeline = pipeline_fixture(user) + assert {:ok, %Pipeline{}} = Pipelines.delete_pipeline(pipeline, admin_user) + assert_raise Ecto.NoResultsError, fn -> Pipelines.get_pipeline!(pipeline.id, user) end + end + + test "change_pipeline/1 returns a pipeline changeset", %{user: user} do + pipeline = pipeline_fixture(user) + assert %Ecto.Changeset{} = Pipelines.change_pipeline(pipeline, user) end end end diff --git a/test/memex_web/live/pipeline_live_test.exs b/test/memex_web/live/pipeline_live_test.exs index 8c2c9b4..76ad9b9 100644 --- a/test/memex_web/live/pipeline_live_test.exs +++ b/test/memex_web/live/pipeline_live_test.exs @@ -4,26 +4,36 @@ defmodule MemexWeb.PipelineLiveTest do import Phoenix.LiveViewTest import Memex.PipelinesFixtures - @create_attrs %{description: "some description", title: "some title", visibility: :public} - @update_attrs %{ - description: "some updated description", - title: "some updated title", - visibility: :private + @create_attrs %{ + "description" => "some description", + "tags_string" => "tag1", + "title" => "some title", + "visibility" => :public + } + @update_attrs %{ + "description" => "some updated description", + "tags_string" => "tag1,tag2", + "title" => "some updated title", + "visibility" => :private + } + @invalid_attrs %{ + "description" => nil, + "tags_string" => "", + "title" => nil, + "visibility" => nil } - @invalid_attrs %{description: nil, title: nil, visibility: nil} - defp create_pipeline(_) do - pipeline = pipeline_fixture() - %{pipeline: pipeline} + defp create_pipeline(%{user: user}) do + [pipeline: pipeline_fixture(user)] end describe "Index" do - setup [:create_pipeline] + setup [:register_and_log_in_user, :create_pipeline] test "lists all pipelines", %{conn: conn, pipeline: pipeline} do {:ok, _index_live, html} = live(conn, Routes.pipeline_index_path(conn, :index)) - assert html =~ "listing pipelines" + assert html =~ "pipelines" assert html =~ pipeline.description end @@ -45,15 +55,15 @@ defmodule MemexWeb.PipelineLiveTest do |> render_submit() |> follow_redirect(conn, Routes.pipeline_index_path(conn, :index)) - assert html =~ "pipeline created successfully" + assert html =~ "#{@create_attrs |> Map.get("title")} created" assert html =~ "some description" end test "updates pipeline in listing", %{conn: conn, pipeline: pipeline} do {:ok, index_live, _html} = live(conn, Routes.pipeline_index_path(conn, :index)) - assert index_live |> element("#pipeline-#{pipeline.id} a", "edit") |> render_click() =~ - "edit pipeline" + assert index_live |> element("[data-qa=\"pipeline-edit-#{pipeline.id}\"]") |> render_click() =~ + "edit" assert_patch(index_live, Routes.pipeline_index_path(conn, :edit, pipeline)) @@ -67,20 +77,23 @@ defmodule MemexWeb.PipelineLiveTest do |> render_submit() |> follow_redirect(conn, Routes.pipeline_index_path(conn, :index)) - assert html =~ "pipeline updated successfully" + assert html =~ "#{@update_attrs |> Map.get("title")} saved" assert html =~ "some updated description" end test "deletes pipeline in listing", %{conn: conn, pipeline: pipeline} do {:ok, index_live, _html} = live(conn, Routes.pipeline_index_path(conn, :index)) - assert index_live |> element("#pipeline-#{pipeline.id} a", "delete") |> render_click() + assert index_live + |> element("[data-qa=\"delete-pipeline-#{pipeline.id}\"]") + |> render_click() + refute has_element?(index_live, "#pipeline-#{pipeline.id}") end end describe "show" do - setup [:create_pipeline] + setup [:register_and_log_in_user, :create_pipeline] test "displays pipeline", %{conn: conn, pipeline: pipeline} do {:ok, _show_live, html} = live(conn, Routes.pipeline_show_path(conn, :show, pipeline)) @@ -92,8 +105,7 @@ defmodule MemexWeb.PipelineLiveTest do test "updates pipeline within modal", %{conn: conn, pipeline: pipeline} do {:ok, show_live, _html} = live(conn, Routes.pipeline_show_path(conn, :show, pipeline)) - assert show_live |> element("a", "edit") |> render_click() =~ - "edit pipeline" + assert show_live |> element("a", "edit") |> render_click() =~ "edit" assert_patch(show_live, Routes.pipeline_show_path(conn, :edit, pipeline)) @@ -107,8 +119,20 @@ defmodule MemexWeb.PipelineLiveTest do |> render_submit() |> follow_redirect(conn, Routes.pipeline_show_path(conn, :show, pipeline)) - assert html =~ "pipeline updated successfully" + assert html =~ "#{@update_attrs |> Map.get("title")} saved" assert html =~ "some updated description" end + + test "deletes pipeline", %{conn: conn, pipeline: pipeline} do + {:ok, show_live, _html} = live(conn, Routes.pipeline_show_path(conn, :show, pipeline)) + + {:ok, index_live, _html} = + show_live + |> element("[data-qa=\"delete-pipeline-#{pipeline.id}\"]") + |> render_click() + |> follow_redirect(conn, Routes.pipeline_index_path(conn, :index)) + + refute has_element?(index_live, "#pipeline-#{pipeline.id}") + end end end diff --git a/test/support/fixtures/pipelines_fixtures.ex b/test/support/fixtures/pipelines_fixtures.ex index f85628b..48168c1 100644 --- a/test/support/fixtures/pipelines_fixtures.ex +++ b/test/support/fixtures/pipelines_fixtures.ex @@ -3,19 +3,23 @@ defmodule Memex.PipelinesFixtures do This module defines test helpers for creating entities via the `Memex.Pipelines` context. """ + alias Memex.{Accounts.User, Pipelines, Pipelines.Pipeline} @doc """ Generate a pipeline. """ - def pipeline_fixture(attrs \\ %{}) do + @spec pipeline_fixture(User.t()) :: Pipeline.t() + @spec pipeline_fixture(attrs :: map(), User.t()) :: Pipeline.t() + def pipeline_fixture(attrs \\ %{}, user) do {:ok, pipeline} = attrs |> Enum.into(%{ description: "some description", + tag: [], title: "some title", - visibility: :public + visibility: :private }) - |> Memex.Pipelines.create_pipeline() + |> Pipelines.create_pipeline(user) pipeline end