mirror of
https://github.com/ash-project/ash_admin.git
synced 2024-09-19 21:03:52 +12:00
feat: more testing resources + relationship argument forms!
This commit is contained in:
parent
535560751e
commit
eb25cc92ff
18 changed files with 837 additions and 264 deletions
1
dev.exs
1
dev.exs
|
@ -18,6 +18,7 @@ Application.put_env(:ash_admin, DemoWeb.Endpoint,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
live_reload: [
|
live_reload: [
|
||||||
|
iframe_attrs: [class: "hidden"],
|
||||||
patterns: [
|
patterns: [
|
||||||
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
||||||
~r"lib/ash_admin/(components|templates/pages)/.*(ex)$"
|
~r"lib/ash_admin/(components|templates/pages)/.*(ex)$"
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Demo.Accounts.User do
|
||||||
field :first_name, type: :short_text
|
field :first_name, type: :short_text
|
||||||
field :last_name, type: :short_text
|
field :last_name, type: :short_text
|
||||||
end
|
end
|
||||||
|
table_columns [:id, :first_name, :last_name, :representative, :admin]
|
||||||
end
|
end
|
||||||
|
|
||||||
policies do
|
policies do
|
||||||
|
|
|
@ -2,12 +2,13 @@ defmodule Demo.Tickets.Api do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
use Ash.Api
|
use Ash.Api
|
||||||
|
|
||||||
alias Demo.Tickets.{Comment, Customer, Representative, Ticket}
|
alias Demo.Tickets.{Comment, Customer, Representative, Ticket, TicketLink}
|
||||||
|
|
||||||
resources do
|
resources do
|
||||||
resource(Customer)
|
resource(Customer)
|
||||||
resource(Representative)
|
resource(Representative)
|
||||||
resource(Ticket)
|
resource(Ticket)
|
||||||
resource(Comment)
|
resource(Comment)
|
||||||
|
resource(TicketLink)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,7 +45,7 @@ defmodule Demo.Tickets.Representative do
|
||||||
|
|
||||||
update :update do
|
update :update do
|
||||||
primary? true
|
primary? true
|
||||||
accept [:first_name, :last_name, :assigned_tickets]
|
accept [:first_name, :last_name]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -61,13 +61,47 @@ defmodule Demo.Tickets.Ticket do
|
||||||
end
|
end
|
||||||
|
|
||||||
create :open do
|
create :open do
|
||||||
accept [:subject, :reporter]
|
accept [:subject]
|
||||||
|
primary? true
|
||||||
|
argument :representative, :map, allow_nil?: false
|
||||||
|
argument :tickets, {:array, :map}, allow_nil?: false
|
||||||
|
|
||||||
|
change manage_relationship(:representative, type: :append)
|
||||||
|
change manage_relationship(:tickets, :source_links, on_lookup: {:relate_and_update, :create, :read, :all})
|
||||||
end
|
end
|
||||||
|
|
||||||
update :update, primary?: true
|
update :update, primary?: true
|
||||||
|
|
||||||
update :assign do
|
update :assign do
|
||||||
accept [:representative]
|
accept []
|
||||||
|
argument :representative, :map
|
||||||
|
argument :reassignment_comment, :map, allow_nil?: false
|
||||||
|
|
||||||
|
change manage_relationship(:representative, type: :append)
|
||||||
|
change manage_relationship(:reassignment_comment, :comments, type: :create)
|
||||||
|
end
|
||||||
|
|
||||||
|
update :link do
|
||||||
|
accept []
|
||||||
|
argument :tickets, {:array, :map}, allow_nil?: false
|
||||||
|
argument :link_comment, :map, type: :create
|
||||||
|
|
||||||
|
# Uses the defult create action of the join table, which accepts the `type`
|
||||||
|
change manage_relationship(:tickets, :source_links, on_lookup: {:relate_and_update, :create, :read, :all})
|
||||||
|
change manage_relationship(:link_comment, :comments, type: :create)
|
||||||
|
end
|
||||||
|
|
||||||
|
update :nested_example do
|
||||||
|
accept [:subject]
|
||||||
|
argument :tickets, {:array, :map}
|
||||||
|
|
||||||
|
change manage_relationship(
|
||||||
|
:tickets,
|
||||||
|
:source_links,
|
||||||
|
type: :direct_control,
|
||||||
|
on_match: {:update, :nested_example, :update, [:type]},
|
||||||
|
on_no_match: {:create, :nested_example, :update, [:type]}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
destroy :destroy
|
destroy :destroy
|
||||||
|
@ -108,5 +142,17 @@ defmodule Demo.Tickets.Ticket do
|
||||||
context %{data_layer: %{table: "ticket_comments"}}
|
context %{data_layer: %{table: "ticket_comments"}}
|
||||||
destination_field :resource_id
|
destination_field :resource_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
many_to_many :source_links, Demo.Tickets.Ticket do
|
||||||
|
through Demo.Tickets.TicketLink
|
||||||
|
source_field_on_join_table :source_id
|
||||||
|
destination_field_on_join_table :destination_id
|
||||||
|
end
|
||||||
|
|
||||||
|
many_to_many :destination_links, Demo.Tickets.Ticket do
|
||||||
|
through Demo.Tickets.TicketLink
|
||||||
|
source_field_on_join_table :destination_id
|
||||||
|
destination_field_on_join_table :source_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
27
dev/resources/tickets/resources/ticket_link.ex
Normal file
27
dev/resources/tickets/resources/ticket_link.ex
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule Demo.Tickets.TicketLink do
|
||||||
|
use Ash.Resource,
|
||||||
|
data_layer: AshPostgres.DataLayer
|
||||||
|
|
||||||
|
postgres do
|
||||||
|
table "ticket_links"
|
||||||
|
repo Demo.Repo
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes do
|
||||||
|
attribute :type, :atom, constraints: [
|
||||||
|
one_of: [:causes, :caused_by, :fixes, :fixed_by]
|
||||||
|
], allow_nil?: false
|
||||||
|
end
|
||||||
|
|
||||||
|
relationships do
|
||||||
|
belongs_to :source, Demo.Tickets.Ticket do
|
||||||
|
primary_key? true
|
||||||
|
required? true
|
||||||
|
end
|
||||||
|
|
||||||
|
belongs_to :destination, Demo.Tickets.Ticket do
|
||||||
|
primary_key? true
|
||||||
|
required? true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,6 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
Checkbox,
|
Checkbox,
|
||||||
ErrorTag,
|
ErrorTag,
|
||||||
FieldContext,
|
FieldContext,
|
||||||
HiddenInput,
|
|
||||||
HiddenInputs,
|
HiddenInputs,
|
||||||
Inputs,
|
Inputs,
|
||||||
Label,
|
Label,
|
||||||
|
@ -173,7 +172,7 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
<FieldContext name={{ attribute.name }}>
|
<FieldContext name={{ attribute.name }}>
|
||||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
||||||
{{ render_attribute_input(assigns, attribute, form) }}
|
{{ render_attribute_input(assigns, attribute, form) }}
|
||||||
<ErrorTag field={{ attribute.name }} />
|
<ErrorTag :if={{!Ash.Type.embedded_type?(attribute.type)}} field={{ attribute.name }} />
|
||||||
</FieldContext>
|
</FieldContext>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -194,7 +193,7 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
<FieldContext name={{ attribute.name }}>
|
<FieldContext name={{ attribute.name }}>
|
||||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
||||||
{{ render_attribute_input(assigns, attribute, form) }}
|
{{ render_attribute_input(assigns, attribute, form) }}
|
||||||
<ErrorTag field={{ attribute.name }} />
|
<ErrorTag :if={{!Ash.Type.embedded_type?(attribute.type)}} field={{ attribute.name }} />
|
||||||
</FieldContext>
|
</FieldContext>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -215,15 +214,14 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
<FieldContext name={{ attribute.name }}>
|
<FieldContext name={{ attribute.name }}>
|
||||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
||||||
{{ render_attribute_input(assigns, attribute, form) }}
|
{{ render_attribute_input(assigns, attribute, form) }}
|
||||||
<ErrorTag field={{ attribute.name }} />
|
<ErrorTag :if={{!Ash.Type.embedded_type?(attribute.type)}} field={{ attribute.name }} />
|
||||||
</FieldContext>
|
</FieldContext>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :for={{{relationship, argument, opts} <- relationship_args}}>
|
<div :for={{{relationship, argument, opts} <- relationship_args}}>
|
||||||
<FieldContext name={{argument.name}} :if={{relationship not in skip and argument.name not in skip}}>
|
<FieldContext name={{argument.name}} :if={{relationship not in skip and argument.name not in skip}}>
|
||||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(argument.name)}}</Label>
|
<Label class="block text-sm font-medium text-gray-700">{{ to_name(argument.name)}}</Label>
|
||||||
{{ render_relationship_input(assigns, Ash.Resource.Info.relationship(form.source.resource, relationship), form, argument.name, relationship_path <> "[#{relationship}]", opts) }}
|
{{ render_relationship_input(assigns, Ash.Resource.Info.relationship(form.source.resource, relationship), form, argument, relationship_path <> "[#{relationship}]", opts) }}
|
||||||
<ErrorTag field={{ relationship }} />
|
|
||||||
</FieldContext>
|
</FieldContext>
|
||||||
</div>
|
</div>
|
||||||
</Context>
|
</Context>
|
||||||
|
@ -232,70 +230,9 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
|
|
||||||
defp render_relationship_input(
|
defp render_relationship_input(
|
||||||
assigns,
|
assigns,
|
||||||
%{cardinality: :one} = relationship,
|
relationship,
|
||||||
form,
|
form,
|
||||||
as,
|
%{type: {:array, _}} = argument,
|
||||||
relationship_path,
|
|
||||||
opts
|
|
||||||
) do
|
|
||||||
~H"""
|
|
||||||
<div :if={{ loaded?(form.source, relationship.name) }}>
|
|
||||||
<Inputs
|
|
||||||
form={{ form }}
|
|
||||||
for={{ relationship.name }}
|
|
||||||
:let={{ form: inner_form }}
|
|
||||||
opts={{ use_data?: true, as: form.name <> "[#{as}]" }}
|
|
||||||
>
|
|
||||||
<HiddenInputs for={{inner_form}} />
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:on-click="remove_related"
|
|
||||||
phx-value-path={{ inner_form.name }}
|
|
||||||
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
|
||||||
>
|
|
||||||
{{ {:safe, Heroicons.Solid.minus(class: "h-4 w-4 text-gray-500")} }}
|
|
||||||
</button>
|
|
||||||
<div class="shadow-lg p-4" :for={{{new_form, fields} <- relationship_forms(inner_form, relationship, opts, @actor) }}>
|
|
||||||
{{ render_attributes(
|
|
||||||
assigns,
|
|
||||||
relationship.destination,
|
|
||||||
new_form.source.action,
|
|
||||||
new_form,
|
|
||||||
fields,
|
|
||||||
[],
|
|
||||||
relationship_path
|
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</Inputs>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:on-click="append_related"
|
|
||||||
:if={{ could_lookup?(opts) && !relationship_set?(form.source, relationship.name, relationship.name) }}
|
|
||||||
phx-value-path={{ form.name <> "[#{as}]" }}
|
|
||||||
phx-value-type={{ "lookup" }}
|
|
||||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
|
||||||
>
|
|
||||||
{{ {:safe, Heroicons.Solid.search_circle(class: "h-4 w-4 text-gray-500")} }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:on-click="append_related"
|
|
||||||
:if={{ could_create?(opts) && !relationship_set?(form.source, relationship.name, relationship.name) }}
|
|
||||||
phx-value-path={{ form.name <> "[#{as}]" }}
|
|
||||||
phx-value-type={{ "create" }}
|
|
||||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
|
||||||
>
|
|
||||||
{{ {:safe, Heroicons.Solid.plus(class: "h-4 w-4 text-gray-500")} }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
defp render_relationship_input(
|
|
||||||
assigns,
|
|
||||||
%{cardinality: :many} = relationship,
|
|
||||||
form,
|
|
||||||
as,
|
|
||||||
relationship_path,
|
relationship_path,
|
||||||
opts
|
opts
|
||||||
) do
|
) do
|
||||||
|
@ -303,59 +240,60 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
<div :if={{ !needs_to_load?(opts) || loaded?(form.source, relationship.name) }}>
|
<div :if={{ !needs_to_load?(opts) || loaded?(form.source, relationship.name) }}>
|
||||||
<Inputs
|
<Inputs
|
||||||
form={{ form }}
|
form={{ form }}
|
||||||
for={{ relationship.name }}
|
for={{ argument.name }}
|
||||||
:let={{ form: inner_form }}
|
:let={{ form: inner_form }}
|
||||||
opts={{ use_data?: true, as: form.name <> "[#{as}]" }}
|
opts={{ form_opts(form, opts, argument.name, relationship, @actor) }}
|
||||||
>
|
>
|
||||||
<HiddenInputs for={{inner_form}} />
|
<HiddenInputs for={{inner_form}} />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:on-click="remove_related"
|
:on-click="remove_related"
|
||||||
:if={{ relationship_set?(form.source, relationship.name, relationship.name) }}
|
:if={{ can_remove_related?(opts) && relationship_set?(form.source, relationship.name, argument.name) }}
|
||||||
phx-value-path={{ inner_form.name }}
|
phx-value-path={{ inner_form.name }}
|
||||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
>
|
>
|
||||||
{{ {:safe, Heroicons.Solid.minus(class: "h-4 w-4 text-gray-500")} }}
|
{{ {:safe, Heroicons.Solid.minus(class: "h-4 w-4 text-gray-500")} }}
|
||||||
</button>
|
</button>
|
||||||
<div class="shadow-lg p-4" :for={{{new_form, fields} <- relationship_forms(inner_form, relationship, opts, @actor) }}>
|
<div class="shadow-lg p-4">
|
||||||
<HiddenInput form={{inner_form}} :if={{inner_form.source.params["_lookup"] == "true"}} field="_lookup" value="true"/>
|
<div :for={{{inner_form, field_limit, relationship} <- relationship_forms(form, inner_form, relationship, opts, @actor)}}>
|
||||||
{{ render_attributes(
|
{{ render_attributes(
|
||||||
assigns,
|
assigns,
|
||||||
relationship.destination,
|
relationship.destination,
|
||||||
new_form.source.action,
|
inner_form.source.action || :_lookup,
|
||||||
new_form,
|
maybe_clear_errors(inner_form), # We clear errors from lookup forms
|
||||||
fields,
|
relationship_fields(inner_form, field_limit),
|
||||||
[],
|
skip_related(relationship, is_nil(inner_form.source.action)),
|
||||||
relationship_path
|
relationship_path
|
||||||
) }}
|
) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Inputs>
|
</Inputs>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:on-click="append_related"
|
:on-click="append_related"
|
||||||
:if={{ could_lookup?(opts) }}
|
:if={{ could_create?(opts) }}
|
||||||
phx-value-path={{ form.name <> "[#{as}]" }}
|
phx-value-path={{ form.name <> "[#{argument.name}]" }}
|
||||||
|
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
|
>
|
||||||
|
{{ {:safe, Heroicons.Solid.plus(class: "h-4 w-4 text-gray-500")} }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:on-click="append_related"
|
||||||
|
:if={{ could_lookup?(opts) && !relationship_set?(form.source, relationship.name, argument.name) }}
|
||||||
|
phx-value-path={{ form.name <> "[#{argument.name}]" }}
|
||||||
phx-value-type={{ "lookup" }}
|
phx-value-type={{ "lookup" }}
|
||||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
>
|
>
|
||||||
{{ {:safe, Heroicons.Solid.search_circle(class: "h-4 w-4 text-gray-500")} }}
|
{{ {:safe, Heroicons.Solid.search_circle(class: "h-4 w-4 text-gray-500")} }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:on-click="append_related"
|
|
||||||
:if={{ could_create?(opts) }}
|
|
||||||
phx-value-path={{ form.name <> "[#{as}]" }}
|
|
||||||
phx-value-type={{ "create" }}
|
|
||||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
|
||||||
>
|
|
||||||
{{ {:safe, Heroicons.Solid.plus(class: "h-4 w-4 text-gray-500")} }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div :if={{ needs_to_load?(opts) && !loaded?(form.source, relationship.name) }}>
|
<div :if={{ needs_to_load?(opts) && !loaded?(form.source, relationship.name) }}>
|
||||||
<button
|
<button
|
||||||
:on-click="load"
|
:on-click="load"
|
||||||
phx-value-relationship={{ relationship_path }}
|
phx-value-relationship={{ relationship_path }}
|
||||||
phx-value-path={{form.name <> "[#{as}]"}}
|
phx-value-path={{form.name <> "[#{argument.name}]"}}
|
||||||
type="button"
|
type="button"
|
||||||
class="flex py-2 ml-4 px-4 mt-2 bg-indigo-600 text-white border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
class="flex py-2 ml-4 px-4 mt-2 bg-indigo-600 text-white border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
>
|
>
|
||||||
|
@ -371,143 +309,422 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp relationship_forms(form, relationship, opts, actor) do
|
defp render_relationship_input(
|
||||||
forms =
|
assigns,
|
||||||
cond do
|
relationship,
|
||||||
form.source.params["_lookup"] == "true" ->
|
form,
|
||||||
relationship_forms_for_lookup(form, relationship, opts, actor)
|
argument,
|
||||||
|
relationship_path,
|
||||||
|
opts
|
||||||
|
) do
|
||||||
|
~H"""
|
||||||
|
<div :if={{ !(needs_to_load?(opts) && !loaded?(form.source, relationship.name)) }}>
|
||||||
|
<Inputs
|
||||||
|
form={{ form }}
|
||||||
|
for={{ argument.name }}
|
||||||
|
:let={{ form: inner_form }}
|
||||||
|
opts={{ form_opts(form, opts, argument.name, relationship, @actor) }}
|
||||||
|
>
|
||||||
|
<HiddenInputs for={{inner_form}} />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:on-click="remove_related"
|
||||||
|
:if={{can_remove_related?(opts)}}
|
||||||
|
phx-value-path={{ inner_form.name }}
|
||||||
|
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
|
>
|
||||||
|
{{ {:safe, Heroicons.Solid.minus(class: "h-4 w-4 text-gray-500")} }}
|
||||||
|
</button>
|
||||||
|
<div class="shadow-lg p-4">
|
||||||
|
<div :for={{{inner_form, type, relationship} <- relationship_forms(form, inner_form, relationship, opts, @actor)}}>
|
||||||
|
{{ render_attributes(
|
||||||
|
assigns,
|
||||||
|
relationship.destination,
|
||||||
|
inner_form.source.action || :_lookup,
|
||||||
|
maybe_clear_errors(inner_form), # We clear errors from lookup forms
|
||||||
|
relationship_fields(inner_form, type),
|
||||||
|
skip_related(relationship, is_nil(inner_form.source.action)),
|
||||||
|
relationship_path
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Inputs>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:on-click="append_related"
|
||||||
|
:if={{ could_lookup?(opts) && !relationship_set?(form.source, relationship.name, argument.name) }}
|
||||||
|
phx-value-path={{ form.name <> "[#{argument.name}]" }}
|
||||||
|
phx-value-type={{ "lookup" }}
|
||||||
|
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
|
>
|
||||||
|
{{ {:safe, Heroicons.Solid.search_circle(class: "h-4 w-4 text-gray-500")} }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:on-click="append_related"
|
||||||
|
:if={{ could_create?(opts) && !relationship_set?(form.source, relationship.name, argument.name) }}
|
||||||
|
phx-value-path={{ form.name <> "[#{argument.name}]" }}
|
||||||
|
phx-value-type={{ "create" }}
|
||||||
|
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
|
>
|
||||||
|
{{ {:safe, Heroicons.Solid.plus(class: "h-4 w-4 text-gray-500")} }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
form.source.action_type == :update ->
|
defp relationship_forms(form, inner_form, relationship, opts, actor) do
|
||||||
relationship_forms_for_update(form, relationship, opts, actor)
|
cond do
|
||||||
|
is_nil(inner_form.source.action) ->
|
||||||
|
with_lookup_forms =
|
||||||
|
[{inner_form, nil, relationship}] ++
|
||||||
|
lookup_forms(form, inner_form, opts, relationship, actor)
|
||||||
|
|
||||||
form.source.action_type == :create ->
|
case inner_form.source.action_type do
|
||||||
relationship_forms_for_create(form, relationship, opts, actor)
|
:create ->
|
||||||
|
with_lookup_forms ++
|
||||||
|
create_forms(form, inner_form, opts, relationship, actor)
|
||||||
|
|
||||||
|
:update ->
|
||||||
|
with_lookup_forms ++
|
||||||
|
update_forms(form, inner_form, opts, relationship, actor)
|
||||||
|
|
||||||
|
:destroy ->
|
||||||
|
with_lookup_forms ++
|
||||||
|
destroy_forms(form, inner_form, opts, relationship, actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
inner_form.source.action_type == :update ->
|
||||||
|
[{inner_form, nil, relationship}] ++
|
||||||
|
update_forms(form, inner_form, opts, relationship, actor)
|
||||||
|
|
||||||
|
inner_form.source.action_type == :destroy ->
|
||||||
|
[{inner_form, nil, relationship}] ++
|
||||||
|
destroy_forms(form, inner_form, opts, relationship, actor)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
[{inner_form, nil, relationship}] ++
|
||||||
|
create_forms(form, inner_form, opts, relationship, actor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp lookup_forms(form, inner_form, opts, relationship, actor) do
|
||||||
|
opts
|
||||||
|
|> Ash.Changeset.ManagedRelationshipHelpers.on_lookup_update_action(relationship)
|
||||||
|
|> action_form(form, inner_form, relationship, actor)
|
||||||
|
|> List.wrap()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_forms(form, inner_form, opts, relationship, actor) do
|
||||||
|
opts
|
||||||
|
|> Ash.Changeset.ManagedRelationshipHelpers.on_match_destination_actions(relationship)
|
||||||
|
|> Kernel.||([])
|
||||||
|
|> drop_destination_form()
|
||||||
|
|> Enum.map(&action_form(&1, form, inner_form, relationship, actor))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_forms(form, inner_form, opts, relationship, actor) do
|
||||||
|
opts
|
||||||
|
|> Ash.Changeset.ManagedRelationshipHelpers.on_no_match_destination_actions(relationship)
|
||||||
|
|> Kernel.||([])
|
||||||
|
|> drop_destination_form()
|
||||||
|
|> Enum.map(&action_form(&1, form, inner_form, relationship, actor))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp destroy_forms(form, inner_form, opts, relationship, actor) do
|
||||||
|
opts
|
||||||
|
|> Ash.Changeset.ManagedRelationshipHelpers.on_missing_destination_actions(relationship)
|
||||||
|
|> drop_destination_form()
|
||||||
|
|> Enum.map(&action_form(&1, form, inner_form, relationship, actor))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp drop_destination_form([{:destination, _} | rest]), do: rest
|
||||||
|
defp drop_destination_form(other), do: other
|
||||||
|
|
||||||
|
defp action_form(nil, _, _, _, _), do: nil
|
||||||
|
|
||||||
|
defp action_form({:source, action_name}, form, inner_form, relationship, actor) do
|
||||||
|
new_inner_form =
|
||||||
|
form.source.data
|
||||||
|
|> Ash.Changeset.for_update(action_name, inner_form.params, actor: actor)
|
||||||
|
|> retain_hiding_errors(inner_form.source)
|
||||||
|
|> Phoenix.HTML.FormData.to_form(as: inner_form.name)
|
||||||
|
|
||||||
|
{new_inner_form, nil, relationship}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp action_form({:destination, action_name}, _form, inner_form, relationship, actor) do
|
||||||
|
new_inner_form =
|
||||||
|
inner_form.data
|
||||||
|
|> Ash.Changeset.for_update(action_name, inner_form.params, actor: actor)
|
||||||
|
|> retain_hiding_errors(inner_form.source)
|
||||||
|
|> Phoenix.HTML.FormData.to_form(as: inner_form.name)
|
||||||
|
|
||||||
|
{new_inner_form, nil, relationship}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp action_form({:join, action_name, keys}, form, inner_form, relationship, actor) do
|
||||||
|
limit =
|
||||||
|
if keys == :all do
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
keys
|
||||||
end
|
end
|
||||||
|
|
||||||
List.wrap(forms)
|
new_inner_form =
|
||||||
end
|
if inner_form.source.action_type == :update do
|
||||||
|
value = find_join(form.source.data, inner_form.source.data, relationship)
|
||||||
|
|
||||||
defp relationship_forms_for_lookup(form, relationship, opts, actor) do
|
if value do
|
||||||
case opts[:on_lookup] do
|
value
|
||||||
{key, create, read, fields} when relationship.type == :many_to_many ->
|
|> Ash.Changeset.for_update(action_name, inner_form.params, actor: actor)
|
||||||
query =
|
|> retain_hiding_errors(inner_form.source)
|
||||||
relationship.destination
|
|> Phoenix.HTML.FormData.to_form(as: inner_form.name)
|
||||||
|> Ash.Query.for_read(read, form.source.params, actor: actor)
|
else
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
changeset =
|
|
||||||
relationship.through
|
|
||||||
|> Ash.Changeset.for_create(create, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
[{query, Ash.Resource.Info.primary_key(relationship.destination)}, {changeset, fields}] ++
|
|
||||||
lookup_update(key, form, relationship, opts, actor)
|
|
||||||
|
|
||||||
{key, _update, read} ->
|
|
||||||
query =
|
|
||||||
relationship.destination
|
|
||||||
|> Ash.Query.for_read(read, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
[{query, Ash.Resource.Info.primary_key(relationship.destination)}] ++
|
|
||||||
lookup_update(key, form, relationship, opts, actor)
|
|
||||||
|
|
||||||
{key, create, read, fields} ->
|
|
||||||
query =
|
|
||||||
relationship.destination
|
|
||||||
|> Ash.Query.for_read(read, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
changeset =
|
|
||||||
relationship.through
|
|
||||||
|> Ash.Changeset.for_create(create, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
[{query, Ash.Resource.Info.primary_key(relationship.destination)}, {changeset, fields}] ++
|
|
||||||
lookup_update(key, form, relationship, opts, actor)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp lookup_update(:relate_and_update, form, relationship, opts, actor) do
|
|
||||||
relationship_forms_for_update(form, relationship, opts, actor)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp lookup_update(_, _, _, _, _), do: []
|
|
||||||
|
|
||||||
defp relationship_forms_for_create(form, relationship, opts, actor) do
|
|
||||||
case opts[:on_no_match] do
|
|
||||||
{:create, action_name, join_action_name, fields} ->
|
|
||||||
join_form =
|
|
||||||
relationship.through
|
|
||||||
|> Ash.Changeset.for_create(join_action_name, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
destination_form =
|
|
||||||
relationship.destination
|
|
||||||
|> Ash.Changeset.for_create(action_name, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
[{destination_form, nil}, {join_form, fields}]
|
|
||||||
|
|
||||||
{:create, action_name} ->
|
|
||||||
destination_form =
|
|
||||||
relationship.destination
|
|
||||||
|> Ash.Changeset.for_create(action_name, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
[{destination_form, nil}]
|
|
||||||
|
|
||||||
:error ->
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp relationship_forms_for_update(form, relationship, opts, actor) do
|
|
||||||
case opts[:on_match] do
|
|
||||||
{:update, update, join_update, fields} ->
|
|
||||||
join_form =
|
|
||||||
form.source.data
|
|
||||||
|> Ash.Changeset.for_update(update, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
destination_form =
|
|
||||||
relationship.through.__struct__
|
relationship.through.__struct__
|
||||||
|> Ash.Changeset.for_update(join_update, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
[{destination_form, nil}, {join_form, fields}]
|
|
||||||
|
|
||||||
{:update, action_name} ->
|
|
||||||
destination_form =
|
|
||||||
form.source.data
|
|
||||||
|> Ash.Changeset.for_update(action_name, form.source.params, actor: actor)
|
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|
||||||
|
|
||||||
[{destination_form, nil}]
|
|
||||||
|
|
||||||
{:unrelate, _action} ->
|
|
||||||
changeset =
|
|
||||||
form.source.data
|
|
||||||
|> Ash.Changeset.new()
|
|> Ash.Changeset.new()
|
||||||
|> Map.put(:params, form.source.params)
|
|> retain_hiding_errors(inner_form.source)
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
|> Map.put(:params, inner_form.params)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
relationship.through
|
||||||
|
|> Ash.Changeset.for_create(action_name, inner_form.params, actor: actor)
|
||||||
|
|> retain_hiding_errors(inner_form.source)
|
||||||
|
|> Phoenix.HTML.FormData.to_form(as: inner_form.name)
|
||||||
|
end
|
||||||
|
|
||||||
{changeset, Ash.Resource.Info.primary_key(relationship.destination)}
|
{new_inner_form, limit,
|
||||||
|
Ash.Resource.Info.relationship(relationship.source, relationship.join_relationship)}
|
||||||
|
end
|
||||||
|
|
||||||
value when value in [:ignore, :error] ->
|
defp retain_hiding_errors(changeset, source_changeset) do
|
||||||
changeset =
|
if AshPhoenix.hiding_errors?(source_changeset) do
|
||||||
form.source.data
|
AshPhoenix.hide_errors(changeset)
|
||||||
|> Ash.Changeset.new()
|
else
|
||||||
|> Map.put(:params, form.source.params)
|
changeset
|
||||||
|> Phoenix.HTML.FormData.to_form(as: form.name)
|
end
|
||||||
|
end
|
||||||
|
|
||||||
{changeset, Ash.Resource.Info.primary_key(relationship.destination)}
|
defp find_join(source, destination, relationship) do
|
||||||
|
case Map.get(source, relationship.join_relationship) do
|
||||||
|
%Ash.NotLoaded{} ->
|
||||||
|
source.__struct__.__struct__
|
||||||
|
|
||||||
|
related ->
|
||||||
|
related
|
||||||
|
|> List.wrap()
|
||||||
|
|> Enum.find(
|
||||||
|
source.__struct__.__struct__,
|
||||||
|
fn candidate ->
|
||||||
|
Map.get(candidate, relationship.destination_field_on_join_table) ==
|
||||||
|
Map.get(destination, relationship.destination_field)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_clear_errors(form) do
|
||||||
|
if form.source.action do
|
||||||
|
form
|
||||||
|
else
|
||||||
|
%{form | source: AshPhoenix.hide_errors(form.source), errors: []}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp relationship_fields(inner_form, limit) do
|
||||||
|
if is_nil(inner_form.source.action) do
|
||||||
|
Ash.Resource.Info.primary_key(inner_form.source.resource)
|
||||||
|
else
|
||||||
|
limit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_action(opts, relationship) do
|
||||||
|
case Ash.Changeset.ManagedRelationshipHelpers.on_no_match_destination_actions(
|
||||||
|
opts,
|
||||||
|
relationship
|
||||||
|
) do
|
||||||
|
[{:destination, action} | _rest] ->
|
||||||
|
# do something with rest here
|
||||||
|
action
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
relationship_forms_for_create(form, relationship, opts, actor)
|
:_raw
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp update_action(opts, relationship) do
|
||||||
|
case Ash.Changeset.ManagedRelationshipHelpers.on_match_destination_actions(opts, relationship) do
|
||||||
|
[{:destination, action} | _rest] ->
|
||||||
|
# do something with rest here
|
||||||
|
action
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
:_raw
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp form_opts(form, opts, as, relationship, actor) do
|
||||||
|
[
|
||||||
|
use_data?: use_data?(opts),
|
||||||
|
as: form.name <> "[#{as}]",
|
||||||
|
create_action: create_action(opts, relationship),
|
||||||
|
update_action: update_action(opts, relationship),
|
||||||
|
actor: actor
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp use_data?(opts) do
|
||||||
|
Ash.Changeset.ManagedRelationshipHelpers.must_load?(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp skip_related(_, true) do
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp skip_related(relationship, _) do
|
||||||
|
if relationship.type == :belongs_to do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[relationship.destination_field]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# defp relationship_forms(form, relationship, opts, actor) do
|
||||||
|
# forms =
|
||||||
|
# cond do
|
||||||
|
# form.source.action_type == :destroy ->
|
||||||
|
# form.source.action_type == :update ->
|
||||||
|
# relationship_forms_for_update(form, relationship, opts, actor)
|
||||||
|
|
||||||
|
# form.source.action_type == :create ->
|
||||||
|
# relationship_forms_for_create(form, relationship, opts, actor)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# List.wrap(forms)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# defp relationship_forms_for_lookup(form, relationship, opts, actor) do
|
||||||
|
# case opts[:on_lookup] do
|
||||||
|
# {key, create, read, fields} when relationship.type == :many_to_many ->
|
||||||
|
# query =
|
||||||
|
# relationship.destination
|
||||||
|
# |> Ash.Query.for_read(read, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# changeset =
|
||||||
|
# relationship.through
|
||||||
|
# |> Ash.Changeset.for_create(create, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# [{query, Ash.Resource.Info.primary_key(relationship.destination)}, {changeset, fields}] ++
|
||||||
|
# lookup_update(key, form, relationship, opts, actor)
|
||||||
|
|
||||||
|
# {key, _update, read} ->
|
||||||
|
# query =
|
||||||
|
# relationship.destination
|
||||||
|
# |> Ash.Query.for_read(read, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# [{query, Ash.Resource.Info.primary_key(relationship.destination)}] ++
|
||||||
|
# lookup_update(key, form, relationship, opts, actor)
|
||||||
|
|
||||||
|
# {key, create, read, fields} ->
|
||||||
|
# query =
|
||||||
|
# relationship.destination
|
||||||
|
# |> Ash.Query.for_read(read, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# changeset =
|
||||||
|
# relationship.through
|
||||||
|
# |> Ash.Changeset.for_create(create, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# [{query, Ash.Resource.Info.primary_key(relationship.destination)}, {changeset, fields}] ++
|
||||||
|
# lookup_update(key, form, relationship, opts, actor)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# defp lookup_update(:relate_and_update, form, relationship, opts, actor) do
|
||||||
|
# relationship_forms_for_update(form, relationship, opts, actor)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# defp lookup_update(_, _, _, _, _), do: []
|
||||||
|
|
||||||
|
# defp relationship_forms_for_create(form, relationship, opts, actor) do
|
||||||
|
# case opts[:on_no_match] do
|
||||||
|
# {:create, action_name, join_action_name, fields} ->
|
||||||
|
# join_form =
|
||||||
|
# relationship.through
|
||||||
|
# |> Ash.Changeset.for_create(join_action_name, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# destination_form =
|
||||||
|
# relationship.destination
|
||||||
|
# |> Ash.Changeset.for_create(action_name, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# [{destination_form, nil}, {join_form, fields}]
|
||||||
|
|
||||||
|
# {:create, action_name} ->
|
||||||
|
# destination_form =
|
||||||
|
# relationship.destination
|
||||||
|
# |> Ash.Changeset.for_create(action_name, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# [{destination_form, nil}]
|
||||||
|
|
||||||
|
# :error ->
|
||||||
|
# []
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# defp relationship_forms_for_update(form, relationship, opts, actor) do
|
||||||
|
# case opts[:on_match] do
|
||||||
|
# {:update, update, join_update, fields} ->
|
||||||
|
# join_form =
|
||||||
|
# form.source.data
|
||||||
|
# |> Ash.Changeset.for_update(update, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# destination_form =
|
||||||
|
# relationship.through.__struct__
|
||||||
|
# |> Ash.Changeset.for_update(join_update, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# [{destination_form, nil}, {join_form, fields}]
|
||||||
|
|
||||||
|
# {:update, action_name} ->
|
||||||
|
# destination_form =
|
||||||
|
# form.source.data
|
||||||
|
# |> Ash.Changeset.for_update(action_name, form.source.params, actor: actor)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# [{destination_form, nil}]
|
||||||
|
|
||||||
|
# {:unrelate, _action} ->
|
||||||
|
# changeset =
|
||||||
|
# form.source.data
|
||||||
|
# |> Ash.Changeset.new()
|
||||||
|
# |> Map.put(:params, form.source.params)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# {changeset, Ash.Resource.Info.primary_key(relationship.destination)}
|
||||||
|
|
||||||
|
# value when value in [:ignore, :error] ->
|
||||||
|
# changeset =
|
||||||
|
# form.source.data
|
||||||
|
# |> Ash.Changeset.new()
|
||||||
|
# |> Map.put(:params, form.source.params)
|
||||||
|
# |> Phoenix.HTML.FormData.to_form(as: form.name)
|
||||||
|
|
||||||
|
# {changeset, Ash.Resource.Info.primary_key(relationship.destination)}
|
||||||
|
|
||||||
|
# _ ->
|
||||||
|
# relationship_forms_for_create(form, relationship, opts, actor)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
defp needs_to_load?(opts) do
|
defp needs_to_load?(opts) do
|
||||||
Ash.Changeset.ManagedRelationshipHelpers.must_load?(opts)
|
Ash.Changeset.ManagedRelationshipHelpers.must_load?(opts)
|
||||||
end
|
end
|
||||||
|
@ -541,7 +758,10 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
manage not in [[], nil]
|
manage not in [[], nil]
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> Kernel.||(Map.get(changeset.data, relationship) not in [nil, []])
|
end
|
||||||
|
|
||||||
|
defp can_remove_related?(opts) do
|
||||||
|
Ash.Changeset.ManagedRelationshipHelpers.could_handle_missing?(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp could_lookup?(opts) do
|
defp could_lookup?(opts) do
|
||||||
|
@ -599,7 +819,7 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
form
|
form
|
||||||
) do
|
) do
|
||||||
~H"""
|
~H"""
|
||||||
<Select form={{ form }} field={{ name }} options={{ Nil: nil, True: "true", False: "false" }} />
|
<Select form={{ form }} field={{ name }} options={{ Nil: nil, True: "true", False: "false" }} selected={{boolean_selected(Phoenix.HTML.FormData.input_value(form.source, form, name))}} />
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -619,7 +839,8 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
<Select
|
<Select
|
||||||
form={{ form }}
|
form={{ form }}
|
||||||
field={{ name }}
|
field={{ name }}
|
||||||
options={{ Enum.map(attribute.constraints[:one_of], &{to_name(&1), &1}) }}
|
options={{ Enum.map(attribute.constraints[:one_of], &{to_name(&1), &1}) ++ allow_nil_option(attribute) }}
|
||||||
|
selected={{Phoenix.HTML.FormData.input_value(form.source, form, attribute.name)}}
|
||||||
/>
|
/>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -679,6 +900,7 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:on-click="append_embed"
|
:on-click="append_embed"
|
||||||
|
:if={{can_append_embed?(form.source, attribute.name)}}
|
||||||
phx-value-path={{ form.name <> "[#{attribute.name}]" }}
|
phx-value-path={{ form.name <> "[#{attribute.name}]" }}
|
||||||
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||||
>
|
>
|
||||||
|
@ -697,6 +919,27 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp allow_nil_option(%{allow_nil?: true}), do: [{"", nil}]
|
||||||
|
defp allow_nil_option(%{default: nil}), do: [{"", nil}]
|
||||||
|
defp allow_nil_option(_), do: []
|
||||||
|
|
||||||
|
defp can_append_embed?(changeset, attribute) do
|
||||||
|
case Ash.Changeset.get_attribute(changeset, attribute) do
|
||||||
|
nil ->
|
||||||
|
true
|
||||||
|
|
||||||
|
value when is_list(value) ->
|
||||||
|
true
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp boolean_selected(nil), do: :Nil
|
||||||
|
defp boolean_selected(true), do: :True
|
||||||
|
defp boolean_selected(false), do: :False
|
||||||
|
|
||||||
defp placeholder(value) when is_function(value) do
|
defp placeholder(value) when is_function(value) do
|
||||||
"DEFAULT"
|
"DEFAULT"
|
||||||
end
|
end
|
||||||
|
@ -900,32 +1143,24 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("append_related", %{"path" => path, "type" => type}, socket) do
|
def handle_event("append_related", %{"path" => path}, socket) do
|
||||||
decoded_path = AshPhoenix.decode_path(path)
|
decoded_path = AshPhoenix.decode_path(path)
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|> add_target(decoded_path)
|
|> add_target(decoded_path)
|
||||||
|> add_target(decoded_path ++ ["~", "_lookup"])
|
|> add_target(decoded_path ++ ["*"])
|
||||||
|
|
||||||
# |> add_target(decoded_path ++ ["*"])
|
new_changeset = AshPhoenix.add_related(socket.assigns.changeset, path, "change")
|
||||||
|
|
||||||
initial =
|
|
||||||
if type == "lookup" do
|
|
||||||
%{"_lookup" => "true"}
|
|
||||||
else
|
|
||||||
%{}
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(
|
|> assign(changeset: new_changeset)}
|
||||||
changeset: AshPhoenix.add_related(socket.assigns.changeset, path, "change", add: initial)
|
|
||||||
)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("remove_related", %{"path" => path}, socket) do
|
def handle_event("remove_related", %{"path" => path}, socket) do
|
||||||
socket = add_target(socket, AshPhoenix.decode_path(path))
|
socket = add_target(socket, AshPhoenix.decode_path(path))
|
||||||
|
|
||||||
{record, changeset} = AshPhoenix.remove_related(socket.assigns.changeset, path, "change")
|
{record, changeset} = AshPhoenix.remove_related(socket.assigns.changeset, path, "change")
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
|
@ -936,11 +1171,25 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("append_embed", %{"path" => path}, socket) do
|
def handle_event("append_embed", %{"path" => path}, socket) do
|
||||||
|
decoded_path = AshPhoenix.decode_path(path)
|
||||||
|
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> add_target(decoded_path)
|
||||||
|
|> add_target(decoded_path ++ ["*"])
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
assign(socket, changeset: AshPhoenix.add_embed(socket.assigns.changeset, path, "change"))}
|
assign(socket, changeset: AshPhoenix.add_embed(socket.assigns.changeset, path, "change"))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("remove_embed", %{"path" => path}, socket) do
|
def handle_event("remove_embed", %{"path" => path}, socket) do
|
||||||
|
decoded_path = AshPhoenix.decode_path(path)
|
||||||
|
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> add_target(decoded_path)
|
||||||
|
|> add_target(decoded_path ++ ["*"])
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
changeset: AshPhoenix.remove_embed(socket.assigns.changeset, path, "change")
|
changeset: AshPhoenix.remove_embed(socket.assigns.changeset, path, "change")
|
||||||
|
@ -963,7 +1212,10 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
|
|
||||||
changeset
|
changeset
|
||||||
|> set_table(socket.assigns.table)
|
|> set_table(socket.assigns.table)
|
||||||
|> socket.assigns.api.create()
|
|> socket.assigns.api.create(
|
||||||
|
authorize?: socket.assigns[:authorizing],
|
||||||
|
actor: socket.assigns[:actor]
|
||||||
|
)
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, created} ->
|
{:ok, created} ->
|
||||||
redirect_to(socket, created)
|
redirect_to(socket, created)
|
||||||
|
@ -981,7 +1233,10 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
tenant: socket.assigns[:tenant]
|
tenant: socket.assigns[:tenant]
|
||||||
)
|
)
|
||||||
|> set_table(socket.assigns.table)
|
|> set_table(socket.assigns.table)
|
||||||
|> socket.assigns.api.update()
|
|> socket.assigns.api.update(
|
||||||
|
authorize?: socket.assigns[:authorizing],
|
||||||
|
actor: socket.assigns[:actor]
|
||||||
|
)
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, updated} ->
|
{:ok, updated} ->
|
||||||
redirect_to(socket, updated)
|
redirect_to(socket, updated)
|
||||||
|
@ -999,7 +1254,10 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
tenant: socket.assigns[:tenant]
|
tenant: socket.assigns[:tenant]
|
||||||
)
|
)
|
||||||
|> set_table(socket.assigns.table)
|
|> set_table(socket.assigns.table)
|
||||||
|> socket.assigns.api.destroy()
|
|> socket.assigns.api.destroy(
|
||||||
|
authorize?: socket.assigns[:authorizing],
|
||||||
|
actor: socket.assigns[:actor]
|
||||||
|
)
|
||||||
|> case do
|
|> case do
|
||||||
:ok ->
|
:ok ->
|
||||||
{:noreply,
|
{:noreply,
|
||||||
|
@ -1076,7 +1334,10 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
defp load(record_or_records, [path], socket) when is_binary(path) do
|
defp load(record_or_records, [path], socket) when is_binary(path) do
|
||||||
path = String.to_existing_atom(path)
|
path = String.to_existing_atom(path)
|
||||||
|
|
||||||
case socket.assigns.api.load(record_or_records, [path], actor: socket.assigns.actor) do
|
case socket.assigns.api.load(record_or_records, [path],
|
||||||
|
actor: socket.assigns.actor,
|
||||||
|
authorize?: socket.assigns[:authorizing]
|
||||||
|
) do
|
||||||
{:ok, loaded} ->
|
{:ok, loaded} ->
|
||||||
{:ok, Map.get(loaded, path), loaded}
|
{:ok, Map.get(loaded, path), loaded}
|
||||||
|
|
||||||
|
@ -1180,11 +1441,13 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
resource
|
resource
|
||||||
|> Ash.Resource.Info.relationships()
|
|> Ash.Resource.Info.relationships()
|
||||||
|> Enum.filter(&(&1.name in exactly))
|
|> Enum.filter(&(&1.name in exactly))
|
||||||
|
|> sort_relationships()
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships(resource, :show, _) do
|
def relationships(resource, :show, _) do
|
||||||
resource
|
resource
|
||||||
|> Ash.Resource.Info.relationships()
|
|> Ash.Resource.Info.relationships()
|
||||||
|
|> sort_relationships()
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships(_resource, %{type: :destroy}, _) do
|
def relationships(_resource, %{type: :destroy}, _) do
|
||||||
|
@ -1201,22 +1464,32 @@ defmodule AshAdmin.Components.Resource.Form do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp sort_relationships(relationships) do
|
defp sort_relationships(relationships) do
|
||||||
[:belongs_to, :has_one, :has_many, :many_to_many]
|
{join_assocs, regular_assocs} =
|
||||||
|> Enum.map(fn type ->
|
[:belongs_to, :has_one, :has_many, :many_to_many]
|
||||||
Enum.filter(relationships, &(&1.type == type))
|
|> Enum.map(fn type ->
|
||||||
end)
|
Enum.filter(relationships, &(&1.type == type))
|
||||||
|> Enum.concat()
|
end)
|
||||||
|
|> Enum.concat()
|
||||||
|
|> Enum.split_with(&String.ends_with?(to_string(&1.name), "join_assoc"))
|
||||||
|
|
||||||
|
regular_assocs ++ join_assocs
|
||||||
end
|
end
|
||||||
|
|
||||||
def attributes(resource, action, exactly \\ nil)
|
def attributes(resource, action, exactly \\ nil)
|
||||||
|
|
||||||
|
def attributes(resource, :_lookup, _exactly) do
|
||||||
|
resource
|
||||||
|
|> Ash.Resource.Info.attributes()
|
||||||
|
|> Enum.filter(& &1.primary_key?)
|
||||||
|
|> Enum.map(&Map.put(&1, :default, nil))
|
||||||
|
|> sort_attributes(resource)
|
||||||
|
end
|
||||||
|
|
||||||
def attributes(resource, %{type: :read, arguments: arguments}, exactly)
|
def attributes(resource, %{type: :read, arguments: arguments}, exactly)
|
||||||
when not is_nil(exactly) do
|
when not is_nil(exactly) do
|
||||||
resource
|
resource
|
||||||
|> Ash.Resource.Info.attributes()
|
|> Ash.Resource.Info.attributes()
|
||||||
|> Enum.map(fn attribute ->
|
|> Enum.map(&Map.put(&1, :default, nil))
|
||||||
%{attribute | default: nil}
|
|
||||||
end)
|
|
||||||
|> Enum.concat(arguments)
|
|> Enum.concat(arguments)
|
||||||
|> Enum.filter(&(&1.name in exactly))
|
|> Enum.filter(&(&1.name in exactly))
|
||||||
|> sort_attributes(resource)
|
|> sort_attributes(resource)
|
||||||
|
|
|
@ -50,7 +50,7 @@ defmodule AshAdmin.Components.TopNav do
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-10 flex items-center">
|
<div class="ml-10 flex items-center">
|
||||||
<ActorSelect
|
<ActorSelect
|
||||||
:if={{ show_actor_select?(@actor_resources) }}
|
:if={{ @actor_resources != []}}
|
||||||
actor_resources={{ @actor_resources }}
|
actor_resources={{ @actor_resources }}
|
||||||
authorizing={{ @authorizing }}
|
authorizing={{ @authorizing }}
|
||||||
actor_paused={{ @actor_paused }}
|
actor_paused={{ @actor_paused }}
|
||||||
|
@ -104,7 +104,7 @@ defmodule AshAdmin.Components.TopNav do
|
||||||
<div class="relative px-2 pt-2 pb-3 sm:px-3">
|
<div class="relative px-2 pt-2 pb-3 sm:px-3">
|
||||||
<div class="block px-4 py-2 text-sm">
|
<div class="block px-4 py-2 text-sm">
|
||||||
<ActorSelect
|
<ActorSelect
|
||||||
:if={{ show_actor_select?(@actor_resources) }}
|
:if={{ @actor_resources != [] }}
|
||||||
actor_resources={{ @actor_resources }}
|
actor_resources={{ @actor_resources }}
|
||||||
authorizing={{ @authorizing }}
|
authorizing={{ @authorizing }}
|
||||||
actor_paused={{ @actor_paused }}
|
actor_paused={{ @actor_paused }}
|
||||||
|
@ -163,7 +163,4 @@ defmodule AshAdmin.Components.TopNav do
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp show_actor_select?([]), do: false
|
|
||||||
defp show_actor_select?(_), do: true
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,9 +19,9 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id="actor-hook" class="relative mr-5 text-white" phx-hook="Actor">
|
<div id="actor-hook" class="flex items-center mr-5 text-white" phx-hook="Actor">
|
||||||
<div :if={{ @actor }}>
|
<div>
|
||||||
<span :if={{ @actor }}>
|
<span>
|
||||||
<button :on-click={{ @toggle_authorizing }} type="button">
|
<button :on-click={{ @toggle_authorizing }} type="button">
|
||||||
<svg
|
<svg
|
||||||
:if={{ @authorizing }}
|
:if={{ @authorizing }}
|
||||||
|
@ -50,7 +50,7 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button :on-click={{ @toggle_actor_paused }} type="button">
|
<button :if={{@actor}} :on-click={{ @toggle_actor_paused }} type="button">
|
||||||
<svg
|
<svg
|
||||||
:if={{ @actor_paused }}
|
:if={{ @actor_paused }}
|
||||||
width="1em"
|
width="1em"
|
||||||
|
@ -73,6 +73,7 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<LiveRedirect
|
<LiveRedirect
|
||||||
|
:if={{@actor}}
|
||||||
class="hover:text-blue-400 hover:underline"
|
class="hover:text-blue-400 hover:underline"
|
||||||
to={{ash_show_path(
|
to={{ash_show_path(
|
||||||
@prefix,
|
@prefix,
|
||||||
|
@ -84,7 +85,7 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
||||||
>
|
>
|
||||||
{{ user_display(@actor) }}
|
{{ user_display(@actor) }}
|
||||||
</LiveRedirect>
|
</LiveRedirect>
|
||||||
<button :on-click={{ @clear_actor }} type="button">
|
<button :if={{@actor}} :on-click={{ @clear_actor }} type="button">
|
||||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg">
|
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
|
|
|
@ -74,6 +74,8 @@ defmodule AshAdmin.Router do
|
||||||
path
|
path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
prefix = String.trim_trailing(prefix, "/")
|
||||||
|
|
||||||
apis = opts[:apis]
|
apis = opts[:apis]
|
||||||
Enum.each(apis, &Code.ensure_compiled/1)
|
Enum.each(apis, &Code.ensure_compiled/1)
|
||||||
api = List.first(opts[:apis])
|
api = List.first(opts[:apis])
|
||||||
|
|
4
mix.exs
4
mix.exs
|
@ -88,8 +88,8 @@ defmodule AshAdmin.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:ash, "~> 1.37"},
|
{:ash, "~> 1.37 and >= 1.37.1"},
|
||||||
{:ash_phoenix, "~> 0.4 and >= 0.4.6"},
|
{:ash_phoenix, "~> 0.4 and >= 0.4.7"},
|
||||||
{:surface, "~> 0.3.2"},
|
{:surface, "~> 0.3.2"},
|
||||||
{:phoenix_live_view, "~> 0.15.4"},
|
{:phoenix_live_view, "~> 0.15.4"},
|
||||||
{:phoenix_html, "~> 2.14.1 or ~> 2.15"},
|
{:phoenix_html, "~> 2.14.1 or ~> 2.15"},
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -1,6 +1,6 @@
|
||||||
%{
|
%{
|
||||||
"ash": {:hex, :ash, "1.37.0", "c3a60bef3a4d429f127d9392f533c8958e153990c8dd69d8d3e029f2a3ce24cc", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "d09b1f82d3e20dd3dd6f16f3dcbf4bc183b0c903c1343858fdbb0069aec969bc"},
|
"ash": {:hex, :ash, "1.37.1", "b9138bc129603d66e7c1826bc061bfb6e7cbafcb94fbc55e2f27825a6b354e3b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "0d5911191c247ea7170145ee765d939864375a484972a2c259f1d65c9a72ce29"},
|
||||||
"ash_phoenix": {:hex, :ash_phoenix, "0.4.6", "a484b582416f7045159194bed2e4aa24d0a624187015f8e26d2872af459490d8", [:mix], [{:ash, "~> 1.37", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "b320a51694ae62b60629d997cd2a0965b58de2c250985a24e492b35db07a4a40"},
|
"ash_phoenix": {:hex, :ash_phoenix, "0.4.7", "90315781a97accf0cd26367573ae39673d98b71370bf11d14b7208fbc272fbfa", [:mix], [{:ash, "~> 1.37", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "733abb6829b828a4f412479281fb9e172c8b0be8917a408a12fa53091c5ec41f"},
|
||||||
"ash_policy_authorizer": {:hex, :ash_policy_authorizer, "0.16.0", "18b2352facf8f860458bf81aeed481ced9a7e13e8eb2826e7ae714bf0fbd4792", [:mix], [{:ash, "~> 1.35 and >= 1.35.1", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "40b2fd1071a60da1404c5a133f43bf416f5cb2c4f7313a7949b638340f482a61"},
|
"ash_policy_authorizer": {:hex, :ash_policy_authorizer, "0.16.0", "18b2352facf8f860458bf81aeed481ced9a7e13e8eb2826e7ae714bf0fbd4792", [:mix], [{:ash, "~> 1.35 and >= 1.35.1", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "40b2fd1071a60da1404c5a133f43bf416f5cb2c4f7313a7949b638340f482a61"},
|
||||||
"ash_postgres": {:hex, :ash_postgres, "0.35.4", "f72b0780c839f588888985578aa0a03ae027f4e460fbeab4ddf209f97f794c77", [:mix], [{:ash, "~> 1.34 and >= 1.34.6", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.5", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "cba9e3ad50b1888dbb4623d80b88b75997f94912e088e69554259f9f00543a98"},
|
"ash_postgres": {:hex, :ash_postgres, "0.35.4", "f72b0780c839f588888985578aa0a03ae027f4e460fbeab4ddf209f97f794c77", [:mix], [{:ash, "~> 1.34 and >= 1.34.6", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.5", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "cba9e3ad50b1888dbb4623d80b88b75997f94912e088e69554259f9f00543a98"},
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||||
|
|
20
priv/repo/migrations/20210326183237_migrate_resources6.exs
Normal file
20
priv/repo/migrations/20210326183237_migrate_resources6.exs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Demo.Repo.Migrations.MigrateResources6 do
|
||||||
|
@moduledoc """
|
||||||
|
Updates resources based on their most recent snapshots.
|
||||||
|
|
||||||
|
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
create table(:ticket_links, primary_key: false) do
|
||||||
|
add :source_id, references(:tickets, type: :uuid, column: :id), primary_key: true
|
||||||
|
add :destination_id, references(:tickets, type: :uuid, column: :id), primary_key: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
drop table(:ticket_links)
|
||||||
|
end
|
||||||
|
end
|
21
priv/repo/migrations/20210326184142_migrate_resources7.exs
Normal file
21
priv/repo/migrations/20210326184142_migrate_resources7.exs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Demo.Repo.Migrations.MigrateResources7 do
|
||||||
|
@moduledoc """
|
||||||
|
Updates resources based on their most recent snapshots.
|
||||||
|
|
||||||
|
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:ticket_links) do
|
||||||
|
add :type, :text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:ticket_links) do
|
||||||
|
remove :type
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
21
priv/repo/migrations/20210326184148_migrate_resources8.exs
Normal file
21
priv/repo/migrations/20210326184148_migrate_resources8.exs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Demo.Repo.Migrations.MigrateResources8 do
|
||||||
|
@moduledoc """
|
||||||
|
Updates resources based on their most recent snapshots.
|
||||||
|
|
||||||
|
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:ticket_links) do
|
||||||
|
modify :type, :text, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:ticket_links) do
|
||||||
|
modify :type, :text, null: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "destination_id",
|
||||||
|
"primary_key?": true,
|
||||||
|
"references": {
|
||||||
|
"destination_field": "id",
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"table": "tickets"
|
||||||
|
},
|
||||||
|
"type": "uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "source_id",
|
||||||
|
"primary_key?": true,
|
||||||
|
"references": {
|
||||||
|
"destination_field": "id",
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"table": "tickets"
|
||||||
|
},
|
||||||
|
"type": "uuid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"base_filter": null,
|
||||||
|
"hash": "366F7BEE4E14C4BE0D18E45E655650E3FA327F53AE9AD730E09382896E4C2A6A",
|
||||||
|
"identities": [],
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"repo": "Elixir.Demo.Repo",
|
||||||
|
"table": "ticket_links"
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "destination_id",
|
||||||
|
"primary_key?": true,
|
||||||
|
"references": {
|
||||||
|
"destination_field": "id",
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"table": "tickets"
|
||||||
|
},
|
||||||
|
"type": "uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "source_id",
|
||||||
|
"primary_key?": true,
|
||||||
|
"references": {
|
||||||
|
"destination_field": "id",
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"table": "tickets"
|
||||||
|
},
|
||||||
|
"type": "uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": true,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "type",
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"base_filter": null,
|
||||||
|
"hash": "53C30FF7B236DF0866D9DE5D166C6AB5AF1CD86B6DAC48D93653D70E259F4B1C",
|
||||||
|
"identities": [],
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"repo": "Elixir.Demo.Repo",
|
||||||
|
"table": "ticket_links"
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "destination_id",
|
||||||
|
"primary_key?": true,
|
||||||
|
"references": {
|
||||||
|
"destination_field": "id",
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"table": "tickets"
|
||||||
|
},
|
||||||
|
"type": "uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "source_id",
|
||||||
|
"primary_key?": true,
|
||||||
|
"references": {
|
||||||
|
"destination_field": "id",
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"table": "tickets"
|
||||||
|
},
|
||||||
|
"type": "uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"name": "type",
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"base_filter": null,
|
||||||
|
"hash": "DF68E19931FD501968359AD5E18F6366F494A39E658968C708E2522B3BC9C81C",
|
||||||
|
"identities": [],
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"repo": "Elixir.Demo.Repo",
|
||||||
|
"table": "ticket_links"
|
||||||
|
}
|
Loading…
Reference in a new issue