Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>
#
# SPDX-License-Identifier: MIT

defmodule Diffo.Provider.Calculations.FieldViaAssignedRelationship do
@moduledoc false
use Ash.Resource.Calculation

@impl true
def load(_query, _opts, _context), do: []

@impl true
def calculate(records, opts, _context) do
via = opts[:via]
field = opts[:field]

Enum.map(records, fn record ->
record.id
|> traverse(via)
|> Enum.flat_map(fn source_id ->
Diffo.Provider.Instance
|> Ash.Query.filter_input(id: source_id)
|> Ash.read!(domain: Diffo.Provider)
|> Enum.map(&Map.get(&1, field))
end)
end)
end

defp traverse(id, nil) do
Diffo.Provider.AssignmentRelationship
|> Ash.Query.filter_input(target_id: id)
|> Ash.read!(domain: Diffo.Provider)
|> Enum.map(& &1.source_id)
end

defp traverse(id, via) do
Enum.reduce(via, [id], fn alias_step, ids ->
Enum.flat_map(ids, fn i ->
Diffo.Provider.AssignmentRelationship
|> Ash.Query.filter_input(target_id: i, alias: alias_step)
|> Ash.read!(domain: Diffo.Provider)
|> Enum.map(& &1.source_id)
end)
end)
end
end
123 changes: 123 additions & 0 deletions test/provider/extension/field_via_assigned_relationship_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>
#
# SPDX-License-Identifier: MIT

defmodule Diffo.Provider.Extension.FieldViaAssignedRelationshipTest do
@moduledoc false
use ExUnit.Case, async: true
@moduletag :domain_extended

alias Diffo.Provider.Assignment
alias Diffo.Test.Servo

setup do
AshNeo4j.Sandbox.checkout()
on_exit(&AshNeo4j.Sandbox.rollback/0)
end

defp setup_card(name) do
updates = [
card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus],
ports: [first: 1, last: 48, assignable_type: "ADSL2+"]
]

{:ok, card} = Servo.build_card(%{name: name})
{:ok, card} = Servo.define_card(card, %{characteristic_value_updates: updates})
{:ok, card} = Servo.lifecycle_card(card, %{resource_state: :operating})
card
end

describe "FieldViaAssignedRelationship — aliased via" do
test "returns field from source instance reached via alias" do
card = setup_card("cvc-01")
{:ok, service} = Servo.build_access_service(%{})

{:ok, _card} =
Servo.assign_port(card, %{
assignment: %Assignment{
assignee_id: service.id,
operation: :auto_assign,
alias: :primary
}
})

service = Ash.load!(service, [:assigner_name], domain: Servo)

assert service.assigner_name == ["cvc-01"]
end

test "returns empty list when no assignment exists" do
{:ok, service} = Servo.build_access_service(%{})

service = Ash.load!(service, [:assigner_name], domain: Servo)

assert service.assigner_name == []
end

test "alias filters to only the matching source" do
card_a = setup_card("cvc-01")
card_b = setup_card("cvc-02")
{:ok, service} = Servo.build_access_service(%{})

{:ok, _card_a} =
Servo.assign_port(card_a, %{
assignment: %Assignment{
assignee_id: service.id,
operation: :auto_assign,
alias: :primary
}
})

{:ok, _card_b} =
Servo.assign_port(card_b, %{
assignment: %Assignment{
assignee_id: service.id,
operation: :auto_assign,
alias: :secondary
}
})

service = Ash.load!(service, [:assigner_name], domain: Servo)

assert service.assigner_name == ["cvc-01"]
end
end

describe "FieldViaAssignedRelationship — unaliased (all assigners)" do
test "returns fields from all source instances regardless of alias" do
card_a = setup_card("cvc-01")
card_b = setup_card("cvc-02")
{:ok, service} = Servo.build_access_service(%{})

{:ok, _card_a} =
Servo.assign_port(card_a, %{
assignment: %Assignment{
assignee_id: service.id,
operation: :auto_assign,
alias: :primary
}
})

{:ok, _card_b} =
Servo.assign_port(card_b, %{
assignment: %Assignment{
assignee_id: service.id,
operation: :auto_assign,
alias: :secondary
}
})

service = Ash.load!(service, [:assigner_names], domain: Servo)

assert Enum.sort(service.assigner_names) == ["cvc-01", "cvc-02"]
end

test "returns empty list when no assignments exist" do
{:ok, service} = Servo.build_access_service(%{})

service = Ash.load!(service, [:assigner_names], domain: Servo)

assert service.assigner_names == []
end
end
end
8 changes: 8 additions & 0 deletions test/support/resource/instance/access_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ defmodule Diffo.Test.Instance.AccessService do
end
end

calculations do
calculate :assigner_name, {:array, :string},
{Diffo.Provider.Calculations.FieldViaAssignedRelationship, [via: [:primary], field: :name]}

calculate :assigner_names, {:array, :string},
{Diffo.Provider.Calculations.FieldViaAssignedRelationship, [field: :name]}
end

actions do
create :build do
accept [:id, :name, :type]
Expand Down
Loading