How to use Pagination on Calculations? Do I need to handcraft my own Pagination macro?

This is the channel query for that response

{
  channel(id: "channel ID") {
    id
    channelMessages(offset: 0, limit: 100) {
      count
      hasNextPage
      results {
        ...on TextChannelMessage {
          id
        }
        ...on ImageChannelMessages {
          id
        }
      }
    }
  }
}

This is the calculation module

defmodule MyApp.ChannelMessages do
  use Ash.Calculation

  def load(_, _, context) do
    limit = context[:limit] || 100
    offset = context[:offset] || 0

    [
      :channel_messages_count,
      messages: MyApp.Message |> Ash.Query.limit(limit) |> Ash.Query.offset(offset)
    ]
  end

  def calculate([record], _,  _) do
     {:ok,
     [
       %{
         id: record.id,
         count: record.channel_messages_count,
         has_next_page: false,
         results:
           record.messages
           |> Enum.map(
             &%Ash.Union{type: MyApp.ChannelMessageUnion.struct_to_name(&1), value: &1}
           )
       }
     ]}
  end
end

This calculation is set in the Channel resource.

We also made sure that in iex we get the result for channel_messages from the calculation

Okay, interesting…so this is probably an issue with AshGraphql resolving the appropriate union types… :thinking: You’re sure that the value in {type: MyApp.ChannelMessageUnion.struct_to_name(&1) corresponds to the key in the type list of the union definition? Nothing jumps out at me as to why this would be happening otherwise. I may need a reproduction I can run locally to figure this one out.

Is there a way I can introspect the generated schema? I was thinking that as well so I played around with it changing the type name but direct calculate with {:array, union} works. I assumed it was correct. Our message resources have an attribute :type which are :text|:image|other types. Here are some examples again for reference to how it’s setup

defmodule MyApp.ChannelMessageUnion do
  @moduledoc false
  use AshGraphql.Type

  alias MyApp.{ImageChannelMessage, TextChannelMessage}

  @types [
    image: [
      type: ImageChannelMessage,
      tag: :type,
      tag_value: :image_channel_message
    ],
    text: [
      type: TextChannelMessage,
      tag: :type,
      tag_value: :text_channel_message
    ]
  ]

  @structs_to_names Keyword.new(@types, fn {key, _value} ->
                      {key, key}
                    end)

  use Ash.Type.NewType,
    subtype_of: :union,
    constraints: [
      types: @types
    ]

  def struct_to_name(%_message_struct{type: type}), do: @structs_to_names[type]

  @impl true
  def graphql_type(_), do: :message

  @impl true
  def graphql_unnested_unions(_), do: Keyword.keys(@types)
end
  • Working with direct channel message union
calculations do
  calculate :channel_messages, {:array, MyApp.ChannelMessageUnion}, fn record, _ ->
     ... load messages on record

    record.messages
    |> Enum.map(&%Ash.Union{type: MyApp.ChannelMessageUnion.struct_to_name(&1), value: &1})
  end do
      argument :offset, :integer do
        default 0
      end
  
      argument :limit, :integer do
        default 10
      end
  end
end
  • Not working with indirect PageOfChannelMessages
defmodule MyApp.PageOfChannelMessages do
  use AshGraphql.Type

  use Ash.Type.NewType,
    subtype_of: :map,
    constraints: [
      fields: [
        count: [
          type: :integer,
          allow_nil?: false
        ],
        has_next_page: [
          type: :boolean,
          allow_nil?: false
        ],
        results: [
          type: {:array, MyApp.ChannelMessageUnion},
          allow_nil?: false
        ]
      ]
    ]

  @impl true
  def graphql_type(_), do: :page_of_channel_messages
end

And corresponding calculation using the PageOfChannelMessages

calculations do
  calculate :channel_messages,
              MyApp.PageOfChannelMessages,
              MyApp.Calculations.ChannelMessages do
      argument :offset, :integer do
        default 0
      end
  
      argument :limit, :integer do
        default 10
      end
  end
end

There are ways with absinthe to spit out the underlying gql schema, but from what I can tell this is an error in our union resolution code. I’m hoping this is the last hurdle to getting this working (didn’t anticipate this being such a big lift, sorry about that). This is a powerful capability to be able to do, so hopefully we can get it squared away soon. Unfortunately it’s hard for me to track down where in our union resolution code this is happening. If you could either 1. give me access to your project to clone it and add a test case that reproduces the issue or 2. reproduce this in a test in ash_graphql (preferred, but more time intensive I know), that would enable me to resolve the issue very quickly I’m sure.

Sounds good. Let me try to set up one for you tomorrow or over the weekend and post a git repo link for you.

1 Like

I’ve added a custom_paginate_test for this in my forked repo. GitHub - jichon/ash_graphql: An absinthe backed graphql API extension for the Ash Framework. Thanks

This is awesome, thank you! :bowing_man: I will look at this tomorrow or Monday, as soon as I have a chance :slight_smile:

That helped tons. Thank you. So this type resolution space can be a bit complex, there may still be some issues, just let me know. I’ve found and fixed the issue in question however.

Nice. it’s working now. I looked at your fix and it’s a simple one. Amazing…

1 Like