From 0c07802210b621d47ba84558b8c787ac7b8f75ca Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Fri, 24 May 2024 11:50:39 +0200 Subject: [PATCH] base_image: add localized attributes Signed-off-by: Riccardo Binetti --- .../base_images/base_image/base_image.ex | 73 +++++++- .../mutation/create_base_image_test.exs | 56 ++++++ .../mutation/update_base_image_test.exs | 87 ++++++++- .../schema/query/base_image_test.exs | 174 +++++++++++++++++- 4 files changed, 386 insertions(+), 4 deletions(-) diff --git a/backend/lib/edgehog/base_images/base_image/base_image.ex b/backend/lib/edgehog/base_images/base_image/base_image.ex index 9bd888bf0..2288ceb2e 100644 --- a/backend/lib/edgehog/base_images/base_image/base_image.ex +++ b/backend/lib/edgehog/base_images/base_image/base_image.ex @@ -26,6 +26,7 @@ defmodule Edgehog.BaseImages.BaseImage do ] alias Edgehog.BaseImages.BaseImage.Changes + alias Edgehog.Localization alias Edgehog.Validations resource do @@ -39,6 +40,9 @@ defmodule Edgehog.BaseImages.BaseImage do graphql do type :base_image + field_names localized_description: :description, + localized_release_display_name: :release_display_name + queries do get :base_image, :get end @@ -70,8 +74,23 @@ defmodule Edgehog.BaseImages.BaseImage do allow_nil? false end + argument :localized_descriptions, {:array, Localization.LocalizedAttribute} do + description "A list of descriptions in different languages." + end + + argument :localized_release_display_names, {:array, Localization.LocalizedAttribute} do + description "A list of release display names in different languages." + end + change Changes.HandleFileUpload change manage_relationship(:base_image_collection_id, :base_image_collection, type: :append) + + change {Localization.Changes.UpsertLocalizedAttribute, + input_argument: :localized_descriptions, target_attribute: :description} + + change {Localization.Changes.UpsertLocalizedAttribute, + input_argument: :localized_release_display_names, + target_attribute: :release_display_name} end create :create_fixture do @@ -82,7 +101,22 @@ defmodule Edgehog.BaseImages.BaseImage do allow_nil? false end + argument :localized_descriptions, {:array, Localization.LocalizedAttribute} do + description "A list of descriptions in different languages." + end + + argument :localized_release_display_names, {:array, Localization.LocalizedAttribute} do + description "A list of release display names in different languages." + end + change manage_relationship(:base_image_collection_id, :base_image_collection, type: :append) + + change {Localization.Changes.UpsertLocalizedAttribute, + input_argument: :localized_descriptions, target_attribute: :description} + + change {Localization.Changes.UpsertLocalizedAttribute, + input_argument: :localized_release_display_names, + target_attribute: :release_display_name} end read :get do @@ -99,7 +133,26 @@ defmodule Edgehog.BaseImages.BaseImage do description "Updates a base image." primary? true + # Needed because UpsertLocalizedAttribute is not atomic + require_atomic? false + + argument :localized_descriptions, {:array, Localization.LocalizedAttributeUpdateInput} do + description "A list of descriptions in different languages." + end + + argument :localized_release_display_names, + {:array, Localization.LocalizedAttributeUpdateInput} do + description "A list of release display names in different languages." + end + accept [:starting_version_requirement] + + change {Localization.Changes.UpsertLocalizedAttribute, + input_argument: :localized_descriptions, target_attribute: :description} + + change {Localization.Changes.UpsertLocalizedAttribute, + input_argument: :localized_release_display_names, + target_attribute: :release_display_name} end destroy :destroy do @@ -142,8 +195,8 @@ defmodule Edgehog.BaseImages.BaseImage do allow_nil? false end - # TODO: localized description - # TODO: localized release_display_name + attribute :description, :map + attribute :release_display_name, :map create_timestamp :inserted_at update_timestamp :updated_at @@ -158,6 +211,22 @@ defmodule Edgehog.BaseImages.BaseImage do end end + calculations do + calculate :localized_descriptions, {:array, Localization.LocalizedAttribute} do + public? true + description "A list of descriptions in different languages." + calculation {Localization.Calculations.LocalizedAttribute, attribute: :description} + argument :preferred_language_tags, {:array, :string} + end + + calculate :localized_release_display_names, {:array, Localization.LocalizedAttribute} do + public? true + description "A list of release display names in different languages." + calculation {Localization.Calculations.LocalizedAttribute, attribute: :release_display_name} + argument :preferred_language_tags, {:array, :string} + end + end + identities do # These have to be named this way to match the existing unique indexes # we already have. Ash uses identities to add a `unique_constraint` to the diff --git a/backend/test/edgehog_web/schema/mutation/create_base_image_test.exs b/backend/test/edgehog_web/schema/mutation/create_base_image_test.exs index 13a3d51d4..9bf5917e2 100644 --- a/backend/test/edgehog_web/schema/mutation/create_base_image_test.exs +++ b/backend/test/edgehog_web/schema/mutation/create_base_image_test.exs @@ -59,6 +59,52 @@ defmodule EdgehogWeb.Schema.Mutation.CreateBaseImageTest do } = base_image end + test "allows passing localized descriptions", %{tenant: tenant} do + localized_descriptions = [ + %{"languageTag" => "en", "value" => "My Base Image"}, + %{"languageTag" => "it", "value" => "La mia Base Image"} + ] + + result = + create_base_image_mutation( + tenant: tenant, + localized_descriptions: localized_descriptions + ) + + assert %{"localizedDescriptions" => localized_descriptions} = extract_result!(result) + assert length(localized_descriptions) == 2 + assert %{"languageTag" => "en", "value" => "My Base Image"} in localized_descriptions + assert %{"languageTag" => "it", "value" => "La mia Base Image"} in localized_descriptions + end + + test "allows passing localized release display names", %{tenant: tenant} do + localized_release_display_names = [ + %{"languageTag" => "en", "value" => "Initial version"}, + %{"languageTag" => "it", "value" => "Versione iniziale"} + ] + + result = + create_base_image_mutation( + tenant: tenant, + localized_release_display_names: localized_release_display_names + ) + + assert %{"localizedReleaseDisplayNames" => localized_release_display_names} = + extract_result!(result) + + assert length(localized_release_display_names) == 2 + + assert %{ + "languageTag" => "en", + "value" => "Initial version" + } in localized_release_display_names + + assert %{ + "languageTag" => "it", + "value" => "Versione iniziale" + } in localized_release_display_names + end + test "returns error for non-existing base image collection", %{tenant: tenant} do base_image_collection = base_image_collection_fixture(tenant: tenant) base_image_collection_id = AshGraphql.Resource.encode_relay_id(base_image_collection) @@ -193,6 +239,14 @@ defmodule EdgehogWeb.Schema.Mutation.CreateBaseImageTest do id version url + localizedDescriptions { + languageTag + value + } + localizedReleaseDisplayNames { + languageTag + value + } startingVersionRequirement baseImageCollection { id @@ -220,6 +274,8 @@ defmodule EdgehogWeb.Schema.Mutation.CreateBaseImageTest do input = %{ "baseImageCollectionId" => base_image_collection_id, "version" => version, + "localizedDescriptions" => opts[:localized_descriptions], + "localizedReleaseDisplayNames" => opts[:localized_release_display_names], "startingVersionRequirement" => opts[:starting_version_requirement], "file" => file && "file" } diff --git a/backend/test/edgehog_web/schema/mutation/update_base_image_test.exs b/backend/test/edgehog_web/schema/mutation/update_base_image_test.exs index 42992fb10..a6e1f4e8c 100644 --- a/backend/test/edgehog_web/schema/mutation/update_base_image_test.exs +++ b/backend/test/edgehog_web/schema/mutation/update_base_image_test.exs @@ -53,6 +53,81 @@ defmodule EdgehogWeb.Schema.Mutation.UpdateBaseImageTest do } = base_image end + test "allows updating localized descriptions", %{tenant: tenant} do + initial_localized_descriptions = [ + %{language_tag: "en", value: "Description"}, + %{language_tag: "it", value: "Descrizione"} + ] + + fixture = + base_image_fixture( + tenant: tenant, + localized_descriptions: initial_localized_descriptions + ) + + id = AshGraphql.Resource.encode_relay_id(fixture) + + updated_localized_descriptions = [ + # nil value, so it will be removed + %{"languageTag" => "en", "value" => nil}, + %{"languageTag" => "it", "value" => "Nuova descrizione"}, + %{"languageTag" => "bs", "value" => "Opis"} + ] + + result = + update_base_image_mutation( + tenant: tenant, + id: id, + localized_descriptions: updated_localized_descriptions + ) + + assert %{"localizedDescriptions" => localized_descriptions} = extract_result!(result) + assert length(localized_descriptions) == 2 + assert %{"languageTag" => "it", "value" => "Nuova descrizione"} in localized_descriptions + assert %{"languageTag" => "bs", "value" => "Opis"} in localized_descriptions + end + + test "allows updating localized release display names", %{tenant: tenant} do + initial_localized_release_display_names = [ + %{language_tag: "en", value: "Release display name"}, + %{language_tag: "it", value: "Nome del rilascio"} + ] + + fixture = + base_image_fixture( + tenant: tenant, + localized_release_display_names: initial_localized_release_display_names + ) + + id = AshGraphql.Resource.encode_relay_id(fixture) + + updated_localized_release_display_names = [ + # nil value, so it will be removed + %{"languageTag" => "en", "value" => nil}, + %{"languageTag" => "it", "value" => "Nuovo nome del rilascio"}, + %{"languageTag" => "bs", "value" => "Ime"} + ] + + result = + update_base_image_mutation( + tenant: tenant, + id: id, + localized_release_display_names: updated_localized_release_display_names + ) + + assert %{"localizedReleaseDisplayNames" => localized_release_display_names} = + extract_result!(result) + + assert length(localized_release_display_names) == 2 + + assert %{ + "languageTag" => "it", + "value" => "Nuovo nome del rilascio" + } in localized_release_display_names + + assert %{"languageTag" => "bs", "value" => "Ime"} in localized_release_display_names + end + test "returns error for invalid starting version requirement", %{ tenant: tenant, base_image: base_image, @@ -94,6 +169,14 @@ defmodule EdgehogWeb.Schema.Mutation.UpdateBaseImageTest do id version url + localizedDescriptions { + languageTag + value + } + localizedReleaseDisplayNames { + languageTag + value + } startingVersionRequirement baseImageCollection { id @@ -109,7 +192,9 @@ defmodule EdgehogWeb.Schema.Mutation.UpdateBaseImageTest do input = %{ - "startingVersionRequirement" => opts[:starting_version_requirement] + "startingVersionRequirement" => opts[:starting_version_requirement], + "localizedDescriptions" => opts[:localized_descriptions], + "localizedReleaseDisplayNames" => opts[:localized_release_display_names] } |> Enum.filter(fn {_k, v} -> v != nil end) |> Enum.into(%{}) diff --git a/backend/test/edgehog_web/schema/query/base_image_test.exs b/backend/test/edgehog_web/schema/query/base_image_test.exs index cda88d258..d50a7791a 100644 --- a/backend/test/edgehog_web/schema/query/base_image_test.exs +++ b/backend/test/edgehog_web/schema/query/base_image_test.exs @@ -61,6 +61,175 @@ defmodule EdgehogWeb.Schema.Query.BaseImageTest do end end + describe "baseImage localized descriptions" do + setup %{tenant: tenant} do + localized_descriptions = [ + %{language_tag: "en-US", value: "My Base Image"}, + %{language_tag: "it", value: "La mia Base Image"}, + %{language_tag: "fr", value: "Mon Base Image"} + ] + + base_image = + base_image_fixture(tenant: tenant, localized_descriptions: localized_descriptions) + + id = AshGraphql.Resource.encode_relay_id(base_image) + + document = """ + query ($id: ID!, $preferredLanguageTags: [String!]) { + baseImage(id: $id) { + localizedDescriptions(preferredLanguageTags: $preferredLanguageTags) { + languageTag + value + } + } + } + """ + + %{base_image: base_image, id: id, document: document} + end + + test "returns all localized descriptions with no preferredLanguageTags", ctx do + %{tenant: tenant, id: id, document: document} = ctx + + %{"localizedDescriptions" => localized_descriptions} = + base_image_query( + tenant: tenant, + id: id, + document: document + ) + |> extract_result!() + + assert length(localized_descriptions) == 3 + assert %{"languageTag" => "en-US", "value" => "My Base Image"} in localized_descriptions + assert %{"languageTag" => "it", "value" => "La mia Base Image"} in localized_descriptions + assert %{"languageTag" => "fr", "value" => "Mon Base Image"} in localized_descriptions + end + + test "returns filtered localized descriptions with preferredLanguageTags", ctx do + %{tenant: tenant, id: id, document: document} = ctx + preferred_language_tags = ["it", "fr"] + + %{"localizedDescriptions" => localized_descriptions} = + base_image_query( + tenant: tenant, + id: id, + extra_variables: %{"preferredLanguageTags" => preferred_language_tags}, + document: document + ) + |> extract_result!() + + assert length(localized_descriptions) == 2 + assert %{"languageTag" => "it", "value" => "La mia Base Image"} in localized_descriptions + assert %{"languageTag" => "fr", "value" => "Mon Base Image"} in localized_descriptions + end + + test "returns empty localized descriptions if no language tag matches exactly", ctx do + %{tenant: tenant, id: id, document: document} = ctx + preferred_language_tags = ["en-GB", "de"] + + %{"localizedDescriptions" => []} = + base_image_query( + tenant: tenant, + id: id, + extra_variables: %{"preferredLanguageTags" => preferred_language_tags}, + document: document + ) + |> extract_result!() + end + end + + describe "baseImage localized release display names" do + setup %{tenant: tenant} do + localized_release_display_names = [ + %{language_tag: "en-US", value: "Initial version"}, + %{language_tag: "it", value: "Versione iniziale"}, + %{language_tag: "fr", value: "Version initiale"} + ] + + base_image = + base_image_fixture( + tenant: tenant, + localized_release_display_names: localized_release_display_names + ) + + id = AshGraphql.Resource.encode_relay_id(base_image) + + document = """ + query ($id: ID!, $preferredLanguageTags: [String!]) { + baseImage(id: $id) { + localizedReleaseDisplayNames(preferredLanguageTags: $preferredLanguageTags) { + languageTag + value + } + } + } + """ + + %{base_image: base_image, id: id, document: document} + end + + test "returns all localized release display names with no preferredLanguageTags", ctx do + %{tenant: tenant, id: id, document: document} = ctx + + %{"localizedReleaseDisplayNames" => localized_release_display_names} = + base_image_query( + tenant: tenant, + id: id, + document: document + ) + |> extract_result!() + + assert length(localized_release_display_names) == 3 + + assert %{"languageTag" => "en-US", "value" => "Initial version"} in localized_release_display_names + + assert %{"languageTag" => "it", "value" => "Versione iniziale"} in localized_release_display_names + + assert %{"languageTag" => "fr", "value" => "Version initiale"} in localized_release_display_names + end + + test "returns filtered localized release display names with preferredLanguageTags", ctx do + %{tenant: tenant, id: id, document: document} = ctx + preferred_language_tags = ["it", "fr"] + + %{"localizedReleaseDisplayNames" => localized_release_display_names} = + base_image_query( + tenant: tenant, + id: id, + extra_variables: %{"preferredLanguageTags" => preferred_language_tags}, + document: document + ) + |> extract_result!() + + assert length(localized_release_display_names) == 2 + + assert %{ + "languageTag" => "it", + "value" => "Versione iniziale" + } in localized_release_display_names + + assert %{ + "languageTag" => "fr", + "value" => "Version initiale" + } in localized_release_display_names + end + + test "returns empty localized release display names if no language tag matches exactly", + ctx do + %{tenant: tenant, id: id, document: document} = ctx + preferred_language_tags = ["en-GB", "de"] + + %{"localizedReleaseDisplayNames" => []} = + base_image_query( + tenant: tenant, + id: id, + extra_variables: %{"preferredLanguageTags" => preferred_language_tags}, + document: document + ) + |> extract_result!() + end + end + defp base_image_query(opts) do default_document = """ query ($id: ID!) { @@ -79,7 +248,10 @@ defmodule EdgehogWeb.Schema.Query.BaseImageTest do tenant = Keyword.fetch!(opts, :tenant) id = Keyword.fetch!(opts, :id) - variables = %{"id" => id} + variables = + opts + |> Keyword.get(:extra_variables, %{}) + |> Map.merge(%{"id" => id}) document = Keyword.get(opts, :document, default_document)