diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5ace4..7df3a9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog -## [7.0.1] 2025-01-13 +## [Unreleased] + +### Added + +- [TD-7106] Issues with customisation and sorting in implementations tables + +## [7.0.1] 2025-01-13 ### Changed diff --git a/lib/td_cache/implementation_cache.ex b/lib/td_cache/implementation_cache.ex index ec9a34d..088a512 100644 --- a/lib/td_cache/implementation_cache.ex +++ b/lib/td_cache/implementation_cache.ex @@ -128,7 +128,9 @@ defmodule TdCache.ImplementationCache do {:minimum, :float}, {:result_type, :string}, {:updated_at, :datetime}, - {:status, :string} + {:status, :string}, + {:df_content, :map}, + {:data_structures, :list} ] @rule_props [ @@ -172,6 +174,8 @@ defmodule TdCache.ImplementationCache do implementation |> MapHelpers.parse_fields(@props) + |> decode_df_content() + |> decode_data_structures() |> put_optional(:execution_result_info, execution_result_info) |> put_optional(:rule, rule) |> put_optional(:concepts_links, concepts) @@ -269,6 +273,8 @@ defmodule TdCache.ImplementationCache do implementation_props = implementation |> Map.take(props_keys) + |> encode_df_content() + |> encode_data_structures() result_props_keys = Enum.map(@result_props, fn {key, _} -> key end) @@ -307,4 +313,81 @@ defmodule TdCache.ImplementationCache do _ -> ["SADD", "implementation:deleted_ids", implementation_ref] end end + + defp encode_df_content(%{df_content: content} = props) when is_map(content) do + %{props | df_content: Jason.encode!(content)} + end + + defp encode_df_content(props), do: props + + defp decode_df_content(%{df_content: content} = implementation) when is_binary(content) do + case Jason.decode(content) do + {:ok, decoded} -> %{implementation | df_content: decoded} + _ -> implementation + end + end + + defp decode_df_content(implementation), do: implementation + + defp encode_data_structures(%{data_structures: data_structures} = props) do + encoded_data_structures = + data_structures + |> Enum.map(fn %{ + data_structure: %{current_version: current_version, domains: domains} = ds, + type: link_type + } -> + cv = + Map.new() + |> Map.put(:name, Map.get(current_version, :name)) + |> Map.put(:path, Map.get(current_version, :path)) + + data_structure = + Map.new() + |> Map.put(:id, Map.get(ds, :id)) + |> Map.put(:external_id, Map.get(ds, :external_id)) + |> Map.put(:domains, domains) + |> Map.put(:current_version, cv) + + %{data_structure: data_structure, type: link_type} + end) + |> Jason.encode!() + + Map.put(props, :data_structures, encoded_data_structures) + end + + defp encode_data_structures(props), do: props + + defp decode_data_structures(%{data_structures: data_structures} = implementation) do + implementation + |> Map.delete(:data_structures) + |> Map.put("data_structures", data_structures) + |> decode_data_structures() + end + + defp decode_data_structures(%{"data_structures" => data_structures} = implementation) do + case Jason.decode(data_structures) do + {:ok, decoded_data_structures} -> + decoded_data_structures = + decoded_data_structures + |> Enum.map(fn %{ + "type" => link_type, + "data_structure" => + %{"domains" => domains} = + data_structure + } -> + ds = %{data_structure | "domains" => domains} + + %{"type" => String.to_atom(link_type), "data_structure" => ds} + end) + + implementation + |> Map.delete("data_structures") + |> Map.put(:data_structures, MapHelpers.atomize_keys(decoded_data_structures)) + + _ -> + [] + end + end + + defp decode_data_structures(implementation), do: implementation end diff --git a/lib/td_cache/utils/map_helpers.ex b/lib/td_cache/utils/map_helpers.ex index d197691..d7d9910 100644 --- a/lib/td_cache/utils/map_helpers.ex +++ b/lib/td_cache/utils/map_helpers.ex @@ -17,4 +17,58 @@ defmodule TdCache.Utils.MapHelpers do def parse_string(:string, value) when is_atom(value), do: Atom.to_string(value) # def parse_string(:datetime, value) when is_binary(value), do: DateTime.from_iso8601(value) def parse_string(_, value), do: value + + def atomize_keys(nil), do: nil + + # Structs don't do enumerable and anyway the keys are already + # atoms + def atomize_keys(%{__struct__: _} = struct) do + struct + end + + def atomize_keys(%{} = map) do + map + |> Enum.into(%{}, fn {k, v} -> {to_atom_key(k), atomize_keys(v)} end) + end + + # Walk the list and atomize the keys of + # of any map members + def atomize_keys([head | rest]) do + [atomize_keys(head) | atomize_keys(rest)] + end + + def atomize_keys(not_a_map) do + not_a_map + end + + @doc """ + Convert map atom keys to strings + """ + def stringify_keys(nil), do: nil + + def stringify_keys(%{} = map) do + Enum.into(map, %{}, fn {k, v} -> {to_string_key(k), stringify_keys(v)} end) + end + + # Walk the list and stringify the keys of + # of any map members + def stringify_keys([head | rest]) do + [stringify_keys(head) | stringify_keys(rest)] + end + + def stringify_keys(not_a_map) do + not_a_map + end + + defp to_atom_key(key) when is_binary(key) do + String.to_atom(key) + end + + defp to_atom_key(key), do: key + + defp to_string_key(key) when is_atom(key) do + Atom.to_string(key) + end + + defp to_string_key(key), do: Atom.to_string(key) end diff --git a/test/td_cache/implementation_cache_test.exs b/test/td_cache/implementation_cache_test.exs index 92131a8..03af12a 100644 --- a/test/td_cache/implementation_cache_test.exs +++ b/test/td_cache/implementation_cache_test.exs @@ -11,7 +11,70 @@ defmodule TdCache.ImplementationCacheTest do alias TdCache.Redix setup do - implementation = build(:implementation) + dfcontent = %{ + "Impact" => %{"origin" => "user", "value" => "High"}, + "Quality Principles" => %{"origin" => "user", "value" => "Completeness"} + } + + domains = [ + %{ + id: 111, + name: "domain_name_111", + external_id: "domain_external_id_111" + }, + %{ + id: 222, + name: "domain_name_222", + external_id: "domain_external_id_222" + }, + %{ + id: 333, + name: "domain_name_333", + external_id: "domain_external_id_333" + } + ] + + data_structures = [ + %{ + data_structure: %{ + id: 111, + external_id: "data_tructure_111", + domains: domains, + current_version: %{ + path: ["this", "is", "a", "path"], + name: "data_structure_name_111" + } + }, + type: :dataset + }, + %{ + data_structure: %{ + id: 222, + external_id: "data_tructure_222", + domains: domains, + current_version: %{ + path: ["this", "is", "a", "path"], + name: "data_structure_name_222" + } + }, + type: :population + }, + %{ + data_structure: %{ + id: 333, + external_id: "data_tructure_333", + domains: domains, + current_version: %{ + path: ["this", "is", "a", "path"], + name: "data_structure_name_333" + } + }, + type: :validation + } + ] + + implementation = + build(:implementation, df_content: dfcontent, data_structures: data_structures) on_exit(fn -> ImplementationCache.delete(implementation.id) @@ -19,14 +82,14 @@ defmodule TdCache.ImplementationCacheTest do Redix.command(["DEL", "relation_impl_id_to_impl_ref"]) end) - {:ok, implementation: implementation} + {:ok, implementation: implementation, dfcontent: dfcontent} end describe "ImplementationCache" do test "writes an implementation entry in redis and reads it back", %{ implementation: implementation } do - assert {:ok, [10, 0, 1, 0]} = ImplementationCache.put(implementation) + assert {:ok, [12, 0, 1, 0]} = ImplementationCache.put(implementation) {:ok, impl} = ImplementationCache.get(implementation.id) @@ -38,7 +101,9 @@ defmodule TdCache.ImplementationCacheTest do :goal, :minimum, :rule_id, - :status + :status, + :df_content, + :data_structures ]) refute Map.has_key?(impl, :execution_result_info) @@ -85,12 +150,13 @@ defmodule TdCache.ImplementationCacheTest do end test "writes implementations entries in redis and list implementations keys", %{ - implementation: %{id: impl_id} = implementation + implementation: %{id: impl_id} = implementation, + dfcontent: dfcontent } do - %{id: impl2_id} = impl2 = build(:implementation) + %{id: impl2_id} = impl2 = build(:implementation, df_content: dfcontent) %{id: impl3_id} = impl3 = build(:implementation) - assert {:ok, [10, 0, 1, 0]} = ImplementationCache.put(implementation) - assert {:ok, [10, 0, 1, 0]} = ImplementationCache.put(impl2) + assert {:ok, [12, 0, 1, 0]} = ImplementationCache.put(implementation) + assert {:ok, [11, 0, 1, 0]} = ImplementationCache.put(impl2) assert {:ok, [10, 0, 1, 0]} = ImplementationCache.put(impl3) assert [