From d3f8ef3972f6f61ef10cdf996a49b4848a22e00f Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Fri, 24 Oct 2025 18:41:50 +1030 Subject: [PATCH 1/7] formatter fix --- .formatter.exs | 3 +- .../provider/assigner/assignable_value.ex | 2 +- lib/diffo/provider/assigner/assignment.ex | 2 +- .../provider/components/base_instance.ex | 2 +- lib/diffo/provider/components/entity.ex | 2 +- lib/diffo/provider/components/event.ex | 140 ++++++++++++++++++ lib/diffo/provider/components/party.ex | 2 +- lib/diffo/provider/components/place.ex | 2 +- test/support/resource/card_value.ex | 2 +- test/support/resource/shelf_value.ex | 2 +- 10 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 lib/diffo/provider/components/event.ex diff --git a/.formatter.exs b/.formatter.exs index f710659..b11f473 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -20,7 +20,8 @@ locals_without_parens = [ initial_states: 1, default_initial_state: 1, state_attribute: 1, - transition: 1 + transition: 1, + compact: 1 ] [ diff --git a/lib/diffo/provider/assigner/assignable_value.ex b/lib/diffo/provider/assigner/assignable_value.ex index 4358856..e049eb8 100644 --- a/lib/diffo/provider/assigner/assignable_value.ex +++ b/lib/diffo/provider/assigner/assignable_value.ex @@ -12,7 +12,7 @@ defmodule Diffo.Provider.AssignableValue do jason do pick [:first, :last, :free, :type, :algorithm] - compact(true) + compact true end typed_struct do diff --git a/lib/diffo/provider/assigner/assignment.ex b/lib/diffo/provider/assigner/assignment.ex index 131f041..79370b5 100644 --- a/lib/diffo/provider/assigner/assignment.ex +++ b/lib/diffo/provider/assigner/assignment.ex @@ -12,7 +12,7 @@ defmodule Diffo.Provider.Assignment do jason do pick [:id, :assignee_id, :operation] - compact(true) + compact true end typed_struct do diff --git a/lib/diffo/provider/components/base_instance.ex b/lib/diffo/provider/components/base_instance.ex index 11a911f..3f10855 100644 --- a/lib/diffo/provider/components/base_instance.ex +++ b/lib/diffo/provider/components/base_instance.ex @@ -58,7 +58,7 @@ defmodule Diffo.Provider.BaseInstance do :type ] - compact(true) + compact true customize fn result, record -> result diff --git a/lib/diffo/provider/components/entity.ex b/lib/diffo/provider/components/entity.ex index a64e1e2..4dc7d36 100644 --- a/lib/diffo/provider/components/entity.ex +++ b/lib/diffo/provider/components/entity.ex @@ -29,7 +29,7 @@ defmodule Diffo.Provider.Entity do jason do pick [:id, :href, :name, :referredType, :type] - compact(true) + compact true rename referredType: "@referredType", type: "@type" end diff --git a/lib/diffo/provider/components/event.ex b/lib/diffo/provider/components/event.ex new file mode 100644 index 0000000..5a5e8d4 --- /dev/null +++ b/lib/diffo/provider/components/event.ex @@ -0,0 +1,140 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Provider.Event do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Event - Ash Resource for a TMF Event + """ + use Ash.Resource, + otp_app: :diffo, + domain: Diffo.Provider, + data_layer: AshNeo4j.DataLayer, + extensions: [AshOutstanding.Resource, AshJason.Resource] + + alias Diffo.Util, as: Util + + resource do + description "An Ash Resource for a TMF Entity Reference" + plural_name :events + end + + neo4j do + relate [ + {:instance, :FIRED, :incoming, :Instance}, + #{:earlier_event, :AFTER, :outgoing, :Event} + ] + end + + jason do + rename id: :eventId, inserted_at: :eventTime, type: :eventType + + customize fn result, record -> + result + |> Event.time(record) + |> Util.rename(:instance, record.type) + |> Util.nest([record.type], :event) + end + + order [:eventId, :eventTime, :eventType, :event] + end + + outstanding do + expect [:eventType, :eventTime, :event] + end + + actions do + defaults [:read, :destroy] + + create :create do + description "creates an event, fired by an instance" + accept [:type] + argument :instance_id, :uuid + argument :earlier_event_id, :uuid + + change manage_relationship(:instance_id, :instance, type: :append_and_remove) + #change manage_relationship(:earlier_event_id, :earlier_event, type: :append_and_remove) + change load [:instance_type] + end + + read :list do + description "lists all events" + end + + read :list_events_by_instance_id do + description "lists events by instance id" + argument :instance_id, :uuid + filter expr(instance_id == ^arg(:instance_id)) + end + end + + attributes do + uuid_primary_key :id do + description "a uuid4, unique to this entity ref, generated by default" + public? false + end + + attribute :type, :atom do + description "the type of the event" + allow_nil? false + public? true + end + + create_timestamp :inserted_at + + update_timestamp :updated_at + end + + relationships do + belongs_to :instance, Diffo.Provider.Instance do + description "the instance which fired the event" + allow_nil? false + public? true + end + + #has_one :earlier_event, Diffo.Provider.Event do + # description "the earlier event, if any" + # allow_nil? true + # public? true + #end + end + + calculations do + calculate :instance_type, + :atom, + expr(instance.type) do + description "the type of the instance which fired the event" + end + end + + preparations do + prepare build(load: [:instance_type], sort: [inserted_at: :desc]) + end + + @doc """ + Assists in encoding event time + """ + def time(result, record) do + result + |> Diffo.Util.set( + :eventTime, + Diffo.Util.to_iso8601(record.inserted_at) + ) + end + + @doc """ + Compares two event, by id + ## Examples + iex> Diffo.Provider.Event.compare(%{id: "a"}, %{id: "a"}) + :eq + iex> Diffo.Provider.Event.compare(%{id: "b"}, %{id: "a"}) + :gt + iex> Diffo.Provider.Event.compare(%{id: "a"}, %{id: "b"}) + :lt + + """ + def compare(%{id: id0}, %{id: id1}), + do: Diffo.Util.compare(id0, id1) +end diff --git a/lib/diffo/provider/components/party.ex b/lib/diffo/provider/components/party.ex index 36028c3..fac4fa1 100644 --- a/lib/diffo/provider/components/party.ex +++ b/lib/diffo/provider/components/party.ex @@ -35,7 +35,7 @@ defmodule Diffo.Provider.Party do jason do pick [:id, :href, :name, :referredType, :type] - compact(true) + compact true rename referredType: "@referredType", type: "@type" end diff --git a/lib/diffo/provider/components/place.ex b/lib/diffo/provider/components/place.ex index dadbbec..ea39a9b 100644 --- a/lib/diffo/provider/components/place.ex +++ b/lib/diffo/provider/components/place.ex @@ -29,7 +29,7 @@ defmodule Diffo.Provider.Place do jason do pick [:id, :href, :name, :referredType, :type] - compact(true) + compact true rename referredType: "@referredType", type: "@type" end diff --git a/test/support/resource/card_value.ex b/test/support/resource/card_value.ex index f1e280c..f6dce53 100644 --- a/test/support/resource/card_value.ex +++ b/test/support/resource/card_value.ex @@ -12,7 +12,7 @@ defmodule Diffo.Test.CardValue do jason do pick [:name, :family, :model, :technology] - compact(true) + compact true end outstanding do diff --git a/test/support/resource/shelf_value.ex b/test/support/resource/shelf_value.ex index b7580dc..0cdbc0e 100644 --- a/test/support/resource/shelf_value.ex +++ b/test/support/resource/shelf_value.ex @@ -12,7 +12,7 @@ defmodule Diffo.Test.ShelfValue do jason do pick [:name, :family, :model, :technology] - compact(true) + compact true end outstanding do From 20d8bc02c8196cbdb51095f7a757925045752979 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Fri, 24 Oct 2025 18:42:53 +1030 Subject: [PATCH 2/7] event --- lib/diffo/helpers/util.ex | 14 ++++++++++++++ lib/diffo/provider.ex | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/diffo/helpers/util.ex b/lib/diffo/helpers/util.ex index 5618bc5..4129222 100644 --- a/lib/diffo/helpers/util.ex +++ b/lib/diffo/helpers/util.ex @@ -402,6 +402,20 @@ defmodule Diffo.Util do end end + @doc """ + Nest values from list of tuples into a list, with a new tuple key + ## Examples + iex> list = [state: :up, weeks: 1, days: 5] + iex> Diffo.Util.nest(list, [:weeks, :days], :duration) + iex> + [state: :up, duration: [weeks: 1, days: 5]] + + """ + def nest(list, tuple_keys, new_tuple_key) when is_list(list) and is_list(tuple_keys) do + {nested_list, remainder_list} = Enum.split_with(list, fn {tuple_key, _} -> tuple_key in tuple_keys end) + set(remainder_list, new_tuple_key, nested_list) + end + defimpl Jason.Encoder, for: Tuple do def encode(tuple, _opts) when is_tuple(tuple) do tuple diff --git a/lib/diffo/provider.ex b/lib/diffo/provider.ex index cd67fec..7b61ef5 100644 --- a/lib/diffo/provider.ex +++ b/lib/diffo/provider.ex @@ -215,5 +215,17 @@ defmodule Diffo.Provider do define :update_entity_ref, action: :update define :delete_entity_ref, action: :destroy end + + resource Diffo.Provider.Event do + define :create_event, action: :create + define :get_event_by_id, action: :read, get_by: :id + define :list_events, action: :list + + define :list_events_by_instance_id, + action: :list_events_by_instance_id, + args: [:instance_id] + + define :delete_event, action: :destroy + end end end From de8538486460d84363197ed16cd3264dffb799d8 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Mon, 27 Oct 2025 18:03:19 +1030 Subject: [PATCH 3/7] event_test --- lib/diffo/helpers/util.ex | 20 ++- .../provider/components/base_instance.ex | 4 +- .../provider/components/characteristic.ex | 2 +- lib/diffo/provider/components/entity.ex | 2 +- lib/diffo/provider/components/entity_ref.ex | 4 +- lib/diffo/provider/components/event.ex | 50 ++++-- .../components/external_identifier.ex | 14 +- lib/diffo/provider/components/feature.ex | 2 +- .../components/instance/extension/party.ex | 2 +- .../components/instance/extension/place.ex | 2 +- .../provider/components/instance/util.ex | 2 +- lib/diffo/provider/components/party.ex | 2 +- lib/diffo/provider/components/party_ref.ex | 4 +- lib/diffo/provider/components/place.ex | 2 +- lib/diffo/provider/components/place_ref.ex | 4 +- .../provider/components/process_status.ex | 5 + lib/diffo/provider/components/relationship.ex | 9 +- .../provider/components/specification.ex | 2 +- test/provider/event_test.exs | 160 ++++++++++++++++++ test/provider/process_status_test.exs | 4 +- 20 files changed, 250 insertions(+), 46 deletions(-) create mode 100644 test/provider/event_test.exs diff --git a/lib/diffo/helpers/util.ex b/lib/diffo/helpers/util.ex index 4129222..4924516 100644 --- a/lib/diffo/helpers/util.ex +++ b/lib/diffo/helpers/util.ex @@ -412,10 +412,28 @@ defmodule Diffo.Util do """ def nest(list, tuple_keys, new_tuple_key) when is_list(list) and is_list(tuple_keys) do - {nested_list, remainder_list} = Enum.split_with(list, fn {tuple_key, _} -> tuple_key in tuple_keys end) + {nested_list, remainder_list} = + Enum.split_with(list, fn {tuple_key, _} -> tuple_key in tuple_keys end) + set(remainder_list, new_tuple_key, nested_list) end + @doc """ + Nest values from list of tuples into a map, with a new tuple key + ## Examples + iex> list = [state: :up, weeks: 1, days: 5] + iex> Diffo.Util.nest_as_map(list, [:weeks, :days], :duration) + iex> + [state: :up, duration: %{weeks: 1, days: 5}] + + """ + def nest_as_map(list, tuple_keys, new_tuple_key) when is_list(list) and is_list(tuple_keys) do + {nested_list, remainder_list} = + Enum.split_with(list, fn {tuple_key, _} -> tuple_key in tuple_keys end) + + set(remainder_list, new_tuple_key, Map.new(nested_list)) + end + defimpl Jason.Encoder, for: Tuple do def encode(tuple, _opts) when is_tuple(tuple) do tuple diff --git a/lib/diffo/provider/components/base_instance.ex b/lib/diffo/provider/components/base_instance.ex index 3f10855..da40b81 100644 --- a/lib/diffo/provider/components/base_instance.ex +++ b/lib/diffo/provider/components/base_instance.ex @@ -204,7 +204,7 @@ defmodule Diffo.Provider.BaseInstance do constraints one_of: Diffo.Provider.Service.service_operating_statuses() end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at @@ -479,7 +479,7 @@ defmodule Diffo.Provider.BaseInstance do :places, :parties ], - sort: [inserted_at: :desc] + sort: [created_at: :desc] ) end diff --git a/lib/diffo/provider/components/characteristic.ex b/lib/diffo/provider/components/characteristic.ex index 121b8d6..f5cb120 100644 --- a/lib/diffo/provider/components/characteristic.ex +++ b/lib/diffo/provider/components/characteristic.ex @@ -107,7 +107,7 @@ defmodule Diffo.Provider.Characteristic do constraints one_of: [:instance, :feature, :relationship] end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end diff --git a/lib/diffo/provider/components/entity.ex b/lib/diffo/provider/components/entity.ex index 4dc7d36..0279bce 100644 --- a/lib/diffo/provider/components/entity.ex +++ b/lib/diffo/provider/components/entity.ex @@ -112,7 +112,7 @@ defmodule Diffo.Provider.Entity do public? true end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end diff --git a/lib/diffo/provider/components/entity_ref.ex b/lib/diffo/provider/components/entity_ref.ex index 2236526..f85cbc3 100644 --- a/lib/diffo/provider/components/entity_ref.ex +++ b/lib/diffo/provider/components/entity_ref.ex @@ -94,7 +94,7 @@ defmodule Diffo.Provider.EntityRef do public? true end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end @@ -121,7 +121,7 @@ defmodule Diffo.Provider.EntityRef do end preparations do - prepare build(load: [:entity], sort: [inserted_at: :desc]) + prepare build(load: [:entity], sort: [created_at: :desc]) end @doc """ diff --git a/lib/diffo/provider/components/event.ex b/lib/diffo/provider/components/event.ex index 5a5e8d4..781a86c 100644 --- a/lib/diffo/provider/components/event.ex +++ b/lib/diffo/provider/components/event.ex @@ -4,7 +4,7 @@ defmodule Diffo.Provider.Event do @moduledoc """ - Diffo - TMF Service and Resource Management with a difference + Diffo - TMF Service and Reource Management with a difference Event - Ash Resource for a TMF Event """ @@ -21,28 +21,34 @@ defmodule Diffo.Provider.Event do plural_name :events end + code_interface do + define :create + define :destroy + end + neo4j do relate [ - {:instance, :FIRED, :incoming, :Instance}, - #{:earlier_event, :AFTER, :outgoing, :Event} + {:instance, :FIRED, :incoming, :Instance} + # {:earlier_event, :AFTER, :outgoing, :Event} ] end jason do - rename id: :eventId, inserted_at: :eventTime, type: :eventType + pick [:id, :created_at, :type, :instance] + rename id: :eventId, created_at: :eventTime, type: :eventType customize fn result, record -> result - |> Event.time(record) - |> Util.rename(:instance, record.type) - |> Util.nest([record.type], :event) + |> __MODULE__.time(record) + |> Util.rename(:instance, record.instance_type) + |> Util.nest_as_map([record.instance_type], :event) end order [:eventId, :eventTime, :eventType, :event] end outstanding do - expect [:eventType, :eventTime, :event] + expect [:type, :created_at, :instance_id, :instance] end actions do @@ -52,11 +58,11 @@ defmodule Diffo.Provider.Event do description "creates an event, fired by an instance" accept [:type] argument :instance_id, :uuid - argument :earlier_event_id, :uuid + # argument :earlier_event_id, :uuid change manage_relationship(:instance_id, :instance, type: :append_and_remove) - #change manage_relationship(:earlier_event_id, :earlier_event, type: :append_and_remove) - change load [:instance_type] + # change manage_relationship(:earlier_event_id, :earlier_event, type: :append_and_remove) + change load [:instance_type, :instance] end read :list do @@ -80,9 +86,20 @@ defmodule Diffo.Provider.Event do description "the type of the event" allow_nil? false public? true + + constraints one_of: [ + :serviceCreateEvent, + :serviceStateChangeEvent, + :serviceAttributeValueChangeEvent, + :serviceDeleteEvent, + :resourceCreateEvent, + :resourceStateChangeEvent, + :resourceAttributeValueChangeEvent, + :resourceDeleteEvent + ] end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end @@ -94,11 +111,11 @@ defmodule Diffo.Provider.Event do public? true end - #has_one :earlier_event, Diffo.Provider.Event do + # has_one :earlier_event, Diffo.Provider.Event do # description "the earlier event, if any" # allow_nil? true # public? true - #end + # end end calculations do @@ -110,9 +127,10 @@ defmodule Diffo.Provider.Event do end preparations do - prepare build(load: [:instance_type], sort: [inserted_at: :desc]) + prepare build(load: [:instance_type, :instance], sort: [created_at: :desc]) end + @spec time([tuple()], any()) :: [tuple()] @doc """ Assists in encoding event time """ @@ -120,7 +138,7 @@ defmodule Diffo.Provider.Event do result |> Diffo.Util.set( :eventTime, - Diffo.Util.to_iso8601(record.inserted_at) + Diffo.Util.to_iso8601(record.created_at) ) end diff --git a/lib/diffo/provider/components/external_identifier.ex b/lib/diffo/provider/components/external_identifier.ex index 1d222b2..63f09b6 100644 --- a/lib/diffo/provider/components/external_identifier.ex +++ b/lib/diffo/provider/components/external_identifier.ex @@ -111,7 +111,7 @@ defmodule Diffo.Provider.ExternalIdentifier do public? true end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end @@ -144,20 +144,20 @@ defmodule Diffo.Provider.ExternalIdentifier do end preparations do - prepare build(load: [:owner], sort: [inserted_at: :desc]) + prepare build(load: [:owner], sort: [created_at: :desc]) end @doc """ Compares two external identifier, by most recent insertion order ## Examples - iex> Diffo.Provider.ExternalIdentifier.compare(%{inserted_at: "a"}, %{inserted_at: "a"}) + iex> Diffo.Provider.ExternalIdentifier.compare(%{created_at: "a"}, %{created_at: "a"}) :eq - iex> Diffo.Provider.ExternalIdentifier.compare(%{inserted_at: "b"}, %{inserted_at: "a"}) + iex> Diffo.Provider.ExternalIdentifier.compare(%{created_at: "b"}, %{created_at: "a"}) :gt - iex> Diffo.Provider.ExternalIdentifier.compare(%{inserted_at: "a"}, %{inserted_at: "b"}) + iex> Diffo.Provider.ExternalIdentifier.compare(%{created_at: "a"}, %{created_at: "b"}) :lt """ - def compare(%{inserted_at: inserted_at0}, %{inserted_at: inserted_at1}), - do: Diffo.Util.compare(inserted_at0, inserted_at1) + def compare(%{created_at: created_at0}, %{created_at: created_at1}), + do: Diffo.Util.compare(created_at0, created_at1) end diff --git a/lib/diffo/provider/components/feature.ex b/lib/diffo/provider/components/feature.ex index ef20f18..3300781 100644 --- a/lib/diffo/provider/components/feature.ex +++ b/lib/diffo/provider/components/feature.ex @@ -109,7 +109,7 @@ defmodule Diffo.Provider.Feature do default true end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end diff --git a/lib/diffo/provider/components/instance/extension/party.ex b/lib/diffo/provider/components/instance/extension/party.ex index ee63035..e8b808e 100644 --- a/lib/diffo/provider/components/instance/extension/party.ex +++ b/lib/diffo/provider/components/instance/extension/party.ex @@ -47,7 +47,7 @@ defmodule Diffo.Provider.Instance.Party do {:error, "couldn't relate parties"} _ -> - # sorted = Ash.Sort.runtime_sort(party_refs, [role: :asc, inserted_at: :desc]) + # sorted = Ash.Sort.runtime_sort(party_refs, [role: :asc, created_at: :desc]) {:ok, result |> Map.put(:parties, party_refs)} end end diff --git a/lib/diffo/provider/components/instance/extension/place.ex b/lib/diffo/provider/components/instance/extension/place.ex index feebb6d..50a3be6 100644 --- a/lib/diffo/provider/components/instance/extension/place.ex +++ b/lib/diffo/provider/components/instance/extension/place.ex @@ -47,7 +47,7 @@ defmodule Diffo.Provider.Instance.Place do {:error, "couldn't relate places"} _ -> - # sorted = Ash.Sort.runtime_sort(place_refs, [role: :asc, inserted_at: :desc]) + # sorted = Ash.Sort.runtime_sort(place_refs, [role: :asc, created_at: :desc]) {:ok, result |> Map.put(:places, place_refs)} end end diff --git a/lib/diffo/provider/components/instance/util.ex b/lib/diffo/provider/components/instance/util.ex index c5162a8..6251caf 100644 --- a/lib/diffo/provider/components/instance/util.ex +++ b/lib/diffo/provider/components/instance/util.ex @@ -54,7 +54,7 @@ defmodule Diffo.Provider.Instance.Util do result |> Diffo.Util.set( derive_create_date_name(record.type), - Diffo.Util.to_iso8601(record.inserted_at) + Diffo.Util.to_iso8601(record.created_at) ) |> Diffo.Util.set( derive_start_date_name(record.type), diff --git a/lib/diffo/provider/components/party.ex b/lib/diffo/provider/components/party.ex index fac4fa1..cbf66cb 100644 --- a/lib/diffo/provider/components/party.ex +++ b/lib/diffo/provider/components/party.ex @@ -120,7 +120,7 @@ defmodule Diffo.Provider.Party do constraints one_of: [:Individual, :Organization, :Entity] end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end diff --git a/lib/diffo/provider/components/party_ref.ex b/lib/diffo/provider/components/party_ref.ex index 987a30f..28dced8 100644 --- a/lib/diffo/provider/components/party_ref.ex +++ b/lib/diffo/provider/components/party_ref.ex @@ -105,7 +105,7 @@ defmodule Diffo.Provider.PartyRef do public? true end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end @@ -134,7 +134,7 @@ defmodule Diffo.Provider.PartyRef do preparations do prepare build( load: [:party], - sort: [role: :asc, inserted_at: :desc] + sort: [role: :asc, created_at: :desc] ) end diff --git a/lib/diffo/provider/components/place.ex b/lib/diffo/provider/components/place.ex index ea39a9b..29cc186 100644 --- a/lib/diffo/provider/components/place.ex +++ b/lib/diffo/provider/components/place.ex @@ -114,7 +114,7 @@ defmodule Diffo.Provider.Place do constraints one_of: [:GeographicSite, :GeographicLocation, :GeographicAddress] end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end diff --git a/lib/diffo/provider/components/place_ref.ex b/lib/diffo/provider/components/place_ref.ex index 3de1c26..8533874 100644 --- a/lib/diffo/provider/components/place_ref.ex +++ b/lib/diffo/provider/components/place_ref.ex @@ -93,7 +93,7 @@ defmodule Diffo.Provider.PlaceRef do public? true end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end @@ -122,7 +122,7 @@ defmodule Diffo.Provider.PlaceRef do preparations do prepare build( load: [:place], - sort: [role: :asc, inserted_at: :desc] + sort: [role: :asc, created_at: :desc] ) end diff --git a/lib/diffo/provider/components/process_status.ex b/lib/diffo/provider/components/process_status.ex index 2918267..ff11aa2 100644 --- a/lib/diffo/provider/components/process_status.ex +++ b/lib/diffo/provider/components/process_status.ex @@ -14,6 +14,11 @@ defmodule Diffo.Provider.ProcessStatus do data_layer: AshNeo4j.DataLayer, extensions: [AshOutstanding.Resource, AshJason.Resource] + resource do + description "An Ash Resource for a TMF Process Status" + plural_name :processStatuses + end + code_interface do define :create define :update diff --git a/lib/diffo/provider/components/relationship.ex b/lib/diffo/provider/components/relationship.ex index 83b22d5..5de0e6a 100644 --- a/lib/diffo/provider/components/relationship.ex +++ b/lib/diffo/provider/components/relationship.ex @@ -15,6 +15,11 @@ defmodule Diffo.Provider.Relationship do data_layer: AshNeo4j.DataLayer, extensions: [AshOutstanding.Resource, AshJason.Resource] + resource do + description "An Ash Resource for a TMF Service or Resource Relationship" + plural_name :relationships + end + neo4j do relate [ {:source, :RELATES, :incoming, :Instance}, @@ -145,7 +150,7 @@ defmodule Diffo.Provider.Relationship do public? true end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end @@ -191,7 +196,7 @@ defmodule Diffo.Provider.Relationship do preparations do prepare build( load: [:characteristics], - sort: [alias: :asc, type: :asc, inserted_at: :asc] + sort: [alias: :asc, type: :asc, created_at: :asc] ) end diff --git a/lib/diffo/provider/components/specification.ex b/lib/diffo/provider/components/specification.ex index 18a7adf..e7837bb 100644 --- a/lib/diffo/provider/components/specification.ex +++ b/lib/diffo/provider/components/specification.ex @@ -178,7 +178,7 @@ defmodule Diffo.Provider.Specification do constraints min: 1 end - create_timestamp :inserted_at + create_timestamp :created_at update_timestamp :updated_at end diff --git a/test/provider/event_test.exs b/test/provider/event_test.exs new file mode 100644 index 0000000..87a67d6 --- /dev/null +++ b/test/provider/event_test.exs @@ -0,0 +1,160 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Provider.EventTest do + @moduledoc false + use ExUnit.Case + + setup_all do + AshNeo4j.BoltxHelper.start() + end + + setup do + on_exit(fn -> + AshNeo4j.Neo4jHelper.delete_nodes(:Event) + end) + end + + describe "Diffo.Provider.Event create" do + test "create an event - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + + event = + Diffo.Provider.Event.create!(%{ + instance_id: instance.id, + type: :serviceCreateEvent + }) + + assert event.type == :serviceCreateEvent + assert event.instance_type == :service + end + end + + describe "Diffo.Provider.Event encode" do + test "encode json with service instance - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + + event = + Diffo.Provider.Event.create!(%{ + instance_id: instance.id, + type: :serviceCreateEvent + }) + + encoding = Jason.encode!(event) |> Diffo.Util.summarise_dates() + + assert encoding == + ~s({\"eventId\":\"#{event.id}\",\"eventTime\":\"now\",\"eventType\":\"serviceCreateEvent\",\"event\":{\"service\":{\"id\":\"#{instance.id}\",\"href\":\"serviceInventoryManagement/v4/service/nbnAccess/#{instance.id}\",\"serviceSpecification\":{\"id\":\"#{specification.id}\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/#{specification.id}\",\"name\":\"nbnAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"initial\"}}}) + end + end + + describe "Diffo.Provider outstanding Event" do + use Outstand + @now DateTime.utc_now() + @type_only %Diffo.Provider.Event{type: :serviceCreateEvent} + @time_only %Diffo.Provider.Event{created_at: @now} + @uuid UUID.uuid4() + @instance_id_only %Diffo.Provider.Event{instance_id: @uuid} + @instance_only %Diffo.Provider.Event{ + instance: %Diffo.Provider.Instance{service_state: :active} + } + @specific_event %Diffo.Provider.Event{ + type: :serviceCreateEvent, + created_at: @now, + instance_id: @uuid, + instance: %Diffo.Provider.Instance{service_state: :active} + } + + @generic_event %Diffo.Provider.Event{ + type: &__MODULE__.service_event_type/1, + created_at: nil, + instance_id: nil, + instance_type: :service, + instance: nil + } + @actual_event %Diffo.Provider.Event{ + type: :serviceCreateEvent, + created_at: @now, + instance_id: @uuid, + instance: %Diffo.Provider.Instance{id: @uuid, service_state: :active} + } + + gen_nothing_outstanding_test( + "specific nothing outstanding", + @specific_event, + @actual_event + ) + + gen_result_outstanding_test( + "specific event result", + @specific_event, + nil, + Ash.Test.strip_metadata(@specific_event) + ) + + gen_result_outstanding_test( + "specific type result", + @specific_event, + Map.delete(@actual_event, :type), + Ash.Test.strip_metadata(@type_only) + ) + + gen_result_outstanding_test( + "specific time result", + @specific_event, + Map.delete(@actual_event, :created_at), + Ash.Test.strip_metadata(@time_only) + ) + + gen_result_outstanding_test( + "specific instance_id result", + @specific_event, + Map.put(@actual_event, :instance_id, nil), + Ash.Test.strip_metadata(@instance_id_only) + ) + + gen_result_outstanding_test( + "specific instance.service_state result", + @specific_event, + Kernel.update_in(@actual_event.instance.service_state, fn _ -> nil end), + Ash.Test.strip_metadata(@instance_only) + ) + + gen_nothing_outstanding_test( + "generic nothing outstanding", + @generic_event, + @actual_event + ) + end + + describe "Diffo.Provider delete Event" do + test "delete event with related instance - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + + event = + Diffo.Provider.Event.create!(%{ + instance_id: instance.id, + type: :serviceCreateEvent + }) + + :ok = Diffo.Provider.delete_event(event) + {:error, _error} = Diffo.Provider.get_event_by_id(event.id) + end + end + + def service_event_type(actual) do + cond do + actual == nil -> + :service_event_type + + Regex.match?(~r/service/, String.Chars.to_string(actual)) -> + nil + + true -> + :service_event_type + end + end +end diff --git a/test/provider/process_status_test.exs b/test/provider/process_status_test.exs index 9fc7bbc..28169af 100644 --- a/test/provider/process_status_test.exs +++ b/test/provider/process_status_test.exs @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -defmodule Diffo.Provider.ProcessStatus.ProcessStatus do +defmodule Diffo.Provider.ProcessStatusTest do @moduledoc false use ExUnit.Case @@ -232,8 +232,6 @@ defmodule Diffo.Provider.ProcessStatus.ProcessStatus do end end - [:code, :severity, :message, :parameterized_message, :timestamp] - describe "Diffo.Provider outstanding ProcessStatus" do use Outstand @now DateTime.utc_now() From a1c7fbfed32319962392672f99d812e939174463 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Tue, 28 Oct 2025 12:01:36 +1030 Subject: [PATCH 4/7] chainable events --- .../provider/components/characteristic.ex | 6 ++ lib/diffo/provider/components/event.ex | 40 ++++++-- test/provider/event_test.exs | 97 ++++++++++++++++++- 3 files changed, 132 insertions(+), 11 deletions(-) diff --git a/lib/diffo/provider/components/characteristic.ex b/lib/diffo/provider/components/characteristic.ex index f5cb120..9960aba 100644 --- a/lib/diffo/provider/components/characteristic.ex +++ b/lib/diffo/provider/components/characteristic.ex @@ -149,6 +149,12 @@ defmodule Diffo.Provider.Characteristic do end end + validations do + validate present([:instance_id, :feature_id, :relationship_id], at_most: 1) do + message "characteristic must be related to at most one of an instance, feature or relationship" + end + end + preparations do prepare build(sort: [name: :asc]) end diff --git a/lib/diffo/provider/components/event.ex b/lib/diffo/provider/components/event.ex index 781a86c..4a2d722 100644 --- a/lib/diffo/provider/components/event.ex +++ b/lib/diffo/provider/components/event.ex @@ -23,13 +23,15 @@ defmodule Diffo.Provider.Event do code_interface do define :create + define :chain define :destroy end neo4j do relate [ - {:instance, :FIRED, :incoming, :Instance} - # {:earlier_event, :AFTER, :outgoing, :Event} + {:instance, :FIRED, :incoming, :Instance}, + {:head, :AFTER, :incoming, :Event}, + {:tail, :AFTER, :outgoing, :Event} ] end @@ -58,13 +60,21 @@ defmodule Diffo.Provider.Event do description "creates an event, fired by an instance" accept [:type] argument :instance_id, :uuid - # argument :earlier_event_id, :uuid - change manage_relationship(:instance_id, :instance, type: :append_and_remove) - # change manage_relationship(:earlier_event_id, :earlier_event, type: :append_and_remove) + # ideally capture the id of the event last fired by the instance, if any, and call before on it to bump it down the chain + change manage_relationship(:instance_id, :instance, type: :append) change load [:instance_type, :instance] end + update :chain do + description "chains the event from the head event" + primary? true + argument :head_id, :uuid + argument :instance_id, :uuid + change manage_relationship(:instance_id, :instance, type: :remove) + change manage_relationship(:head_id, :head, type: :append) + end + read :list do description "lists all events" end @@ -107,14 +117,24 @@ defmodule Diffo.Provider.Event do relationships do belongs_to :instance, Diffo.Provider.Instance do description "the instance which fired the event" - allow_nil? false + allow_nil? true public? true end - # has_one :earlier_event, Diffo.Provider.Event do - # description "the earlier event, if any" - # allow_nil? true - # public? true + belongs_to :head, Diffo.Provider.Event, + allow_nil?: true, + public?: true, + source_attribute: :head_id + + belongs_to :tail, Diffo.Provider.Event, + allow_nil?: true, + public?: true, + source_attribute: :tail_id + end + + validations do + # validate present [:instance_id, :head_id], at_most: 1 do + # message "event cannot be related to both instance and head event" # end end diff --git a/test/provider/event_test.exs b/test/provider/event_test.exs index 87a67d6..78d42a2 100644 --- a/test/provider/event_test.exs +++ b/test/provider/event_test.exs @@ -12,7 +12,7 @@ defmodule Diffo.Provider.EventTest do setup do on_exit(fn -> - AshNeo4j.Neo4jHelper.delete_nodes(:Event) + AshNeo4j.Neo4jHelper.delete_all() end) end @@ -29,6 +29,101 @@ defmodule Diffo.Provider.EventTest do assert event.type == :serviceCreateEvent assert event.instance_type == :service + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event.id}, + :FIRED, + :outgoing + ) + end + + test "create multiple events (no chaining) - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + + event_1 = + Diffo.Provider.Event.create!(%{ + instance_id: instance.id, + type: :serviceCreateEvent + }) + + event_2 = + Diffo.Provider.Event.create!(%{ + instance_id: instance.id, + type: :serviceStateChangeEvent + }) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event_1.id}, + :FIRED, + :outgoing + ) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event_2.id}, + :FIRED, + :outgoing + ) + end + + test "create event and chain it before previous event - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + + event_1 = + Diffo.Provider.Event.create!(%{ + instance_id: instance.id, + type: :serviceCreateEvent + }) + + event_2 = + Diffo.Provider.Event.create!(%{ + instance_id: instance.id, + type: :serviceStateChangeEvent + }) + + event_1 = + event_1 + |> Diffo.Provider.Event.chain!(%{ + instance_id: instance.id, + head_id: event_2.id + }) + + refute AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event_1.id}, + :FIRED, + :outgoing + ) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event_2.id}, + :FIRED, + :outgoing + ) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Event, + %{uuid: event_2.id}, + :Event, + %{uuid: event_1.id}, + :AFTER, + :outgoing + ) end end From 6fd58957adc3aa64d0cb815e0942991f6fbf4b29 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Wed, 29 Oct 2025 12:04:11 +1030 Subject: [PATCH 5/7] fire_instance_event wip --- .formatter.exs | 3 +- lib/diffo/provider.ex | 2 +- .../provider/components/base_instance.ex | 20 ++- lib/diffo/provider/components/event.ex | 84 +++++------ test/provider/event_test.exs | 131 +++++++----------- 5 files changed, 105 insertions(+), 135 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index b11f473..c5e71a2 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -21,7 +21,8 @@ locals_without_parens = [ default_initial_state: 1, state_attribute: 1, transition: 1, - compact: 1 + compact: 1, + label: 1 ] [ diff --git a/lib/diffo/provider.ex b/lib/diffo/provider.ex index 7b61ef5..fae5a1f 100644 --- a/lib/diffo/provider.ex +++ b/lib/diffo/provider.ex @@ -55,6 +55,7 @@ defmodule Diffo.Provider do define :relate_instance_characteristics, action: :relate_characteristics define :unrelate_instance_characteristics, action: :unrelate_characteristics define :annotate_instance, action: :annotate + define :fire_instance_event, action: :fire_event define :delete_instance, action: :destroy end @@ -217,7 +218,6 @@ defmodule Diffo.Provider do end resource Diffo.Provider.Event do - define :create_event, action: :create define :get_event_by_id, action: :read, get_by: :id define :list_events, action: :list diff --git a/lib/diffo/provider/components/base_instance.ex b/lib/diffo/provider/components/base_instance.ex index da40b81..5f3884c 100644 --- a/lib/diffo/provider/components/base_instance.ex +++ b/lib/diffo/provider/components/base_instance.ex @@ -34,11 +34,12 @@ defmodule Diffo.Provider.BaseInstance do {:characteristics, :HAS, :outgoing, :Characteristic}, {:entities, :RELATES, :outgoing, :EntityRef}, {:notes, :ANNOTATES, :incoming, :Note}, + {:event, :FIRED, :outgoing, :Event}, {:places, :LOCATED_BY, :outgoing, :PlaceRef}, {:parties, :INVOLVED_WITH, :outgoing, :PartyRef} ] - label(:Instance) + label :Instance end jason do @@ -271,6 +272,12 @@ defmodule Diffo.Provider.BaseInstance do destination_attribute :instance_id end + has_one :event, Diffo.Provider.Event do + description "the most recently fired event" + public? true + destination_attribute :instance_id + end + has_many :places, Diffo.Provider.PlaceRef do description "the instance's collection of related places" public? true @@ -451,6 +458,17 @@ defmodule Diffo.Provider.BaseInstance do argument :note, :uuid change manage_relationship(:note, :notes, type: :append) end + + update :fire_event do + description "fires an event, maintaining the event chain" + argument :event, :map do + allow_nil? false + end + # TODO custom change? + # we want to inject the firing_type, firing_snapshot, earlier_id into the created event + change manage_relationship(:event, type: :create) + change load [:event] + end end code_interface do diff --git a/lib/diffo/provider/components/event.ex b/lib/diffo/provider/components/event.ex index 4a2d722..bd456c9 100644 --- a/lib/diffo/provider/components/event.ex +++ b/lib/diffo/provider/components/event.ex @@ -22,35 +22,32 @@ defmodule Diffo.Provider.Event do end code_interface do - define :create - define :chain define :destroy end neo4j do relate [ {:instance, :FIRED, :incoming, :Instance}, - {:head, :AFTER, :incoming, :Event}, - {:tail, :AFTER, :outgoing, :Event} + {:later, :AFTER, :incoming, :Event}, + {:earlier, :AFTER, :outgoing, :Event} ] end jason do - pick [:id, :created_at, :type, :instance] rename id: :eventId, created_at: :eventTime, type: :eventType customize fn result, record -> result |> __MODULE__.time(record) - |> Util.rename(:instance, record.instance_type) - |> Util.nest_as_map([record.instance_type], :event) + |> Util.rename(:firing_snapshot, record.firing_type) + |> Util.nest_as_map([record.firing_type], :event) end order [:eventId, :eventTime, :eventType, :event] end outstanding do - expect [:type, :created_at, :instance_id, :instance] + expect [:type, :created_at, :instance_id, :firing_type, :firing_snapshot] end actions do @@ -58,21 +55,9 @@ defmodule Diffo.Provider.Event do create :create do description "creates an event, fired by an instance" - accept [:type] + primary? :true + accept [:type, :firing_type, :firing_snapshot] argument :instance_id, :uuid - - # ideally capture the id of the event last fired by the instance, if any, and call before on it to bump it down the chain - change manage_relationship(:instance_id, :instance, type: :append) - change load [:instance_type, :instance] - end - - update :chain do - description "chains the event from the head event" - primary? true - argument :head_id, :uuid - argument :instance_id, :uuid - change manage_relationship(:instance_id, :instance, type: :remove) - change manage_relationship(:head_id, :head, type: :append) end read :list do @@ -88,8 +73,26 @@ defmodule Diffo.Provider.Event do attributes do uuid_primary_key :id do - description "a uuid4, unique to this entity ref, generated by default" - public? false + description "a uuid4, unique to this event, generated by default" + public? true + end + + attribute :instance_id, :uuid do + description "the id of the instance that fired the event" + allow_nil? true + public? true + end + + attribute :firing_type, :atom do + description "the type of resource that fired the event" + allow_nil? true + public? true + end + + attribute :firing_snapshot, :string do + description "the json serialisation of the resource which fired the event" + allow_nil? true + public? true end attribute :type, :atom do @@ -115,39 +118,24 @@ defmodule Diffo.Provider.Event do end relationships do - belongs_to :instance, Diffo.Provider.Instance do - description "the instance which fired the event" - allow_nil? true - public? true - end - - belongs_to :head, Diffo.Provider.Event, + belongs_to :instance, Diffo.Provider.Instance, allow_nil?: true, public?: true, - source_attribute: :head_id + source_attribute: :instance_id - belongs_to :tail, Diffo.Provider.Event, + belongs_to :later, Diffo.Provider.Event, allow_nil?: true, public?: true, - source_attribute: :tail_id - end - - validations do - # validate present [:instance_id, :head_id], at_most: 1 do - # message "event cannot be related to both instance and head event" - # end - end + source_attribute: :later_id - calculations do - calculate :instance_type, - :atom, - expr(instance.type) do - description "the type of the instance which fired the event" - end + belongs_to :earlier, Diffo.Provider.Event, + allow_nil?: true, + public?: true, + source_attribute: :earlier_id end preparations do - prepare build(load: [:instance_type, :instance], sort: [created_at: :desc]) + prepare build(sort: [created_at: :desc]) end @spec time([tuple()], any()) :: [tuple()] diff --git a/test/provider/event_test.exs b/test/provider/event_test.exs index 78d42a2..f163a70 100644 --- a/test/provider/event_test.exs +++ b/test/provider/event_test.exs @@ -12,23 +12,23 @@ defmodule Diffo.Provider.EventTest do setup do on_exit(fn -> - AshNeo4j.Neo4jHelper.delete_all() + :ok #AshNeo4j.Neo4jHelper.delete_all() end) end describe "Diffo.Provider.Event create" do - test "create an event - success" do + test "fire an event - success" do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - event = - Diffo.Provider.Event.create!(%{ - instance_id: instance.id, - type: :serviceCreateEvent - }) + instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + assert instance.event + event = instance.event assert event.type == :serviceCreateEvent - assert event.instance_type == :service + assert event.firing_type == :service + assert event.firing_id == instance.id + assert event.firing_snapshot assert AshNeo4j.Neo4jHelper.nodes_relate_how?( :Instance, @@ -40,63 +40,18 @@ defmodule Diffo.Provider.EventTest do ) end - test "create multiple events (no chaining) - success" do + @tag debug: true + test "fired events are chained - success" do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - event_1 = - Diffo.Provider.Event.create!(%{ - instance_id: instance.id, - type: :serviceCreateEvent - }) + instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + event_1 = instance.event - event_2 = - Diffo.Provider.Event.create!(%{ - instance_id: instance.id, - type: :serviceStateChangeEvent - }) + instance = Diffo.Provider.activate_service!(instance) - assert AshNeo4j.Neo4jHelper.nodes_relate_how?( - :Instance, - %{uuid: instance.id}, - :Event, - %{uuid: event_1.id}, - :FIRED, - :outgoing - ) - - assert AshNeo4j.Neo4jHelper.nodes_relate_how?( - :Instance, - %{uuid: instance.id}, - :Event, - %{uuid: event_2.id}, - :FIRED, - :outgoing - ) - end - - test "create event and chain it before previous event - success" do - specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) - instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - - event_1 = - Diffo.Provider.Event.create!(%{ - instance_id: instance.id, - type: :serviceCreateEvent - }) - - event_2 = - Diffo.Provider.Event.create!(%{ - instance_id: instance.id, - type: :serviceStateChangeEvent - }) - - event_1 = - event_1 - |> Diffo.Provider.Event.chain!(%{ - instance_id: instance.id, - head_id: event_2.id - }) + instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceStateChangeEvent}}) + event_2 = instance.event refute AshNeo4j.Neo4jHelper.nodes_relate_how?( :Instance, @@ -132,11 +87,9 @@ defmodule Diffo.Provider.EventTest do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - event = - Diffo.Provider.Event.create!(%{ - instance_id: instance.id, - type: :serviceCreateEvent - }) + instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + assert instance.event + event = instance.event encoding = Jason.encode!(event) |> Diffo.Util.summarise_dates() @@ -148,32 +101,38 @@ defmodule Diffo.Provider.EventTest do describe "Diffo.Provider outstanding Event" do use Outstand @now DateTime.utc_now() + @instance_id UUID.uuid4() + @instance %Diffo.Provider.Instance{service_state: :active} + @firing_type :service + @firing_snapshot Jason.encode(!@instnace) + @type_only %Diffo.Provider.Event{type: :serviceCreateEvent} @time_only %Diffo.Provider.Event{created_at: @now} - @uuid UUID.uuid4() - @instance_id_only %Diffo.Provider.Event{instance_id: @uuid} - @instance_only %Diffo.Provider.Event{ - instance: %Diffo.Provider.Instance{service_state: :active} - } + @instance_id_only %Diffo.Provider.Event{instance_id: @instance_id} + @firing_type_only %Diffo.Provider.Event{firing_type: @firing_type} + @firing_snapshot_only %Diffo.Provider.Event{firing_snapshot: @firing_snapshot} @specific_event %Diffo.Provider.Event{ type: :serviceCreateEvent, created_at: @now, - instance_id: @uuid, - instance: %Diffo.Provider.Instance{service_state: :active} + instance_id: @instance_id, + firing_type: @firing_type, + firing_snapshot: @firing_snapshot } @generic_event %Diffo.Provider.Event{ type: &__MODULE__.service_event_type/1, created_at: nil, instance_id: nil, - instance_type: :service, - instance: nil + firing_type: :service, + firing_snapshot: nil } + @actual_event %Diffo.Provider.Event{ type: :serviceCreateEvent, created_at: @now, - instance_id: @uuid, - instance: %Diffo.Provider.Instance{id: @uuid, service_state: :active} + instance_id: @instance_id, + firing_type: @firing_type, + firing_snapshot: @firing_snapshot } gen_nothing_outstanding_test( @@ -211,10 +170,17 @@ defmodule Diffo.Provider.EventTest do ) gen_result_outstanding_test( - "specific instance.service_state result", + "specific firing_type result", + @specific_event, + Map.put(@actual_event, :firing_type, nil), + Ash.Test.strip_metadata(@firing_type_only) + ) + + gen_result_outstanding_test( + "specific firing_snapshot result", @specific_event, - Kernel.update_in(@actual_event.instance.service_state, fn _ -> nil end), - Ash.Test.strip_metadata(@instance_only) + Map.put(@actual_event, :firing_snapshot, nil), + Ash.Test.strip_metadata(@firing_snapshot_only) ) gen_nothing_outstanding_test( @@ -229,11 +195,8 @@ defmodule Diffo.Provider.EventTest do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - event = - Diffo.Provider.Event.create!(%{ - instance_id: instance.id, - type: :serviceCreateEvent - }) + instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + event = instance.event :ok = Diffo.Provider.delete_event(event) {:error, _error} = Diffo.Provider.get_event_by_id(event.id) From a11b198644225295c13809709999c4fa46a18072 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Mon, 10 Nov 2025 19:34:01 +1030 Subject: [PATCH 6/7] fire_instance_event wip2 --- lib/diffo/helpers/util.ex | 18 +++++++- lib/diffo/provider/components/event.ex | 4 ++ mix.lock | 13 +++--- test/provider/event_test.exs | 63 +++++++++++++++++--------- 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/lib/diffo/helpers/util.ex b/lib/diffo/helpers/util.ex index 4924516..5c23eda 100644 --- a/lib/diffo/helpers/util.ex +++ b/lib/diffo/helpers/util.ex @@ -407,7 +407,6 @@ defmodule Diffo.Util do ## Examples iex> list = [state: :up, weeks: 1, days: 5] iex> Diffo.Util.nest(list, [:weeks, :days], :duration) - iex> [state: :up, duration: [weeks: 1, days: 5]] """ @@ -423,7 +422,6 @@ defmodule Diffo.Util do ## Examples iex> list = [state: :up, weeks: 1, days: 5] iex> Diffo.Util.nest_as_map(list, [:weeks, :days], :duration) - iex> [state: :up, duration: %{weeks: 1, days: 5}] """ @@ -434,6 +432,22 @@ defmodule Diffo.Util do set(remainder_list, new_tuple_key, Map.new(nested_list)) end + @doc """ + Encapsulate a tuple value as a Jason Fragment + ## Examples + iex> list = [state: :initial, service: Jason.encode!(%{state: :initial})] + iex> list = Diffo.Util.fragment(list, :service) + iex> {:service, %name{}} = List.keyfind(list, :service, 0) + iex> name + Jason.Fragment + + """ + def fragment(list, tuple_key) when is_list(list) do + tuple_value = get(list, tuple_key) + value = Jason.Fragment.new(tuple_value) + list |> List.keystore(tuple_key, 0, {tuple_key, value}) + end + defimpl Jason.Encoder, for: Tuple do def encode(tuple, _opts) when is_tuple(tuple) do tuple diff --git a/lib/diffo/provider/components/event.ex b/lib/diffo/provider/components/event.ex index bd456c9..8dcd4e5 100644 --- a/lib/diffo/provider/components/event.ex +++ b/lib/diffo/provider/components/event.ex @@ -22,6 +22,7 @@ defmodule Diffo.Provider.Event do end code_interface do + define :create define :destroy end @@ -39,6 +40,7 @@ defmodule Diffo.Provider.Event do customize fn result, record -> result |> __MODULE__.time(record) + |> Util.fragment(:firing_snapshot) |> Util.rename(:firing_snapshot, record.firing_type) |> Util.nest_as_map([record.firing_type], :event) end @@ -58,6 +60,8 @@ defmodule Diffo.Provider.Event do primary? :true accept [:type, :firing_type, :firing_snapshot] argument :instance_id, :uuid + + change manage_relationship(:instance_id, :instance, type: :append) end read :list do diff --git a/mix.lock b/mix.lock index 72f9357..ef867e2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.7.6", "a0358e8467da4e2a94855542d07d7fca8e74cb6bc89c42af2181b4caa91f8415", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6003aa4dec5868e6371c3bf2efdb89507c59c05f5dbec13a13b73a92b938a258"}, - "ash_jason": {:hex, :ash_jason, "3.0.2", "919ac953f99d3caf56cfc1d30ae4fd5457125f5927c45d75db13a44b38b9fb37", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "f959b2d1e09df42681311c17c7f078dbaf0695ea2442454fd5e92b906ca871dc"}, + "ash": {:hex, :ash, "3.9.0", "004371ffb181a142cda09544342dad1ffedf360a5636219d71cb5431ccbe3ad6", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e3e80a182c6e8b4d87f1caa4dba774da210f1f75f1420650ba012eb4388dc8c3"}, + "ash_jason": {:hex, :ash_jason, "3.0.3", "05349fe257c958ce84bcfc35871da7647960fcbcb288025623d1eb19839dc5fd", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ex_check, "~> 0.16.0", [hex: :ex_check, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "1ef2cd24be16a46ee46a9e0de1f319acc6cc71ff31e0bc3e7bfd800955a4c550"}, "ash_neo4j": {:hex, :ash_neo4j, "0.2.11", "41433b79c8dfe1f371faf411757c03b44cb56f14870375e80b348819fb17ddf1", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:boltx, ">= 0.0.6", [hex: :boltx, repo: "hexpm", optional: false]}], "hexpm", "b26a165908af327d45e951794c0fc1eaf4002d23239c41d6c4c01b1203471293"}, "ash_outstanding": {:hex, :ash_outstanding, "0.2.3", "dc8ec13028ea7bd1d74b46569b9db08f0d275d63700e2418d9e33fe4b21af2eb", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:outstanding, "~> 0.2.4", [hex: :outstanding, repo: "hexpm", optional: false]}], "hexpm", "05e2718b59937d9f7e77b7bc90f70e8f28c3f328de7cabf3ea55ca04a1abed52"}, "ash_state_machine": {:hex, :ash_state_machine, "0.2.12", "c0f7ebb8a176584f70c6ed196b7d0118c930d73e0590ade705d2dddc48aa7311", [:mix], [{:ash, ">= 3.4.66 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "394ce761ce82358e3c715e1cae6c5cf1390be27c03a8b661f2e5a2fda849873d"}, @@ -9,13 +9,14 @@ "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [:mix], [{:decimal, "~> 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", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"}, + "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 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", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, - "ex_doc": {:hex, :ex_doc, "0.38.4", "ab48dff7a8af84226bf23baddcdda329f467255d924380a0cf0cee97bb9a9ede", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "f7b62346408a83911c2580154e35613eb314e0278aeea72ed7fedef9c1f165b2"}, + "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, + "ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"}, "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.30", "83a466369ebb8fe009e0823c7bf04314dc545122c2d48f896172fc79df33e99d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "76a14d5b7f850bb03b5243088c3649d54a2e52e34a2aa1104dee23cf50a8bae0"}, + "igniter": {:hex, :igniter, "0.7.0", "6848714fa5afa14258c82924a57af9364745316241a409435cf39cbe11e3ae80", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "1e7254780dbf4b44c9eccd6d86d47aa961efc298d7f520c24acb0258c8e90ba9"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -33,7 +34,7 @@ "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.3.7", "04018d1bc47613a40d4a804395883d5cc5cfdb05cd14282de35da99e1bb14ee3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "c6799d4385c4556f540652203359c090941cc3b11e783cd985aa0168914f310d"}, + "spark": {:hex, :spark, "2.3.14", "a08420d08e6e0e49d740aed3e160f1cb894ba8f6b3f5e6c63253e9df1995265c", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "af50c4ea5dd67eba822247f1c98e1d4e598cb7f6c28ccf5d002f0e0718096f4f"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, diff --git a/test/provider/event_test.exs b/test/provider/event_test.exs index f163a70..b8178b8 100644 --- a/test/provider/event_test.exs +++ b/test/provider/event_test.exs @@ -7,7 +7,7 @@ defmodule Diffo.Provider.EventTest do use ExUnit.Case setup_all do - AshNeo4j.BoltxHelper.start() + :ok #AshNeo4j.BoltxHelper.start() end setup do @@ -17,7 +17,45 @@ defmodule Diffo.Provider.EventTest do end describe "Diffo.Provider.Event create" do - test "fire an event - success" do + test "create an event using Event code interface - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + snapshot = Jason.encode!(instance) + + event = Diffo.Provider.Event.create!(%{type: :serviceCreateEvent, firing_type: instance.type, firing_snapshot: snapshot, instance_id: instance.id}) + + assert event.instance_id == instance.id + assert event.type == :serviceCreateEvent + assert event.firing_type == :service + assert event.firing_snapshot + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event.id}, + :FIRED, + :outgoing + ) + end + end + + describe "Diffo.Provider.Event encode" do + test "encode json with service instance - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + snapshot = Jason.encode!(instance) + event = Diffo.Provider.Event.create!(%{type: :serviceCreateEvent, firing_type: instance.type, firing_snapshot: snapshot, instance_id: instance.id}) + + encoding = Jason.encode!(event) |> Diffo.Util.summarise_dates() + + assert encoding == + ~s({\"eventId\":\"#{event.id}\",\"eventTime\":\"now\",\"eventType\":\"serviceCreateEvent\",\"event\":{\"service\":{\"id\":\"#{instance.id}\",\"href\":\"serviceInventoryManagement/v4/service/nbnAccess/#{instance.id}\",\"serviceSpecification\":{\"id\":\"#{specification.id}\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/#{specification.id}\",\"name\":\"nbnAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"initial\"}}}) + end + end + + describe "Diffo.Provider.Event provider API" do + test "fire an instance event - success" do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) @@ -25,9 +63,9 @@ defmodule Diffo.Provider.EventTest do assert instance.event event = instance.event + assert event.instance_id == instance.id assert event.type == :serviceCreateEvent assert event.firing_type == :service - assert event.firing_id == instance.id assert event.firing_snapshot assert AshNeo4j.Neo4jHelper.nodes_relate_how?( @@ -40,7 +78,6 @@ defmodule Diffo.Provider.EventTest do ) end - @tag debug: true test "fired events are chained - success" do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) @@ -82,29 +119,13 @@ defmodule Diffo.Provider.EventTest do end end - describe "Diffo.Provider.Event encode" do - test "encode json with service instance - success" do - specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) - instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - - instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) - assert instance.event - event = instance.event - - encoding = Jason.encode!(event) |> Diffo.Util.summarise_dates() - - assert encoding == - ~s({\"eventId\":\"#{event.id}\",\"eventTime\":\"now\",\"eventType\":\"serviceCreateEvent\",\"event\":{\"service\":{\"id\":\"#{instance.id}\",\"href\":\"serviceInventoryManagement/v4/service/nbnAccess/#{instance.id}\",\"serviceSpecification\":{\"id\":\"#{specification.id}\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/#{specification.id}\",\"name\":\"nbnAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"initial\"}}}) - end - end - describe "Diffo.Provider outstanding Event" do use Outstand @now DateTime.utc_now() @instance_id UUID.uuid4() @instance %Diffo.Provider.Instance{service_state: :active} @firing_type :service - @firing_snapshot Jason.encode(!@instnace) + @firing_snapshot Jason.encode(!@instance) @type_only %Diffo.Provider.Event{type: :serviceCreateEvent} @time_only %Diffo.Provider.Event{created_at: @now} From 69c5226f05faf52062444c33e6f7cf205f2d38bd Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Mon, 10 Nov 2025 21:22:11 +1030 Subject: [PATCH 7/7] instance events --- lib/diffo/changes/detail_event.ex | 42 ++++++++ .../provider/components/base_instance.ex | 5 +- lib/diffo/provider/components/event.ex | 6 +- test/provider/event_test.exs | 96 +++++++++++++++++-- 4 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 lib/diffo/changes/detail_event.ex diff --git a/lib/diffo/changes/detail_event.ex b/lib/diffo/changes/detail_event.ex new file mode 100644 index 0000000..b6c7911 --- /dev/null +++ b/lib/diffo/changes/detail_event.ex @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Changes.DetailEvent do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + DetailEvent - Ash Resource Change for detailing an Event + + """ + use Ash.Resource.Change + + def change(changeset, _opts, _context) do + instance_id = Ash.Changeset.get_data(changeset, :id) + + case Diffo.Provider.get_instance_by_id(instance_id, load: [:event]) do + {:ok, instance} -> + event = + Ash.Changeset.get_argument(changeset, :event) + |> Map.put(:firing_type, instance.type) + |> Map.put(:firing_snapshot, Jason.encode!(instance)) + + earlier_event = Map.get(instance, :event) + + event = + if earlier_event do + Map.put(event, :earlier_id, earlier_event.id) + else + event + end + + Ash.Changeset.force_set_argument(changeset, :event, event) + + {:error, _error} -> + changeset + end + end + + # this can be used as follows, where it will set the Event's firing_type, firing_snapshot and earlier_id from the refreshed Instance + # change Diffo.Changes.DetailEvent +end diff --git a/lib/diffo/provider/components/base_instance.ex b/lib/diffo/provider/components/base_instance.ex index 5f3884c..acdf1fb 100644 --- a/lib/diffo/provider/components/base_instance.ex +++ b/lib/diffo/provider/components/base_instance.ex @@ -461,11 +461,12 @@ defmodule Diffo.Provider.BaseInstance do update :fire_event do description "fires an event, maintaining the event chain" + argument :event, :map do allow_nil? false end - # TODO custom change? - # we want to inject the firing_type, firing_snapshot, earlier_id into the created event + + change Diffo.Changes.DetailEvent change manage_relationship(:event, type: :create) change load [:event] end diff --git a/lib/diffo/provider/components/event.ex b/lib/diffo/provider/components/event.ex index 8dcd4e5..3173273 100644 --- a/lib/diffo/provider/components/event.ex +++ b/lib/diffo/provider/components/event.ex @@ -56,12 +56,14 @@ defmodule Diffo.Provider.Event do defaults [:read, :destroy] create :create do - description "creates an event, fired by an instance" - primary? :true + description "creates an event, fired by an instance, optionally related to earlier event" + primary? true accept [:type, :firing_type, :firing_snapshot] argument :instance_id, :uuid + argument :earlier_id, :uuid change manage_relationship(:instance_id, :instance, type: :append) + change manage_relationship(:earlier_id, :earlier, type: :append_and_remove) end read :list do diff --git a/test/provider/event_test.exs b/test/provider/event_test.exs index b8178b8..9450162 100644 --- a/test/provider/event_test.exs +++ b/test/provider/event_test.exs @@ -7,12 +7,14 @@ defmodule Diffo.Provider.EventTest do use ExUnit.Case setup_all do - :ok #AshNeo4j.BoltxHelper.start() + # AshNeo4j.BoltxHelper.start() + :ok end setup do on_exit(fn -> - :ok #AshNeo4j.Neo4jHelper.delete_all() + # AshNeo4j.Neo4jHelper.delete_all() + :ok end) end @@ -22,7 +24,13 @@ defmodule Diffo.Provider.EventTest do instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) snapshot = Jason.encode!(instance) - event = Diffo.Provider.Event.create!(%{type: :serviceCreateEvent, firing_type: instance.type, firing_snapshot: snapshot, instance_id: instance.id}) + event = + Diffo.Provider.Event.create!(%{ + type: :serviceCreateEvent, + firing_type: instance.type, + firing_snapshot: snapshot, + instance_id: instance.id + }) assert event.instance_id == instance.id assert event.type == :serviceCreateEvent @@ -38,6 +46,60 @@ defmodule Diffo.Provider.EventTest do :outgoing ) end + + test "events created with code interface can be chained - success" do + specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) + instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) + snapshot = Jason.encode!(instance) + + event_1 = + Diffo.Provider.Event.create!(%{ + type: :serviceCreateEvent, + firing_type: instance.type, + firing_snapshot: snapshot, + instance_id: instance.id + }) + + activated_instance = Diffo.Provider.activate_service!(instance) + refreshed_instance = Diffo.Provider.get_instance_by_id!(activated_instance.id) + snapshot = Jason.encode!(refreshed_instance) + + event_2 = + Diffo.Provider.Event.create!(%{ + type: :serviceStateChangeEvent, + firing_type: instance.type, + firing_snapshot: snapshot, + instance_id: instance.id, + earlier_id: event_1.id + }) + + refute AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event_1.id}, + :FIRED, + :outgoing + ) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: instance.id}, + :Event, + %{uuid: event_2.id}, + :FIRED, + :outgoing + ) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Event, + %{uuid: event_2.id}, + :Event, + %{uuid: event_1.id}, + :AFTER, + :outgoing + ) + end end describe "Diffo.Provider.Event encode" do @@ -45,7 +107,14 @@ defmodule Diffo.Provider.EventTest do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) snapshot = Jason.encode!(instance) - event = Diffo.Provider.Event.create!(%{type: :serviceCreateEvent, firing_type: instance.type, firing_snapshot: snapshot, instance_id: instance.id}) + + event = + Diffo.Provider.Event.create!(%{ + type: :serviceCreateEvent, + firing_type: instance.type, + firing_snapshot: snapshot, + instance_id: instance.id + }) encoding = Jason.encode!(event) |> Diffo.Util.summarise_dates() @@ -59,7 +128,9 @@ defmodule Diffo.Provider.EventTest do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + instance = + Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + assert instance.event event = instance.event @@ -78,16 +149,21 @@ defmodule Diffo.Provider.EventTest do ) end - test "fired events are chained - success" do + @tag debug: true + test "fired instance events are chained - success" do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + instance = + Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + event_1 = instance.event instance = Diffo.Provider.activate_service!(instance) - instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceStateChangeEvent}}) + instance = + Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceStateChangeEvent}}) + event_2 = instance.event refute AshNeo4j.Neo4jHelper.nodes_relate_how?( @@ -216,7 +292,9 @@ defmodule Diffo.Provider.EventTest do specification = Diffo.Provider.create_specification!(%{name: "nbnAccess"}) instance = Diffo.Provider.create_instance!(%{specified_by: specification.id}) - instance = Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + instance = + Diffo.Provider.fire_instance_event!(instance, %{event: %{type: :serviceCreateEvent}}) + event = instance.event :ok = Diffo.Provider.delete_event(event)