mirror of
https://github.com/ash-project/ash_graphql.git
synced 2024-09-20 05:13:33 +12:00
parent
3a11d988ab
commit
9cc9da0f2e
3 changed files with 163 additions and 83 deletions
|
@ -788,18 +788,19 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
case Map.fetch(args, :sort) do
|
case Map.fetch(args, :sort) do
|
||||||
{:ok, sort} ->
|
{:ok, sort} ->
|
||||||
keyword_sort =
|
keyword_sort =
|
||||||
Enum.map(sort, fn %{order: order, field: field} ->
|
Enum.map(sort, fn %{order: order, field: field} = input ->
|
||||||
{field, order}
|
case Ash.Resource.Info.calculation(resource, field) do
|
||||||
|
%{arguments: [_ | _]} ->
|
||||||
|
input_name = String.to_existing_atom("#{field}_input")
|
||||||
|
|
||||||
|
{field, {order, input[input_name] || %{}}}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{field, order}
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
fields =
|
Ash.Query.sort(query, keyword_sort)
|
||||||
keyword_sort
|
|
||||||
|> Keyword.keys()
|
|
||||||
|> Enum.filter(&Ash.Resource.Info.public_aggregate(resource, &1))
|
|
||||||
|
|
||||||
query
|
|
||||||
|> Ash.Query.load(fields)
|
|
||||||
|> Ash.Query.sort(keyword_sort)
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
query
|
query
|
||||||
|
|
|
@ -789,7 +789,7 @@ defmodule AshGraphql.Resource do
|
||||||
|> maybe_wrap_non_null(argument_required?(argument))
|
|> maybe_wrap_non_null(argument_required?(argument))
|
||||||
|
|
||||||
%Absinthe.Blueprint.Schema.FieldDefinition{
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
identifier: argument.name,
|
identifier: name,
|
||||||
module: schema,
|
module: schema,
|
||||||
name: to_string(name),
|
name: to_string(name),
|
||||||
type: type,
|
type: type,
|
||||||
|
@ -1788,25 +1788,26 @@ defmodule AshGraphql.Resource do
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
%Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
%Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
||||||
fields: [
|
fields:
|
||||||
%Absinthe.Blueprint.Schema.FieldDefinition{
|
[
|
||||||
identifier: :order,
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
module: schema,
|
identifier: :order,
|
||||||
name: "order",
|
module: schema,
|
||||||
default_value: :asc,
|
name: "order",
|
||||||
type: :sort_order,
|
default_value: :asc,
|
||||||
__reference__: ref(__ENV__)
|
type: :sort_order,
|
||||||
},
|
__reference__: ref(__ENV__)
|
||||||
%Absinthe.Blueprint.Schema.FieldDefinition{
|
|
||||||
identifier: :field,
|
|
||||||
module: schema,
|
|
||||||
name: "field",
|
|
||||||
type: %Absinthe.Blueprint.TypeReference.NonNull{
|
|
||||||
of_type: resource_sort_field_type(resource)
|
|
||||||
},
|
},
|
||||||
__reference__: ref(__ENV__)
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
}
|
identifier: :field,
|
||||||
],
|
module: schema,
|
||||||
|
name: "field",
|
||||||
|
type: %Absinthe.Blueprint.TypeReference.NonNull{
|
||||||
|
of_type: resource_sort_field_type(resource)
|
||||||
|
},
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
] ++ calc_input_fields(resource, schema),
|
||||||
identifier: resource_sort_type(resource),
|
identifier: resource_sort_type(resource),
|
||||||
module: schema,
|
module: schema,
|
||||||
name: resource |> resource_sort_type() |> to_string() |> Macro.camelize(),
|
name: resource |> resource_sort_type() |> to_string() |> Macro.camelize(),
|
||||||
|
@ -1818,6 +1819,43 @@ defmodule AshGraphql.Resource do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# sobelow_skip ["DOS.StringToAtom"]
|
||||||
|
defp calc_input_fields(resource, schema) do
|
||||||
|
calcs =
|
||||||
|
resource
|
||||||
|
|> Ash.Resource.Info.public_calculations()
|
||||||
|
|> Enum.reject(fn
|
||||||
|
%{type: {:array, _}} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
calc ->
|
||||||
|
Ash.Type.embedded_type?(calc.type) || Enum.empty?(calc.arguments)
|
||||||
|
end)
|
||||||
|
|
||||||
|
field_names = AshGraphql.Resource.Info.field_names(resource)
|
||||||
|
|
||||||
|
Enum.map(calcs, fn calc ->
|
||||||
|
input_name = "#{field_names[calc.name] || calc.name}_input"
|
||||||
|
|
||||||
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
|
identifier: String.to_atom("#{calc.name}_input"),
|
||||||
|
module: schema,
|
||||||
|
name: input_name,
|
||||||
|
type: calc_input_type(calc.name, resource),
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
# sobelow_skip ["DOS.StringToAtom"]
|
||||||
|
defp calc_input_type(calc, resource) do
|
||||||
|
field_names = AshGraphql.Resource.Info.field_names(resource)
|
||||||
|
|
||||||
|
String.to_atom(
|
||||||
|
"#{AshGraphql.Resource.Info.type(resource)}_#{field_names[calc] || calc}_input"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp filter_input(resource, schema) do
|
defp filter_input(resource, schema) do
|
||||||
case resource_filter_fields(resource, schema) do
|
case resource_filter_fields(resource, schema) do
|
||||||
[] ->
|
[] ->
|
||||||
|
@ -1838,11 +1876,9 @@ defmodule AshGraphql.Resource do
|
||||||
defp calculation_input(resource, schema) do
|
defp calculation_input(resource, schema) do
|
||||||
resource
|
resource
|
||||||
|> Ash.Resource.Info.public_calculations()
|
|> Ash.Resource.Info.public_calculations()
|
||||||
|> Enum.filter(fn %{calculation: {module, _}} ->
|
|> Enum.flat_map(fn %{calculation: {module, _}} = calculation ->
|
||||||
Code.ensure_compiled(module)
|
Code.ensure_compiled(module)
|
||||||
:erlang.function_exported(module, :expression, 2)
|
filterable? = :erlang.function_exported(module, :expression, 2)
|
||||||
end)
|
|
||||||
|> Enum.flat_map(fn calculation ->
|
|
||||||
field_type = calculation_type(calculation, resource)
|
field_type = calculation_type(calculation, resource)
|
||||||
|
|
||||||
arguments = calculation_args(calculation, resource, schema)
|
arguments = calculation_args(calculation, resource, schema)
|
||||||
|
@ -1864,56 +1900,54 @@ defmodule AshGraphql.Resource do
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
filter_input = %Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
input = %Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
||||||
fields: arguments,
|
fields: arguments,
|
||||||
identifier:
|
identifier: String.to_atom(to_string(calc_input_type(calculation.name, resource))),
|
||||||
String.to_atom(
|
|
||||||
to_string(calculation_filter_field_type(resource, calculation)) <> "_input"
|
|
||||||
),
|
|
||||||
module: schema,
|
module: schema,
|
||||||
name:
|
name: Macro.camelize(to_string(calc_input_type(calculation.name, resource))),
|
||||||
Macro.camelize(
|
|
||||||
to_string(calculation_filter_field_type(resource, calculation)) <> "_input"
|
|
||||||
),
|
|
||||||
__reference__: ref(__ENV__)
|
__reference__: ref(__ENV__)
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_input_field = %Absinthe.Blueprint.Schema.FieldDefinition{
|
types =
|
||||||
identifier: :input,
|
if Enum.empty?(arguments) do
|
||||||
module: schema,
|
[]
|
||||||
name: "input",
|
else
|
||||||
type:
|
[input]
|
||||||
String.to_atom(
|
end
|
||||||
to_string(calculation_filter_field_type(resource, calculation)) <> "_input"
|
|
||||||
),
|
|
||||||
__reference__: ref(__ENV__)
|
|
||||||
}
|
|
||||||
|
|
||||||
if Enum.empty?(arguments) do
|
if filterable? do
|
||||||
type_def = %Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
type_def =
|
||||||
fields: filter_fields,
|
if Enum.empty?(arguments) do
|
||||||
identifier: calculation_filter_field_type(resource, calculation),
|
%Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
||||||
module: schema,
|
fields: filter_fields,
|
||||||
name: Macro.camelize(to_string(calculation_filter_field_type(resource, calculation))),
|
identifier: calculation_filter_field_type(resource, calculation),
|
||||||
__reference__: ref(__ENV__)
|
module: schema,
|
||||||
}
|
name:
|
||||||
|
Macro.camelize(to_string(calculation_filter_field_type(resource, calculation))),
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
filter_input_field = %Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
|
identifier: :input,
|
||||||
|
module: schema,
|
||||||
|
name: "input",
|
||||||
|
type: String.to_atom(to_string(calc_input_type(calculation.name, resource))),
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
|
||||||
[
|
%Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
||||||
type_def
|
fields: [filter_input_field | filter_fields],
|
||||||
]
|
identifier: calculation_filter_field_type(resource, calculation),
|
||||||
|
module: schema,
|
||||||
|
name:
|
||||||
|
Macro.camelize(to_string(calculation_filter_field_type(resource, calculation))),
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
[type_def | types]
|
||||||
else
|
else
|
||||||
type_def = %Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
|
types
|
||||||
fields: [filter_input_field | filter_fields],
|
|
||||||
identifier: calculation_filter_field_type(resource, calculation),
|
|
||||||
module: schema,
|
|
||||||
name: Macro.camelize(to_string(calculation_filter_field_type(resource, calculation))),
|
|
||||||
__reference__: ref(__ENV__)
|
|
||||||
}
|
|
||||||
|
|
||||||
[
|
|
||||||
filter_input,
|
|
||||||
type_def
|
|
||||||
]
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -2091,12 +2125,12 @@ defmodule AshGraphql.Resource do
|
||||||
identifier: resource_sort_field_type(resource),
|
identifier: resource_sort_field_type(resource),
|
||||||
__reference__: ref(__ENV__),
|
__reference__: ref(__ENV__),
|
||||||
values:
|
values:
|
||||||
Enum.map(sort_values, fn sort_value ->
|
Enum.map(sort_values, fn {sort_value_alias, sort_value} ->
|
||||||
%Absinthe.Blueprint.Schema.EnumValueDefinition{
|
%Absinthe.Blueprint.Schema.EnumValueDefinition{
|
||||||
module: schema,
|
module: schema,
|
||||||
identifier: sort_value,
|
identifier: sort_value_alias,
|
||||||
__reference__: AshGraphql.Resource.ref(env),
|
__reference__: AshGraphql.Resource.ref(env),
|
||||||
name: String.upcase(to_string(sort_value)),
|
name: String.upcase(to_string(sort_value_alias)),
|
||||||
value: sort_value
|
value: sort_value
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
@ -2164,12 +2198,25 @@ defmodule AshGraphql.Resource do
|
||||||
end)
|
end)
|
||||||
|> Enum.map(& &1.name)
|
|> Enum.map(& &1.name)
|
||||||
|
|
||||||
|
calculation_sort_values =
|
||||||
|
resource
|
||||||
|
|> Ash.Resource.Info.public_calculations()
|
||||||
|
|> Enum.reject(fn
|
||||||
|
%{type: {:array, _}} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
attribute ->
|
||||||
|
Ash.Type.embedded_type?(attribute.type)
|
||||||
|
end)
|
||||||
|
|> Enum.map(& &1.name)
|
||||||
|
|
||||||
attribute_sort_values
|
attribute_sort_values
|
||||||
|> Enum.concat(aggregate_sort_values)
|
|> Enum.concat(aggregate_sort_values)
|
||||||
|> Enum.map(fn name ->
|
|> Enum.concat(calculation_sort_values)
|
||||||
field_names[name] || name
|
|
||||||
end)
|
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|
|> Enum.map(fn name ->
|
||||||
|
{field_names[name] || name, name}
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
# sobelow_skip ["DOS.StringToAtom"]
|
# sobelow_skip ["DOS.StringToAtom"]
|
||||||
|
@ -2687,7 +2734,7 @@ defmodule AshGraphql.Resource do
|
||||||
raise "Cannot construct an input type for #{inspect(type)}"
|
raise "Cannot construct an input type for #{inspect(type)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
AshGraphql.Resource.Info.type(resource)
|
AshGraphql.Resource.Info.type(type)
|
||||||
else
|
else
|
||||||
if Ash.Type.embedded_type?(type) do
|
if Ash.Type.embedded_type?(type) do
|
||||||
if input? do
|
if input? do
|
||||||
|
@ -2733,7 +2780,8 @@ defmodule AshGraphql.Resource do
|
||||||
Ash.Type.Atom,
|
Ash.Type.Atom,
|
||||||
%Ash.Resource.Attribute{constraints: constraints, name: name},
|
%Ash.Resource.Attribute{constraints: constraints, name: name},
|
||||||
resource
|
resource
|
||||||
) do
|
)
|
||||||
|
when resource do
|
||||||
if is_list(constraints[:one_of]) do
|
if is_list(constraints[:one_of]) do
|
||||||
atom_enum_type(resource, name)
|
atom_enum_type(resource, name)
|
||||||
else
|
else
|
||||||
|
|
|
@ -269,6 +269,37 @@ defmodule AshGraphql.ReadTest do
|
||||||
assert %{data: %{"postLibrary" => [%{"foo" => "1foo2", "bar" => "1bar2"}]}} = result
|
assert %{data: %{"postLibrary" => [%{"foo" => "1foo2", "bar" => "1bar2"}]}} = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "the same calculation can be sorted on twice with different arguments via aliases" do
|
||||||
|
AshGraphql.Test.Post
|
||||||
|
|> Ash.Changeset.for_create(:create, text: "bar", text1: "1", text2: "2", published: true)
|
||||||
|
|> AshGraphql.Test.Api.create!()
|
||||||
|
|
||||||
|
AshGraphql.Test.Post
|
||||||
|
|> Ash.Changeset.for_create(:create, text: "bar", text1: "1", text2: "2", published: true)
|
||||||
|
|> AshGraphql.Test.Api.create!()
|
||||||
|
|
||||||
|
resp =
|
||||||
|
"""
|
||||||
|
query PostLibrary($published: Boolean) {
|
||||||
|
postLibrary(published: $published, sort: [{field: TEXT1_AND2, order: DESC, text1And2Input: {separator: "a"}}, {field: TEXT1_AND2, order: DESC, text1And2Input: {separator: "b"}}]) {
|
||||||
|
a: text1And2(separator: "a")
|
||||||
|
b: text1And2(separator: "b")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|> Absinthe.run(AshGraphql.Test.Schema)
|
||||||
|
|
||||||
|
assert {:ok, result} = resp
|
||||||
|
|
||||||
|
refute Map.has_key?(result, :errors)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
data: %{
|
||||||
|
"postLibrary" => [%{"a" => "1a2", "b" => "1b2"}, %{"a" => "1a2", "b" => "1b2"}]
|
||||||
|
}
|
||||||
|
} = result
|
||||||
|
end
|
||||||
|
|
||||||
test "a read with a non-id primary key fills in the id field" do
|
test "a read with a non-id primary key fills in the id field" do
|
||||||
record =
|
record =
|
||||||
AshGraphql.Test.NonIdPrimaryKey
|
AshGraphql.Test.NonIdPrimaryKey
|
||||||
|
|
Loading…
Reference in a new issue