From 2df2866a85af83dc01cafbd5e0da2805e56e4706 Mon Sep 17 00:00:00 2001 From: Gary Rennie Date: Thu, 10 Dec 2020 11:56:44 +0000 Subject: [PATCH 001/140] Allow disabling of new action This commit allows removing the "new" button and form actions by overriding a new `default_actions` function in the admin section. Currently only the `new` action can be disabled, but it should be possible to disable edit and delete with the same API. --- lib/kaffy/resource_admin.ex | 17 +++ .../controllers/resource_controller.ex | 130 ++++++++++-------- .../templates/resource/index.html.eex | 14 +- 3 files changed, 95 insertions(+), 66 deletions(-) diff --git a/lib/kaffy/resource_admin.ex b/lib/kaffy/resource_admin.ex index 97d25e08..b3d9a253 100644 --- a/lib/kaffy/resource_admin.ex +++ b/lib/kaffy/resource_admin.ex @@ -133,6 +133,23 @@ defmodule Kaffy.ResourceAdmin do Utils.get_assigned_value_or_default(resource, :ordering, desc: :id) end + @doc """ + `default_actions/1` takes a schema and returns the default actions for the schema. + + If `default_actions/1` is not defined, Kaffy will return `[:new, :edit, :delete]`. + + Example: + + ```elixir + def default_actions(_schema) do + [:new, :delete] + end + ``` + """ + def default_actions(resource) do + Utils.get_assigned_value_or_default(resource, :default_actions, [:new, :edit, :delete]) + end + @doc """ `authorized?/2` takes the schema and the current Plug.Conn struct and should return a boolean value. diff --git a/lib/kaffy_web/controllers/resource_controller.ex b/lib/kaffy_web/controllers/resource_controller.ex index 2d84c5f3..ccc9bf88 100644 --- a/lib/kaffy_web/controllers/resource_controller.ex +++ b/lib/kaffy_web/controllers/resource_controller.ex @@ -203,21 +203,21 @@ defmodule KaffyWeb.ResourceController do my_resource = Kaffy.Utils.get_resource(conn, context, resource) resource_name = Kaffy.ResourceAdmin.singular_name(my_resource) - case can_proceed?(my_resource, conn) do - false -> - unauthorized_access(conn) - - true -> - changeset = Kaffy.ResourceAdmin.create_changeset(my_resource, %{}) |> Map.put(:errors, []) - - render(conn, "new.html", - layout: {KaffyWeb.LayoutView, "app.html"}, - changeset: changeset, - context: context, - resource: resource, - resource_name: resource_name, - my_resource: my_resource - ) + with {:permitted, true} <- {:permitted, can_proceed?(my_resource, conn)}, + {:enabled, true} <- {:enabled, is_enabled?(my_resource, :new)} do + changeset = Kaffy.ResourceAdmin.create_changeset(my_resource, %{}) |> Map.put(:errors, []) + + render(conn, "new.html", + layout: {KaffyWeb.LayoutView, "app.html"}, + changeset: changeset, + context: context, + resource: resource, + resource_name: resource_name, + my_resource: my_resource + ) + else + {:permitted, false} -> unauthorized_access(conn) + {:enabled, false} -> not_enabled(conn) end end @@ -227,56 +227,56 @@ defmodule KaffyWeb.ResourceController do changes = Map.get(params, resource, %{}) resource_name = Kaffy.ResourceAdmin.singular_name(my_resource) - case can_proceed?(my_resource, conn) do - false -> - unauthorized_access(conn) - - true -> - case Kaffy.ResourceCallbacks.create_callbacks(conn, my_resource, changes) do - {:ok, entry} -> - case Map.get(params, "submit", "Save") do - "Save" -> - put_flash(conn, :success, "Created a new #{resource_name} successfully") - |> redirect( - to: Kaffy.Utils.router().kaffy_resource_path(conn, :index, context, resource) - ) + with {:permitted, true} <- {:permitted, can_proceed?(my_resource, conn)}, + {:enabled, true} <- {:enabled, is_enabled?(my_resource, :new)} do + case Kaffy.ResourceCallbacks.create_callbacks(conn, my_resource, changes) do + {:ok, entry} -> + case Map.get(params, "submit", "Save") do + "Save" -> + put_flash(conn, :success, "Created a new #{resource_name} successfully") + |> redirect( + to: Kaffy.Utils.router().kaffy_resource_path(conn, :index, context, resource) + ) - "Save and add another" -> - conn - |> put_flash(:success, "#{resource_name} saved successfully") - |> redirect( - to: Kaffy.Utils.router().kaffy_resource_path(conn, :new, context, resource) - ) + "Save and add another" -> + conn + |> put_flash(:success, "#{resource_name} saved successfully") + |> redirect( + to: Kaffy.Utils.router().kaffy_resource_path(conn, :new, context, resource) + ) - "Save and continue editing" -> - put_flash(conn, :success, "Created a new #{resource_name} successfully") - |> redirect_to_resource(context, resource, entry) - end + "Save and continue editing" -> + put_flash(conn, :success, "Created a new #{resource_name} successfully") + |> redirect_to_resource(context, resource, entry) + end - {:error, %Ecto.Changeset{} = changeset} -> - render(conn, "new.html", - layout: {KaffyWeb.LayoutView, "app.html"}, - changeset: changeset, - context: context, - resource: resource, - resource_name: resource_name, - my_resource: my_resource - ) + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "new.html", + layout: {KaffyWeb.LayoutView, "app.html"}, + changeset: changeset, + context: context, + resource: resource, + resource_name: resource_name, + my_resource: my_resource + ) - {:error, {entry, error}} when is_binary(error) -> - changeset = Ecto.Changeset.change(entry) + {:error, {entry, error}} when is_binary(error) -> + changeset = Ecto.Changeset.change(entry) - conn - |> put_flash(:error, error) - |> render("new.html", - layout: {KaffyWeb.LayoutView, "app.html"}, - changeset: changeset, - context: context, - resource: resource, - resource_name: resource_name, - my_resource: my_resource - ) - end + conn + |> put_flash(:error, error) + |> render("new.html", + layout: {KaffyWeb.LayoutView, "app.html"}, + changeset: changeset, + context: context, + resource: resource, + resource_name: resource_name, + my_resource: my_resource + ) + end + else + {:permitted, false} -> unauthorized_access(conn) + {:enabled, false} -> not_enabled(conn) end end @@ -374,12 +374,22 @@ defmodule KaffyWeb.ResourceController do Kaffy.ResourceAdmin.authorized?(resource, conn) end + defp is_enabled?(resource, action) do + action in Kaffy.ResourceAdmin.default_actions(resource) + end + defp unauthorized_access(conn) do conn |> put_flash(:error, "You are not authorized to access that page") |> redirect(to: Kaffy.Utils.router().kaffy_home_path(conn, :index)) end + defp not_enabled(conn) do + conn + |> put_flash(:error, "This action has been disabled.") + |> redirect(to: Kaffy.Utils.router().kaffy_home_path(conn, :index)) + end + defp redirect_to_resource(conn, context, resource, entry) do redirect(conn, to: diff --git a/lib/kaffy_web/templates/resource/index.html.eex b/lib/kaffy_web/templates/resource/index.html.eex index 69ae76af..e49abd18 100644 --- a/lib/kaffy_web/templates/resource/index.html.eex +++ b/lib/kaffy_web/templates/resource/index.html.eex @@ -9,12 +9,14 @@
-
- <%= link to: Kaffy.Utils.router().kaffy_resource_path(@conn, :new, @context, @resource), class: "btn btn-outline-primary" do %> - - New <%= Kaffy.ResourceAdmin.singular_name(@my_resource) %> - <% end %> -
+ <%= if :new in Kaffy.ResourceAdmin.default_actions(@my_resource) do %> +
+ <%= link to: Kaffy.Utils.router().kaffy_resource_path(@conn, :new, @context, @resource), class: "btn btn-outline-primary" do %> + + New <%= Kaffy.ResourceAdmin.singular_name(@my_resource) %> + <% end %> +
+ <% end %> From b838a4f9ecbeb355d854beba723909bdd42f8423 Mon Sep 17 00:00:00 2001 From: Bobby Date: Mon, 11 Oct 2021 07:36:36 -0400 Subject: [PATCH 002/140] Support passing in a function to allows rendering an array ecto type as a multi select input (#219) * Support passing in a function to allows rendering an array ecto type as a multi select input * Fix logic error --- README.md | 64 +++++++++++++++++++++++++++----------- lib/kaffy/resource_form.ex | 18 ++++++++--- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index bbffc7d5..54c4d437 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,39 @@ without the need to touch the current codebase. It was inspired by django's love ## Sections -- [Live Demo](#demo) +- [Introduction](#introduction) +- [Sections](#sections) +- [Sponsors](#sponsors) +- [Demo](#demo) - [Minimum Requirements](#minimum-requirements) - [Installation](#installation) -- [Custom Configurations](#configurations) -- [Customize the Side Menu](#side-menu) -- [Customize the Dashboard Page](#dashboard-page) -- [Customize the Index Pages](#index-pages) -- [Customize the Form Pages](#form-pages) -- [Custom Form Fields](#custom-form-fields) -- [Customize the Queries](#customize-the-queries) -- [Extensions](#extensions) -- [Embedded Schemas and JSON Fields](#embedded-schemas-and-json-fields) -- [Searching Records](#search) -- [Authorizing Access To Resources](#authorization) -- [Custom Changesets](#changesets) -- [Customizing Resource Names](#singular-vs-plural) -- [Custom Actions](#custom-actions) -- [Custom Callbacks When Saving Records](#callbacks) -- [Simple Scheduled Tasks](#scheduled-tasks) -- [The Driving Points Behind Kaffy's Development](#the-driving-points) + - [Add `kaffy` as a dependency](#add-kaffy-as-a-dependency) + - [These are the minimum configurations required](#these-are-the-minimum-configurations-required) +- [Customizations](#customizations) + - [Configurations](#configurations) + - [Breaking change in v0.9](#breaking-change-in-v09) + - [Dashboard page](#dashboard-page) + - [Side Menu](#side-menu) + - [Custom Links](#custom-links) + - [Custom Pages](#custom-pages) + - [Index pages](#index-pages) + - [Form Pages](#form-pages) + - [Association Forms](#association-forms) + - [Custom Form Fields](#custom-form-fields) + - [Customize the Queries](#customize-the-queries) + - [Extensions](#extensions) + - [Embedded Schemas and JSON Fields](#embedded-schemas-and-json-fields) + - [Search](#search) + - [Authorization](#authorization) + - [Changesets](#changesets) + - [Singular vs Plural](#singular-vs-plural) + - [Custom Actions](#custom-actions) + - [Single Resource Actions](#single-resource-actions) + - [List Actions](#list-actions) + - [Callbacks](#callbacks) + - [Overwrite actions](#overwrite-actions) + - [Scheduled Tasks](#scheduled-tasks) +- [The Driving Points](#the-driving-points) ## Sponsors @@ -426,6 +439,7 @@ Options can be: - `:create` - can be `:editable` which means it can be edited when creating a new record, or `:readonly` which means this field is visible when creating a new record but cannot be edited, or `:hidden` which means this field shouldn't be visible when creating a new record. It is `:editable` by default. - `:update` - can be `:editable` which means it can be edited when updating an existing record, or `:readonly` which means this field is visible when updating a record but cannot be edited, or `:hidden` which means this field shouldn't be visible when updating record. It is `:editable` by default. - `:help_text` - extra "help text" to be displayed with the form field. +- `:values_fn` - This allows passing in a function to populate the list of possible values for a `:array` field. The field will be rendered as a multi-select input. The function should be of arity 2 and the arguments are the entity and the conn. See example below Result @@ -440,6 +454,20 @@ Notice that: Setting a field's type to `:richtext` will render a rich text editor. +The `:values_fn` is passed the entity you are editing and the conn (in that order) and must return a list of tuples that represent the {name, value} to use in the multi select. An example of this is as follows: +``` + def form_fields(_schema) do + [ + .... + some_array_field: %{ + values_fn: fn entity, conn -> + some_values = MyApp.Thing.fetch_values(entity.id, conn) + Enum.map(some_values, &{&1.name, &1.id}) + end + } + ] + ``` + #### Association Forms A `belongs_to` association should be referenced by the field name, *not* the association name. For example, a schema with the following association: diff --git a/lib/kaffy/resource_form.ex b/lib/kaffy/resource_form.ex index e1085a35..dee40e0b 100644 --- a/lib/kaffy/resource_form.ex +++ b/lib/kaffy/resource_form.ex @@ -185,12 +185,20 @@ defmodule Kaffy.ResourceForm do multiple_select(form, field, values, [value: value] ++ opts) {:array, _} -> - value = - data - |> Map.get(field, "") - |> Kaffy.Utils.json().encode!(escape: :html_safe, pretty: true) + case !is_nil(options[:values_fn]) && is_function(options[:values_fn], 2) do + true -> + values = options[:values_fn].(data, conn) + value = Map.get(data, field, nil) + multiple_select(form, field, values, [value: value] ++ opts) - textarea(form, field, [value: value, rows: 4, placeholder: "JSON Content"] ++ opts) + false -> + value = + data + |> Map.get(field, "") + |> Kaffy.Utils.json().encode!(escape: :html_safe, pretty: true) + + textarea(form, field, [value: value, rows: 4, placeholder: "JSON Content"] ++ opts) + end :file -> file_input(form, field, opts) From 947fc63fece29c6f2bd43118e4500bc0fbd0fb53 Mon Sep 17 00:00:00 2001 From: Kel Cecil Date: Mon, 11 Oct 2021 07:38:03 -0400 Subject: [PATCH 003/140] Decode array before adding to changeset (#212) Array values are encoded before added to the form for editing but not decoded before evaluation by the changeset. This extra formatting causes the changeset to mark arrays as invalid with no recourse by the user. This commit modifies `Kaffy.ResourceSchema.get_map_fields/1` to return `{:array, _}` fields to be decoded by `Kaffy.ResourceParams.decode_map_fields/3`. Fixes #130 and #184 --- lib/kaffy/resource_schema.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/kaffy/resource_schema.ex b/lib/kaffy/resource_schema.ex index 62dc62bc..57beecea 100644 --- a/lib/kaffy/resource_schema.ex +++ b/lib/kaffy/resource_schema.ex @@ -304,11 +304,17 @@ defmodule Kaffy.ResourceSchema do def get_map_fields(schema) do get_all_fields(schema) |> Enum.filter(fn - {_f, options} -> - options.type == :map + {_f, %{type: :map}} -> + true + + {_f, %{type: {:array, _}}} -> + true f when is_atom(f) -> f == :map + + _ -> + false end) end From 01fbef7cd7bca342f8c738a0a9c2ba206f26e9db Mon Sep 17 00:00:00 2001 From: Kel Cecil Date: Mon, 11 Oct 2021 07:54:32 -0400 Subject: [PATCH 004/140] Add first pass at CI using Actions This is intended to setup matrix builds for minor versions of Elixir supported by Kaffy on PR submission and push to master. --- .github/workflows/elixir.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/elixir.yml diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml new file mode 100644 index 00000000..f9e675c1 --- /dev/null +++ b/.github/workflows/elixir.yml @@ -0,0 +1,34 @@ +name: Kaffy CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + name: Test ${{matrix.otp}} / Elixir ${{matrix.elixir}} + runs-on: ubuntu-latest + strategy: + matrix: + otp: ['20.3', '21.3', '22.3'] + elixir: ['1.7.4', '1.8.2', '1.9.4', '1.10.4', '1.11.4', '1.12.3'] + + steps: + - uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-elixir@885971a72ed1f9240973bd92ab57af8c1aa68f24 + with: + elixir-version: ${{matrix.elixir}} # Define the elixir version [required] + otp-version: ${{matrix.otp}} # Define the OTP version [required] + - name: Restore dependencies cache + uses: actions/cache@v2 + with: + path: deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix- + - name: Install dependencies + run: mix deps.get + - name: Run tests + run: mix test From c682490d36200a809afdbdea4c7139cc320ad648 Mon Sep 17 00:00:00 2001 From: Kel Cecil Date: Mon, 25 Oct 2021 14:10:55 -0400 Subject: [PATCH 005/140] Add basic CI for supported Elixir versions (#221) * Fix CI setup This commit fixes the Github Actions config to use the setup-beam actions package instead of as is suggested by Github. * Update documentation to note Elixir 1.8 as minimum version The matrix CI operation shows the use of the ceil/1. This method was added to Elixir in 1.8 and effectively means that kaffy won't run on Elixir 1.7 as written. Since this is a relatively old version of Elixir, this updates references to require Elixir 1.8 or higher. --- .github/workflows/elixir.yml | 6 +++--- README.md | 2 +- mix.exs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index f9e675c1..ff1ead0e 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -12,13 +12,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - otp: ['20.3', '21.3', '22.3'] - elixir: ['1.7.4', '1.8.2', '1.9.4', '1.10.4', '1.11.4', '1.12.3'] + otp: ['22.3'] + elixir: ['1.8.2', '1.9.4', '1.10.4', '1.11.4', '1.12.3'] steps: - uses: actions/checkout@v2 - name: Set up Elixir - uses: erlef/setup-elixir@885971a72ed1f9240973bd92ab57af8c1aa68f24 + uses: erlef/setup-beam@v1 with: elixir-version: ${{matrix.elixir}} # Define the elixir version [required] otp-version: ${{matrix.otp}} # Define the OTP version [required] diff --git a/README.md b/README.md index 54c4d437..c9d36c55 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Become a sponsor through Kaffy's [OpenCollective](https://opencollective.com/kaf ## Minimum Requirements -- elixir 1.7.0 +- elixir 1.8.0 - phoenix 1.4.0 ## Installation diff --git a/mix.exs b/mix.exs index 47cb3fff..bcdc8a63 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule Kaffy.MixProject do [ app: :kaffy, version: @version, - elixir: "~> 1.7", + elixir: "~> 1.8", compilers: [:phoenix] ++ Mix.compilers(), start_permanent: Mix.env() == :prod, description: description(), From 7f85dedbe557470f45f1d1c75783a73aaa81f5eb Mon Sep 17 00:00:00 2001 From: Rossukhon Leagmongkol Date: Tue, 26 Oct 2021 23:20:29 +0700 Subject: [PATCH 006/140] Config assets to use static path (#211) * Config assets to use static path * Update README.md --- README.md | 4 ++- lib/kaffy/utils.ex | 8 +++++ lib/kaffy_web/templates/home/_card.html.eex | 2 +- lib/kaffy_web/templates/layout/app.html.eex | 32 ++++++++++---------- lib/kaffy_web/templates/layout/bare.html.eex | 32 ++++++++++---------- 5 files changed, 44 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index c9d36c55..940aa8a2 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,10 @@ use Kaffy.Routes, scope: "/admin", pipe_through: [:some_plug, :authenticate] # [:kaffy_browser, :some_plug, :authenticate] # in your endpoint.ex +# configure the path to your application static assets in :at +# the path must end with `/kaffy` plug Plug.Static, - at: "/kaffy", + at: "/kaffy", # or "/path/to/your/static/kaffy" from: :kaffy, gzip: false, only: ~w(assets) diff --git a/lib/kaffy/utils.ex b/lib/kaffy/utils.ex index 9975f44d..865cdc10 100644 --- a/lib/kaffy/utils.ex +++ b/lib/kaffy/utils.ex @@ -9,6 +9,14 @@ defmodule Kaffy.Utils do env(:admin_title, "Kaffy") end + @doc """ + Returns the static path to the asset. + """ + @spec static_asset_path(Plug.Conn.t(), String.t()) :: String.t() + def static_asset_path(conn, asset_path) do + router().static_path(conn, asset_path) + end + @doc """ Returns the :admin_logo config if present, otherwise returns Kaffy default logo. """ diff --git a/lib/kaffy_web/templates/home/_card.html.eex b/lib/kaffy_web/templates/home/_card.html.eex index eefa250b..7597366b 100644 --- a/lib/kaffy_web/templates/home/_card.html.eex +++ b/lib/kaffy_web/templates/home/_card.html.eex @@ -1,7 +1,7 @@
- circle-image + " class="card-img-absolute" alt="circle-image" />

<%= @widget.title %>

<%= @widget.content %>

<%= @widget.subcontent %>
diff --git a/lib/kaffy_web/templates/layout/app.html.eex b/lib/kaffy_web/templates/layout/app.html.eex index 1aa5308c..8221d42f 100644 --- a/lib/kaffy_web/templates/layout/app.html.eex +++ b/lib/kaffy_web/templates/layout/app.html.eex @@ -4,16 +4,16 @@ <%= Kaffy.Utils.title() %> - Kaffy - - - - - - + "> + "> + "> + "> + "> + " /> <%= for css <- Kaffy.Utils.extensions(@conn).stylesheets do %> <%= css %> <% end %> - +
@@ -154,15 +154,15 @@
- - - - - - - - - + + + + + + + + + <%= for js <- Kaffy.Utils.extensions(@conn).javascripts do %> <%= js %> <% end %> diff --git a/lib/kaffy_web/templates/layout/bare.html.eex b/lib/kaffy_web/templates/layout/bare.html.eex index 603390f7..a6100bf5 100644 --- a/lib/kaffy_web/templates/layout/bare.html.eex +++ b/lib/kaffy_web/templates/layout/bare.html.eex @@ -4,16 +4,16 @@ <%= Kaffy.Utils.title() %> - Kaffy - - - - - - + "> + "> + "> + "> + "> + " /> <%= for css <- Kaffy.Utils.extensions(@conn).stylesheets do %> <%= css %> <% end %> - +
@@ -57,14 +57,14 @@
- - - - - - - - - + + + + + + + + + From 43178b83088e22277fa90456ccc0b9df8cdb0cc0 Mon Sep 17 00:00:00 2001 From: Danni Friedland Date: Mon, 1 Nov 2021 15:08:21 +0200 Subject: [PATCH 007/140] updating phoenix_html (#215) This updates the phoenix_html dependency to allow use of Phoenix 1.6. Looking at changelog between 2.x and 3.0, it appears that Kaffy uses anything deprecated in 3.0. I think this should be safe for a minor version upgrade. --- lib/kaffy/resource_form.ex | 8 ++++---- mix.exs | 2 +- test/kaffy_test.exs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/kaffy/resource_form.ex b/lib/kaffy/resource_form.ex index dee40e0b..957dd641 100644 --- a/lib/kaffy/resource_form.ex +++ b/lib/kaffy/resource_form.ex @@ -73,10 +73,10 @@ defmodule Kaffy.ResourceForm do end end - def form_field(changeset, form, {field, options}, opts) do - type = Kaffy.ResourceSchema.field_type(changeset.data.__struct__, field) - build_html_input(changeset.data, form, {field, options}, type, opts) - end + # def form_field(changeset, form, {field, options}, opts) do + # type = Kaffy.ResourceSchema.field_type(changeset.data.__struct__, field) + # build_html_input(changeset.data, form, {field, options}, type, opts) + # end defp build_html_input(schema, form, {field, options}, type, opts, readonly \\ false) do data = schema diff --git a/mix.exs b/mix.exs index bcdc8a63..e3245fee 100644 --- a/mix.exs +++ b/mix.exs @@ -31,7 +31,7 @@ defmodule Kaffy.MixProject do defp deps do [ {:phoenix, "~> 1.4"}, - {:phoenix_html, "~> 2.11"}, + {:phoenix_html, "~> 2.13 or ~> 3.0"}, {:mock, "~> 0.3.0", only: :test}, {:ecto, "~> 3.0"}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} diff --git a/test/kaffy_test.exs b/test/kaffy_test.exs index 143a0dec..cca2b7e6 100644 --- a/test/kaffy_test.exs +++ b/test/kaffy_test.exs @@ -2,7 +2,7 @@ defmodule KaffyTest do use ExUnit.Case doctest Kaffy alias KaffyTest.Schemas.{Person, Pet} - alias KaffyTest.Admin.PersonAdmin + # alias KaffyTest.Admin.PersonAdmin test "greets the world" do assert Kaffy.hello() == :world @@ -82,7 +82,7 @@ defmodule KaffyTest do end describe "Kaffy.ResourceAdmin" do - alias Kaffy.ResourceAdmin + # alias Kaffy.ResourceAdmin # [Qizot] I don't know if this test should be valid anymore if associations are allowed # test "index/1 should return a keyword list of fields and their values" do From 418893961c67ec77559f9efd90f58c656f051389 Mon Sep 17 00:00:00 2001 From: Kel Cecil Date: Tue, 2 Nov 2021 15:43:55 -0400 Subject: [PATCH 008/140] Expand CI to most recent three minor releases of Elixir (#223) This attempts to expand the CI for PRs and master to run on for the three most recent minor versions of Elixir using corresponding Erlang/OTP versions. Each release of Elixir has varying support for different versions of OTP (see: https://hexdocs.pm/elixir/1.12/compatibility-and-deprecations.html). This attempts to use excludes to remove incompatible combos of Elixir and OTP from the matrix while adding experimental support for Elixir 1.13. --- .github/workflows/{elixir.yml => ci.yml} | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) rename .github/workflows/{elixir.yml => ci.yml} (50%) diff --git a/.github/workflows/elixir.yml b/.github/workflows/ci.yml similarity index 50% rename from .github/workflows/elixir.yml rename to .github/workflows/ci.yml index ff1ead0e..1ce15208 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Kaffy CI +name: Kaffy CI (Elixir 1.10) on: push: @@ -12,9 +12,21 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - otp: ['22.3'] - elixir: ['1.8.2', '1.9.4', '1.10.4', '1.11.4', '1.12.3'] - + otp: ['21', '22', '23', '24'] + elixir: ['1.10.3', '1.11.4', '1.12'] + # Exclude invalid combinations of Elixir and OTP + exclude: + - otp: '21' + elixir: '1.12' + - otp: '24' + elixir: '1.10.3' + # Include the release candidate for the next Elixir, but don't + # fail CI. + include: + - elixir: '1.13' + otp: '24' + experimental: true + steps: - uses: actions/checkout@v2 - name: Set up Elixir @@ -26,8 +38,8 @@ jobs: uses: actions/cache@v2 with: path: deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: ${{ runner.os }}-mix- + key: ${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}-mix- - name: Install dependencies run: mix deps.get - name: Run tests From 336b5bb9d1d566ba1d5bb30ef7058ce780b1a66f Mon Sep 17 00:00:00 2001 From: Abdullah Esmail Date: Wed, 3 Nov 2021 10:39:23 +0300 Subject: [PATCH 009/140] display records correclty for schemas that have field names like "context" and "resource" --- lib/kaffy/resource_query.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kaffy/resource_query.ex b/lib/kaffy/resource_query.ex index 4f13b924..44d19f9f 100644 --- a/lib/kaffy/resource_query.ex +++ b/lib/kaffy/resource_query.ex @@ -8,8 +8,8 @@ defmodule Kaffy.ResourceQuery do page = Map.get(params, "page", "1") |> String.to_integer() search = Map.get(params, "search", "") |> String.trim() search_fields = Kaffy.ResourceAdmin.search_fields(resource) - filtered_fields = get_filter_fields(params, resource) - ordering = get_ordering(resource, params) + filtered_fields = get_filter_fields(conn.query_params, resource) + ordering = get_ordering(resource, conn.query_params) current_offset = (page - 1) * per_page schema = resource[:schema] From f6c6fa13a9d5de19c9a67277a0a2f1c793af0bdf Mon Sep 17 00:00:00 2001 From: Abdullah Esmail Date: Wed, 3 Nov 2021 20:12:23 +0300 Subject: [PATCH 010/140] disable the select html tag when it has custom choices defined. --- lib/kaffy/resource_form.ex | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/kaffy/resource_form.ex b/lib/kaffy/resource_form.ex index 957dd641..f7c58b13 100644 --- a/lib/kaffy/resource_form.ex +++ b/lib/kaffy/resource_form.ex @@ -33,9 +33,7 @@ defmodule Kaffy.ResourceForm do end end - def form_field(changeset, form, field, opts \\ []) - - def form_field(changeset, form, {field, options}, opts) do + def form_field(changeset, form, {field, options}, opts \\ []) do options = options || %{} type = @@ -59,7 +57,7 @@ defmodule Kaffy.ResourceForm do cond do !is_nil(choices) -> - select(form, field, choices, class: "custom-select") + select(form, field, choices, class: "custom-select", disabled: permission == :readonly) true -> build_html_input( From 6172cead4058f7fbc88e6acc9f2cda48aee4e18f Mon Sep 17 00:00:00 2001 From: Abdullah Esmail Date: Wed, 3 Nov 2021 23:23:18 +0300 Subject: [PATCH 011/140] include search support for id, integer, and decimal fields. --- README.md | 12 +++--- lib/kaffy/resource_admin.ex | 6 +-- lib/kaffy/resource_query.ex | 71 +++++++++++++++++++++++++++++++++--- lib/kaffy/resource_schema.ex | 4 +- 4 files changed, 77 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 940aa8a2..db87112a 100644 --- a/README.md +++ b/README.md @@ -456,7 +456,7 @@ Notice that: Setting a field's type to `:richtext` will render a rich text editor. -The `:values_fn` is passed the entity you are editing and the conn (in that order) and must return a list of tuples that represent the {name, value} to use in the multi select. An example of this is as follows: +The `:values_fn` is passed the entity you are editing and the conn (in that order) and must return a list of tuples that represent the {name, value} to use in the multi select. An example of this is as follows: ``` def form_fields(_schema) do [ @@ -475,7 +475,7 @@ The `:values_fn` is passed the entity you are editing and the conn (in that orde A `belongs_to` association should be referenced by the field name, *not* the association name. For example, a schema with the following association: ``` -schema "my_model" do +schema "my_model" do ... belongs_to :owner, App.Owners.Owner ... @@ -657,14 +657,14 @@ Kaffy has support for ecto's [embedded schemas](https://hexdocs.pm/ecto/Ecto.Sch Kaffy provides very basic search capabilities. -Currently, only `:string` and `:text` fields are supported for search. +Supported field types are: `:string`, `:textarea`, `:richtext`, `:id`, `:integer`, and `:decimal`. If you need to customize the list of fields to search against, define the `search_fields/1` function. ```elixir defmodule MyApp.Blog.PostAdmin do def search_fields(_schema) do - [:title, :slug, :body] + [:id, :title, :body, :views] end end ``` @@ -678,6 +678,7 @@ defmodule MyApp.Blog.PostAdmin do [ :title, :body, + :view, category: [:name, :description] ] end @@ -685,9 +686,8 @@ end ``` This function takes a schema and returns a list of schema fields that you want to search. -All the fields must be of type `:string` or `:text`. -If this function is not defined, Kaffy will return all `:string` and `:text` fields by default. +If this function is not defined, Kaffy will return all fields with supported types by default. ### Authorization diff --git a/lib/kaffy/resource_admin.ex b/lib/kaffy/resource_admin.ex index 2eabad26..55ae2734 100644 --- a/lib/kaffy/resource_admin.ex +++ b/lib/kaffy/resource_admin.ex @@ -96,15 +96,15 @@ defmodule Kaffy.ResourceAdmin do end @doc """ - `search_fields/1` takes a schema and must return a list of `:string` fields to search against when typing in the search box. + `search_fields/1` takes a schema and must return a list of `:string`, ':id`, `:integer`, and `:decimal` fields to search against when typing in the search box. - If `search_fields/1` is not defined, Kaffy will return all the `:string` fields of the schema. + If `search_fields/1` is not defined, Kaffy will return all the fields of the schema with the supported types mentioned earlier. Example: ```elixir def search_fields(_schema) do - [:title, :slug, :body] + [:id, :title, :slug, :body, :price, :quantity] end ``` """ diff --git a/lib/kaffy/resource_query.ex b/lib/kaffy/resource_query.ex index 4f13b924..9f4e60a4 100644 --- a/lib/kaffy/resource_query.ex +++ b/lib/kaffy/resource_query.ex @@ -121,22 +121,83 @@ defmodule Kaffy.ResourceQuery do true -> term = search + |> String.trim() |> String.replace("%", "\%") |> String.replace("_", "\_") - term = "%#{term}%" + {term, term_type} = + case Decimal.parse(term) do + {:ok, value} -> + # this is the return value for the decimal package pre-2.0 + number = if value.exp >= 0, do: :integer, else: :decimal + + case number do + :integer -> + v = Decimal.to_integer(value) |> to_string() + {v, number} + + :decimal -> + {term, number} + end + + {value, ""} -> + # this is the return value for the decimal package since 2.0 + number = if Decimal.integer?(value), do: :integer, else: :decimal + + case number do + :integer -> + v = Decimal.to_integer(value) |> to_string() + {v, number} + + :decimal -> + {term, number} + end + + _ -> + {term, :string} + end Enum.reduce(search_fields, query, fn - {association, fields}, q -> + {association, fields}, q when is_list(fields) -> query = from(s in q, join: a in assoc(s, ^association)) Enum.reduce(fields, query, fn f, current_query -> - from([..., r] in current_query, - or_where: ilike(type(field(r, ^f), :string), ^term) - ) + the_association = Kaffy.ResourceSchema.association(schema, association).queryable + + if Kaffy.ResourceSchema.field_type(the_association, f) == :string or + term_type == :string do + term = "%#{term}%" + + from([..., r] in current_query, + or_where: ilike(type(field(r, ^f), :string), ^term) + ) + else + if Kaffy.ResourceSchema.field_type(schema, f) in [:id, :integer] and + term_type == :decimal do + current_query + else + from([..., r] in current_query, + or_where: field(r, ^f) == ^term + ) + end + end end) + {f, t}, q when is_atom(t) -> + if Kaffy.ResourceSchema.field_type(schema, f) == :string or term_type == :string do + term = "%#{term}%" + from(s in q, or_where: ilike(type(field(s, ^f), :string), ^term)) + else + if Kaffy.ResourceSchema.field_type(schema, f) in [:id, :integer] and + term_type == :decimal do + q + else + from(s in q, or_where: field(s, ^f) == ^term) + end + end + f, q -> + term = "%#{term}%" from(s in q, or_where: ilike(type(field(s, ^f), :string), ^term)) end) end diff --git a/lib/kaffy/resource_schema.ex b/lib/kaffy/resource_schema.ex index 57beecea..20557923 100644 --- a/lib/kaffy/resource_schema.ex +++ b/lib/kaffy/resource_schema.ex @@ -289,10 +289,10 @@ defmodule Kaffy.ResourceSchema do Enum.filter(fields(schema), fn f -> field_name = elem(f, 0) - field_type(schema, f).type in [:string, :textarea, :richtext] && + field_type(schema, f).type in [:string, :textarea, :richtext, :id, :integer, :decimal] && field_name in persisted_fields end) - |> Enum.map(fn {f, _} -> f end) + |> Enum.map(fn {f, options} -> {f, options.type} end) end def filter_fields(_), do: nil From 59a73a1798f74ae48bc58a5d2199f0d908cd20dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Sat, 11 Jun 2022 19:46:16 +0200 Subject: [PATCH 012/140] Introduce a bit smarter inflector This is to get a bit better chance to guess the correct plural form of a resource. --- README.md | 2 +- lib/kaffy/inflector.ex | 80 ++++++++++++++++++++++++++++++++++++ lib/kaffy/resource_admin.ex | 2 +- test/inflector_test.exs | 27 ++++++++++++ test/resource_admin_test.exs | 46 +++++++++++++++++++++ 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 lib/kaffy/inflector.ex create mode 100644 test/inflector_test.exs create mode 100644 test/resource_admin_test.exs diff --git a/README.md b/README.md index 940aa8a2..86970671 100644 --- a/README.md +++ b/README.md @@ -735,7 +735,7 @@ And if that is not defined, `Ecto.Changeset.change/2` will be called. ### Singular vs Plural -Some names do not follow the "add an s" rule. Sometimes you just need to change some terms to your liking. +Kaffy makes some effor to guess a correct plural form of the resource, but in some cases it will fail. Should this happen, you may want to set a correct name yourself. This is why `singular_name/1` and `plural_name/1` are there. diff --git a/lib/kaffy/inflector.ex b/lib/kaffy/inflector.ex new file mode 100644 index 00000000..fb8cadac --- /dev/null +++ b/lib/kaffy/inflector.ex @@ -0,0 +1,80 @@ +defmodule Kaffy.Inflector do + @moduledoc """ + Module holding functions related to inflection of words. It does not attempt to + be perfect, rather aiming at being "good enough". + """ + + @exceptions %{ + "Person" => "People", + "Roof" => "Roofs", + "Belief" => "Beliefs", + "Chef" => "Chefs", + "Chief" => "Chiefs", + "Index" => "Indices", + "Sheep" => "Sheep", + "Series" => "Series", + "Species" => "Species", + "Deer" => "Deer", + "Child" => "Children", + "Man" => "Men", + "Woman" => "Women", + "Goose" => "Geese", + "Tooth" => "Teeth", + "Foot" => "Feet", + "Mouse" => "Mice" + } + + @vowels ["a", "e", "i", "o", "u", "y"] + + @doc """ + Attempts to create a correct plural version by following rules outlined at: + https://www.grammarly.com/blog/plural-nouns/ + + Some of these rules are conscously ommited as being rare and hard to implement. + """ + def pluralize(noun) when is_binary(noun) do + case Map.has_key?(@exceptions, noun) do + true -> @exceptions[noun] + false -> try_to_pluralize(noun) + end + end + + defp try_to_pluralize(noun) do + reversed_noun = String.reverse(noun) + + case reversed_noun do + <<"si", rest::binary>> -> + String.reverse(rest) <> "es" + + <<"s", _rest::binary>> -> + noun <> "es" + + <<"hs", _rest::binary>> -> + noun <> "es" + + <<"hc", _rest::binary>> -> + noun <> "es" + + <<"x", _rest::binary>> -> + noun <> "es" + + <<"z", _rest::binary>> -> + noun <> "es" + + <<"ef", rest::binary>> -> + String.reverse(rest) <> "ves" + + <<"f", rest::binary>> -> + String.reverse(rest) <> "ves" + + <<"y", preceding::binary-size(1), rest::binary>> -> + case preceding in @vowels do + true -> noun <> "s" + false -> String.reverse(rest) <> preceding <> "ies" + end + + _ -> + noun <> "s" + end + end +end diff --git a/lib/kaffy/resource_admin.ex b/lib/kaffy/resource_admin.ex index 2eabad26..4da8bac5 100644 --- a/lib/kaffy/resource_admin.ex +++ b/lib/kaffy/resource_admin.ex @@ -281,7 +281,7 @@ defmodule Kaffy.ResourceAdmin do ``` """ def plural_name(resource) do - default = singular_name(resource) <> "s" + default = singular_name(resource) |> Kaffy.Inflector.pluralize() Utils.get_assigned_value_or_default(resource, :plural_name, default) end diff --git a/test/inflector_test.exs b/test/inflector_test.exs new file mode 100644 index 00000000..ec95cf5e --- /dev/null +++ b/test/inflector_test.exs @@ -0,0 +1,27 @@ +defmodule Kaffy.InflectorTest do + use ExUnit.Case, async: true + alias Kaffy.Inflector + + test "known exception", do: assert_plural("Person", "People") + test "known exception matching another rule", do: assert_plural("Chief", "Chiefs") + + test "ending with -s", do: assert_plural("Bus", "Buses") + test "ending with -sh", do: assert_plural("Marsh", "Marshes") + test "ending with -ch", do: assert_plural("Lunch", "Lunches") + test "ending with -x", do: assert_plural("Tax", "Taxes") + test "ending with -z", do: assert_plural("Buzz", "Buzzes") + + test "ending with -fe", do: assert_plural("Wife", "Wives") + test "ending with -f", do: assert_plural("Calf", "Calves") + + test "ending with vowel + -y", do: assert_plural("Clay", "Clays") + test "ending with consonant + -y", do: assert_plural("Company", "Companies") + + test "ending with -is", do: assert_plural("Analysis", "Analyses") + + test "regular word", do: assert_plural("Node", "Nodes") + + defp assert_plural(singular, plural) do + assert Inflector.pluralize(singular) == plural + end + end diff --git a/test/resource_admin_test.exs b/test/resource_admin_test.exs new file mode 100644 index 00000000..db27d0d9 --- /dev/null +++ b/test/resource_admin_test.exs @@ -0,0 +1,46 @@ +defmodule Kaffy.ResourceAdminTest do + use ExUnit.Case, async: true + alias Kaffy.ResourceAdmin + + defmodule Cactus do + end + + defmodule CactusAdmin do + def plural_name(_), do: "Cacti" + end + + defmodule Person do + end + + defmodule PersonAdmin do + end + + defmodule Nested.Node do + end + + defmodule Nested.NodeAdmin do + end + + defmodule NestedNodeAdmin do + def singular_name(_), do: "NestedNode" + end + + describe "plural_name/1" do + test "pluralize standard noun" do + assert ResourceAdmin.plural_name(schema: Nested.Node, admin: Nested.NodeAdmin) == "Nodes" + end + + test "pluralize using non-standard singular phrase" do + assert ResourceAdmin.plural_name(schema: Nested.Node, admin: NestedNodeAdmin) == + "NestedNodes" + end + + test "use custom plural name defined as function in admin" do + assert ResourceAdmin.plural_name(schema: Cactus, admin: CactusAdmin) == "Cacti" + end + + test "use non-standard plural form" do + assert ResourceAdmin.plural_name(schema: Person, admin: PersonAdmin) == "People" + end + end +end From ec4f2efaf56802bf853a4159c2a00bbb843f218b Mon Sep 17 00:00:00 2001 From: Abdullah Esmail Date: Mon, 11 Jul 2022 09:44:21 +0300 Subject: [PATCH 013/140] update version and deps. --- mix.exs | 8 ++++---- mix.lock | 33 ++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/mix.exs b/mix.exs index e3245fee..d8bab2b4 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule Kaffy.MixProject do [ app: :kaffy, version: @version, - elixir: "~> 1.8", + elixir: "~> 1.10", compilers: [:phoenix] ++ Mix.compilers(), start_permanent: Mix.env() == :prod, description: description(), @@ -30,8 +30,8 @@ defmodule Kaffy.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:phoenix, "~> 1.4"}, - {:phoenix_html, "~> 2.13 or ~> 3.0"}, + {:phoenix, "~> 1.5"}, + {:phoenix_html, "~> 3.0"}, {:mock, "~> 0.3.0", only: :test}, {:ecto, "~> 3.0"}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} @@ -48,7 +48,7 @@ defmodule Kaffy.MixProject do licenses: ["MIT"], links: %{ "GitHub" => "https://github.com/aesmail/kaffy", - "Demo" => "https://kaffy.gigalixirapp.com/admin/" + "Demo" => "https://kaffy.fly.dev/admin/" } ] end diff --git a/mix.lock b/mix.lock index 1ac7aed1..32a4662d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,23 +1,26 @@ %{ "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"}, - "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, - "ecto": {:hex, :ecto, "3.4.3", "3a14c2500c3964165245a4f24a463e080762f7ccd0c632c763ea589f75ca205f", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b6f18dea95f2004d0369f6a8346513ca3f706614f4ede219a5f3fe5db5dd962"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"}, + "ecto": {:hex, :ecto, "3.8.4", "e06b8b87e62b27fea17fd2ff6041572ddd10339fd16cdf58446e402c6c90a74b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9244288b8d42db40515463a008cf3f4e0e564bb9c249fe87bf28a6d79fe82d4"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, - "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, + "ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, - "makeup": {:hex, :makeup, "1.0.1", "82f332e461dc6c79dbd82fbe2a9c10d48ed07146f0a478286e590c83c52010b5", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "49736fe5b66a08d8575bf5321d716bac5da20c8e6b97714fec2bcd6febcfa1f8"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, - "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, - "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, - "mock": {:hex, :mock, "0.3.6", "e810a91fabc7adf63ab5fdbec5d9d3b492413b8cda5131a2a8aa34b4185eb9b4", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "bcf1d0a6826fb5aee01bae3d74474669a3fa8b2df274d094af54a25266a1ebd2"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, - "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"}, - "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, - "plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, + "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"}, + "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "phoenix": {:hex, :phoenix, "1.6.10", "7a9e8348c5c62e7fd2f74a1884b88d98251f87186a430048bfbdbab3e3f46736", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "08cf70d42f61dd0ea381805bac3cddef57b7b92ade5acc6f6036aa25ecaca9a2"}, + "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, + "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, - "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, } From 4173e814e603f4dcf5c8121c51641798ec4c19a2 Mon Sep 17 00:00:00 2001 From: Abdullah Esmail Date: Mon, 11 Jul 2022 10:16:20 +0300 Subject: [PATCH 014/140] move demo to fly.io --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 940aa8a2..b364703d 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Become a sponsor through Kaffy's [OpenCollective](https://opencollective.com/kaf ## Demo -[Check out the simple demo here](https://kaffy.gigalixirapp.com/admin/) +[Check out the simple demo here](https://kaffy.fly.dev/admin/) ## Minimum Requirements From a9b91b085d6c1ba61df565f81a7875972587bbcd Mon Sep 17 00:00:00 2001 From: Abdullah Esmail Date: Wed, 3 Aug 2022 10:53:25 +0300 Subject: [PATCH 015/140] prepare for v0.9.2 --- CHANGELOG.md | 8 +++++++- mix.exs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b02ab90..89723dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ -### v0.9.1 (in development) +### v0.9.2 (2020-08-03) +### Bug Fixes + +- Display records from schemas which contain fields with names "context" and "resource". + +### v0.9.1 (2020-07-11) #### Bug Fixes - Clicking on the "Select all" checkbox and performing an action wasn't working properly (#129). - A resource with a `{:array, _}` field type used to crash when rendering the form page (#130). - Tidbit icons weren't shown properly. - Schemas with `has_many` or `many_to_many` associations crashed when trying to save if the schema doesn't have a default `changeset/2` function. +- Support phoenix 1.6 ### v0.9.0 (2020-07-02) diff --git a/mix.exs b/mix.exs index d8bab2b4..ee5c3fda 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Kaffy.MixProject do use Mix.Project - @version "0.9.1" + @version "0.9.2" def project do [ From fcb1a46b8c09234fdd5c2991de8bc0b4eac26f55 Mon Sep 17 00:00:00 2001 From: Abdullah Esmail Date: Wed, 3 Aug 2022 11:19:10 +0300 Subject: [PATCH 016/140] fix release dates. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89723dec..9be0f8b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ -### v0.9.2 (2020-08-03) +### v0.9.2 (2022-08-03) ### Bug Fixes - Display records from schemas which contain fields with names "context" and "resource". -### v0.9.1 (2020-07-11) +### v0.9.1 (2022-07-11) #### Bug Fixes - Clicking on the "Select all" checkbox and performing an action wasn't working properly (#129). From 13f16aba28880eae6110badd9c2791b5bd3e7faa Mon Sep 17 00:00:00 2001 From: Julian Somoza Date: Mon, 8 Aug 2022 21:21:44 -0300 Subject: [PATCH 017/140] Add item position at the bottom of the menu --- README.md | 3 ++- lib/kaffy_web/templates/layout/app.html.eex | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b364703d..b9383612 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ defmodule MyApp.Products.ProductAdmin do [ %{name: "Source Code", url: "https://example.com/repo/issues", order: 2, location: :top, icon: "paperclip"}, %{name: "Products On Site", url: "https://example.com/products", location: :sub, target: "_blank"}, + %{name: "Support us", url: "https://example.com/products", location: :bottom, target: "_blank", icon: "usd"}, ] end end @@ -293,7 +294,7 @@ end - `:url` to contain the actual URL. - `:method` the method to use with the link. - `:order` to hold the displayed order of this link. All `:sub` links are ordered under the schema menu item directly before the following schema. -- `:location` can be either `:sub` or `:top`. `:sub` means it's under the schema sub-item. `:top` means it's displayed at the top of the menu below the "Dashboard" link. Links are ordered based on the `:order` value. The default value is `:sub`. +- `:location` can be either `:sub`, `:top` or `:bottom`. `:sub` means it's under the schema sub-item. `:top` means it's displayed at the top of the menu below the "Dashboard" link. `:bottom` means it's displayed at the bottom of the menu below the last context menu item. Links are ordered based on the `:order` value. The default value is `:sub`. - `:icon` is the icon displayed next to the link. Any FontAwesome-valid icon is valid here. For example: `paperclip`. - `:target` to contain the target to open the link: `_blank` or `_self`. `_blank` will open the link in a new window/tab, `_self` will open the link in the same window. The default value is `_self`. diff --git a/lib/kaffy_web/templates/layout/app.html.eex b/lib/kaffy_web/templates/layout/app.html.eex index 8221d42f..89d299b0 100644 --- a/lib/kaffy_web/templates/layout/app.html.eex +++ b/lib/kaffy_web/templates/layout/app.html.eex @@ -111,6 +111,15 @@
<% end %> + + <%= for custom_link <- Kaffy.ResourceAdmin.collect_links(@conn, :bottom) do %> + + <% end %> From f59be2fa22d1d84c336c6e242e3d6bcc575fe4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Sat, 2 Jul 2022 11:16:49 +0200 Subject: [PATCH 018/140] Fix generating forms for embeds and enums --- lib/kaffy/resource_form.ex | 22 ++++++- lib/kaffy/resource_schema.ex | 3 - mix.exs | 8 ++- mix.lock | 3 +- test/fixtures/travel_schema.ex | 38 +++++++++++ test/kaffy/resource_form_test.exs | 101 ++++++++++++++++++++++++++++++ test/test_helper.exs | 1 + 7 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/travel_schema.ex create mode 100644 test/kaffy/resource_form_test.exs diff --git a/lib/kaffy/resource_form.ex b/lib/kaffy/resource_form.ex index f7c58b13..9c53b7f4 100644 --- a/lib/kaffy/resource_form.ex +++ b/lib/kaffy/resource_form.ex @@ -83,7 +83,7 @@ defmodule Kaffy.ResourceForm do schema = schema.__struct__ case type do - {:embed, _} -> + {:embed, %{cardinality: :one}} -> embed = Kaffy.ResourceSchema.embed_struct(schema, field) embed_fields = Kaffy.ResourceSchema.fields(embed) embed_changeset = Ecto.Changeset.change(Map.get(data, field) || embed.__struct__) @@ -91,12 +91,12 @@ defmodule Kaffy.ResourceForm do inputs_for(form, field, fn fp -> [ {:safe, ~s(
)}, - Enum.reduce(embed_fields, [], fn f, all -> + Enum.reduce(embed_fields, [], fn {f, embed_options}, all -> content_tag :div, class: "form-group" do [ [ form_label(fp, f), - form_field(embed_changeset, fp, {f, options}, class: "form-control") + form_field(embed_changeset, fp, {f, embed_options}, class: "form-control") ] | all ] @@ -106,6 +106,14 @@ defmodule Kaffy.ResourceForm do ] end) + {:embed, _} -> + value = + data + |> Map.get(field, "") + |> Kaffy.Utils.json().encode!(escape: :html_safe, pretty: true) + + textarea(form, field, [value: value, rows: 4, placeholder: "JSON Content"] ++ opts) + :id -> case Kaffy.ResourceSchema.primary_key(schema) == [field] do true -> text_input(form, field, opts) @@ -176,12 +184,20 @@ defmodule Kaffy.ResourceForm do select(form, field, values, [value: value] ++ opts) + {:parameterized, Ecto.Enum, %{mappings: mappings}} -> + value = Map.get(data, field, nil) + select(form, field, mappings, [value: value] ++ opts) + {:array, {:parameterized, Ecto.Enum, %{values: values}}} -> values = Enum.map(values, &to_string/1) value = Map.get(data, field, nil) multiple_select(form, field, values, [value: value] ++ opts) + {:array, {:parameterized, Ecto.Enum, %{mappings: mappings}}} -> + value = Map.get(data, field, nil) + multiple_select(form, field, mappings, [value: value] ++ opts) + {:array, _} -> case !is_nil(options[:values_fn]) && is_function(options[:values_fn], 2) do true -> diff --git a/lib/kaffy/resource_schema.ex b/lib/kaffy/resource_schema.ex index 57beecea..5a80a44c 100644 --- a/lib/kaffy/resource_schema.ex +++ b/lib/kaffy/resource_schema.ex @@ -307,9 +307,6 @@ defmodule Kaffy.ResourceSchema do {_f, %{type: :map}} -> true - {_f, %{type: {:array, _}}} -> - true - f when is_atom(f) -> f == :map diff --git a/mix.exs b/mix.exs index ee5c3fda..f0d28a03 100644 --- a/mix.exs +++ b/mix.exs @@ -9,6 +9,7 @@ defmodule Kaffy.MixProject do version: @version, elixir: "~> 1.10", compilers: [:phoenix] ++ Mix.compilers(), + elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, description: description(), package: package(), @@ -27,6 +28,10 @@ defmodule Kaffy.MixProject do ] end + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/fixtures"] + defp elixirc_paths(_), do: ["lib"] + # Run "mix help deps" to learn about dependencies. defp deps do [ @@ -34,7 +39,8 @@ defmodule Kaffy.MixProject do {:phoenix_html, "~> 3.0"}, {:mock, "~> 0.3.0", only: :test}, {:ecto, "~> 3.0"}, - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:jason, "~> 1.3", only: :test} ] end diff --git a/mix.lock b/mix.lock index 32a4662d..1ceb6e03 100644 --- a/mix.lock +++ b/mix.lock @@ -5,7 +5,8 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"}, "ecto": {:hex, :ecto, "3.8.4", "e06b8b87e62b27fea17fd2ff6041572ddd10339fd16cdf58446e402c6c90a74b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9244288b8d42db40515463a008cf3f4e0e564bb9c249fe87bf28a6d79fe82d4"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, - "ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"}, + "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, diff --git a/test/fixtures/travel_schema.ex b/test/fixtures/travel_schema.ex new file mode 100644 index 00000000..60dd6cb3 --- /dev/null +++ b/test/fixtures/travel_schema.ex @@ -0,0 +1,38 @@ +defmodule Fixtures.TravelSchema do + use Ecto.Schema + + defmodule Stay do + use Ecto.Schema + + embedded_schema do + field(:hotel_name, :string) + field(:from, :date) + field(:to, :date) + field(:price, :decimal) + end + end + + defmodule Metadata do + use Ecto.Schema + + embedded_schema do + field(:travelforce_id, :integer) + end + end + + schema "travels" do + field(:title, :string) + field(:description, :string) + field(:number_of_people, :integer) + field(:requires_passport, :boolean) + field(:travel_type, Ecto.Enum, values: [business: "business", leisure: "leisure", other: "undisclosed"]) + field(:start_time, :utc_datetime) + field(:local_start_time, :naive_datetime) + field(:end_date, :date) + field(:age_of_travelers, {:array, :integer}) + field(:means_of_transport, {:array, Ecto.Enum}, values: [:train, :plane, :car, :bus, :ferry]) + embeds_many(:stays, Stay) + embeds_one(:metadata, Metadata) + timestamps() + end +end diff --git a/test/kaffy/resource_form_test.exs b/test/kaffy/resource_form_test.exs new file mode 100644 index 00000000..ed939940 --- /dev/null +++ b/test/kaffy/resource_form_test.exs @@ -0,0 +1,101 @@ +defmodule Kaffy.ResourceFormTest do + use ExUnit.Case, async: true + alias Kaffy.ResourceForm + alias Fixtures.TravelSchema + + describe "form_field/4" do + test "render string field" do + html = render_field(:title) + assert html =~ ~r/dummy\[title]/ + assert html =~ ~r/^other<\/option>/ + end + + test "render enum array field" do + html = render_field(:means_of_transport) + assert html =~ ~r/dummy\[means_of_transport]/ + assert html =~ ~r/^