improve tagging logic
continuous-integration/drone/push Build is running Details

This commit is contained in:
shibao 2022-12-19 22:29:26 -05:00
parent 926805ba9b
commit 571f6fffdb
22 changed files with 162 additions and 120 deletions

View File

@ -2,6 +2,7 @@
- fix docker-compose - fix docker-compose
- fix newlines in notes/context/step contents - fix newlines in notes/context/step contents
- fix user invite page - fix user invite page
- improve tagging logic
# v0.1.3 # v0.1.3
- backlink to other notes in notes - backlink to other notes in notes

View File

@ -4,7 +4,6 @@ defmodule Memex.Contexts do
""" """
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{Accounts.User, Contexts.Context, Repo} alias Memex.{Accounts.User, Contexts.Context, Repo}
@doc """ @doc """
@ -229,18 +228,4 @@ defmodule Memex.Contexts do
def change_context(%Context{} = context, attrs \\ %{}, user) do def change_context(%Context{} = context, attrs \\ %{}, user) do
context |> Context.update_changeset(attrs, user) context |> Context.update_changeset(attrs, user)
end end
@doc """
Gets a canonical string representation of the `:tags` field for a Note
"""
@spec get_tags_string(Context.t() | Context.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(%Context{tags: tags}), do: tags |> get_tags_string()
def get_tags_string(%Changeset{} = changeset) do
changeset
|> Changeset.get_field(:tags)
|> get_tags_string()
end
end end

View File

@ -27,7 +27,7 @@ defmodule Memex.Contexts.Context do
slug: slug(), slug: slug(),
content: String.t(), content: String.t(),
tags: [String.t()] | nil, tags: [String.t()] | nil,
tags_string: String.t(), tags_string: String.t() | nil,
visibility: :public | :private | :unlisted, visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(), user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(), user_id: User.id(),
@ -66,16 +66,38 @@ defmodule Memex.Contexts.Context do
|> unsafe_validate_unique(:slug, Repo) |> unsafe_validate_unique(:slug, Repo)
end end
defp cast_tags_string(changeset, %{"tags_string" => tags_string}) defp cast_tags_string(changeset, attrs) do
when tags_string |> is_binary() do changeset
tags = |> put_change(:tags_string, changeset |> get_field(:tags) |> get_tags_string())
tags_string |> cast(attrs, [:tags_string])
|> String.split(",", trim: true) |> validate_format(:tags_string, ~r/^[\p{L}\p{N}\-\,]+$/,
|> Enum.map(fn str -> str |> String.trim() end) message:
|> Enum.sort() dgettext(
"errors",
changeset |> change(tags: tags) "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
)
)
|> cast_tags()
end end
defp cast_tags_string(changeset, _attrs), do: changeset defp cast_tags(%{valid?: false} = changeset), do: changeset
defp cast_tags(%{valid?: true} = changeset) do
tags = changeset |> get_field(:tags_string) |> process_tags()
changeset |> put_change(:tags, tags)
end
defp process_tags(tags_string) when tags_string |> is_binary() do
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.reject(fn str -> str |> is_nil() end)
|> Enum.sort()
end
defp process_tags(_other_tags_string), do: []
@spec get_tags_string([String.t()] | nil) :: String.t()
def get_tags_string(nil), do: ""
def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",")
end end

View File

@ -4,7 +4,6 @@ defmodule Memex.Notes do
""" """
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{Accounts.User, Notes.Note, Repo} alias Memex.{Accounts.User, Notes.Note, Repo}
@doc """ @doc """
@ -229,18 +228,4 @@ defmodule Memex.Notes do
def change_note(%Note{} = note, attrs \\ %{}, user) do def change_note(%Note{} = note, attrs \\ %{}, user) do
note |> Note.update_changeset(attrs, user) note |> Note.update_changeset(attrs, user)
end end
@doc """
Gets a canonical string representation of the `:tags` field for a Note
"""
@spec get_tags_string(Note.t() | Note.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(%Note{tags: tags}), do: tags |> get_tags_string()
def get_tags_string(%Changeset{} = changeset) do
changeset
|> Changeset.get_field(:tags)
|> get_tags_string()
end
end end

View File

@ -26,7 +26,7 @@ defmodule Memex.Notes.Note do
slug: slug(), slug: slug(),
content: String.t(), content: String.t(),
tags: [String.t()] | nil, tags: [String.t()] | nil,
tags_string: String.t(), tags_string: String.t() | nil,
visibility: :public | :private | :unlisted, visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(), user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(), user_id: User.id(),
@ -65,16 +65,38 @@ defmodule Memex.Notes.Note do
|> unsafe_validate_unique(:slug, Repo) |> unsafe_validate_unique(:slug, Repo)
end end
defp cast_tags_string(changeset, %{"tags_string" => tags_string}) defp cast_tags_string(changeset, attrs) do
when tags_string |> is_binary() do changeset
tags = |> put_change(:tags_string, changeset |> get_field(:tags) |> get_tags_string())
tags_string |> cast(attrs, [:tags_string])
|> String.split(",", trim: true) |> validate_format(:tags_string, ~r/^[\p{L}\p{N}\-\,]+$/,
|> Enum.map(fn str -> str |> String.trim() end) message:
|> Enum.sort() dgettext(
"errors",
changeset |> change(tags: tags) "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
)
)
|> cast_tags()
end end
defp cast_tags_string(changeset, _attrs), do: changeset defp cast_tags(%{valid?: false} = changeset), do: changeset
defp cast_tags(%{valid?: true} = changeset) do
tags = changeset |> get_field(:tags_string) |> process_tags()
changeset |> put_change(:tags, tags)
end
defp process_tags(tags_string) when tags_string |> is_binary() do
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.reject(fn str -> str |> is_nil() end)
|> Enum.sort()
end
defp process_tags(_other_tags_string), do: []
@spec get_tags_string([String.t()] | nil) :: String.t()
def get_tags_string(nil), do: ""
def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",")
end end

View File

@ -4,7 +4,6 @@ defmodule Memex.Pipelines do
""" """
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{Accounts.User, Pipelines.Pipeline, Repo} alias Memex.{Accounts.User, Pipelines.Pipeline, Repo}
@doc """ @doc """
@ -231,18 +230,4 @@ defmodule Memex.Pipelines do
def change_pipeline(%Pipeline{} = pipeline, attrs \\ %{}, user) do def change_pipeline(%Pipeline{} = pipeline, attrs \\ %{}, user) do
pipeline |> Pipeline.update_changeset(attrs, user) pipeline |> Pipeline.update_changeset(attrs, user)
end 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 end

View File

@ -28,7 +28,7 @@ defmodule Memex.Pipelines.Pipeline do
slug: slug(), slug: slug(),
description: String.t(), description: String.t(),
tags: [String.t()] | nil, tags: [String.t()] | nil,
tags_string: String.t(), tags_string: String.t() | nil,
visibility: :public | :private | :unlisted, visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(), user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(), user_id: User.id(),
@ -67,16 +67,38 @@ defmodule Memex.Pipelines.Pipeline do
|> unsafe_validate_unique(:slug, Repo) |> unsafe_validate_unique(:slug, Repo)
end end
defp cast_tags_string(changeset, %{"tags_string" => tags_string}) defp cast_tags_string(changeset, attrs) do
when tags_string |> is_binary() do changeset
tags = |> put_change(:tags_string, changeset |> get_field(:tags) |> get_tags_string())
tags_string |> cast(attrs, [:tags_string])
|> String.split(",", trim: true) |> validate_format(:tags_string, ~r/^[\p{L}\p{N}\-\,]+$/,
|> Enum.map(fn str -> str |> String.trim() end) message:
|> Enum.sort() dgettext(
"errors",
changeset |> change(tags: tags) "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
)
)
|> cast_tags()
end end
defp cast_tags_string(changeset, _attrs), do: changeset defp cast_tags(%{valid?: false} = changeset), do: changeset
defp cast_tags(%{valid?: true} = changeset) do
tags = changeset |> get_field(:tags_string) |> process_tags()
changeset |> put_change(:tags, tags)
end
defp process_tags(tags_string) when tags_string |> is_binary() do
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.reject(fn str -> str |> is_nil() end)
|> Enum.sort()
end
defp process_tags(_other_tags_string), do: []
@spec get_tags_string([String.t()] | nil) :: String.t()
def get_tags_string(nil), do: ""
def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",")
end end

View File

@ -27,9 +27,7 @@
<%= text_input(f, :tags_string, <%= text_input(f, :tags_string,
id: "tags-input", id: "tags-input",
class: "input input-primary", class: "input input-primary",
placeholder: gettext("tag1,tag2"), placeholder: gettext("tag1,tag2")
phx_update: "ignore",
value: Contexts.get_tags_string(@changeset)
) %> ) %>
<%= error_tag(f, :tags_string) %> <%= error_tag(f, :tags_string) %>

View File

@ -27,9 +27,7 @@
<%= text_input(f, :tags_string, <%= text_input(f, :tags_string,
id: "tags-input", id: "tags-input",
class: "input input-primary", class: "input input-primary",
placeholder: gettext("tag1,tag2"), placeholder: gettext("tag1,tag2")
phx_update: "ignore",
value: Notes.get_tags_string(@changeset)
) %> ) %>
<%= error_tag(f, :tags_string) %> <%= error_tag(f, :tags_string) %>

View File

@ -27,9 +27,7 @@
<%= text_input(f, :tags_string, <%= text_input(f, :tags_string,
id: "tags-input", id: "tags-input",
class: "input input-primary", class: "input input-primary",
placeholder: gettext("tag1,tag2"), placeholder: gettext("tag1,tag2")
phx_update: "ignore",
value: Pipelines.get_tags_string(@changeset)
) %> ) %>
<%= error_tag(f, :tags_string) %> <%= error_tag(f, :tags_string) %>

View File

@ -124,9 +124,9 @@ msgstr ""
msgid "register" msgid "register"
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:42 #: lib/memex_web/live/context_live/form_component.html.heex:40
#: lib/memex_web/live/note_live/form_component.html.heex:42 #: lib/memex_web/live/note_live/form_component.html.heex:40
#: lib/memex_web/live/pipeline_live/form_component.html.heex:42 #: lib/memex_web/live/pipeline_live/form_component.html.heex:40
#: lib/memex_web/live/step_live/form_component.html.heex:28 #: lib/memex_web/live/step_live/form_component.html.heex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "save" msgid "save"

View File

@ -125,9 +125,9 @@ msgstr ""
msgid "register" msgid "register"
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:42 #: lib/memex_web/live/context_live/form_component.html.heex:40
#: lib/memex_web/live/note_live/form_component.html.heex:42 #: lib/memex_web/live/note_live/form_component.html.heex:40
#: lib/memex_web/live/pipeline_live/form_component.html.heex:42 #: lib/memex_web/live/pipeline_live/form_component.html.heex:40
#: lib/memex_web/live/step_live/form_component.html.heex:28 #: lib/memex_web/live/step_live/form_component.html.heex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "save" msgid "save"

View File

@ -301,17 +301,17 @@ msgstr ""
msgid "report bugs or request features" msgid "report bugs or request features"
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:43 #: lib/memex_web/live/context_live/form_component.html.heex:41
#: lib/memex_web/live/note_live/form_component.html.heex:43 #: lib/memex_web/live/note_live/form_component.html.heex:41
#: lib/memex_web/live/pipeline_live/form_component.html.heex:43 #: lib/memex_web/live/pipeline_live/form_component.html.heex:41
#: lib/memex_web/live/step_live/form_component.html.heex:29 #: lib/memex_web/live/step_live/form_component.html.heex:29
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "saving..." msgid "saving..."
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:39 #: lib/memex_web/live/context_live/form_component.html.heex:37
#: lib/memex_web/live/note_live/form_component.html.heex:39 #: lib/memex_web/live/note_live/form_component.html.heex:37
#: lib/memex_web/live/pipeline_live/form_component.html.heex:39 #: lib/memex_web/live/pipeline_live/form_component.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "select privacy" msgid "select privacy"
msgstr "" msgstr ""

View File

@ -131,3 +131,10 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "unauthorized" msgid "unauthorized"
msgstr "" msgstr ""
#: lib/memex/contexts/context.ex:75
#: lib/memex/notes/note.ex:74
#: lib/memex/pipelines/pipeline.ex:76
#, elixir-autogen, elixir-format, fuzzy
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr ""

View File

@ -290,17 +290,17 @@ msgstr ""
msgid "report bugs or request features" msgid "report bugs or request features"
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:43 #: lib/memex_web/live/context_live/form_component.html.heex:41
#: lib/memex_web/live/note_live/form_component.html.heex:43 #: lib/memex_web/live/note_live/form_component.html.heex:41
#: lib/memex_web/live/pipeline_live/form_component.html.heex:43 #: lib/memex_web/live/pipeline_live/form_component.html.heex:41
#: lib/memex_web/live/step_live/form_component.html.heex:29 #: lib/memex_web/live/step_live/form_component.html.heex:29
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "saving..." msgid "saving..."
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:39 #: lib/memex_web/live/context_live/form_component.html.heex:37
#: lib/memex_web/live/note_live/form_component.html.heex:39 #: lib/memex_web/live/note_live/form_component.html.heex:37
#: lib/memex_web/live/pipeline_live/form_component.html.heex:39 #: lib/memex_web/live/pipeline_live/form_component.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "select privacy" msgid "select privacy"
msgstr "" msgstr ""

View File

@ -130,3 +130,10 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "unauthorized" msgid "unauthorized"
msgstr "" msgstr ""
#: lib/memex/contexts/context.ex:75
#: lib/memex/notes/note.ex:74
#: lib/memex/pipelines/pipeline.ex:76
#, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr ""

View File

@ -18,7 +18,7 @@ defmodule MemexWeb.ContextLiveTest do
} }
@invalid_attrs %{ @invalid_attrs %{
"content" => nil, "content" => nil,
"tags_string" => "", "tags_string" => " ",
"slug" => nil, "slug" => nil,
"visibility" => nil "visibility" => nil
} }
@ -114,9 +114,13 @@ defmodule MemexWeb.ContextLiveTest do
assert_patch(show_live, Routes.context_show_path(conn, :edit, context.slug)) assert_patch(show_live, Routes.context_show_path(conn, :edit, context.slug))
assert show_live html =
|> form("#context-form", context: @invalid_attrs) show_live
|> render_change() =~ "can&#39;t be blank" |> form("#context-form", context: @invalid_attrs)
|> render_change()
assert html =~ "can&#39;t be blank"
assert html =~ "tags must be comma-delimited"
{:ok, _, html} = {:ok, _, html} =
show_live show_live

View File

@ -19,7 +19,7 @@ defmodule MemexWeb.NoteLiveTest do
} }
@invalid_attrs %{ @invalid_attrs %{
"content" => nil, "content" => nil,
"tags_string" => "", "tags_string" => " ",
"slug" => nil, "slug" => nil,
"visibility" => nil "visibility" => nil
} }
@ -54,9 +54,13 @@ defmodule MemexWeb.NoteLiveTest do
assert_patch(index_live, Routes.note_index_path(conn, :new)) assert_patch(index_live, Routes.note_index_path(conn, :new))
assert index_live html =
|> form("#note-form", note: @invalid_attrs) index_live
|> render_change() =~ "can&#39;t be blank" |> form("#note-form", note: @invalid_attrs)
|> render_change()
assert html =~ "can&#39;t be blank"
assert html =~ "tags must be comma-delimited"
{:ok, _, html} = {:ok, _, html} =
index_live index_live

View File

@ -17,7 +17,7 @@ defmodule MemexWeb.PipelineLiveTest do
} }
@invalid_attrs %{ @invalid_attrs %{
"description" => nil, "description" => nil,
"tags_string" => "", "tags_string" => " ",
"slug" => nil, "slug" => nil,
"visibility" => nil "visibility" => nil
} }
@ -128,9 +128,13 @@ defmodule MemexWeb.PipelineLiveTest do
assert_patch(show_live, Routes.pipeline_show_path(conn, :edit, pipeline.slug)) assert_patch(show_live, Routes.pipeline_show_path(conn, :edit, pipeline.slug))
assert show_live html =
|> form("#pipeline-form", pipeline: @invalid_attrs) show_live
|> render_change() =~ "can&#39;t be blank" |> form("#pipeline-form", pipeline: @invalid_attrs)
|> render_change()
assert html =~ "can&#39;t be blank"
assert html =~ "tags must be comma-delimited"
{:ok, _, html} = {:ok, _, html} =
show_live show_live

View File

@ -22,6 +22,6 @@ defmodule Memex.ContextsFixtures do
}) })
|> Contexts.create_context(user) |> Contexts.create_context(user)
context %{context | tags_string: nil}
end end
end end

View File

@ -22,6 +22,6 @@ defmodule Memex.NotesFixtures do
}) })
|> Notes.create_note(user) |> Notes.create_note(user)
note %{note | tags_string: nil}
end end
end end

View File

@ -22,6 +22,6 @@ defmodule Memex.PipelinesFixtures do
}) })
|> Pipelines.create_pipeline(user) |> Pipelines.create_pipeline(user)
pipeline %{pipeline | tags_string: nil}
end end
end end