From 810ced9d123bfd38f03d2c73f9c3ce8a8966165f Mon Sep 17 00:00:00 2001 From: emielvdveen Date: Sat, 28 Oct 2023 13:31:50 +0200 Subject: [PATCH 1/3] Refactored integration of external storage services --- core/config/dev.exs | 9 +- core/config/runtime.exs | 7 +- core/frameworks/concept/content_model.ex | 7 ++ ...pdown.selector.ex => dropdown_selector.ex} | 2 +- core/frameworks/pixel/components/form.ex | 2 + core/frameworks/utililty/ecto_helper.ex | 8 ++ core/lib/core/authorization.ex | 5 - .../gettext/en/LC_MESSAGES/eyra-assignment.po | 8 ++ .../priv/gettext/en/LC_MESSAGES/eyra-enums.po | 16 +++ .../gettext/en/LC_MESSAGES/eyra-project.po | 8 ++ .../gettext/en/LC_MESSAGES/eyra-storage.po | 64 ++++++++++ core/priv/gettext/eyra-assignment.pot | 8 ++ core/priv/gettext/eyra-enums.pot | 16 +++ core/priv/gettext/eyra-project.pot | 8 ++ core/priv/gettext/eyra-storage.pot | 64 ++++++++++ .../gettext/nl/LC_MESSAGES/eyra-assignment.po | 8 ++ .../priv/gettext/nl/LC_MESSAGES/eyra-enums.po | 16 +++ .../gettext/nl/LC_MESSAGES/eyra-project.po | 8 ++ .../gettext/nl/LC_MESSAGES/eyra-storage.po | 64 ++++++++++ .../migrations/20231025125051_add_storage.exs | 77 ++++++++++++ core/systems/assignment/_public.ex | 32 ++++- .../assignment/content_page_builder.ex | 12 +- core/systems/assignment/model.ex | 17 ++- core/systems/assignment/settings_view.ex | 110 +++++++++++++++++ core/systems/budget/form.ex | 4 +- .../builders/assignment_landing_page.ex | 4 +- core/systems/citizen/pool/form.ex | 4 +- core/systems/data_donation/_public.ex | 2 - core/systems/data_donation/_routes.ex | 16 --- .../centerdata_fakeapi_controller.ex | 11 -- .../centerdata/centerdata_form.ex | 29 ----- .../data_donation/storage/fake_form.ex | 17 --- .../storage/fake_storage_backend.ex | 8 -- core/systems/data_donation/storage/s3_form.ex | 17 --- core/systems/lab/task_view.ex | 6 +- core/systems/storage/_assembly.ex | 37 ++++++ core/systems/storage/_public.ex | 11 ++ .../aws/backend.ex} | 6 +- core/systems/storage/aws/endpoint_form.ex | 64 ++++++++++ core/systems/storage/aws/endpoint_model.ex | 44 +++++++ .../azure/backend.ex} | 6 +- core/systems/storage/azure/endpoint_form.ex | 63 ++++++++++ core/systems/storage/azure/endpoint_model.ex | 43 +++++++ .../storage_backend.ex => storage/backend.ex} | 2 +- core/systems/storage/backend_types.ex | 7 ++ .../centerdata/backend.ex} | 6 +- .../storage/centerdata/endpoint_form.ex | 65 ++++++++++ .../storage/centerdata/endpoint_model.ex | 41 +++++++ .../{data_donation => storage}/delivery.ex | 8 +- core/systems/storage/endpoint_form.ex | 113 ++++++++++++++++++ core/systems/storage/endpoint_model.ex | 95 +++++++++++++++ core/systems/storage/fake_backend.ex | 8 ++ core/systems/storage/yoda/backend.ex | 2 + core/systems/storage/yoda/endpoint_form.ex | 63 ++++++++++ core/systems/storage/yoda/endpoint_model.ex | 43 +++++++ core/systems/union/_routes.ex | 16 +++ .../centerdata/controller.ex} | 2 +- .../union/centerdata/fakeapi_controller.ex | 11 ++ .../centerdata/fakeapi_page.ex} | 2 +- .../centerdata/http_client.ex} | 2 +- core/test/core/data_donation/tool_test.exs | 10 -- core/test/test_helper.exs | 7 +- 62 files changed, 1310 insertions(+), 161 deletions(-) create mode 100644 core/frameworks/concept/content_model.ex rename core/frameworks/pixel/components/{dropdown.selector.ex => dropdown_selector.ex} (99%) create mode 100644 core/priv/gettext/en/LC_MESSAGES/eyra-storage.po create mode 100644 core/priv/gettext/eyra-storage.pot create mode 100644 core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po create mode 100644 core/priv/repo/migrations/20231025125051_add_storage.exs create mode 100644 core/systems/assignment/settings_view.ex delete mode 100644 core/systems/data_donation/_public.ex delete mode 100644 core/systems/data_donation/_routes.ex delete mode 100644 core/systems/data_donation/centerdata/centerdata_fakeapi_controller.ex delete mode 100644 core/systems/data_donation/centerdata/centerdata_form.ex delete mode 100644 core/systems/data_donation/storage/fake_form.ex delete mode 100644 core/systems/data_donation/storage/fake_storage_backend.ex delete mode 100644 core/systems/data_donation/storage/s3_form.ex create mode 100644 core/systems/storage/_assembly.ex create mode 100644 core/systems/storage/_public.ex rename core/systems/{data_donation/storage/s3_storage_backend.ex => storage/aws/backend.ex} (79%) create mode 100644 core/systems/storage/aws/endpoint_form.ex create mode 100644 core/systems/storage/aws/endpoint_model.ex rename core/systems/{data_donation/storage/azure_storage_backend.ex => storage/azure/backend.ex} (90%) create mode 100644 core/systems/storage/azure/endpoint_form.ex create mode 100644 core/systems/storage/azure/endpoint_model.ex rename core/systems/{data_donation/storage/storage_backend.ex => storage/backend.ex} (73%) create mode 100644 core/systems/storage/backend_types.ex rename core/systems/{data_donation/centerdata/centerdata_storage_backend.ex => storage/centerdata/backend.ex} (86%) create mode 100644 core/systems/storage/centerdata/endpoint_form.ex create mode 100644 core/systems/storage/centerdata/endpoint_model.ex rename core/systems/{data_donation => storage}/delivery.ex (80%) create mode 100644 core/systems/storage/endpoint_form.ex create mode 100644 core/systems/storage/endpoint_model.ex create mode 100644 core/systems/storage/fake_backend.ex create mode 100644 core/systems/storage/yoda/backend.ex create mode 100644 core/systems/storage/yoda/endpoint_form.ex create mode 100644 core/systems/storage/yoda/endpoint_model.ex create mode 100644 core/systems/union/_routes.ex rename core/systems/{data_donation/centerdata/centerdata_controller.ex => union/centerdata/controller.ex} (96%) create mode 100644 core/systems/union/centerdata/fakeapi_controller.ex rename core/systems/{data_donation/centerdata/centerdata_fakeapi_page.ex => union/centerdata/fakeapi_page.ex} (96%) rename core/systems/{data_donation/centerdata/centerdata_http_client.ex => union/centerdata/http_client.ex} (80%) delete mode 100644 core/test/core/data_donation/tool_test.exs diff --git a/core/config/dev.exs b/core/config/dev.exs index 063f976f9..e1526ab02 100644 --- a/core/config/dev.exs +++ b/core/config/dev.exs @@ -87,10 +87,11 @@ config :core, :s3, bucket: "eylixir" config :core, :data_donation_storage_backend, - fake: Systems.DataDonation.FakeStorageBackend, - s3: Systems.DataDonation.S3StorageBackend, - azure: Systems.DataDonation.AzureStorageBackend, - centerdata: Systems.DataDonation.CenterdataStorageBackend + fake: Systems.Storage.FakeBackend, + s3: Systems.Storage.AWS.Backend, + azure: Systems.Storage.Azure.Backend, + centerdata: Systems.Storage.Centerdata.Backend, + yoda: Systems.Storage.YodaBackend # For Minio (local S3) config :ex_aws, diff --git a/core/config/runtime.exs b/core/config/runtime.exs index 12aa9a083..081b99c1a 100644 --- a/core/config/runtime.exs +++ b/core/config/runtime.exs @@ -30,9 +30,10 @@ if config_env() == :prod do config :core, :data_donation_storage_backend, - s3: Systems.DataDonation.S3StorageBackend, - azure: Systems.DataDonation.AzureStorageBackend, - centerdata: Systems.DataDonation.CenterdataStorageBackend + s3: Systems.Storage.AWS.Backend, + azure: Systems.Storage.Azure.Backend, + centerdata: Systems.Storage.Centerdata.Backend, + yoda: Systems.Storage.YodaBackend # MAILGUN diff --git a/core/frameworks/concept/content_model.ex b/core/frameworks/concept/content_model.ex new file mode 100644 index 000000000..70d5e5cd6 --- /dev/null +++ b/core/frameworks/concept/content_model.ex @@ -0,0 +1,7 @@ +defprotocol Frameworks.Concept.ContentModel do + @spec ready?(t) :: boolean() + def ready?(_t) + + @spec form(t) :: atom() + def form(_t) +end diff --git a/core/frameworks/pixel/components/dropdown.selector.ex b/core/frameworks/pixel/components/dropdown_selector.ex similarity index 99% rename from core/frameworks/pixel/components/dropdown.selector.ex rename to core/frameworks/pixel/components/dropdown_selector.ex index 259e13744..cba8a4b54 100644 --- a/core/frameworks/pixel/components/dropdown.selector.ex +++ b/core/frameworks/pixel/components/dropdown_selector.ex @@ -1,4 +1,4 @@ -defmodule Frameworks.Pixel.Dropdown.Selector do +defmodule Frameworks.Pixel.DropdownSelector do use CoreWeb, :live_component import Frameworks.Pixel.FormHelpers, only: [get_border_color: 1] diff --git a/core/frameworks/pixel/components/form.ex b/core/frameworks/pixel/components/form.ex index baff65ba2..9a4d93514 100644 --- a/core/frameworks/pixel/components/form.ex +++ b/core/frameworks/pixel/components/form.ex @@ -239,6 +239,7 @@ defmodule Frameworks.Pixel.Form do attr(:form, :any, required: true) attr(:field, :atom, required: true) + attr(:placeholder, :string, default: "") attr(:label_text, :string) attr(:label_color, :string, default: "text-grey1") attr(:background, :atom, default: :light) @@ -249,6 +250,7 @@ defmodule Frameworks.Pixel.Form do <.input form={@form} field={@field} + placeholder={@placeholder} label_text={@label_text} label_color={@label_color} background={@background} diff --git a/core/frameworks/utililty/ecto_helper.ex b/core/frameworks/utililty/ecto_helper.ex index 96d826e20..cf28fcdd0 100644 --- a/core/frameworks/utililty/ecto_helper.ex +++ b/core/frameworks/utililty/ecto_helper.ex @@ -4,6 +4,14 @@ defmodule Frameworks.Utility.EctoHelper do alias Core.Repo alias Frameworks.Signal + def put_assoc(changeset, key, value, execute?) do + if execute? do + Changeset.put_assoc(changeset, key, value) + else + changeset + end + end + def upsert(%{data: %{id: id}} = changeset) when not is_nil(id) do Repo.update(changeset) end diff --git a/core/lib/core/authorization.ex b/core/lib/core/authorization.ex index f4e341f57..c5ea3b96b 100644 --- a/core/lib/core/authorization.ex +++ b/core/lib/core/authorization.ex @@ -27,7 +27,6 @@ defmodule Core.Authorization do grant_access(Systems.Campaign.Model, [:visitor, :member]) grant_access(Systems.Questionnaire.ToolModel, [:owner, :coordinator, :participant]) grant_access(Systems.Lab.ToolModel, [:owner, :coordinator, :participant]) - grant_access(Systems.DataDonation.ToolModel, [:owner, :coordinator, :participant]) grant_access(Systems.Benchmark.SpotModel, [:owner]) # Pages @@ -55,10 +54,6 @@ defmodule Core.Authorization do grant_access(Systems.Pool.SubmissionPage, [:researcher]) grant_access(Systems.Pool.ParticipantPage, [:researcher]) grant_access(Systems.Test.Page, [:visitor, :member]) - grant_access(Systems.DataDonation.Content, [:owner, :coordinator]) - grant_access(Systems.DataDonation.FlowPage, [:visitor, :member]) - grant_access(Systems.DataDonation.PortPage, [:visitor, :member]) - grant_access(Systems.DataDonation.OverviewPage, [:member]) grant_access(Systems.Project.OverviewPage, [:researcher]) grant_access(Systems.Project.NodePage, [:researcher, :owner]) grant_access(Systems.Benchmark.ContentPage, [:researcher, :owner]) diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po index b9543534c..95fa63bbf 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po @@ -85,3 +85,11 @@ msgstr "Continue" #, elixir-autogen, elixir-format, fuzzy msgid "onboarding.consent.title" msgstr "Consent" + +#, elixir-autogen, elixir-format, fuzzy +msgid "settings.title" +msgstr "Settings" + +#, elixir-autogen, elixir-format +msgid "settings.data_storage.title" +msgstr "Participant data storage" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po b/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po index 53bcc4717..fe9fde1b4 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po @@ -349,3 +349,19 @@ msgstr "Samsung" #, elixir-autogen, elixir-format, fuzzy msgid "platforms.tiktok" msgstr "TikTok" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.azure" +msgstr "Azure Blob" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.centerdata" +msgstr "Centerdata" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.yoda" +msgstr "Yoda" + +#, elixir-autogen, elixir-format, fuzzy +msgid "storage_backend_types.aws" +msgstr "Amazon S3" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-project.po b/core/priv/gettext/en/LC_MESSAGES/eyra-project.po index 33c4142d9..829cac06b 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-project.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-project.po @@ -182,3 +182,11 @@ msgstr "Consent" #, elixir-autogen, elixir-format, fuzzy msgid "tabbar.item.gdpr.forward" msgstr "Go to Consent" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.settings" +msgstr "Settings" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.settings.forward" +msgstr "Go to Settings" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-storage.po b/core/priv/gettext/en/LC_MESSAGES/eyra-storage.po new file mode 100644 index 000000000..b5cc83891 --- /dev/null +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-storage.po @@ -0,0 +1,64 @@ +## "msgid"s in this file come from POT (.pot) files. +### +### Do not add, change, or remove "msgid"s manually here as +### they're tied to the ones in the corresponding POT file +### (with the same domain). +### +### Use "mix gettext.extract --merge" or "mix gettext.merge" +### to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#, elixir-autogen, elixir-format +msgid "endpoint_form.type.label" +msgstr "Choose a storage service" + +#, elixir-autogen, elixir-format +msgid "aws.access_key_id.label" +msgstr "Access key id" + +#, elixir-autogen, elixir-format +msgid "aws.region_code.label" +msgstr "Region code" + +#, elixir-autogen, elixir-format +msgid "aws.s3_bucket_name.label" +msgstr "Bucket name" + +#, elixir-autogen, elixir-format +msgid "aws.secret_access_key.label" +msgstr "Secret access key" + +#, elixir-autogen, elixir-format +msgid "azure.account_name.label" +msgstr "Account name" + +#, elixir-autogen, elixir-format +msgid "azure.container.label" +msgstr "Container" + +#, elixir-autogen, elixir-format +msgid "azure.sas_token.label" +msgstr "SAS token" + +#, elixir-autogen, elixir-format +msgid "centerdata.url.label" +msgstr "URL" + +#, elixir-autogen, elixir-format +msgid "centerdata.url.placeholder" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "yoda.password.label" +msgstr "Password" + +#, elixir-autogen, elixir-format +msgid "yoda.url.label" +msgstr "Portal" + +#, elixir-autogen, elixir-format +msgid "yoda.user.label" +msgstr "Email" diff --git a/core/priv/gettext/eyra-assignment.pot b/core/priv/gettext/eyra-assignment.pot index 4e34ae8f7..ded56e9fa 100644 --- a/core/priv/gettext/eyra-assignment.pot +++ b/core/priv/gettext/eyra-assignment.pot @@ -85,3 +85,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "onboarding.consent.title" msgstr "" + +#, elixir-autogen, elixir-format +msgid "settings.title" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "settings.data_storage.title" +msgstr "" diff --git a/core/priv/gettext/eyra-enums.pot b/core/priv/gettext/eyra-enums.pot index 34a208b63..3db21b754 100644 --- a/core/priv/gettext/eyra-enums.pot +++ b/core/priv/gettext/eyra-enums.pot @@ -349,3 +349,19 @@ msgstr "" #, elixir-autogen, elixir-format msgid "platforms.tiktok" msgstr "" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.azure" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.centerdata" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.yoda" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.aws" +msgstr "" diff --git a/core/priv/gettext/eyra-project.pot b/core/priv/gettext/eyra-project.pot index 5e68a8ae9..a12b9b690 100644 --- a/core/priv/gettext/eyra-project.pot +++ b/core/priv/gettext/eyra-project.pot @@ -182,3 +182,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "tabbar.item.gdpr.forward" msgstr "" + +#, elixir-autogen, elixir-format +msgid "tabbar.item.settings" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "tabbar.item.settings.forward" +msgstr "" diff --git a/core/priv/gettext/eyra-storage.pot b/core/priv/gettext/eyra-storage.pot new file mode 100644 index 000000000..94ec49e94 --- /dev/null +++ b/core/priv/gettext/eyra-storage.pot @@ -0,0 +1,64 @@ +## This file is a PO Template file. +## +## "msgid"s here are often extracted from source code. +## Add new messages manually only if they're dynamic +## messages that can't be statically extracted. +## +## Run "mix gettext.extract" to bring this file up to +## date. Leave "msgstr"s empty as changing them here has no +## effect: edit them in PO (.po) files instead. +# +msgid "" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "endpoint_form.type.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "aws.access_key_id.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "aws.region_code.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "aws.s3_bucket_name.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "aws.secret_access_key.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "azure.account_name.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "azure.container.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "azure.sas_token.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "centerdata.url.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "centerdata.url.placeholder" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "yoda.password.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "yoda.url.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "yoda.user.label" +msgstr "" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po index 6e5e08577..ad14bddd0 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po @@ -85,3 +85,11 @@ msgstr "Continue" #, elixir-autogen, elixir-format, fuzzy msgid "onboarding.consent.title" msgstr "Consent" + +#, elixir-autogen, elixir-format, fuzzy +msgid "settings.title" +msgstr "Instellingen" + +#, elixir-autogen, elixir-format +msgid "settings.data_storage.title" +msgstr "Opslag van deelnemers data" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po index 714599c21..2d33d5d9e 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po @@ -349,3 +349,19 @@ msgstr "Samsung" #, elixir-autogen, elixir-format, fuzzy msgid "platforms.tiktok" msgstr "TikTok" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.azure" +msgstr "Azure Blob" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.centerdata" +msgstr "Centerdata" + +#, elixir-autogen, elixir-format +msgid "storage_backend_types.yoda" +msgstr "Yoda" + +#, elixir-autogen, elixir-format, fuzzy +msgid "storage_backend_types.aws" +msgstr "Amazon S3" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po index 4f5360b74..932a07b11 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po @@ -182,3 +182,11 @@ msgstr "Consent" #, elixir-autogen, elixir-format, fuzzy msgid "tabbar.item.gdpr.forward" msgstr "Ga naar Consent" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.settings" +msgstr "Instellingen" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.settings.forward" +msgstr "Ga naar Instellingen" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po new file mode 100644 index 000000000..d1a8ad45a --- /dev/null +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po @@ -0,0 +1,64 @@ +## "msgid"s in this file come from POT (.pot) files. +### +### Do not add, change, or remove "msgid"s manually here as +### they're tied to the ones in the corresponding POT file +### (with the same domain). +### +### Use "mix gettext.extract --merge" or "mix gettext.merge" +### to merge POT files into PO files. +msgid "" +msgstr "" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#, elixir-autogen, elixir-format +msgid "endpoint_form.type.label" +msgstr "Kies een van de opslag diensten" + +#, elixir-autogen, elixir-format +msgid "aws.access_key_id.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "aws.region_code.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "aws.s3_bucket_name.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "aws.secret_access_key.label" +msgstr "Secret access key" + +#, elixir-autogen, elixir-format +msgid "azure.account_name.label" +msgstr "Account name" + +#, elixir-autogen, elixir-format +msgid "azure.container.label" +msgstr "Container" + +#, elixir-autogen, elixir-format +msgid "azure.sas_token.label" +msgstr "SAS token" + +#, elixir-autogen, elixir-format +msgid "centerdata.url.label" +msgstr "URL" + +#, elixir-autogen, elixir-format +msgid "centerdata.url.placeholder" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "yoda.password.label" +msgstr "Wachtwoord" + +#, elixir-autogen, elixir-format +msgid "yoda.url.label" +msgstr "Portal URL" + +#, elixir-autogen, elixir-format +msgid "yoda.user.label" +msgstr "E-mail" diff --git a/core/priv/repo/migrations/20231025125051_add_storage.exs b/core/priv/repo/migrations/20231025125051_add_storage.exs new file mode 100644 index 000000000..bbeb491eb --- /dev/null +++ b/core/priv/repo/migrations/20231025125051_add_storage.exs @@ -0,0 +1,77 @@ +defmodule Core.Repo.Migrations.AddStorage do + use Ecto.Migration + + def up do + create table(:storage_endpoints_aws) do + add(:access_key_id, :string) + add(:secret_access_key, :string) + add(:s3_bucket_name, :string) + add(:region_code, :string) + + timestamps() + end + + create table(:storage_endpoints_azure) do + add(:account_name, :string) + add(:container, :string) + add(:sas_token, :string) + + timestamps() + end + + create table(:storage_endpoints_centerdata) do + add(:url, :string) + + timestamps() + end + + create table(:storage_endpoints_yoda) do + add(:url, :string) + add(:user, :string) + add(:password, :string) + + timestamps() + end + + create table(:storage_endpoints) do + add(:aws_id, references(:storage_endpoints_aws, on_delete: :nilify_all), null: true) + add(:azure_id, references(:storage_endpoints_azure, on_delete: :nilify_all), null: true) + + add(:centerdata_id, references(:storage_endpoints_centerdata, on_delete: :nilify_all), + null: true + ) + + add(:yoda_id, references(:storage_endpoints_yoda, on_delete: :nilify_all), null: true) + timestamps() + end + + create( + constraint(:storage_endpoints, :must_have_at_most_one_special, + check: """ + 2 > CASE WHEN aws_id IS NULL THEN 0 ELSE 1 END + + CASE WHEN azure_id IS NULL THEN 0 ELSE 1 END + + CASE WHEN centerdata_id IS NULL THEN 0 ELSE 1 END + + CASE WHEN yoda_id IS NULL THEN 0 ELSE 1 END + """ + ) + ) + + alter table(:assignments) do + add(:storage_endpoint_id, references(:storage_endpoints, on_delete: :nilify_all), null: true) + end + end + + def down do + alter table(:assignments) do + remove(:storage_endpoint_id) + end + + drop(constraint(:storage_endpoints, :must_have_at_most_one_special)) + + drop(table(:storage_endpoints)) + drop(table(:storage_endpoints_yoda)) + drop(table(:storage_endpoints_centerdata)) + drop(table(:storage_endpoints_azure)) + drop(table(:storage_endpoints_aws)) + end +end diff --git a/core/systems/assignment/_public.ex b/core/systems/assignment/_public.ex index 9980addc2..07c4b1fcc 100644 --- a/core/systems/assignment/_public.ex +++ b/core/systems/assignment/_public.ex @@ -19,7 +19,8 @@ defmodule Systems.Assignment.Public do Consent, Budget, Workflow, - Crew + Crew, + Storage } @min_expiration_timeout 30 @@ -172,6 +173,35 @@ defmodule Systems.Assignment.Public do |> Ecto.Changeset.put_assoc(:tool_ref, tool_ref) end + def delete_storage_endpoint!(%{storage_endpoint_id: nil} = assignment) do + assignment + end + + def delete_storage_endpoint!(assignment) do + {:ok, assignment} = + Assignment.Model.changeset(assignment, %{}) + |> Ecto.Changeset.put_assoc(:storage_endpoint, nil) + |> Repo.update() + + assignment + end + + def create_storage_endpoint!(%{storage_endpoint_id: nil} = assignment) do + storage_endpoint = + %Storage.EndpointModel{} + |> Storage.EndpointModel.changeset(%{}) + + {:ok, assignment} = + Assignment.Model.changeset(assignment, %{}) + |> Ecto.Changeset.put_assoc(:storage_endpoint, storage_endpoint) + |> Repo.update() + + assignment + |> Repo.preload(Assignment.Model.preload_graph(:down)) + end + + def create_storage_endpoint!(assignment), do: assignment + def copy( %Assignment.Model{} = assignment, %Assignment.InfoModel{} = info, diff --git a/core/systems/assignment/content_page_builder.ex b/core/systems/assignment/content_page_builder.ex index bea04e624..85773bf0c 100644 --- a/core/systems/assignment/content_page_builder.ex +++ b/core/systems/assignment/content_page_builder.ex @@ -159,22 +159,22 @@ defmodule Systems.Assignment.ContentPageBuilder do defp create_tab( :config, - %{info: info}, + assignment, show_errors, _assigns ) do ready? = false %{ - id: :config_form, + id: :settings_form, ready: ready?, show_errors: show_errors, - title: dgettext("eyra-project", "tabbar.item.config"), - forward_title: dgettext("eyra-project", "tabbar.item.config.forward"), + title: dgettext("eyra-project", "tabbar.item.settings"), + forward_title: dgettext("eyra-project", "tabbar.item.settings.forward"), type: :fullpage, - live_component: Assignment.InfoForm, + live_component: Assignment.SettingsView, props: %{ - entity: info + entity: assignment } } end diff --git a/core/systems/assignment/model.ex b/core/systems/assignment/model.ex index f5c588313..b1ea7a0e1 100644 --- a/core/systems/assignment/model.ex +++ b/core/systems/assignment/model.ex @@ -11,7 +11,8 @@ defmodule Systems.Assignment.Model do Assignment, Workflow, Budget, - Consent + Consent, + Storage } schema "assignments" do @@ -20,6 +21,7 @@ defmodule Systems.Assignment.Model do belongs_to(:consent_agreement, Consent.AgreementModel) belongs_to(:info, Assignment.InfoModel) + belongs_to(:storage_endpoint, Storage.EndpointModel, on_replace: :delete) belongs_to(:workflow, Workflow.Model) belongs_to(:crew, Systems.Crew.Model) belongs_to(:budget, Budget.Model, on_replace: :update) @@ -65,7 +67,17 @@ defmodule Systems.Assignment.Model do def flatten(assignment) do assignment - |> Map.take([:id, :consent_agreement, :info, :workflow, :crew, :budget, :excluded, :director]) + |> Map.take([ + :id, + :consent_agreement, + :info, + :storage_endpoint, + :workflow, + :crew, + :budget, + :excluded, + :director + ]) |> Map.put(:tool, tool(assignment)) end @@ -81,6 +93,7 @@ defmodule Systems.Assignment.Model do :excluded, consent_agreement: [:revisions], info: [], + storage_endpoint: Storage.EndpointModel.preload_graph(:down), crew: [:tasks, :members, :auth_node], workflow: Workflow.Model.preload_graph(:down), budget: [:currency, :fund, :reserve], diff --git a/core/systems/assignment/settings_view.ex b/core/systems/assignment/settings_view.ex new file mode 100644 index 000000000..a63041c37 --- /dev/null +++ b/core/systems/assignment/settings_view.ex @@ -0,0 +1,110 @@ +defmodule Systems.Assignment.SettingsView do + use CoreWeb, :live_component + + alias Frameworks.Pixel.Selector + + alias Systems.{ + Assignment, + Storage + } + + # Handle Selector Update + @impl true + def update( + %{active_item_id: active_item_id, selector_id: :enable_storage_selector}, + socket + ) do + { + :ok, + socket + |> update_storage_endpoint(active_item_id) + |> update_enable_storage_selector() + |> update_storage_endpoint_form() + } + end + + @impl true + def update(%{id: id, entity: assignment}, socket) do + { + :ok, + socket + |> assign( + id: id, + entity: assignment + ) + |> update_enable_storage_selector() + |> update_storage_endpoint_form() + } + end + + def update_storage_endpoint(%{assigns: %{entity: assignment}} = socket, enabled) do + assignment = + if enabled == :no do + Assignment.Public.delete_storage_endpoint!(assignment) + else + Assignment.Public.create_storage_endpoint!(assignment) + end + + socket + |> assign(entity: assignment) + |> update_enable_storage_selector() + |> update_storage_endpoint_form() + end + + def update_enable_storage_selector( + %{assigns: %{id: id, entity: %{storage_endpoint_id: storage_endpoint_id}}} = socket + ) do + enabled = storage_endpoint_id != nil + + labels = [ + %{id: :no, value: "No, disable", active: not enabled}, + %{id: :yes, value: "Yes, enable", active: enabled} + ] + + enable_storage_selector = %{ + module: Selector, + id: :enable_storage_selector, + grid_options: "flex flex-row gap-8", + items: labels, + type: :radio, + parent: %{type: __MODULE__, id: id} + } + + assign(socket, enable_storage_selector: enable_storage_selector) + end + + def update_storage_endpoint_form(%{assigns: %{entity: %{storage_endpoint_id: nil}}} = socket) do + assign(socket, storage_form: nil) + end + + def update_storage_endpoint_form( + %{assigns: %{entity: %{storage_endpoint: storage_endpoint}}} = socket + ) do + storage_form = %{ + id: :storage_endpoint_revision, + module: Storage.EndpointForm, + entity: storage_endpoint + } + + assign(socket, storage_form: storage_form) + end + + @impl true + def render(assigns) do + ~H""" +
+ + + <%= dgettext("eyra-assignment", "settings.title") %> + <%= dgettext("eyra-assignment", "settings.data_storage.title") %> + <.spacing value="XS" /> + <.live_component {@enable_storage_selector} /> + <%= if @storage_form do %> + <.spacing value="M" /> + <.live_component {@storage_form} /> + <% end %> + +
+ """ + end +end diff --git a/core/systems/budget/form.ex b/core/systems/budget/form.ex index 291624892..38f2f9faa 100644 --- a/core/systems/budget/form.ex +++ b/core/systems/budget/form.ex @@ -5,7 +5,7 @@ defmodule Systems.Budget.Form do import Frameworks.Pixel.Form alias Frameworks.Utility.EctoHelper - alias Frameworks.Pixel.Dropdown + alias Frameworks.Pixel.DropdownSelector alias Frameworks.Pixel.Text alias Systems.{ @@ -239,7 +239,7 @@ defmodule Systems.Budget.Form do <%= dgettext("eyra-budget", "budget.currency.label") %> <.spacing value="XXS" /> - <.live_component module={Dropdown.Selector} {@currency_selector} /> + <.live_component module={DropdownSelector} {@currency_selector} /> <% end %> <.spacing value="M" /> diff --git a/core/systems/campaign/builders/assignment_landing_page.ex b/core/systems/campaign/builders/assignment_landing_page.ex index be6047d68..62a084c70 100644 --- a/core/systems/campaign/builders/assignment_landing_page.ex +++ b/core/systems/campaign/builders/assignment_landing_page.ex @@ -6,7 +6,7 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do import Frameworks.Utility.LiveCommand, only: [live_command: 2] import Frameworks.Utility.List - alias Frameworks.Pixel.Dropdown + alias Frameworks.Pixel.DropdownSelector alias Systems.{ Campaign, @@ -290,7 +290,7 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do def handle_submit(_, %{assigns: %{selected_time_slot: nil}} = socket) do warning = dgettext("link-lab", "submit.warning.no.selection") - Phoenix.LiveView.send_update(Dropdown.Selector, + Phoenix.LiveView.send_update(DropdownSelector, id: :dropdown_selector, model: %{warning: warning} ) diff --git a/core/systems/citizen/pool/form.ex b/core/systems/citizen/pool/form.ex index c743cc160..9084b2782 100644 --- a/core/systems/citizen/pool/form.ex +++ b/core/systems/citizen/pool/form.ex @@ -5,7 +5,7 @@ defmodule Systems.Citizen.Pool.Form do import Frameworks.Pixel.Form alias Frameworks.Utility.EctoHelper - alias Frameworks.Pixel.Dropdown + alias Frameworks.Pixel.DropdownSelector alias Frameworks.Pixel.Text alias Systems.{ @@ -244,7 +244,7 @@ defmodule Systems.Citizen.Pool.Form do <%= dgettext("link-citizen", "pool.currency.label") %> <.spacing value="XXS" /> - <.live_component module={Dropdown.Selector} {@currency_selector} /> + <.live_component module={DropdownSelector} {@currency_selector} /> <% end %> <.spacing value="M" /> diff --git a/core/systems/data_donation/_public.ex b/core/systems/data_donation/_public.ex deleted file mode 100644 index e695e3fe8..000000000 --- a/core/systems/data_donation/_public.ex +++ /dev/null @@ -1,2 +0,0 @@ -defmodule Systems.DataDonation.Public do -end diff --git a/core/systems/data_donation/_routes.ex b/core/systems/data_donation/_routes.ex deleted file mode 100644 index a151f04f2..000000000 --- a/core/systems/data_donation/_routes.ex +++ /dev/null @@ -1,16 +0,0 @@ -defmodule Systems.DataDonation.Routes do - defmacro routes() do - quote do - scope "/data-donation", Systems.DataDonation do - pipe_through([:browser]) - get("/centerdata/:id", CenterdataController, :create) - live("/centerdata/fakeapi/page", CenterdataFakeApiPage) - end - - scope "/data-donation", Systems.DataDonation do - pipe_through([:browser_unprotected]) - post("/centerdata/:id", CenterdataController, :create) - end - end - end -end diff --git a/core/systems/data_donation/centerdata/centerdata_fakeapi_controller.ex b/core/systems/data_donation/centerdata/centerdata_fakeapi_controller.ex deleted file mode 100644 index 88a4bbb15..000000000 --- a/core/systems/data_donation/centerdata/centerdata_fakeapi_controller.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule Systems.DataDonation.CenterdataFakeApiController do - use CoreWeb, :controller - - def create( - conn, - params - ) do - path = Routes.live_path(conn, Systems.DataDonation.CenterdataFakeApiPage, params: params) - redirect(conn, to: path) - end -end diff --git a/core/systems/data_donation/centerdata/centerdata_form.ex b/core/systems/data_donation/centerdata/centerdata_form.ex deleted file mode 100644 index 967d6bdc8..000000000 --- a/core/systems/data_donation/centerdata/centerdata_form.ex +++ /dev/null @@ -1,29 +0,0 @@ -defmodule Systems.DataDonation.CenterdataForm do - use CoreWeb, :html - - attr(:session, :any, required: true) - attr(:storage_info, :any, required: true) - slot(:inner_block, required: true) - - def centerdata_form(assigns) do - ~H""" - - - """ - end -end diff --git a/core/systems/data_donation/storage/fake_form.ex b/core/systems/data_donation/storage/fake_form.ex deleted file mode 100644 index 5c9608926..000000000 --- a/core/systems/data_donation/storage/fake_form.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Systems.DataDonation.FakeForm do - use CoreWeb, :html - - slot(:inner_block, required: true) - - def fake_form(assigns) do - ~H""" - - - """ - end -end diff --git a/core/systems/data_donation/storage/fake_storage_backend.ex b/core/systems/data_donation/storage/fake_storage_backend.ex deleted file mode 100644 index eaa1ad594..000000000 --- a/core/systems/data_donation/storage/fake_storage_backend.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Systems.DataDonation.FakeStorageBackend do - @behaviour Systems.DataDonation.StorageBackend - - def store(_state, _vm, data) do - IO.puts("fake store: #{data}") - :ok - end -end diff --git a/core/systems/data_donation/storage/s3_form.ex b/core/systems/data_donation/storage/s3_form.ex deleted file mode 100644 index 6f3568290..000000000 --- a/core/systems/data_donation/storage/s3_form.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Systems.DataDonation.S3Form do - use CoreWeb, :html - - slot(:inner_block, required: true) - - def s3_form(assigns) do - ~H""" - - - """ - end -end diff --git a/core/systems/lab/task_view.ex b/core/systems/lab/task_view.ex index 90f914d7b..9d5bdb4cb 100644 --- a/core/systems/lab/task_view.ex +++ b/core/systems/lab/task_view.ex @@ -4,7 +4,7 @@ defmodule Systems.Lab.TaskView do import CoreWeb.UI.Navigation, only: [button_bar: 1] alias Frameworks.Utility.LiveCommand alias Frameworks.Pixel.Text - alias Frameworks.Pixel.Dropdown + alias Frameworks.Pixel.DropdownSelector alias Systems.{ Lab @@ -153,7 +153,7 @@ defmodule Systems.Lab.TaskView do defp update_selector(%{assigns: %{time_slots: time_slots}} = socket) do options = time_slots |> Enum.map(&to_option(&1)) - send_update(Dropdown.Selector, id: :dropdown_selector, model: %{options: options}) + send_update(DropdownSelector, id: :dropdown_selector, model: %{options: options}) socket end @@ -232,7 +232,7 @@ defmodule Systems.Lab.TaskView do <.spacing value="M" /> <%= dgettext("link-lab", "timeslot.selector.label") %> <.spacing value="XXS" /> - <.live_component module={Dropdown.Selector} {@selector} /> + <.live_component module={DropdownSelector} {@selector} /> <% end %> <% end %> diff --git a/core/systems/storage/_assembly.ex b/core/systems/storage/_assembly.ex new file mode 100644 index 000000000..765971363 --- /dev/null +++ b/core/systems/storage/_assembly.ex @@ -0,0 +1,37 @@ +defmodule Systems.Storage.Assembly do + alias Core.Repo + + alias Systems.Storage + alias Systems.Storage.AWS + alias Systems.Storage.Azure + alias Systems.Storage.Centerdata + alias Systems.Storage.Yoda + + def prepare_endpoint_special(nil), do: nil + + def prepare_endpoint_special(:aws) do + %AWS.EndpointModel{} + |> AWS.EndpointModel.changeset(%{}) + end + + def prepare_endpoint_special(:azure) do + %Azure.EndpointModel{} + |> Azure.EndpointModel.changeset(%{}) + end + + def prepare_endpoint_special(:centerdata) do + %Centerdata.EndpointModel{} + |> Centerdata.EndpointModel.changeset(%{}) + end + + def prepare_endpoint_special(:yoda) do + %Yoda.EndpointModel{} + |> Yoda.EndpointModel.changeset(%{}) + end + + def delete_endpoint_special(endpoint) do + if special = Storage.EndpointModel.special(endpoint) do + Repo.delete(special) + end + end +end diff --git a/core/systems/storage/_public.ex b/core/systems/storage/_public.ex new file mode 100644 index 000000000..aa339279e --- /dev/null +++ b/core/systems/storage/_public.ex @@ -0,0 +1,11 @@ +defmodule Systems.Storage.Public do + alias Systems.{ + Storage + } + + def deliver(%Storage.EndpointModel{} = _endpoint, data) when is_binary(data) do + %{endpoint: %{type: :aws}, data: data} + |> Storage.Delivery.new() + |> Oban.insert() + end +end diff --git a/core/systems/data_donation/storage/s3_storage_backend.ex b/core/systems/storage/aws/backend.ex similarity index 79% rename from core/systems/data_donation/storage/s3_storage_backend.ex rename to core/systems/storage/aws/backend.ex index d0a13b54f..51ebeffb5 100644 --- a/core/systems/data_donation/storage/s3_storage_backend.ex +++ b/core/systems/storage/aws/backend.ex @@ -1,5 +1,5 @@ -defmodule Systems.DataDonation.S3StorageBackend do - @behaviour Systems.DataDonation.StorageBackend +defmodule Systems.Storage.AWS.Backend do + @behaviour Systems.Storage.Backend alias ExAws.S3 @@ -15,7 +15,7 @@ defmodule Systems.DataDonation.S3StorageBackend do defp bucket do :core - |> Application.fetch_env!(:s3) + |> Application.fetch_env!(:aws) |> Keyword.fetch!(:bucket) end diff --git a/core/systems/storage/aws/endpoint_form.ex b/core/systems/storage/aws/endpoint_form.ex new file mode 100644 index 000000000..418152e6d --- /dev/null +++ b/core/systems/storage/aws/endpoint_form.ex @@ -0,0 +1,64 @@ +defmodule Systems.Storage.AWS.EndpointForm do + use CoreWeb.LiveForm + + alias Systems.Storage.AWS.EndpointModel + + # Handle initial update + @impl true + def update( + %{id: id, entity: endpoint}, + socket + ) do + changeset = EndpointModel.changeset(endpoint, %{}) + + { + :ok, + socket + |> assign( + id: id, + entity: endpoint, + changeset: changeset + ) + } + end + + # Handle Events + @impl true + def handle_event("save", %{"endpoint_model" => attrs}, socket) do + { + :noreply, + socket + |> save_entity(attrs) + } + end + + # Saving + def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do + changeset = EndpointModel.changeset(entity, attrs) + + socket + |> save(changeset) + |> validate(changeset) + end + + def validate(socket, changeset) do + changeset = EndpointModel.validate(changeset) + + socket + |> assign(changeset: changeset) + end + + @impl true + def render(assigns) do + ~H""" +
+ <.form id={"#{@id}_aws_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > + <.text_input form={form} field={:access_key_id} label_text={dgettext("eyra-storage", "aws.access_key_id.label")} /> + <.password_input form={form} field={:secret_access_key} label_text={dgettext("eyra-storage", "aws.secret_access_key.label")} /> + <.text_input form={form} field={:s3_bucket_name} label_text={dgettext("eyra-storage", "aws.s3_bucket_name.label")} /> + <.text_input form={form} field={:region_code} label_text={dgettext("eyra-storage", "aws.region_code.label")} /> + +
+ """ + end +end diff --git a/core/systems/storage/aws/endpoint_model.ex b/core/systems/storage/aws/endpoint_model.ex new file mode 100644 index 000000000..c6be57792 --- /dev/null +++ b/core/systems/storage/aws/endpoint_model.ex @@ -0,0 +1,44 @@ +defmodule Systems.Storage.AWS.EndpointModel do + use Ecto.Schema + use Frameworks.Utility.Schema + + import Ecto.Changeset + + schema "storage_endpoints_aws" do + field(:access_key_id, :string) + field(:secret_access_key, :string) + field(:s3_bucket_name, :string) + field(:region_code, :string) + + timestamps() + end + + @fields ~w(access_key_id secret_access_key s3_bucket_name region_code)a + @required_fields @fields + + def changeset(model, params) do + model + |> cast(params, @fields) + end + + def validate(changeset) do + changeset + |> validate_required(@required_fields) + end + + def ready?(tool) do + changeset = + changeset(tool, %{}) + |> validate() + + changeset.valid?() + end + + def preload_graph(:down), do: [] + + defimpl Frameworks.Concept.ContentModel do + alias Systems.Storage.AWS + def form(_), do: AWS.EndpointForm + def ready?(endpoint), do: AWS.EndpointModel.ready?(endpoint) + end +end diff --git a/core/systems/data_donation/storage/azure_storage_backend.ex b/core/systems/storage/azure/backend.ex similarity index 90% rename from core/systems/data_donation/storage/azure_storage_backend.ex rename to core/systems/storage/azure/backend.ex index faf1a425e..c1a065757 100644 --- a/core/systems/data_donation/storage/azure_storage_backend.ex +++ b/core/systems/storage/azure/backend.ex @@ -1,5 +1,5 @@ -defmodule Systems.DataDonation.AzureStorageBackend do - @behaviour Systems.DataDonation.StorageBackend +defmodule Systems.Storage.Azure.Backend do + @behaviour Systems.Storage.Backend require Logger @@ -32,7 +32,7 @@ defmodule Systems.DataDonation.AzureStorageBackend do end {:error, error} -> - Logger.error("[AzureStorageBackend] #{error}") + Logger.error("[Azure.Backend] #{error}") {:error, error} end end diff --git a/core/systems/storage/azure/endpoint_form.ex b/core/systems/storage/azure/endpoint_form.ex new file mode 100644 index 000000000..d1374ef1d --- /dev/null +++ b/core/systems/storage/azure/endpoint_form.ex @@ -0,0 +1,63 @@ +defmodule Systems.Storage.Azure.EndpointForm do + use CoreWeb.LiveForm + + alias Systems.Storage.Azure.EndpointModel + + # Handle initial update + @impl true + def update( + %{id: id, entity: endpoint}, + socket + ) do + changeset = EndpointModel.changeset(endpoint, %{}) + + { + :ok, + socket + |> assign( + id: id, + entity: endpoint, + changeset: changeset + ) + } + end + + # Handle Events + @impl true + def handle_event("save", %{"endpoint_model" => attrs}, socket) do + { + :noreply, + socket + |> save_entity(attrs) + } + end + + # Saving + def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do + changeset = EndpointModel.changeset(entity, attrs) + + socket + |> save(changeset) + |> validate(changeset) + end + + def validate(socket, changeset) do + changeset = EndpointModel.validate(changeset) + + socket + |> assign(changeset: changeset) + end + + @impl true + def render(assigns) do + ~H""" +
+ <.form id={"#{@id}_azure_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > + <.text_input form={form} field={:account_name} label_text={dgettext("eyra-storage", "azure.account_name.label")} /> + <.text_input form={form} field={:container} label_text={dgettext("eyra-storage", "azure.container.label")} /> + <.text_input form={form} field={:sas_token} label_text={dgettext("eyra-storage", "azure.sas_token.label")} /> + +
+ """ + end +end diff --git a/core/systems/storage/azure/endpoint_model.ex b/core/systems/storage/azure/endpoint_model.ex new file mode 100644 index 000000000..384e1dfa8 --- /dev/null +++ b/core/systems/storage/azure/endpoint_model.ex @@ -0,0 +1,43 @@ +defmodule Systems.Storage.Azure.EndpointModel do + use Ecto.Schema + use Frameworks.Utility.Schema + + import Ecto.Changeset + + schema "storage_endpoints_azure" do + field(:account_name, :string) + field(:container, :string) + field(:sas_token, :string) + + timestamps() + end + + @fields ~w(account_name container sas_token)a + @required_fields @fields + + def changeset(model, params) do + model + |> cast(params, @fields) + end + + def validate(changeset) do + changeset + |> validate_required(@required_fields) + end + + def ready?(tool) do + changeset = + changeset(tool, %{}) + |> validate() + + changeset.valid?() + end + + def preload_graph(:down), do: [] + + defimpl Frameworks.Concept.ContentModel do + alias Systems.Storage.Azure + def form(_), do: Azure.EndpointForm + def ready?(endpoint), do: Azure.EndpointModel.ready?(endpoint) + end +end diff --git a/core/systems/data_donation/storage/storage_backend.ex b/core/systems/storage/backend.ex similarity index 73% rename from core/systems/data_donation/storage/storage_backend.ex rename to core/systems/storage/backend.ex index 5678f035d..6ca300cc7 100644 --- a/core/systems/data_donation/storage/storage_backend.ex +++ b/core/systems/storage/backend.ex @@ -1,4 +1,4 @@ -defmodule Systems.DataDonation.StorageBackend do +defmodule Systems.Storage.Backend do @callback store( session :: map(), vm :: map(), diff --git a/core/systems/storage/backend_types.ex b/core/systems/storage/backend_types.ex new file mode 100644 index 000000000..80f17a0a4 --- /dev/null +++ b/core/systems/storage/backend_types.ex @@ -0,0 +1,7 @@ +defmodule Systems.Storage.BackendTypes do + @moduledoc """ + Defines types of external data storage services + """ + use Core.Enums.Base, + {:storage_backend_types, [:aws, :azure, :centerdata, :yoda]} +end diff --git a/core/systems/data_donation/centerdata/centerdata_storage_backend.ex b/core/systems/storage/centerdata/backend.ex similarity index 86% rename from core/systems/data_donation/centerdata/centerdata_storage_backend.ex rename to core/systems/storage/centerdata/backend.ex index b4364b489..964b4cd60 100644 --- a/core/systems/data_donation/centerdata/centerdata_storage_backend.ex +++ b/core/systems/storage/centerdata/backend.ex @@ -1,5 +1,5 @@ -defmodule Systems.DataDonation.CenterdataStorageBackend do - @behaviour Systems.DataDonation.StorageBackend +defmodule Systems.Storage.Centerdata.Backend do + @behaviour Systems.Storage.Backend require Logger @@ -36,7 +36,7 @@ defmodule Systems.DataDonation.CenterdataStorageBackend do Application.get_env( :core, :data_donation_http_client, - Systems.DataDonation.CenterdataHTTPClient + Systems.Union.Centerdata.HTTPClient ) end end diff --git a/core/systems/storage/centerdata/endpoint_form.ex b/core/systems/storage/centerdata/endpoint_form.ex new file mode 100644 index 000000000..ee1f84ef5 --- /dev/null +++ b/core/systems/storage/centerdata/endpoint_form.ex @@ -0,0 +1,65 @@ +defmodule Systems.Storage.Centerdata.EndpointForm do + use CoreWeb.LiveForm + + alias Systems.Storage.Centerdata.EndpointModel + + # Handle initial update + @impl true + def update( + %{id: id, entity: endpoint}, + socket + ) do + changeset = EndpointModel.changeset(endpoint, %{}) + + { + :ok, + socket + |> assign( + id: id, + entity: endpoint, + changeset: changeset + ) + } + end + + # Handle Events + @impl true + def handle_event("save", %{"endpoint_model" => attrs}, socket) do + { + :noreply, + socket + |> save_entity(attrs) + } + end + + # Saving + def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do + changeset = EndpointModel.changeset(entity, attrs) + + socket + |> save(changeset) + |> validate(changeset) + end + + def validate(socket, changeset) do + changeset = EndpointModel.validate(changeset) + + socket + |> assign(changeset: changeset) + end + + @impl true + def render(assigns) do + ~H""" +
+ <.form id={"#{@id}_centerdata_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > + <.url_input + form={form} + field={:url} + label_text={dgettext("eyra-storage", "centerdata.url.label")} + /> + +
+ """ + end +end diff --git a/core/systems/storage/centerdata/endpoint_model.ex b/core/systems/storage/centerdata/endpoint_model.ex new file mode 100644 index 000000000..47f4ad874 --- /dev/null +++ b/core/systems/storage/centerdata/endpoint_model.ex @@ -0,0 +1,41 @@ +defmodule Systems.Storage.Centerdata.EndpointModel do + use Ecto.Schema + use Frameworks.Utility.Schema + + import Ecto.Changeset + + schema "storage_endpoints_centerdata" do + field(:url, :string) + + timestamps() + end + + @fields ~w(url)a + @required_fields @fields + + def changeset(model, params) do + model + |> cast(params, @fields) + end + + def validate(changeset) do + changeset + |> validate_required(@required_fields) + end + + def ready?(tool) do + changeset = + changeset(tool, %{}) + |> validate() + + changeset.valid?() + end + + def preload_graph(:down), do: [] + + defimpl Frameworks.Concept.ContentModel do + alias Systems.Storage.Centerdata + def form(_), do: Centerdata.EndpointForm + def ready?(endpoint), do: Centerdata.EndpointModel.ready?(endpoint) + end +end diff --git a/core/systems/data_donation/delivery.ex b/core/systems/storage/delivery.ex similarity index 80% rename from core/systems/data_donation/delivery.ex rename to core/systems/storage/delivery.ex index d42b801cd..49970cf48 100644 --- a/core/systems/data_donation/delivery.ex +++ b/core/systems/storage/delivery.ex @@ -1,4 +1,4 @@ -defmodule Systems.DataDonation.Delivery do +defmodule Systems.Storage.Delivery do defmodule DeliveryError do @moduledoc false defexception [:message] @@ -16,11 +16,11 @@ defmodule Systems.DataDonation.Delivery do def perform(%Oban.Job{args: args}) do case deliver(args) do {:error, error} -> - Logger.error("Data-donation delivery error: #{error}") + Logger.error("Data delivery error: #{error}") {:error, error} _ -> - Logger.debug("Data-donation delivery succeeded") + Logger.debug("Data delivery succeeded") :ok end end @@ -42,7 +42,7 @@ defmodule Systems.DataDonation.Delivery do defp storage(storage_key) do config = config() - case Keyword.get(config, String.to_atom(storage_key)) do + case Keyword.get(config, String.to_existing_atom(storage_key)) do nil -> raise DeliveryError, "Could not deliver donated data, invalid config for #{storage_key}" diff --git a/core/systems/storage/endpoint_form.ex b/core/systems/storage/endpoint_form.ex new file mode 100644 index 000000000..044bc3377 --- /dev/null +++ b/core/systems/storage/endpoint_form.ex @@ -0,0 +1,113 @@ +defmodule Systems.Storage.EndpointForm do + use CoreWeb.LiveForm + + alias Frameworks.Concept + alias Frameworks.Pixel.Selector + + alias Systems.{ + Storage + } + + # Handle Selector Update + @impl true + def update( + %{active_item_id: special_field, selector_id: :storage_type_selector}, + %{assigns: %{entity: endpoint}} = socket + ) do + special = Storage.Assembly.prepare_endpoint_special(special_field) + + changeset = Storage.EndpointModel.reset_special(endpoint, special_field, special) + + { + :ok, + socket + |> save(changeset) + |> update_selected_type() + |> update_special() + |> update_special_form() + } + end + + # Handle initial update + @impl true + def update( + %{id: id, entity: endpoint}, + socket + ) do + changeset = Storage.EndpointModel.changeset(endpoint, %{}) + + { + :ok, + socket + |> assign( + id: id, + entity: endpoint, + changeset: changeset + ) + |> update_selected_type() + |> update_type_selector() + |> update_special() + |> update_special_form() + } + end + + defp update_selected_type(%{assigns: %{entity: entity}} = socket) do + selected_type = Storage.EndpointModel.special_field(entity) + assign(socket, selected_type: selected_type) + end + + defp update_type_selector(%{assigns: %{id: id, selected_type: selected_type}} = socket) do + items = Storage.BackendTypes.labels(selected_type) + + type_selector = %{ + module: Selector, + id: :storage_type_selector, + grid_options: "grid gap-3 grid-cols-2", + items: items, + type: :radio, + parent: %{type: __MODULE__, id: id} + } + + assign(socket, type_selector: type_selector) + end + + defp update_special(%{assigns: %{entity: entity}} = socket) do + special = Storage.EndpointModel.special(entity) + assign(socket, special: special) + end + + defp update_special_form(%{assigns: %{selected_type: nil}} = socket) do + assign(socket, special_form: nil, special_form_title: nil) + end + + defp update_special_form(%{assigns: %{special: special, selected_type: selected_type}} = socket) do + special_form_title = Storage.BackendTypes.translate(selected_type) + + special_form = %{ + module: Concept.ContentModel.form(special), + id: :storage_endpoint_special_form, + entity: special + } + + assign(socket, special_form: special_form, special_form_title: special_form_title) + end + + @impl true + def render(assigns) do + ~H""" +
+ <%= dgettext("eyra-storage", "endpoint_form.type.label") %> + <.spacing value="XS" /> +
+ <.live_component {@type_selector} /> +
+ <.spacing value="L" /> + <%= if @special_form do %> + <%= @special_form_title %> + <.spacing value="XS" /> + <.live_component {@special_form} /> + <% end %> +
+ """ + end +end diff --git a/core/systems/storage/endpoint_model.ex b/core/systems/storage/endpoint_model.ex new file mode 100644 index 000000000..e6b7bafac --- /dev/null +++ b/core/systems/storage/endpoint_model.ex @@ -0,0 +1,95 @@ +defmodule Systems.Storage.EndpointModel do + use Ecto.Schema + use Frameworks.Utility.Schema + + import Ecto.Changeset + + alias Frameworks.Concept + + alias Systems.{ + Storage + } + + require Storage.BackendTypes + + schema "storage_endpoints" do + belongs_to(:aws, Storage.AWS.EndpointModel, on_replace: :delete) + belongs_to(:azure, Storage.Azure.EndpointModel, on_replace: :delete) + belongs_to(:centerdata, Storage.Centerdata.EndpointModel, on_replace: :delete) + belongs_to(:yoda, Storage.Yoda.EndpointModel, on_replace: :delete) + + timestamps() + end + + @fields ~w()a + @required_fields @fields + @special_fields ~w(aws azure centerdata yoda)a + + def changeset(endpoint, params) do + endpoint + |> cast(params, @fields) + end + + def validate(changeset) do + changeset + |> validate_required(@required_fields) + end + + def preload_graph(:down), do: @special_fields + + def reset_special(endpoint, special_field, special) when is_atom(special_field) do + specials = + Enum.map( + @special_fields, + &{&1, + if &1 == special_field do + special + else + nil + end} + ) + + changeset(endpoint, %{}) + |> then( + &Enum.reduce(specials, &1, fn {field, value}, changeset -> + put_assoc(changeset, field, value) + end) + ) + end + + def special(endpoint) do + Enum.reduce(@special_fields, nil, fn field, acc -> + if special = Map.get(endpoint, field) do + special + else + acc + end + end) + end + + def special_field(endpoint) do + Enum.reduce(@special_fields, nil, fn field, acc -> + field_id = String.to_existing_atom("#{field}_id") + + if Map.get(endpoint, field_id) != nil do + field + else + acc + end + end) + end + + def ready?(endpoint) do + if special = special(endpoint) do + Concept.ContentModel.ready?(special) + else + false + end + end + + defimpl Frameworks.Concept.ContentModel do + alias Systems.Storage + def form(_), do: Storage.EndpointForm + def ready?(endpoint), do: Storage.EndpointModel.ready?(endpoint) + end +end diff --git a/core/systems/storage/fake_backend.ex b/core/systems/storage/fake_backend.ex new file mode 100644 index 000000000..bff1109fc --- /dev/null +++ b/core/systems/storage/fake_backend.ex @@ -0,0 +1,8 @@ +defmodule Systems.Storage.FakeBackend do + @behaviour Systems.Storage.Backend + + def store(_state, _vm, data) do + IO.puts("fake store: #{data}") + :ok + end +end diff --git a/core/systems/storage/yoda/backend.ex b/core/systems/storage/yoda/backend.ex new file mode 100644 index 000000000..abdbe669d --- /dev/null +++ b/core/systems/storage/yoda/backend.ex @@ -0,0 +1,2 @@ +defmodule Systems.Storage.Yoda.Backend do +end diff --git a/core/systems/storage/yoda/endpoint_form.ex b/core/systems/storage/yoda/endpoint_form.ex new file mode 100644 index 000000000..adeb6f81c --- /dev/null +++ b/core/systems/storage/yoda/endpoint_form.ex @@ -0,0 +1,63 @@ +defmodule Systems.Storage.Yoda.EndpointForm do + use CoreWeb.LiveForm + + alias Systems.Storage.Yoda.EndpointModel + + # Handle initial update + @impl true + def update( + %{id: id, entity: endpoint}, + socket + ) do + changeset = EndpointModel.changeset(endpoint, %{}) + + { + :ok, + socket + |> assign( + id: id, + entity: endpoint, + changeset: changeset + ) + } + end + + # Handle Events + @impl true + def handle_event("save", %{"endpoint_model" => attrs}, socket) do + { + :noreply, + socket + |> save_entity(attrs) + } + end + + # Saving + def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do + changeset = EndpointModel.changeset(entity, attrs) + + socket + |> save(changeset) + |> validate(changeset) + end + + def validate(socket, changeset) do + changeset = EndpointModel.validate(changeset) + + socket + |> assign(changeset: changeset) + end + + @impl true + def render(assigns) do + ~H""" +
+ <.form id={"#{@id}_yoda_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > + <.text_input form={form} field={:url} label_text={dgettext("eyra-storage", "yoda.url.label")} /> + <.text_input form={form} field={:user} label_text={dgettext("eyra-storage", "yoda.user.label")} /> + <.password_input form={form} field={:password} label_text={dgettext("eyra-storage", "yoda.password.label")} /> + +
+ """ + end +end diff --git a/core/systems/storage/yoda/endpoint_model.ex b/core/systems/storage/yoda/endpoint_model.ex new file mode 100644 index 000000000..2f7e300fc --- /dev/null +++ b/core/systems/storage/yoda/endpoint_model.ex @@ -0,0 +1,43 @@ +defmodule Systems.Storage.Yoda.EndpointModel do + use Ecto.Schema + use Frameworks.Utility.Schema + + import Ecto.Changeset + + schema "storage_endpoints_yoda" do + field(:url, :string) + field(:user, :string) + field(:password, :string) + + timestamps() + end + + @fields ~w(url user password)a + @required_fields @fields + + def changeset(endpoint, params) do + endpoint + |> cast(params, @fields) + end + + def validate(changeset) do + changeset + |> validate_required(@required_fields) + end + + def ready?(endpoint) do + changeset = + changeset(endpoint, %{}) + |> validate() + + changeset.valid?() + end + + def preload_graph(:down), do: [] + + defimpl Frameworks.Concept.ContentModel do + alias Systems.Storage.Yoda + def form(_), do: Yoda.EndpointForm + def ready?(endpoint), do: Yoda.EndpointModel.ready?(endpoint) + end +end diff --git a/core/systems/union/_routes.ex b/core/systems/union/_routes.ex new file mode 100644 index 000000000..6b597481e --- /dev/null +++ b/core/systems/union/_routes.ex @@ -0,0 +1,16 @@ +defmodule Systems.Union.Routes do + defmacro routes() do + quote do + scope "/centerdata", Systems.Union.Centerdata do + pipe_through([:browser]) + get("/:id", Controller, :create) + live("/fakeapi/page", FakeApiPage) + end + + scope "/centerdata", Systems.Union.Centerdata do + pipe_through([:browser_unprotected]) + post("/:id", Controller, :create) + end + end + end +end diff --git a/core/systems/data_donation/centerdata/centerdata_controller.ex b/core/systems/union/centerdata/controller.ex similarity index 96% rename from core/systems/data_donation/centerdata/centerdata_controller.ex rename to core/systems/union/centerdata/controller.ex index b6a32c7f7..26fac244a 100644 --- a/core/systems/data_donation/centerdata/centerdata_controller.ex +++ b/core/systems/union/centerdata/controller.ex @@ -1,4 +1,4 @@ -defmodule Systems.DataDonation.CenterdataController do +defmodule Systems.Union.Centerdata.Controller do use CoreWeb, :controller @supported_locales ~w(nl en) diff --git a/core/systems/union/centerdata/fakeapi_controller.ex b/core/systems/union/centerdata/fakeapi_controller.ex new file mode 100644 index 000000000..5c8537da3 --- /dev/null +++ b/core/systems/union/centerdata/fakeapi_controller.ex @@ -0,0 +1,11 @@ +defmodule Systems.Union.Centerdata.FakeApiController do + use CoreWeb, :controller + + def create( + conn, + params + ) do + path = Routes.live_path(conn, Systems.Union.Centerdata.FakeApiPage, params: params) + redirect(conn, to: path) + end +end diff --git a/core/systems/data_donation/centerdata/centerdata_fakeapi_page.ex b/core/systems/union/centerdata/fakeapi_page.ex similarity index 96% rename from core/systems/data_donation/centerdata/centerdata_fakeapi_page.ex rename to core/systems/union/centerdata/fakeapi_page.ex index 9ac9f8565..6ddfdfac1 100644 --- a/core/systems/data_donation/centerdata/centerdata_fakeapi_page.ex +++ b/core/systems/union/centerdata/fakeapi_page.ex @@ -1,4 +1,4 @@ -defmodule Systems.DataDonation.CenterdataFakeApiPage do +defmodule Systems.Union.Centerdata.FakeApiPage do use CoreWeb, :live_view use CoreWeb.LiveLocale use CoreWeb.LiveAssignHelper diff --git a/core/systems/data_donation/centerdata/centerdata_http_client.ex b/core/systems/union/centerdata/http_client.ex similarity index 80% rename from core/systems/data_donation/centerdata/centerdata_http_client.ex rename to core/systems/union/centerdata/http_client.ex index 8b68cf2fd..816808477 100644 --- a/core/systems/data_donation/centerdata/centerdata_http_client.ex +++ b/core/systems/union/centerdata/http_client.ex @@ -1,4 +1,4 @@ -defmodule Systems.DataDonation.CenterdataHTTPClient do +defmodule Systems.Union.Centerdata.HTTPClient do use HTTPoison.Base @impl HTTPoison.Base diff --git a/core/test/core/data_donation/tool_test.exs b/core/test/core/data_donation/tool_test.exs deleted file mode 100644 index 068cacc5a..000000000 --- a/core/test/core/data_donation/tool_test.exs +++ /dev/null @@ -1,10 +0,0 @@ -defmodule Systems.DataDonation.ToolTest do - use Core.DataCase, async: true - alias Core.Factories - - describe "store_results/2" do - setup do - {:ok, tool: Factories.insert!(:data_donation_tool), user: Factories.insert!(:member)} - end - end -end diff --git a/core/test/test_helper.exs b/core/test/test_helper.exs index 4ed23dca8..33c77e5a6 100644 --- a/core/test/test_helper.exs +++ b/core/test/test_helper.exs @@ -26,11 +26,12 @@ Application.put_env(:core, :banking_backend, Systems.Banking.MockBackend) Mox.defmock(BankingClient.MockClient, for: BankingClient.API) Application.put_env(:core, BankingClient, client: BankingClient.MockClient) -Mox.defmock(Systems.DataDonation.MockStorageBackend, for: Systems.DataDonation.StorageBackend) +Mox.defmock(Systems.Storage.MockBackend, for: Systems.Storage.Backend) Application.put_env( :core, :data_donation_storage_backend, - s3: Systems.DataDonation.MockStorageBackend, - centerdata: Systems.DataDonation.MockStorageBackend + s3: Systems.Storage.MockBackend, + centerdata: Systems.Storage.MockBackend, + yoda: Systems.Storage.MockBackend ) From f3a90a0ea6dbcccf75bf539bda426297001aaa2e Mon Sep 17 00:00:00 2001 From: emielvdveen Date: Thu, 16 Nov 2023 13:02:09 +0100 Subject: [PATCH 2/3] Added Fabric framework and Assignment Connections (Panel/Storage) --- core/assets/css/app.css | 297 +++++++----- core/assets/js/live_content.js | 201 ++++---- .../images/icons/disconnect_tertiary.svg | 8 + .../static/images/icons/edit_tertiary.svg | 3 + .../images/wysiwyg/bullet-secondary.svg | 3 + core/assets/tailwind.config.js | 27 +- core/config/config.exs | 4 + core/frameworks/concept/content_model.ex | 5 + core/frameworks/fabric.ex | 131 +++++ core/frameworks/fabric/html.ex | 18 + core/frameworks/fabric/live_component.ex | 31 ++ core/frameworks/fabric/live_view.ex | 35 ++ core/frameworks/fabric/model.ex | 16 + .../frameworks/pixel/components/annotation.ex | 25 + .../pixel/components/button_face.ex | 3 +- core/frameworks/pixel/components/form.ex | 10 +- core/frameworks/pixel/error_helpers.ex | 9 + core/frameworks/pixel/form_helpers.ex | 8 + .../utililty}/http_client.ex | 2 +- core/lib/core/enums/base.ex | 29 +- core/lib/core_web.ex | 2 +- core/lib/core_web/ui/dialog/dialog.ex | 4 +- core/lib/core_web/ui/tabbar.ex | 6 +- .../gettext/en/LC_MESSAGES/eyra-account.po | 2 +- .../gettext/en/LC_MESSAGES/eyra-alliance.po | 16 +- .../gettext/en/LC_MESSAGES/eyra-assignment.po | 84 +++- core/priv/gettext/en/LC_MESSAGES/eyra-crew.po | 2 +- .../gettext/en/LC_MESSAGES/eyra-dashboard.po | 2 +- .../priv/gettext/en/LC_MESSAGES/eyra-enums.po | 22 +- .../gettext/en/LC_MESSAGES/eyra-project.po | 32 +- .../gettext/en/LC_MESSAGES/eyra-storage.po | 4 - core/priv/gettext/en/LC_MESSAGES/eyra-ui.po | 8 + core/priv/gettext/en/LC_MESSAGES/link-lab.po | 4 +- .../gettext/en/LC_MESSAGES/link-monitor.po | 2 +- core/priv/gettext/eyra-alliance.pot | 10 +- core/priv/gettext/eyra-assignment.pot | 80 ++++ core/priv/gettext/eyra-enums.pot | 16 +- core/priv/gettext/eyra-project.pot | 28 +- core/priv/gettext/eyra-storage.pot | 4 - core/priv/gettext/eyra-ui.pot | 8 + .../gettext/nl/LC_MESSAGES/eyra-account.po | 2 +- .../gettext/nl/LC_MESSAGES/eyra-alliance.po | 16 +- .../gettext/nl/LC_MESSAGES/eyra-assignment.po | 84 +++- core/priv/gettext/nl/LC_MESSAGES/eyra-crew.po | 2 +- .../gettext/nl/LC_MESSAGES/eyra-dashboard.po | 2 +- .../priv/gettext/nl/LC_MESSAGES/eyra-enums.po | 22 +- .../gettext/nl/LC_MESSAGES/eyra-project.po | 28 +- .../gettext/nl/LC_MESSAGES/eyra-storage.po | 4 - core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po | 8 + .../migrations/20231025125051_add_storage.exs | 3 + core/systems/alliance/tool_form.ex | 16 +- core/systems/alliance/tool_model.ex | 4 +- core/systems/assignment/_private.ex | 37 +- core/systems/assignment/_public.ex | 79 ++-- core/systems/assignment/_routes.ex | 15 + core/systems/assignment/_switch.ex | 18 +- .../centerdata/fakeapi_controller.ex | 11 + .../centerdata/fakeapi_page.ex | 9 +- core/systems/assignment/connection_view.ex | 121 +++++ .../assignment/connection_view_panel.ex | 100 ++++ .../assignment/connection_view_storage.ex | 35 ++ .../assignment/connector_popup_panel.ex | 169 +++++++ .../assignment/connector_popup_storage.ex | 133 ++++++ core/systems/assignment/connector_view.ex | 126 +++++ core/systems/assignment/content_page.ex | 1 + .../assignment/content_page_builder.ex | 27 +- core/systems/assignment/controller.ex | 2 +- core/systems/assignment/crew_page.ex | 25 +- .../assignment/external_panel_controller.ex | 57 +++ core/systems/assignment/external_panel_ids.ex | 7 + core/systems/assignment/model.ex | 3 +- core/systems/assignment/panel_form.ex | 104 ++++ core/systems/assignment/panel_views.ex | 47 ++ core/systems/assignment/settings_view.ex | 116 ++--- .../systems/benchmark/content_page_builder.ex | 8 +- core/systems/campaign/_public.ex | 2 +- .../builders/assignment_landing_page.ex | 6 +- core/systems/content/page.ex | 22 +- core/systems/email/factory.ex | 2 +- core/systems/feldspar/app_page.ex | 3 +- core/systems/project/invite_form.ex | 43 -- core/systems/project/tool_ref_model.ex | 4 +- core/systems/storage/_assembly.ex | 26 - core/systems/storage/_private.ex | 18 + core/systems/storage/_public.ex | 30 +- core/systems/storage/aws/endpoint_form.ex | 53 +-- core/systems/storage/azure/endpoint_form.ex | 53 +-- core/systems/storage/backend_types.ex | 7 - core/systems/storage/centerdata/backend.ex | 2 +- .../storage/centerdata/endpoint_form.ex | 53 +-- core/systems/storage/endpoint_form.ex | 109 +++-- core/systems/storage/endpoint_form_helper.ex | 55 +++ core/systems/storage/endpoint_model.ex | 26 +- core/systems/storage/service_ids.ex | 7 + core/systems/storage/yoda/endpoint_form.ex | 53 +-- core/systems/union/_routes.ex | 16 - core/systems/union/centerdata/controller.ex | 66 --- .../union/centerdata/fakeapi_controller.ex | 11 - core/systems/workflow/item_model.ex | 4 +- core/test/frameworks/fabric/factories.ex | 20 + core/test/frameworks/fabric/test.ex | 446 ++++++++++++++++++ .../frameworks/fabric/test_live_component.ex | 21 + core/test/frameworks/fabric/test_live_view.ex | 21 + .../systems/assignment/landing_page_test.exs | 2 +- .../systems/campaign/view_model_builder.exs | 6 +- core/test/systems/campaign/x.html | 201 -------- 106 files changed, 2880 insertions(+), 1150 deletions(-) create mode 100644 core/assets/static/images/icons/disconnect_tertiary.svg create mode 100644 core/assets/static/images/icons/edit_tertiary.svg create mode 100644 core/assets/static/images/wysiwyg/bullet-secondary.svg create mode 100644 core/frameworks/fabric.ex create mode 100644 core/frameworks/fabric/html.ex create mode 100644 core/frameworks/fabric/live_component.ex create mode 100644 core/frameworks/fabric/live_view.ex create mode 100644 core/frameworks/fabric/model.ex create mode 100644 core/frameworks/pixel/components/annotation.ex rename core/{systems/union/centerdata => frameworks/utililty}/http_client.ex (82%) create mode 100644 core/systems/assignment/centerdata/fakeapi_controller.ex rename core/systems/{union => assignment}/centerdata/fakeapi_page.ex (87%) create mode 100644 core/systems/assignment/connection_view.ex create mode 100644 core/systems/assignment/connection_view_panel.ex create mode 100644 core/systems/assignment/connection_view_storage.ex create mode 100644 core/systems/assignment/connector_popup_panel.ex create mode 100644 core/systems/assignment/connector_popup_storage.ex create mode 100644 core/systems/assignment/connector_view.ex create mode 100644 core/systems/assignment/external_panel_controller.ex create mode 100644 core/systems/assignment/external_panel_ids.ex create mode 100644 core/systems/assignment/panel_form.ex create mode 100644 core/systems/assignment/panel_views.ex delete mode 100644 core/systems/project/invite_form.ex create mode 100644 core/systems/storage/_private.ex delete mode 100644 core/systems/storage/backend_types.ex create mode 100644 core/systems/storage/endpoint_form_helper.ex create mode 100644 core/systems/storage/service_ids.ex delete mode 100644 core/systems/union/_routes.ex delete mode 100644 core/systems/union/centerdata/controller.ex delete mode 100644 core/systems/union/centerdata/fakeapi_controller.ex create mode 100644 core/test/frameworks/fabric/factories.ex create mode 100644 core/test/frameworks/fabric/test.ex create mode 100644 core/test/frameworks/fabric/test_live_component.ex create mode 100644 core/test/frameworks/fabric/test_live_view.ex delete mode 100644 core/test/systems/campaign/x.html diff --git a/core/assets/css/app.css b/core/assets/css/app.css index 7b6c25547..25a2f807d 100644 --- a/core/assets/css/app.css +++ b/core/assets/css/app.css @@ -3,7 +3,7 @@ @tailwind components; trix-toolbar { - @apply mb-4 drop-shadow-lg h-8; + @apply mb-4 drop-shadow-lg h-8; } trix-toolbar .trix-button-row { @@ -11,23 +11,29 @@ trix-toolbar .trix-button-row { display: flex; flex-wrap: nowrap; justify-content: space-between; - overflow-x: auto; + overflow-x: auto; } trix-toolbar .trix-button-group { - @apply flex rounded-lg overflow-hidden h-full; - } + @apply flex rounded-lg overflow-hidden h-full; +} +trix-toolbar .trix-button-group:not(:first-child) { + margin-left: 1.5vw; +} +@media (max-width: 768px) { trix-toolbar .trix-button-group:not(:first-child) { - margin-left: 1.5vw; } - @media (max-width: 768px) { - trix-toolbar .trix-button-group:not(:first-child) { - margin-left: 0; } } + margin-left: 0; + } +} trix-toolbar .trix-button-group-spacer { - flex-grow: 1; } - @media (max-width: 768px) { - trix-toolbar .trix-button-group-spacer { - display: none; } } + flex-grow: 1; +} +@media (max-width: 768px) { + trix-toolbar .trix-button-group-spacer { + display: none; + } +} trix-toolbar .trix-button { height: 100%; @@ -41,112 +47,125 @@ trix-toolbar .trix-button { border: none; padding: 4px; border-radius: 0; - background: #fff; } - trix-toolbar .trix-button:not(:first-child) { - border-left: 1px solid #eee; } - trix-toolbar .trix-button.trix-active { - @apply bg-primarylight; + background: #fff; +} +trix-toolbar .trix-button:not(:first-child) { + border-left: 1px solid #eee; +} +trix-toolbar .trix-button.trix-active { + @apply bg-primarylight; +} +trix-toolbar .trix-button:not(:disabled) { + cursor: pointer; +} +trix-toolbar .trix-button:disabled { + color: rgba(0, 0, 0, 0.3); +} +@media (max-width: 768px) { + trix-toolbar .trix-button { + letter-spacing: -0.01em; + padding: 0 0.3em; } - trix-toolbar .trix-button:not(:disabled) { - cursor: pointer; } - trix-toolbar .trix-button:disabled { - color: rgba(0, 0, 0, 0.3); } - @media (max-width: 768px) { - trix-toolbar .trix-button { - letter-spacing: -0.01em; - padding: 0 0.3em; } } - -trix-toolbar .trix-button--icon { +} + +trix-toolbar .trix-button--icon { font-size: inherit; width: 2.6em; max-width: calc(0.8em + 4vw); - text-indent: -9999px; } - @media (max-width: 768px) { - trix-toolbar .trix-button--icon { - height: 100%; - max-width: calc(0.8em + 3.5vw); } } + text-indent: -9999px; +} +@media (max-width: 768px) { + trix-toolbar .trix-button--icon { + height: 100%; + max-width: calc(0.8em + 3.5vw); + } +} +trix-toolbar .trix-button--icon::before { + display: inline-block; + position: absolute; + top: 4px; + right: 4px; + bottom: 4px; + left: 4px; + content: ""; + background-position: center; + background-repeat: no-repeat; + background-size: contain; +} +@media (max-width: 768px) { trix-toolbar .trix-button--icon::before { - display: inline-block; - position: absolute; - top: 4px; - right: 4px; - bottom: 4px; - left: 4px; - content: ""; - background-position: center; - background-repeat: no-repeat; - background-size: contain; } - @media (max-width: 768px) { - trix-toolbar .trix-button--icon::before { - right: 6%; - left: 6%; } } - trix-toolbar .trix-button--icon:disabled::before { - opacity: 0.3; } - + right: 6%; + left: 6%; + } +} +trix-toolbar .trix-button--icon:disabled::before { + opacity: 0.3; +} + .trix-button-group--file-tools { border: 0 !important; -} - +} + .trix-button--icon-attach, .trix-button--icon-decrease-nesting-level, .trix-button--icon-increase-nesting-level { - display :none; + display: none; } trix-toolbar .trix-button--icon-attach::before { - @apply bg-wysiwyg-attach + @apply bg-wysiwyg-attach; } trix-toolbar .trix-button--icon-bold::before { - @apply bg-wysiwyg-bold + @apply bg-wysiwyg-bold; } trix-toolbar .trix-button--icon-italic::before { - @apply bg-wysiwyg-italic + @apply bg-wysiwyg-italic; } trix-toolbar .trix-button--icon-link::before { - @apply bg-wysiwyg-link + @apply bg-wysiwyg-link; } trix-toolbar .trix-button--icon-strike::before { - @apply bg-wysiwyg-strike + @apply bg-wysiwyg-strike; } trix-toolbar .trix-button--icon-quote::before { - @apply bg-wysiwyg-quote + @apply bg-wysiwyg-quote; } trix-toolbar .trix-button--icon-heading-1::before { - @apply bg-wysiwyg-heading + @apply bg-wysiwyg-heading; } trix-toolbar .trix-button--icon-code::before { - @apply bg-wysiwyg-code + @apply bg-wysiwyg-code; } trix-toolbar .trix-button--icon-bullet-list::before { - @apply bg-wysiwyg-list-bullet + @apply bg-wysiwyg-list-bullet; } trix-toolbar .trix-button--icon-number-list::before { - @apply bg-wysiwyg-list-number + @apply bg-wysiwyg-list-number; } trix-toolbar .trix-button--icon-undo::before { - @apply bg-wysiwyg-history-undo + @apply bg-wysiwyg-history-undo; } trix-toolbar .trix-button--icon-redo::before { - @apply bg-wysiwyg-history-redo + @apply bg-wysiwyg-history-redo; } trix-toolbar .trix-button--icon-decrease-nesting-level::before { - @apply bg-wysiwyg-nesting-level-decrease + @apply bg-wysiwyg-nesting-level-decrease; } trix-toolbar .trix-button--icon-increase-nesting-level::before { - @apply bg-wysiwyg-nesting-level-increase + @apply bg-wysiwyg-nesting-level-increase; } trix-toolbar .trix-dialogs { @@ -161,11 +180,11 @@ trix-toolbar .trix-input--dialog { @apply h-12 rounded bg-white outline-none pl-4 font-body text-bodymedium placeholder-grey2 text-grey1 border-2 border-grey3 focus:border-primary; } trix-toolbar .trix-input--dialog.validate:invalid { - @apply border-warning bg-white + @apply border-warning bg-white; } trix-toolbar [data-trix-dialog] [data-trix-validate]:invalid { - @apply border-warning bg-white + @apply border-warning bg-white; } trix-toolbar .trix-button--dialog { @@ -177,98 +196,160 @@ trix-toolbar .trix-dialog__link-fields .trix-button-group { } trix-toolbar .trix-dialog__link-fields .trix-button { - border-left: 0px; + border-left: 0px; } trix-toolbar .trix-dialog--link { - max-width: 600px; } + max-width: 600px; +} trix-toolbar .trix-dialog__link-fields { display: flex; - align-items: baseline; } - trix-toolbar .trix-dialog__link-fields .trix-input { - flex: 1; } - trix-toolbar .trix-dialog__link-fields .trix-button-group { - flex: 0 0 content; - margin: 0; } - + align-items: baseline; +} +trix-toolbar .trix-dialog__link-fields .trix-input { + flex: 1; +} +trix-toolbar .trix-dialog__link-fields .trix-button-group { + flex: 0 0 content; + margin: 0; +} -trix-editor, .wysiwig { - @apply text-bodylarge font-body text-grey1 w-full outline-none; +trix-editor, +.wysiwig { + @apply text-grey1 text-bodylarge font-body w-full outline-none; } -trix-editor h1, .wysiwig h1 { +trix-editor h1, +.wysiwig h1 { @apply text-title2 font-title2 mb-8; } -trix-editor strong, .wysiwig strong { +trix-editor h2, +.wysiwig h2 { + @apply text-title3 font-title3 mb-8; +} + +trix-editor h3, +.wysiwig h3 { + @apply text-title4 font-title4 mb-8; +} + +trix-editor strong, +.wysiwig strong { @apply font-bold; } -trix-editor h1 strong, .wysiwig h1 strong { +trix-editor h1 strong, +.wysiwig h1 strong { @apply font-title1; } -trix-editor strong, .wysiwig strong { - @apply font-bold; +trix-editor h2 strong, +.wysiwig h2 strong { + @apply font-title1; } -trix-editor a:not(.no-underline), .wysiwig a:not(.no-underline) { - @apply text-primary underline cursor-pointer; +trix-editor h3 strong, +.wysiwig h3 strong { + @apply font-title1; } -trix-editor a:visited, .wysiwig a:visited { +trix-editor strong, +.wysiwig strong { + @apply font-bold; +} + +trix-editor a, +.wysiwig a { @apply text-primary; -} +} -trix-editor ul, .wysiwig ul { +.wysiwig-dark a { + @apply text-tertiary; +} + +trix-editor a:not(.no-underline), +.wysiwig a:not(.no-underline) { + @apply underline cursor-pointer; +} + +trix-editor ul, +.wysiwig ul { @apply mb-8 list-none; } -trix-editor ul li::before, .wysiwig ul li::before { +trix-editor ul li::before, +.wysiwig ul li::before { @apply bg-wysiwyg-bullet text-primary; vertical-align: center; - content: "\2022"; /* Add content: \2022 is the CSS Code/unicode for a bullet */ + content: "\2022"; /* Add content: \2022 is the CSS Code/unicode for a bullet */ display: inline-block; /* Needed to add space between the bullet and the text */ width: 30px; /* Also needed for space (tweak if needed) */ margin-left: 6px; /* Also needed for space (tweak if needed) */ - background-size: 10px 10px; - background-repeat: no-repeat; - background-position: left center; -} - -trix-editor ol, .wysiwig ol { + background-size: 10px 10px; + background-repeat: no-repeat; + background-position: left center; +} + +trix-editor ol, +.wysiwig ol { @apply mb-8 list-none; counter-reset: li; } -trix-editor ol li::before, .wysiwig ol li::before { - @apply text-primary font-label text-title5; - content: counter(li) "."; color: theme('colors.primary'); +trix-editor ol li::before, +.wysiwig ol li::before { + @apply font-label text-title5; + content: counter(li) "."; + color: theme("colors.primary"); display: inline-block; width: 30px; /* Also needed for space (tweak if needed) */ margin-left: 6px; /* Also needed for space (tweak if needed) */ } -trix-editor ol li, .wysiwig ol li { - counter-increment: li -} +trix-editor ol li, +.wysiwig ol li { + counter-increment: li; +} -trix-editor pre, .wysiwig pre { +trix-editor pre, +.wysiwig pre { @apply text-mono font-mono text-grey2 mb-8 p-8 bg-grey5 rounded-lg relative w-full whitespace-pre-wrap; vertical-align: top; } -trix-editor blockquote, .wysiwig blockquote { +trix-editor blockquote, +.wysiwig blockquote { @apply pl-11 pr-11 mb-8 relative font-quote text-quote; } -trix-editor blockquote::before, .wysiwig blockquote::before { - @apply text-title0 font-title0 text-primary absolute -left-2px top-6px; +trix-editor blockquote::before, +.wysiwig blockquote::before { + @apply text-primary text-title0 font-title0 absolute -left-2px top-6px; vertical-align: center; content: "”"; width: 0px; -} +} + +/* DARK MODE */ + +.wysiwig-dark { + @apply text-white; +} + +.wysiwig-dark ul li::before { + @apply bg-wysiwyg-bullet-dark text-tertiary; +} + +.wysiwig-dark ol li::before { + content: counter(li) "."; + color: theme("colors.secondary"); +} + +.wysiwig-dark blockquote::before { + @apply text-tertiary; +} @tailwind utilities; diff --git a/core/assets/js/live_content.js b/core/assets/js/live_content.js index ca91674d4..a2c227f30 100644 --- a/core/assets/js/live_content.js +++ b/core/assets/js/live_content.js @@ -1,7 +1,3 @@ -let liveContentId = ""; -let liveContentShowErrors = false; -let liveContentActiveField = ""; - const STATIC_CLASS_ATTR = "__eyra_field_static_class"; const HAS_ERRORS_ATTR = "__eyra_field_has_errors"; const ACTIVE_COLOR_ATTR = "__eyra_field_active_color"; @@ -22,122 +18,149 @@ const HIDDEN = "hidden"; export const LiveContent = { mounted() { - liveContentId = this.el.id; - liveContentShowErrors = this.el.getAttribute(DATA_SHOW_ERRORS) != null; + console.log("LiveContent mounted", this.el); + this.showErrors = this.el.getAttribute(DATA_SHOW_ERRORS) != null; + this.activeField = undefined; this.el.addEventListener("click", (event) => { - makeActive(undefined); + this.activeField = undefined; + this.applyActiveField(); }); - applyErrors(); - makeActive(undefined); - }, + this.el.addEventListener("field-activated", (event) => { + event.stopPropagation(); + this.activeField = event.target.dataset.fieldId; + this.applyActiveField(); + }); + + this.el.addEventListener("field-deactivated", (event) => { + event.stopPropagation(); + if (this.activeField == event.target.id) { + this.activeField = undefined; + this.applyActiveField(); + } + }); + this.applyErrors(); + this.applyActiveField(); + }, updated() { - liveContentShowErrors = this.el.getAttribute(DATA_SHOW_ERRORS) != null; - applyErrors(); + this.showErrors = this.el.getAttribute(DATA_SHOW_ERRORS) != null; + this.applyErrors(); + this.applyActiveField(); }, - onBeforeElUpdated(from, to) { const field_id = from.getAttribute("__eyra_field_id"); if (field_id != null) { - updateFieldItem(to, field_id); + to.classList = from.classList; + } + }, + applyErrors() { + const fieldErrors = Array.from( + this.el.querySelectorAll(`.${FIELD_ERROR_TYPE}`) + ); + + console.log("fieldErrors", fieldErrors); + + if (this.showErrors) { + fieldErrors.forEach((fieldError) => fieldError.classList.remove(HIDDEN)); + } else { + fieldErrors.forEach((fieldError) => fieldError.classList.add(HIDDEN)); + } + }, + updateFieldItem(fieldItem, activate) { + const hasErrors = fieldItem.getAttribute(HAS_ERRORS_ATTR) != null; + + if (fieldItem.classList.contains(FIELD_LABEL_TYPE)) { + this.updateFieldLabel(fieldItem, activate, hasErrors); + } else if (fieldItem.classList.contains(FIELD_INPUT_TYPE)) { + this.updateFieldInput(fieldItem, activate, hasErrors); + } + }, + applyActiveField() { + var fields = Array.from(this.el.querySelectorAll('[id^="field-"]')); + fields.forEach((field) => { + var activate = field.dataset.fieldId === this.activeField; + this.updateField(field, activate); + }); + }, + updateField(field, activate) { + const label = field.getElementsByClassName(FIELD_LABEL_TYPE)[0]; + const input = field.getElementsByClassName(FIELD_INPUT_TYPE)[0]; + + const hasErrors = field.getElementsByClassName(FIELD_ERROR_TYPE)[0] != null; + + if (label) { + this.updateFieldLabel(label, activate, hasErrors); + } + + if (input) { + this.updateFieldInput(input, activate, hasErrors); } }, + updateFieldLabel(label, activate, hasErrors) { + this.updateFieldItemClass( + label, + activate, + hasErrors, + LABEL_IDLE, + LABEL_ERROR + ); + }, + updateFieldInput(input, activate, hasErrors) { + this.updateFieldItemClass( + input, + activate, + hasErrors, + INPUT_IDLE, + INPUT_ERROR + ); + }, + updateFieldItemClass( + fieldItem, + activate, + hasErrors, + idle_class, + error_class + ) { + var dynamic_class = idle_class; + if (activate) { + dynamic_class = fieldItem.getAttribute(ACTIVE_COLOR_ATTR); + } else if (this.showErrors && hasErrors) { + dynamic_class = error_class; + } + const static_class = fieldItem.getAttribute(STATIC_CLASS_ATTR); + fieldItem.setAttribute("class", static_class + " " + dynamic_class); + }, }; export const LiveField = { mounted() { - const fieldId = this.el.dataset.fieldId; + console.log("LiveField mounted"); const input = this.el.getElementsByClassName(FIELD_INPUT_TYPE)[0]; if (input) { input.addEventListener("click", (event) => { event.stopPropagation(); - makeActive(fieldId); + this.activate(); }); input.addEventListener("focus", (event) => { event.stopPropagation(); - makeActive(fieldId); + this.activate(); }); input.addEventListener("blur", (event) => { event.stopPropagation(); - makeActive(undefined); + this.deactivate(); }); } }, + activate() { + this.el.dispatchEvent(new Event("field-activated", { bubbles: true })); + }, + deactivate() { + this.el.dispatchEvent(new Event("field-deactivated", { bubbles: true })); + }, }; - -function applyErrors() { - const fieldErrors = Array.from( - document.querySelectorAll(`.${FIELD_ERROR_TYPE}`) - ); - if (liveContentShowErrors) { - fieldErrors.forEach((fieldError) => fieldError.classList.remove(HIDDEN)); - } else { - fieldErrors.forEach((fieldError) => fieldError.classList.add(HIDDEN)); - } -} - -function makeActive(fieldId) { - liveContentActiveField = fieldId; - var fields = Array.from(document.querySelectorAll('[id^="field-"]')); - fields.forEach((field) => { - var activate = field.dataset.fieldId === fieldId; - updateField(field, activate); - }); -} - -function updateField(field, activate) { - const label = field.getElementsByClassName(FIELD_LABEL_TYPE)[0]; - const input = field.getElementsByClassName(FIELD_INPUT_TYPE)[0]; - - const hasErrors = field.getElementsByClassName(FIELD_ERROR_TYPE)[0] != null; - - if (label) { - updateFieldLabel(label, activate, hasErrors); - } - - if (input) { - updateFieldInput(input, activate, hasErrors); - } -} - -function updateFieldItem(fieldItem, field) { - const activate = field == liveContentActiveField; - const hasErrors = fieldItem.getAttribute(HAS_ERRORS_ATTR) != null; - - if (fieldItem.classList.contains(FIELD_LABEL_TYPE)) { - updateFieldLabel(fieldItem, activate, hasErrors); - } else if (fieldItem.classList.contains(FIELD_INPUT_TYPE)) { - updateFieldInput(fieldItem, activate, hasErrors); - } -} - -function updateFieldLabel(label, activate, hasErrors) { - updateFieldItemClass(label, activate, hasErrors, LABEL_IDLE, LABEL_ERROR); -} - -function updateFieldInput(input, activate, hasErrors) { - updateFieldItemClass(input, activate, hasErrors, INPUT_IDLE, INPUT_ERROR); -} - -function updateFieldItemClass( - fieldItem, - activate, - hasErrors, - idle_class, - error_class -) { - var dynamic_class = idle_class; - if (activate) { - dynamic_class = fieldItem.getAttribute(ACTIVE_COLOR_ATTR); - } else if (liveContentShowErrors && hasErrors) { - dynamic_class = error_class; - } - const static_class = fieldItem.getAttribute(STATIC_CLASS_ATTR); - fieldItem.setAttribute("class", static_class + " " + dynamic_class); -} diff --git a/core/assets/static/images/icons/disconnect_tertiary.svg b/core/assets/static/images/icons/disconnect_tertiary.svg new file mode 100644 index 000000000..5c9526abf --- /dev/null +++ b/core/assets/static/images/icons/disconnect_tertiary.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/core/assets/static/images/icons/edit_tertiary.svg b/core/assets/static/images/icons/edit_tertiary.svg new file mode 100644 index 000000000..4f816365e --- /dev/null +++ b/core/assets/static/images/icons/edit_tertiary.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/assets/static/images/wysiwyg/bullet-secondary.svg b/core/assets/static/images/wysiwyg/bullet-secondary.svg new file mode 100644 index 000000000..1559442fa --- /dev/null +++ b/core/assets/static/images/wysiwyg/bullet-secondary.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/assets/tailwind.config.js b/core/assets/tailwind.config.js index 2b493d70f..2dc3c04cc 100644 --- a/core/assets/tailwind.config.js +++ b/core/assets/tailwind.config.js @@ -8,14 +8,14 @@ module.exports = { "./js/**/*.js", ], safelist: [ - 'drop-shadow-2xl', - 'text-bold', - 'text-pre', - 'font-pre', - {pattern: /bg-wysiwyg-./ }, - {pattern: /h-wysiwyg-./ }, - {pattern: /border-./ }, - ], + "drop-shadow-2xl", + "text-bold", + "text-pre", + "font-pre", + { pattern: /bg-wysiwyg-./ }, + { pattern: /h-wysiwyg-./ }, + { pattern: /border-./ }, + ], theme: { boxShadow: { top4px: "inset 0 4px 0 0 rgba(0, 0, 0, 0.15)", @@ -65,12 +65,15 @@ module.exports = { "wysiwyg-code": "url('/images/wysiwyg/code.svg')", "wysiwyg-list-bullet": "url('/images/wysiwyg/list_bullet.svg')", "wysiwyg-list-number": "url('/images/wysiwyg/list_number.svg')", - "wysiwyg-nesting-level-decrease": "url('/images/wysiwyg/nesting_level_decrease.svg')", - "wysiwyg-nesting-level-increase": "url('/images/wysiwyg/nesting_level_increase.svg')", + "wysiwyg-nesting-level-decrease": + "url('/images/wysiwyg/nesting_level_decrease.svg')", + "wysiwyg-nesting-level-increase": + "url('/images/wysiwyg/nesting_level_increase.svg')", "wysiwyg-attach": "url('/images/wysiwyg/attach.svg')", "wysiwyg-history-undo": "url('/images/wysiwyg/history_undo.svg')", "wysiwyg-history-redo": "url('/images/wysiwyg/history_redo.svg')", "wysiwyg-bullet": "url('/images/wysiwyg/bullet.svg')", + "wysiwyg-bullet-dark": "url('/images/wysiwyg/bullet-secondary.svg')", }, spacing: { "1px": "1px", @@ -194,7 +197,7 @@ module.exports = { bold: ["Finador-Bold", "sans-serif"], quote: ["Finador-Bold", "sans-serif"], }, - fontSize: { + fontSize: { title0: ["64px", "68px"], title1: ["50px", "55px"], title2: ["40px", "44px"], @@ -233,7 +236,7 @@ module.exports = { card: "376px", form: "400px", sheet: "760px", - popup: "480px", + popup: "480px", "popup-sm": "520px", "popup-md": "730px", "popup-lg": "1228px", diff --git a/core/config/config.exs b/core/config/config.exs index da56ee0df..af91257d5 100644 --- a/core/config/config.exs +++ b/core/config/config.exs @@ -109,6 +109,10 @@ config :web_push_encryption, :vapid_details, config :core, :version, System.get_env("VERSION", "dev") +config :core, :assignment, external_panels: ~w(liss ioresearch generic) + +config :core, :storage, services: ~w(azure aws yoda) + config :core, BankingClient, host: 'localhost', port: 5555, diff --git a/core/frameworks/concept/content_model.ex b/core/frameworks/concept/content_model.ex index 70d5e5cd6..94b15bc6f 100644 --- a/core/frameworks/concept/content_model.ex +++ b/core/frameworks/concept/content_model.ex @@ -5,3 +5,8 @@ defprotocol Frameworks.Concept.ContentModel do @spec form(t) :: atom() def form(_t) end + +defimpl Frameworks.Concept.ContentModel, for: Ecto.Changeset do + def form(%{data: data}), do: Frameworks.Concept.ContentModel.form(data) + def ready?(changeset), do: changeset.valid?() +end diff --git a/core/frameworks/fabric.ex b/core/frameworks/fabric.ex new file mode 100644 index 000000000..cfb66aee2 --- /dev/null +++ b/core/frameworks/fabric.ex @@ -0,0 +1,131 @@ +defmodule Fabric do + alias Fabric.LiveView + alias Fabric.LiveComponent + + def prepare_child( + %Phoenix.LiveView.Socket{assigns: %{fabric: fabric}}, + child_id, + module, + params + ) do + prepare_child(fabric, child_id, module, params) + end + + def prepare_child(%Fabric.Model{self: self}, child_id, module, params) do + child_ref = %LiveComponent.RefModel{id: child_id, module: module} + child_fabric = %Fabric.Model{parent: self, self: child_ref, children: []} + params = Map.put(params, :fabric, child_fabric) + %LiveComponent.Model{ref: child_ref, params: params} + end + + def get_child(%Phoenix.LiveView.Socket{assigns: %{fabric: fabric}}, child_id) do + get_child(fabric, child_id) + end + + def get_child(%Fabric.Model{children: children}, child_id) do + Enum.find(children, &(&1.ref.id == child_id)) + end + + def new_fabric(%Phoenix.LiveView.Socket{} = socket) do + fabric = new_fabric() + Phoenix.Component.assign(socket, :fabric, fabric) + end + + def new_fabric() do + %Fabric.Model{parent: nil, children: []} + end + + def show_child(%Phoenix.LiveView.Socket{} = socket, %LiveComponent.Model{} = child) do + socket |> add_child(child) + end + + def replace_child( + %Phoenix.LiveView.Socket{} = socket, + %LiveComponent.Model{ref: %{id: id}} = child + ) do + socket + |> remove_child(id) + |> add_child(child) + end + + def hide_child(%Phoenix.LiveView.Socket{} = socket, child_id) do + socket |> remove_child(child_id) + end + + def show_popup(%Phoenix.LiveView.Socket{} = socket, %LiveComponent.Model{} = child) do + socket + |> add_child(child) + |> send_event(:root, "show_popup", child) + end + + def hide_popup(%Phoenix.LiveView.Socket{} = socket, child_id) do + socket + |> remove_child(child_id) + |> send_event(:root, "hide_popup") + end + + def add_child(%Phoenix.LiveView.Socket{assigns: %{fabric: fabric}} = socket, child) do + fabric = add_child(fabric, child) + Phoenix.Component.assign(socket, :fabric, fabric) + end + + def add_child(%Fabric.Model{children: children} = fabric, %LiveComponent.Model{} = child) do + %Fabric.Model{fabric | children: children ++ [child]} + end + + def remove_child(%Phoenix.LiveView.Socket{assigns: %{fabric: fabric}} = socket, child_id) do + fabric = remove_child(fabric, child_id) + Phoenix.Component.assign(socket, :fabric, fabric) + end + + def remove_child(%Fabric.Model{children: children} = fabric, child_id) do + %Fabric.Model{fabric | children: Enum.filter(children, &(&1.ref.id != child_id))} + end + + def send_event(_, _, _, payload \\ %{}) + + def send_event( + %Phoenix.LiveView.Socket{assigns: %{fabric: fabric}} = socket, + target, + name, + payload + ) do + send_event(fabric, target, name, payload) + socket + end + + def send_event(%Fabric.Model{}, :root, name, payload) do + send_event(self(), %{name: name, payload: payload}) + end + + def send_event(%Fabric.Model{parent: nil}, :parent, name, _payload) do + raise "Sending event '#{name}' to non-existing parent" + end + + def send_event(%Fabric.Model{parent: parent, self: self}, :parent, name, payload) do + payload = Map.put(payload, :source, self) + send_event(parent, %{name: name, payload: payload}) + end + + def send_event(%Fabric.Model{self: self}, :self, name, payload) do + send_event(self, %{name: name, payload: payload}) + end + + def send_event(%Fabric.Model{} = fabric, child_id, name, payload) do + if child = get_child(fabric, child_id) do + send_event(child.ref, %{name: name, payload: payload}) + end + end + + def send_event(%LiveComponent.RefModel{id: id, module: module}, event) do + Phoenix.LiveView.send_update(module, %{id: id, fabric_event: event}) + end + + def send_event(%LiveView.RefModel{pid: pid}, event) do + send_event(pid, event) + end + + def send_event(pid, event) when is_pid(pid) do + send(pid, %{fabric_event: event}) + end +end diff --git a/core/frameworks/fabric/html.ex b/core/frameworks/fabric/html.ex new file mode 100644 index 000000000..b2f04f6a5 --- /dev/null +++ b/core/frameworks/fabric/html.ex @@ -0,0 +1,18 @@ +defmodule Fabric.Html do + use Phoenix.Component + + attr(:id, :any, required: true) + attr(:fabric, :map, required: true) + slot(:header) + slot(:footer) + + def child(assigns) do + ~H""" + <%= if child = Fabric.get_child(@fabric, @id) do %> + <%= render_slot(@header) %> + <.live_component {Map.from_struct(child.ref)} {child.params}/> + <%= render_slot(@footer) %> + <% end %> + """ + end +end diff --git a/core/frameworks/fabric/live_component.ex b/core/frameworks/fabric/live_component.ex new file mode 100644 index 000000000..b0f22da5c --- /dev/null +++ b/core/frameworks/fabric/live_component.ex @@ -0,0 +1,31 @@ +defmodule Fabric.LiveComponent do + defmodule RefModel do + @type t :: %__MODULE__{id: atom() | binary(), module: atom()} + defstruct [:id, :module] + end + + defmodule Model do + @type ref :: Fabric.LiveComponent.RefModel.t() + @type params :: map() + @type t :: %__MODULE__{ref: ref, params: params} + defstruct [:ref, :params] + end + + defmacro __using__(_opts) do + quote do + import Fabric + import Fabric.Html + + def update(%{fabric_event: %{name: name, payload: payload}}, socket) do + {:noreply, socket} = __MODULE__.handle_event(name, payload, socket) + {:ok, socket} + end + + def update(%{id: _id, fabric: fabric} = params, socket) do + params = Map.drop(params, [:fabric]) + socket = assign(socket, fabric: fabric) + update(params, socket) + end + end + end +end diff --git a/core/frameworks/fabric/live_view.ex b/core/frameworks/fabric/live_view.ex new file mode 100644 index 000000000..79e376979 --- /dev/null +++ b/core/frameworks/fabric/live_view.ex @@ -0,0 +1,35 @@ +defmodule Fabric.LiveView do + defmodule RefModel do + @type t :: %__MODULE__{pid: pid()} + defstruct [:pid] + end + + defmacro __using__(_opts) do + quote do + import Fabric + import Fabric.Html + + @before_compile Fabric.LiveView + + def handle_info(%{fabric_event: %{name: name, payload: payload}}, socket) do + __MODULE__.handle_event(name, payload, socket) + end + end + end + + defmacro __before_compile__(_env) do + quote do + defoverridable mount: 3 + + @doc """ + Automatically assigns Fabric to the socket on mount + """ + def mount(params, session, socket) do + self = %Fabric.LiveView.RefModel{pid: self()} + fabric = %Fabric.Model{parent: nil, self: self, children: []} + socket = Phoenix.Component.assign(socket, :fabric, fabric) + super(params, session, socket) + end + end + end +end diff --git a/core/frameworks/fabric/model.ex b/core/frameworks/fabric/model.ex new file mode 100644 index 000000000..44202f992 --- /dev/null +++ b/core/frameworks/fabric/model.ex @@ -0,0 +1,16 @@ +defmodule Fabric.Model do + alias Fabric.LiveView + alias Fabric.LiveComponent + + @type ref :: LiveView.RefModel.t() | LiveComponent.RefModel.t() + @type ref_optional :: ref | nil + @type model :: LiveComponent.Model.t() + + @type t :: %__MODULE__{ + parent: ref_optional(), + self: ref, + children: [model()] + } + + defstruct [:parent, :self, :children] +end diff --git a/core/frameworks/pixel/components/annotation.ex b/core/frameworks/pixel/components/annotation.ex new file mode 100644 index 000000000..29a78a8ad --- /dev/null +++ b/core/frameworks/pixel/components/annotation.ex @@ -0,0 +1,25 @@ +defmodule Frameworks.Pixel.Annotation do + use CoreWeb, :html + + alias Frameworks.Pixel.Panel + + attr(:annotation, :string, required: true) + + def panel(assigns) do + ~H""" + + <.view annotation={@annotation} /> + + """ + end + + attr(:annotation, :string, required: true) + + def view(assigns) do + ~H""" +
+ <%= raw @annotation %> +
+ """ + end +end diff --git a/core/frameworks/pixel/components/button_face.ex b/core/frameworks/pixel/components/button_face.ex index 21c9e2166..c36a54353 100644 --- a/core/frameworks/pixel/components/button_face.ex +++ b/core/frameworks/pixel/components/button_face.ex @@ -34,6 +34,7 @@ defmodule Frameworks.Pixel.Button.Face do attr(:label, :string, required: true) attr(:icon, :atom, required: true) attr(:text_color, :string, default: "text-grey1") + attr(:height, :string, default: "h-10") def label_icon(assigns) do ~H""" @@ -42,7 +43,7 @@ defmodule Frameworks.Pixel.Button.Face do
{@label}
-
+
<%= @label %> diff --git a/core/frameworks/pixel/components/form.ex b/core/frameworks/pixel/components/form.ex index 9a4d93514..9c11fd777 100644 --- a/core/frameworks/pixel/components/form.ex +++ b/core/frameworks/pixel/components/form.ex @@ -44,7 +44,7 @@ defmodule Frameworks.Pixel.Form do error_space_id = field_item_id(field, @error_space) error_message_id = field_item_id(field, @error_message) - error_static_class = "#{field_tag(@error)} text-caption font-caption text-warning hidden" + error_static_class = "#{field_tag(@error)} text-caption font-caption text-warning" label_id = field_item_id(field, @label) label_static_class = "#{field_tag(@label)} mt-0.5 text-title6 font-title6 leading-snug" @@ -121,7 +121,7 @@ defmodule Frameworks.Pixel.Form do attr(:maxlength, :string, default: "1000") def input(%{form: form, field: field} = assigns) do - errors = form[field].errors + errors = guarded_errors(form, field) field_id = String.to_atom(input_id(form, field)) input_id = field_item_id(field_id, @input) @@ -334,7 +334,7 @@ defmodule Frameworks.Pixel.Form do attr(:debounce, :string, default: "1000") def text_area(%{form: form, field: field} = assigns) do - errors = form[field].errors + errors = guarded_errors(form, field) has_errors = Enum.count(errors) > 0 field_id = String.to_atom(input_id(form, field)) @@ -391,7 +391,7 @@ defmodule Frameworks.Pixel.Form do attr(:max_height, :string, default: "max-h-wysiwyg-editor") def wysiwyg_area(%{form: form, field: field} = assigns) do - errors = form[field].errors + errors = guarded_errors(form, field) has_errors = Enum.count(errors) > 0 field_id = String.to_atom(input_id(form, field)) @@ -582,7 +582,7 @@ defmodule Frameworks.Pixel.Form do attr(:value, :any, default: nil) def dropdown(%{form: form, field: field, options: options} = assigns) do - errors = form[field].errors + errors = guarded_errors(form, field) field_id = String.to_atom(input_id(form, field)) options_id = "#{field_id}-options" diff --git a/core/frameworks/pixel/error_helpers.ex b/core/frameworks/pixel/error_helpers.ex index 687ef6965..51b8a6b77 100644 --- a/core/frameworks/pixel/error_helpers.ex +++ b/core/frameworks/pixel/error_helpers.ex @@ -6,6 +6,15 @@ defmodule Frameworks.Pixel.ErrorHelpers do alias Phoenix.HTML.Form alias Phoenix.HTML.Tag + @doc """ + Checkss if there are errors. + """ + def show_errors?(form) do + form + |> Map.get(:options, []) + |> Keyword.get(:"data-show-errors", true) + end + @doc """ Checkss if there are errors. """ diff --git a/core/frameworks/pixel/form_helpers.ex b/core/frameworks/pixel/form_helpers.ex index ac969fc10..97a90ad6e 100644 --- a/core/frameworks/pixel/form_helpers.ex +++ b/core/frameworks/pixel/form_helpers.ex @@ -5,6 +5,14 @@ defmodule Frameworks.Pixel.FormHelpers do alias Frameworks.Pixel.ErrorHelpers alias Phoenix.LiveView.JS + def guarded_errors(form, field) do + if ErrorHelpers.show_errors?(form) do + form[field].errors + else + [] + end + end + def field_has_error?(assigns, form) do ErrorHelpers.has_error?(form, assigns.field) end diff --git a/core/systems/union/centerdata/http_client.ex b/core/frameworks/utililty/http_client.ex similarity index 82% rename from core/systems/union/centerdata/http_client.ex rename to core/frameworks/utililty/http_client.ex index 816808477..08203034a 100644 --- a/core/systems/union/centerdata/http_client.ex +++ b/core/frameworks/utililty/http_client.ex @@ -1,4 +1,4 @@ -defmodule Systems.Union.Centerdata.HTTPClient do +defmodule Frameworks.Utility.HTTPClient do use HTTPoison.Base @impl HTTPoison.Base diff --git a/core/lib/core/enums/base.ex b/core/lib/core/enums/base.ex index 052b0db88..6eb3df401 100644 --- a/core/lib/core/enums/base.ex +++ b/core/lib/core/enums/base.ex @@ -5,8 +5,17 @@ defmodule Core.Enums.Base do quote do import CoreWeb.Gettext - def values do + def values(filter \\ nil) + + def values(nil) do + unquote(values) + end + + def values(filter) when is_list(filter) do + filter = convert_to_atoms(filter) + unquote(values) + |> Enum.filter(&Enum.member?(filter, &1)) end def contains(atom) when is_atom(atom) do @@ -27,30 +36,26 @@ defmodule Core.Enums.Base do end end - def labels() do - labels([]) - end + def labels(active_values \\ [], filter \\ nil) - def labels(nil) do - labels([]) - end + def labels(nil, filter), do: labels([], filter) - def labels(active_values) when is_list(active_values) do + def labels(active_values, filter) when is_list(active_values) do active_values = convert_to_atoms(active_values) - values() + values(filter) |> Enum.map(&convert_to_label(&1, active_values)) end - def labels(active_value) do - labels([active_value]) + def labels(active_value, filter) do + labels([active_value], filter) end defp convert_to_atoms(values) when is_list(values) do Enum.map(values, &convert_to_atom(&1)) end - defp convert_to_atom(value) when is_binary(value), do: String.to_atom(value) + defp convert_to_atom(value) when is_binary(value), do: String.to_existing_atom(value) defp convert_to_atom(value) when is_atom(value), do: value defp convert_to_label(value, active_values) when is_atom(value) do diff --git a/core/lib/core_web.ex b/core/lib/core_web.ex index 05123093a..0fa0f969c 100644 --- a/core/lib/core_web.ex +++ b/core/lib/core_web.ex @@ -107,7 +107,7 @@ defmodule CoreWeb do use Phoenix.LiveComponent - def update_target(%{type: type, id: id}, message) when is_map(message) do + def update_target(%{id: id, type: type}, message) when is_map(message) do send_update(type, message |> Map.put(:id, id)) end diff --git a/core/lib/core_web/ui/dialog/dialog.ex b/core/lib/core_web/ui/dialog/dialog.ex index b21f2f1ec..e8967ee77 100644 --- a/core/lib/core_web/ui/dialog/dialog.ex +++ b/core/lib/core_web/ui/dialog/dialog.ex @@ -18,7 +18,9 @@ defmodule CoreWeb.UI.Dialog do
<%= @text %>
- <%= render_slot(@inner_block) %> +
+ <%= render_slot(@inner_block) %> +
<%= for button <- @buttons do %> diff --git a/core/lib/core_web/ui/tabbar.ex b/core/lib/core_web/ui/tabbar.ex index ce9b90eef..c3f5db98a 100644 --- a/core/lib/core_web/ui/tabbar.ex +++ b/core/lib/core_web/ui/tabbar.ex @@ -102,7 +102,11 @@ defmodule CoreWeb.UI.Tabbar do <.tab id={tab.id}> <%= if Map.has_key?(tab, :live_component) do %> <.live_component id={tab.id} module={tab.live_component} {tab.props} /> - <% else %> + <% end %> + <%= if Map.has_key?(tab, :child) do %> + <.live_component {Map.from_struct(tab.child.ref)} {tab.child.params} /> + <% end %> + <%= if Map.has_key?(tab, :function_component) do %> <.function_component function={tab.function_component} props={tab.props} /> <% end %> diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-account.po b/core/priv/gettext/en/LC_MESSAGES/eyra-account.po index 29bfae1c6..f235cd190 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-account.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-account.po @@ -128,7 +128,7 @@ msgstr "Sign in" #, elixir-autogen, elixir-format msgid "login.surf.message.part1" -msgstr "You can use your VU Amsterdam credentials to sign in on Panl, through " +msgstr "You can use your VU Amsterdam credentials to sign in on Next, through " #, elixir-autogen, elixir-format msgid "login.surf.message.part2" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-alliance.po b/core/priv/gettext/en/LC_MESSAGES/eyra-alliance.po index 164468629..3344ff8d5 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-alliance.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-alliance.po @@ -95,12 +95,12 @@ msgid "ethical.title" msgstr "Ethical review" #, elixir-autogen, elixir-format -msgid "panlid.description" -msgstr "Panl automatically adds the participant ID to your Qualtrics URL. To use this ID in Qualtrics, add an embedded data element field with the name \"panl_id\", as %{link} in \"PART 2\"." +msgid "nextid.description" +msgstr "Next automatically adds the participant ID to your Qualtrics URL. To use this ID in Qualtrics, add an embedded data element field with the name \"next_id\", as %{link} in \"PART 2\"." #, elixir-autogen, elixir-format -msgid "panlid.title" -msgstr "Panl ID" +msgid "nextid.title" +msgstr "Next ID" #, elixir-autogen, elixir-format msgid "setup.title" @@ -115,15 +115,15 @@ msgid "ethical.description" msgstr "Make sure that you completed the %{link} and that you have approval from the SBE Ethics Review Board (ERB), if required for your study. Provide your received ERB code." #, elixir-autogen, elixir-format -msgid "study.link.description" +msgid "link.description" msgstr "To obtain the link to your Qualtrics questionnaire, follow %{link}. Paste your link in the \"Questionnaire link\" field in the form below." #, elixir-autogen, elixir-format -msgid "study.link.title" -msgstr "Anonymous Questionnaire Link" +msgid "link.title" +msgstr "Questionnaire Link" #, elixir-autogen, elixir-format -msgid "panlid.instructions.link" +msgid "nextid.instructions.link" msgstr "explained here" #, elixir-autogen, elixir-format diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po index 95fa63bbf..299a60a4d 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po @@ -36,7 +36,7 @@ msgstr "Settings" #, elixir-autogen, elixir-format, ex-autogen msgid "ticket.title" -msgstr "Your Panl ID" +msgstr "Your Next ID" #, elixir-autogen, elixir-format, ex-autogen msgid "earned.label" @@ -92,4 +92,84 @@ msgstr "Settings" #, elixir-autogen, elixir-format msgid "settings.data_storage.title" -msgstr "Participant data storage" +msgstr "Storage" + +#, elixir-autogen, elixir-format +msgid "panel.title" +msgstr "Panel" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.label" +msgstr "Select your panel" + +#, elixir-autogen, elixir-format, fuzzy +msgid "connect.button" +msgstr "Set up connection" + +#, elixir-autogen, elixir-format +msgid "connector.cancel.button" +msgstr "Cancel" + +#, elixir-autogen, elixir-format +msgid "connector.connect.button" +msgstr "Connect" + +#, elixir-autogen, elixir-format +msgid "connector.panel.text" +msgstr "Connect your panel" + +#, elixir-autogen, elixir-format +msgid "connector.panel.title" +msgstr "Set up connection" + +#, elixir-autogen, elixir-format +msgid "connector.storage.text" +msgstr "Connect your participant database" + +#, elixir-autogen, elixir-format +msgid "connector.storage.title" +msgstr "Set up connection" + +#, elixir-autogen, elixir-format, fuzzy +msgid "settings.data_storage.body" +msgstr "Connection to the database that will be used to store the participant data." + +#, elixir-autogen, elixir-format +msgid "settings.panel.body" +msgstr "Connection to the panel that will provide for the participants." + +#, elixir-autogen, elixir-format, fuzzy +msgid "settings.panel.title" +msgstr "Panel" + +#, elixir-autogen, elixir-format, fuzzy +msgid "disconnect.button" +msgstr "Disconnect" + +#, elixir-autogen, elixir-format +msgid "panel_form.type.label" +msgstr "Select your panel" + +#, elixir-autogen, elixir-format +msgid "panel.liss.connector.annotation" +msgstr "
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

For more information see www.lissdata.nl
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.generic.connector.annotation" +msgstr "
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
" + +#, elixir-autogen, elixir-format +msgid "panel.ioresearch.connector.annotation" +msgstr "
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

For more information see www.ioresearch.nl
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.generic.connection.annotation" +msgstr "
Copy the url below and hand it over to your contact person at the panel agency. They use this url to send participants to this assignment.

The url contains the following parameters:
  • participant: identifier from the panel agency
  • language: 'nl' or 'en'
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.ioresearch.connection.annotation" +msgstr "
Copy the url below and hand it over to your contact person at I&O Research. They use this url to send participants to this assignment.

The url contains the following parameters:
  • participant: identifier from I&O Research
  • language: 'nl' or 'en'
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.liss.connection.annotation" +msgstr "
Copy the url below and hand it over to your contact person at LISS. They use this url to send participants to this assignment.
" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-crew.po b/core/priv/gettext/en/LC_MESSAGES/eyra-crew.po index 8a0d79ac1..34c24acb7 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-crew.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-crew.po @@ -36,4 +36,4 @@ msgstr "✅ Round trip confirmed" #, elixir-autogen, elixir-format, ex-autogen msgid "tester.completed.text" -msgstr "If the title of this page corresponds with the title of your campaign, the roundtrip from Panl to Qualtrics and back was successful." +msgstr "If the title of this page corresponds with the title of your campaign, the roundtrip from Next to Qualtrics and back was successful." diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-dashboard.po b/core/priv/gettext/en/LC_MESSAGES/eyra-dashboard.po index 79456f00b..86327b7be 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-dashboard.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-dashboard.po @@ -20,7 +20,7 @@ msgstr "Recent items" #, elixir-autogen, elixir-format msgid "empty.description" -msgstr "Start working with Panl and this console will provide an overview of your activities. It is the place to be, to quickly pick up where you left off during your last visit." +msgstr "Start working with Next and this console will provide an overview of your activities. It is the place to be, to quickly pick up where you left off during your last visit." #, elixir-autogen, elixir-format msgid "empty.title" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po b/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po index fe9fde1b4..1f7f30003 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po @@ -351,17 +351,25 @@ msgid "platforms.tiktok" msgstr "TikTok" #, elixir-autogen, elixir-format -msgid "storage_backend_types.azure" +msgid "storage_service_ids.azure" msgstr "Azure Blob" #, elixir-autogen, elixir-format -msgid "storage_backend_types.centerdata" -msgstr "Centerdata" - -#, elixir-autogen, elixir-format -msgid "storage_backend_types.yoda" +msgid "storage_service_ids.yoda" msgstr "Yoda" #, elixir-autogen, elixir-format, fuzzy -msgid "storage_backend_types.aws" +msgid "storage_service_ids.aws" msgstr "Amazon S3" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.generic" +msgstr "Other" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.ioresearch" +msgstr "I&O Research" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.liss" +msgstr "LISS" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-project.po b/core/priv/gettext/en/LC_MESSAGES/eyra-project.po index 829cac06b..0c3c48d88 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-project.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-project.po @@ -24,12 +24,12 @@ msgid "tabbar.item.config.forward" msgstr "Go to Settings" #, elixir-autogen, elixir-format -msgid "tabbar.item.invite" -msgstr "Invite" +msgid "tabbar.item.panel" +msgstr "Panel" #, elixir-autogen, elixir-format -msgid "tabbar.item.invite.forward" -msgstr "Go to Invite" +msgid "tabbar.item.panel.forward" +msgstr "Go to Panel" #, elixir-autogen, elixir-format msgid "tabbar.item.monitor" @@ -79,10 +79,6 @@ msgstr "Settings" msgid "form.name.label" msgstr "Name" -#, elixir-autogen, elixir-format -msgid "invite.title" -msgstr "Invite" - #, elixir-autogen, elixir-format msgid "monitor.title" msgstr "Monitor" @@ -111,18 +107,6 @@ msgstr "Add new item" msgid "add.new.item.button.short" msgstr "New item" -#, elixir-autogen, elixir-format -msgid "campaign.invite.title" -msgstr "Invite by campaign" - -#, elixir-autogen, elixir-format -msgid "direct.invite.description" -msgstr "You can invite people directly using the url below." - -#, elixir-autogen, elixir-format -msgid "direct.invite.title" -msgstr "Invite by url" - #, elixir-autogen, elixir-format msgid "label.concept" msgstr "Concept" @@ -190,3 +174,11 @@ msgstr "Settings" #, elixir-autogen, elixir-format, fuzzy msgid "tabbar.item.settings.forward" msgstr "Go to Settings" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.invite" +msgstr "Panel" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.invite.forward" +msgstr "Go to Settings" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-storage.po b/core/priv/gettext/en/LC_MESSAGES/eyra-storage.po index b5cc83891..c7758c9e8 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-storage.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-storage.po @@ -47,10 +47,6 @@ msgstr "SAS token" msgid "centerdata.url.label" msgstr "URL" -#, elixir-autogen, elixir-format -msgid "centerdata.url.placeholder" -msgstr "" - #, elixir-autogen, elixir-format msgid "yoda.password.label" msgstr "Password" diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po b/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po index c96fbec0d..4ea109a7d 100644 --- a/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po +++ b/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po @@ -245,3 +245,11 @@ msgstr "Privacy" #, elixir-autogen, elixir-format msgid "terms.link" msgstr "Terms" + +#, elixir-autogen, elixir-format, fuzzy +msgid "edit.button" +msgstr "Edit" + +#, elixir-autogen, elixir-format +msgid "copy.clipboard.button" +msgstr "Copy" diff --git a/core/priv/gettext/en/LC_MESSAGES/link-lab.po b/core/priv/gettext/en/LC_MESSAGES/link-lab.po index 79cfa654d..ce2a65bad 100644 --- a/core/priv/gettext/en/LC_MESSAGES/link-lab.po +++ b/core/priv/gettext/en/LC_MESSAGES/link-lab.po @@ -122,7 +122,7 @@ msgstr "Search participant" #, elixir-autogen, elixir-format, ex-autogen, fuzzy msgid "search.subject.body" -msgstr "On arrival, enter the participant Panl ID or email below. Their reserved time slot will appear and you can check them in." +msgstr "On arrival, enter the participant Next ID or email below. Their reserved time slot will appear and you can check them in." #, elixir-autogen, elixir-format, ex-autogen, fuzzy msgid "search.subject.expired" @@ -134,7 +134,7 @@ msgstr "No results" #, elixir-autogen, elixir-format, ex-autogen msgid "inquiry.checkin.label" -msgstr "Check in with Panl ID:" +msgstr "Check in with Next ID:" #, elixir-autogen, elixir-format, ex-autogen, fuzzy msgid "search.email.not.found" diff --git a/core/priv/gettext/en/LC_MESSAGES/link-monitor.po b/core/priv/gettext/en/LC_MESSAGES/link-monitor.po index 5ee8b7207..772dd358e 100644 --- a/core/priv/gettext/en/LC_MESSAGES/link-monitor.po +++ b/core/priv/gettext/en/LC_MESSAGES/link-monitor.po @@ -16,7 +16,7 @@ msgstr "Attention" #, elixir-autogen, elixir-format msgid "started.at.message" -msgstr "Started %{date} but not returned to Panl" +msgstr "Started %{date} but not returned to Next" #, elixir-autogen, elixir-format msgid "accept.all.button" diff --git a/core/priv/gettext/eyra-alliance.pot b/core/priv/gettext/eyra-alliance.pot index c012b8c41..65e85e803 100644 --- a/core/priv/gettext/eyra-alliance.pot +++ b/core/priv/gettext/eyra-alliance.pot @@ -95,11 +95,11 @@ msgid "ethical.title" msgstr "" #, elixir-autogen, elixir-format -msgid "panlid.description" +msgid "nextid.description" msgstr "" #, elixir-autogen, elixir-format -msgid "panlid.title" +msgid "nextid.title" msgstr "" #, elixir-autogen, elixir-format @@ -115,15 +115,15 @@ msgid "ethical.description" msgstr "" #, elixir-autogen, elixir-format -msgid "study.link.description" +msgid "link.description" msgstr "" #, elixir-autogen, elixir-format -msgid "study.link.title" +msgid "link.title" msgstr "" #, elixir-autogen, elixir-format -msgid "panlid.instructions.link" +msgid "nextid.instructions.link" msgstr "" #, elixir-autogen, elixir-format diff --git a/core/priv/gettext/eyra-assignment.pot b/core/priv/gettext/eyra-assignment.pot index ded56e9fa..ca27d3fca 100644 --- a/core/priv/gettext/eyra-assignment.pot +++ b/core/priv/gettext/eyra-assignment.pot @@ -93,3 +93,83 @@ msgstr "" #, elixir-autogen, elixir-format msgid "settings.data_storage.title" msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.title" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connect.button" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.cancel.button" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.connect.button" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.panel.text" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.panel.title" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.storage.text" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.storage.title" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "settings.data_storage.body" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "settings.panel.body" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "settings.panel.title" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "disconnect.button" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel_form.type.label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.liss.connector.annotation" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.generic.connector.annotation" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.ioresearch.connector.annotation" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.generic.connection.annotation" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.ioresearch.connection.annotation" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "panel.liss.connection.annotation" +msgstr "" diff --git a/core/priv/gettext/eyra-enums.pot b/core/priv/gettext/eyra-enums.pot index 3db21b754..b5fc1b3e3 100644 --- a/core/priv/gettext/eyra-enums.pot +++ b/core/priv/gettext/eyra-enums.pot @@ -351,17 +351,25 @@ msgid "platforms.tiktok" msgstr "" #, elixir-autogen, elixir-format -msgid "storage_backend_types.azure" +msgid "storage_service_ids.azure" msgstr "" #, elixir-autogen, elixir-format -msgid "storage_backend_types.centerdata" +msgid "storage_service_ids.yoda" msgstr "" #, elixir-autogen, elixir-format -msgid "storage_backend_types.yoda" +msgid "storage_service_ids.aws" msgstr "" #, elixir-autogen, elixir-format -msgid "storage_backend_types.aws" +msgid "external_panel_ids.generic" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.ioresearch" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.liss" msgstr "" diff --git a/core/priv/gettext/eyra-project.pot b/core/priv/gettext/eyra-project.pot index a12b9b690..94192cb46 100644 --- a/core/priv/gettext/eyra-project.pot +++ b/core/priv/gettext/eyra-project.pot @@ -24,11 +24,11 @@ msgid "tabbar.item.config.forward" msgstr "" #, elixir-autogen, elixir-format -msgid "tabbar.item.invite" +msgid "tabbar.item.panel" msgstr "" #, elixir-autogen, elixir-format -msgid "tabbar.item.invite.forward" +msgid "tabbar.item.panel.forward" msgstr "" #, elixir-autogen, elixir-format @@ -79,10 +79,6 @@ msgstr "" msgid "form.name.label" msgstr "" -#, elixir-autogen, elixir-format -msgid "invite.title" -msgstr "" - #, elixir-autogen, elixir-format msgid "monitor.title" msgstr "" @@ -111,18 +107,6 @@ msgstr "" msgid "add.new.item.button.short" msgstr "" -#, elixir-autogen, elixir-format -msgid "campaign.invite.title" -msgstr "" - -#, elixir-autogen, elixir-format -msgid "direct.invite.description" -msgstr "" - -#, elixir-autogen, elixir-format -msgid "direct.invite.title" -msgstr "" - #, elixir-autogen, elixir-format msgid "label.concept" msgstr "" @@ -190,3 +174,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "tabbar.item.settings.forward" msgstr "" + +#, elixir-autogen, elixir-format +msgid "tabbar.item.invite" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "tabbar.item.invite.forward" +msgstr "" diff --git a/core/priv/gettext/eyra-storage.pot b/core/priv/gettext/eyra-storage.pot index 94ec49e94..5cc416175 100644 --- a/core/priv/gettext/eyra-storage.pot +++ b/core/priv/gettext/eyra-storage.pot @@ -47,10 +47,6 @@ msgstr "" msgid "centerdata.url.label" msgstr "" -#, elixir-autogen, elixir-format -msgid "centerdata.url.placeholder" -msgstr "" - #, elixir-autogen, elixir-format msgid "yoda.password.label" msgstr "" diff --git a/core/priv/gettext/eyra-ui.pot b/core/priv/gettext/eyra-ui.pot index 8d6d38a15..6239d7106 100644 --- a/core/priv/gettext/eyra-ui.pot +++ b/core/priv/gettext/eyra-ui.pot @@ -245,3 +245,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "terms.link" msgstr "" + +#, elixir-autogen, elixir-format +msgid "edit.button" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "copy.clipboard.button" +msgstr "" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-account.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-account.po index b699299ea..8b534c4c5 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-account.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-account.po @@ -128,7 +128,7 @@ msgstr "Log in" #, elixir-autogen, elixir-format msgid "login.surf.message.part1" -msgstr "Om in te loggen op Panl kun je gebruik maken van je VU Amsterdam account. Dit werkt via " +msgstr "Om in te loggen op Next kun je gebruik maken van je VU Amsterdam account. Dit werkt via " #, elixir-autogen, elixir-format msgid "login.surf.message.part2" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-alliance.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-alliance.po index 98528df05..53f2ac11b 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-alliance.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-alliance.po @@ -95,12 +95,12 @@ msgid "ethical.title" msgstr "Ethische toetsing" #, elixir-autogen, elixir-format -msgid "panlid.description" -msgstr "Panl voegt automatisch een ID van de deelnemer toe aan de URL van je Qualtrics vragenlijst. Om deze ID in Qualtrics te kunnen gebruiken, voeg je een \"embedded data element field\" toe met de naam \"panl_id\". Instructies hiervoor %{link} in \"PART 2\"." +msgid "nextid.description" +msgstr "Next voegt automatisch een ID van de deelnemer toe aan de URL van je Qualtrics vragenlijst. Om deze ID in Qualtrics te kunnen gebruiken, voeg je een \"embedded data element field\" toe met de naam \"next_id\". Instructies hiervoor %{link} in \"PART 2\"." #, elixir-autogen, elixir-format -msgid "panlid.title" -msgstr "Panl ID" +msgid "nextid.title" +msgstr "Next ID" #, elixir-autogen, elixir-format msgid "setup.title" @@ -115,15 +115,15 @@ msgid "ethical.description" msgstr "Zorg ervoor dat je de %{link} doorlopen hebt en dat je goedkeuring hebt van de SBE Ethics Review Board (ERB), wanneer dat vereist is voor jouw studie. Vul de ERB code in die je ontvangen hebt." #, elixir-autogen, elixir-format -msgid "study.link.description" +msgid "link.description" msgstr "Volg %{link} om jouw Qualtrics vragenlijst link aan te maken op te halen. Plak deze link in het \"Vragenlijst link\" veld van het formulier hieronder." #, elixir-autogen, elixir-format -msgid "study.link.title" -msgstr "Anonymous Questionnaire Link" +msgid "link.title" +msgstr "Vragenlijst Link" #, elixir-autogen, elixir-format -msgid "panlid.instructions.link" +msgid "nextid.instructions.link" msgstr "vind je hier" #, elixir-autogen, elixir-format diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po index ad14bddd0..ef16a9c7a 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po @@ -36,7 +36,7 @@ msgstr "Opdracht" #, elixir-autogen, elixir-format, ex-autogen msgid "ticket.title" -msgstr "Jouw Panl ID" +msgstr "Jouw Next ID" #, elixir-autogen, elixir-format, ex-autogen msgid "earned.label" @@ -60,7 +60,7 @@ msgstr "Afronden" #, elixir-autogen, elixir-format, fuzzy msgid "content.title" -msgstr "Jouw Panl ID" +msgstr "Jouw Next ID" #, elixir-autogen, elixir-format msgid "open.button" @@ -93,3 +93,83 @@ msgstr "Instellingen" #, elixir-autogen, elixir-format msgid "settings.data_storage.title" msgstr "Opslag van deelnemers data" + +#, elixir-autogen, elixir-format +msgid "panel.title" +msgstr "Panel" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.label" +msgstr "Selecteer jou panel" + +#, elixir-autogen, elixir-format, fuzzy +msgid "connect.button" +msgstr "Koppeling maken" + +#, elixir-autogen, elixir-format +msgid "connector.cancel.button" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.connect.button" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.panel.text" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.panel.title" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.storage.text" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "connector.storage.title" +msgstr "" + +#, elixir-autogen, elixir-format, fuzzy +msgid "settings.data_storage.body" +msgstr "Opslag van deelnemers data" + +#, elixir-autogen, elixir-format +msgid "settings.panel.body" +msgstr "" + +#, elixir-autogen, elixir-format, fuzzy +msgid "settings.panel.title" +msgstr "Instellingen" + +#, elixir-autogen, elixir-format, fuzzy +msgid "disconnect.button" +msgstr "Ontkoppel" + +#, elixir-autogen, elixir-format +msgid "panel_form.type.label" +msgstr "Kies jouw panel" + +#, elixir-autogen, elixir-format +msgid "panel.liss.connector.annotation" +msgstr "

LISS

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
Ga naar www.lissdata.nl voor meer informatie.
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.generic.connector.annotation" +msgstr "

Other

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
" + +#, elixir-autogen, elixir-format +msgid "panel.ioresearch.connector.annotation" +msgstr "

I&O Research

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
Ga naar www.ioresearch.nl voor meer informatie.
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.generic.connection.annotation" +msgstr "

Other

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.ioresearch.connection.annotation" +msgstr "

I&O Research

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
Ga naar www.ioresearch.nl voor meer informatie.
" + +#, elixir-autogen, elixir-format, fuzzy +msgid "panel.liss.connection.annotation" +msgstr "

LISS

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
Ga naar www.lissdata.nl voor meer informatie.
" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-crew.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-crew.po index 20835fa30..7ca1628f2 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-crew.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-crew.po @@ -36,4 +36,4 @@ msgstr "✅ Round trip bevestigd" #, elixir-autogen, elixir-format, ex-autogen msgid "tester.completed.text" -msgstr "Als de titel van deze pagina overeenkomt met de titel van jouw campagne, dan is de \"round trip\" van Panl naar Qualtrics en terug geslaagd." +msgstr "Als de titel van deze pagina overeenkomt met de titel van jouw campagne, dan is de \"round trip\" van Next naar Qualtrics en terug geslaagd." diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-dashboard.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-dashboard.po index 686a0cbe7..fd22e546a 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-dashboard.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-dashboard.po @@ -20,7 +20,7 @@ msgstr "Recente items" #, elixir-autogen, elixir-format msgid "empty.description" -msgstr "Zodra je aan de slag gaat met Panl, vind je op deze plek een overzicht van je activiteiten. Bezoek je console om snel weer verder te gaan waar je bij je vorige bezoek was gestopt." +msgstr "Zodra je aan de slag gaat met Next, vind je op deze plek een overzicht van je activiteiten. Bezoek je console om snel weer verder te gaan waar je bij je vorige bezoek was gestopt." #, elixir-autogen, elixir-format msgid "empty.title" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po index 2d33d5d9e..2762ea632 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po @@ -351,17 +351,25 @@ msgid "platforms.tiktok" msgstr "TikTok" #, elixir-autogen, elixir-format -msgid "storage_backend_types.azure" +msgid "storage_service_ids.azure" msgstr "Azure Blob" #, elixir-autogen, elixir-format -msgid "storage_backend_types.centerdata" -msgstr "Centerdata" - -#, elixir-autogen, elixir-format -msgid "storage_backend_types.yoda" +msgid "storage_service_ids.yoda" msgstr "Yoda" #, elixir-autogen, elixir-format, fuzzy -msgid "storage_backend_types.aws" +msgid "storage_service_ids.aws" msgstr "Amazon S3" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.generic" +msgstr "Andere" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.ioresearch" +msgstr "I&O Research" + +#, elixir-autogen, elixir-format +msgid "external_panel_ids.liss" +msgstr "LISS" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po index 932a07b11..8cf2a8835 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po @@ -24,11 +24,11 @@ msgid "tabbar.item.config.forward" msgstr "Ga naar Instellingen" #, elixir-autogen, elixir-format -msgid "tabbar.item.invite" +msgid "tabbar.item.panel" msgstr "Uitnodigen" #, elixir-autogen, elixir-format -msgid "tabbar.item.invite.forward" +msgid "tabbar.item.panel.forward" msgstr "Ga naar Uitnodigen" #, elixir-autogen, elixir-format @@ -79,10 +79,6 @@ msgstr "Instellingen" msgid "form.name.label" msgstr "Naam" -#, elixir-autogen, elixir-format -msgid "invite.title" -msgstr "Invite" - #, elixir-autogen, elixir-format msgid "monitor.title" msgstr "Monitor" @@ -111,18 +107,6 @@ msgstr "Voeg nieuw item toe" msgid "add.new.item.button.short" msgstr "Nieuw item" -#, elixir-autogen, elixir-format -msgid "campaign.invite.title" -msgstr "Nodig mensen uit met een campagne" - -#, elixir-autogen, elixir-format -msgid "direct.invite.description" -msgstr "Je kan mensen direct uitnodigigen op de onderstaande url." - -#, elixir-autogen, elixir-format -msgid "direct.invite.title" -msgstr "Nodig mensen uit met een url" - #, elixir-autogen, elixir-format msgid "label.concept" msgstr "Concept" @@ -190,3 +174,11 @@ msgstr "Instellingen" #, elixir-autogen, elixir-format, fuzzy msgid "tabbar.item.settings.forward" msgstr "Ga naar Instellingen" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.invite" +msgstr "Uitnodigen" + +#, elixir-autogen, elixir-format, fuzzy +msgid "tabbar.item.invite.forward" +msgstr "Ga naar Instellingen" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po index d1a8ad45a..141a00a7e 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-storage.po @@ -47,10 +47,6 @@ msgstr "SAS token" msgid "centerdata.url.label" msgstr "URL" -#, elixir-autogen, elixir-format -msgid "centerdata.url.placeholder" -msgstr "" - #, elixir-autogen, elixir-format msgid "yoda.password.label" msgstr "Wachtwoord" diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po index 3a72c717d..407f32385 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po +++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po @@ -245,3 +245,11 @@ msgstr "Privacy" #, elixir-autogen, elixir-format msgid "terms.link" msgstr "Voorwaarden" + +#, elixir-autogen, elixir-format, fuzzy +msgid "edit.button" +msgstr "Bewerken" + +#, elixir-autogen, elixir-format +msgid "copy.clipboard.button" +msgstr "Kopieer" diff --git a/core/priv/repo/migrations/20231025125051_add_storage.exs b/core/priv/repo/migrations/20231025125051_add_storage.exs index bbeb491eb..8eb43326c 100644 --- a/core/priv/repo/migrations/20231025125051_add_storage.exs +++ b/core/priv/repo/migrations/20231025125051_add_storage.exs @@ -58,12 +58,15 @@ defmodule Core.Repo.Migrations.AddStorage do alter table(:assignments) do add(:storage_endpoint_id, references(:storage_endpoints, on_delete: :nilify_all), null: true) + + add(:external_panel, :string, null: true) end end def down do alter table(:assignments) do remove(:storage_endpoint_id) + remove(:external_panel) end drop(constraint(:storage_endpoints, :must_have_at_most_one_special)) diff --git a/core/systems/alliance/tool_form.ex b/core/systems/alliance/tool_form.ex index a00f1718a..1bb327a82 100644 --- a/core/systems/alliance/tool_form.ex +++ b/core/systems/alliance/tool_form.ex @@ -52,8 +52,8 @@ defmodule Systems.Alliance.ToolForm do # if changeset.valid? do # Directable.director(entity).assign_tester_role(entity, user) - # fake_panl_id = "TEST-" <> Faker.UUID.v4() - # external_path = Alliance.ToolModel.external_path(entity, fake_panl_id) + # fake_next_id = "TEST-" <> Faker.UUID.v4() + # external_path = Alliance.ToolModel.external_path(entity, fake_next_id) # {:noreply, LiveView.redirect(socket, external: external_path)} # else @@ -108,9 +108,9 @@ defmodule Systems.Alliance.ToolForm do ) end - defp panlid_instructions_link() do + defp nextid_instructions_link() do link_as_string( - dgettext("eyra-alliance", "panlid.instructions.link"), + dgettext("eyra-alliance", "nextid.instructions.link"), "https://www.qualtrics.com/support/survey-platform/survey-module/survey-flow/standard-elements/passing-information-through-query-strings/?parent=p001135#PassingInformationIntoAQuestionnaire" ) end @@ -147,9 +147,9 @@ defmodule Systems.Alliance.ToolForm do <.step_indicator text="1" bg_color="bg-tertiary" text_color="text-grey1" />
- <%= dgettext("eyra-alliance", "panlid.title") %> + <%= dgettext("eyra-alliance", "nextid.title") %> <.spacing value="XS" /> - <%= raw(dgettext("eyra-alliance", "panlid.description", link: panlid_instructions_link())) %> + <%= raw(dgettext("eyra-alliance", "nextid.description", link: nextid_instructions_link())) %>
@@ -184,9 +184,9 @@ defmodule Systems.Alliance.ToolForm do <.step_indicator text="3" bg_color="bg-tertiary" text_color="text-grey1" />
- <%= dgettext("eyra-alliance", "study.link.title") %> + <%= dgettext("eyra-alliance", "link.title") %> <.spacing value="XS" /> - <%= raw(dgettext("eyra-alliance", "study.link.description", link: study_instructions_link())) %> + <%= raw(dgettext("eyra-alliance", "link.description", link: study_instructions_link())) %>
diff --git a/core/systems/alliance/tool_model.ex b/core/systems/alliance/tool_model.ex index a20234c9e..8723dd029 100644 --- a/core/systems/alliance/tool_model.ex +++ b/core/systems/alliance/tool_model.ex @@ -148,14 +148,14 @@ defmodule Systems.Alliance.ToolModel do |> to_string() end - def external_path(%{url: url}, panl_id) + def external_path(%{url: url}, next_id) when not is_nil(url) do url_components = URI.parse(url) query = url_components.query |> decode_query() - |> Map.put(:panl_id, panl_id) + |> Map.put(:next_id, next_id) |> URI.encode_query(:rfc3986) url_components diff --git a/core/systems/assignment/_private.ex b/core/systems/assignment/_private.ex index 4e1f87f9e..162c18f81 100644 --- a/core/systems/assignment/_private.ex +++ b/core/systems/assignment/_private.ex @@ -1,11 +1,46 @@ defmodule Systems.Assignment.Private do + use CoreWeb, :verified_routes + require Logger alias Systems.{ + Assignment, Workflow, - Crew + Crew, + Storage } + def allowed_external_panel_ids() do + Keyword.get(config(), :external_panels, []) + end + + defp config() do + Application.get_env(:core, :assignment) + end + + def panel_function_component(%Assignment.Model{external_panel: nil}), do: nil + + def panel_function_component(%Assignment.Model{external_panel: :liss}), + do: &Assignment.PanelViews.liss/1 + + def panel_function_component(%Assignment.Model{external_panel: _}), + do: &Assignment.PanelViews.default/1 + + def connector_popup_module(:storage), do: Assignment.ConnectorPopupStorage + def connector_popup_module(:panel), do: Assignment.ConnectorPopupPanel + + def connection_view_module(:storage), do: Assignment.ConnectionViewStorage + def connection_view_module(:panel), do: Assignment.ConnectionViewPanel + + def connection_title(:storage, %{storage_endpoint: storage_endpoint}), + do: connection_title(:storage, Storage.EndpointModel.special_field(storage_endpoint)) + + def connection_title(:storage, storage_service_id), + do: Storage.ServiceIds.translate(storage_service_id) + + def connection_title(:panel, %{external_panel: external_panel}), + do: Assignment.ExternalPanelIds.translate(external_panel) + def task_template(%{special: :data_donation}, %Workflow.ItemModel{id: item_id}) do ["item=#{item_id}"] end diff --git a/core/systems/assignment/_public.ex b/core/systems/assignment/_public.ex index 07c4b1fcc..5287fa452 100644 --- a/core/systems/assignment/_public.ex +++ b/core/systems/assignment/_public.ex @@ -44,50 +44,20 @@ defmodule Systems.Assignment.Public do |> Repo.all() end - def list_by_workflow(workflow, preload \\ []) + def get_by(association, preload \\ []) + def get_by(%Assignment.InfoModel{id: id}, preload), do: get_by(:info_id, id, preload) - def list_by_workflow(%{id: workflow_id}, preload), do: list_by_workflow(workflow_id, preload) + def get_by(%Storage.EndpointModel{id: id}, preload), + do: get_by(:storage_endpoint_id, id, preload) - def list_by_workflow(workflow_id, preload) when is_number(workflow_id) do - from(a in Assignment.Model, where: a.workflow_id == ^workflow_id, preload: ^preload) - |> Repo.all() - end - - def get_by_info!(info, preload \\ []) - - def get_by_info!(%Assignment.InfoModel{id: id}, preload), do: get_by_info!(id, preload) - - def get_by_info!(info_id, preload) do - from(assignment in Assignment.Model, - where: assignment.info_id == ^info_id, - preload: ^preload - ) - |> Repo.one!() - end - - def get_by_consent_agreement(consent_agreement, preload \\ []) - - def get_by_consent_agreement(%Consent.AgreementModel{id: id}, preload), - do: get_by_consent_agreement(id, preload) - - def get_by_consent_agreement(consent_agreement_id, preload) do - from(assignment in Assignment.Model, - where: assignment.consent_agreement_id == ^consent_agreement_id, - preload: ^preload - ) - |> Repo.one() - end + def get_by(%Consent.AgreementModel{id: id}, preload), + do: get_by(:consent_agreement_id, id, preload) - def get_by_workflow(workflow, preload \\ []) + def get_by(%Workflow.Model{id: id}, preload), do: get_by(:workflow_id, id, preload) - def get_by_workflow(%Workflow.Model{id: id}, preload), do: get_by_workflow(id, preload) - - def get_by_workflow(workflow_id, preload) do - from(assignment in Assignment.Model, - where: assignment.workflow_id == ^workflow_id, - preload: ^preload - ) - |> Repo.one() + def get_by(field_name, id, preload) when is_atom(field_name) do + Repo.get_by(Assignment.Model, [{field_name, id}]) + |> Repo.preload(preload) end def get_by_tool_ref(workflow, preload \\ []) @@ -178,10 +148,11 @@ defmodule Systems.Assignment.Public do end def delete_storage_endpoint!(assignment) do - {:ok, assignment} = + changeset = Assignment.Model.changeset(assignment, %{}) |> Ecto.Changeset.put_assoc(:storage_endpoint, nil) - |> Repo.update() + + {:ok, assignment} = Core.Persister.save(changeset.data, changeset) assignment end @@ -248,10 +219,17 @@ defmodule Systems.Assignment.Public do |> Repo.transaction() end - def update(assignment, budget) do - assignment - |> Assignment.Model.changeset(budget) - |> Repo.update!() + def update(assignment, %{} = attrs) do + changeset = Assignment.Model.changeset(assignment, attrs) + Core.Persister.save(assignment, changeset) + end + + def update_budget(assignment, budget) do + changeset = + Assignment.Model.changeset(assignment, %{}) + |> Ecto.Changeset.put_assoc(:budget, budget) + + Core.Persister.save(assignment, changeset) end def is_owner?(assignment, user) do @@ -625,3 +603,12 @@ defmodule Systems.Assignment.Public do Budget.Public.rewarded_amount(idempotence_key) end end + +defimpl Core.Persister, for: Systems.Assignment.Model do + def save(_assignment, changeset) do + case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :assignment) do + {:ok, %{assignment: assignment}} -> {:ok, assignment} + _ -> {:error, changeset} + end + end +end diff --git a/core/systems/assignment/_routes.ex b/core/systems/assignment/_routes.ex index b136b28ce..fd9a3eb22 100644 --- a/core/systems/assignment/_routes.ex +++ b/core/systems/assignment/_routes.ex @@ -8,6 +8,21 @@ defmodule Systems.Assignment.Routes do live("/assignment/:id/content", ContentPage) get("/assignment/callback/:item", Controller, :callback) end + + scope "/assignment", Systems.Assignment.Centerdata do + pipe_through([:browser]) + live("/centerdata/fakeapi/page", Centerdata.FakeApiPage) + end + + scope "/assignment", Systems.Assignment do + pipe_through([:browser]) + get("/:id/:panel", ExternalPanelController, :create) + end + + scope "/assignment/centerdata", Systems.Assignment do + pipe_through([:browser_unprotected]) + post("/:id/:panel", ExternalPanelController, :create) + end end end end diff --git a/core/systems/assignment/_switch.ex b/core/systems/assignment/_switch.ex index 1fd20ee67..b5881f421 100644 --- a/core/systems/assignment/_switch.ex +++ b/core/systems/assignment/_switch.ex @@ -17,8 +17,7 @@ defmodule Systems.Assignment.Switch do @impl true def intercept({:workflow, _} = signal, %{workflow: workflow} = message) do - if assignment = - Assignment.Public.get_by_workflow(workflow, Assignment.Model.preload_graph(:down)) do + if assignment = Assignment.Public.get_by(workflow, Assignment.Model.preload_graph(:down)) do dispatch!( {:assignment, signal}, Map.merge(message, %{assignment: assignment}) @@ -28,7 +27,18 @@ defmodule Systems.Assignment.Switch do @impl true def intercept({:assignment_info, _} = signal, %{info: info} = message) do - if assignment = Assignment.Public.get_by_info!(info, Assignment.Model.preload_graph(:down)) do + if assignment = Assignment.Public.get_by(info, Assignment.Model.preload_graph(:down)) do + handle( + {:assignment, signal}, + Map.merge(message, %{assignment: assignment}) + ) + end + end + + @impl true + def intercept({:storage_endpoint, _} = signal, %{storage_endpoint: storage_endpoint} = message) do + if assignment = + Assignment.Public.get_by(storage_endpoint, Assignment.Model.preload_graph(:down)) do handle( {:assignment, signal}, Map.merge(message, %{assignment: assignment}) @@ -46,7 +56,7 @@ defmodule Systems.Assignment.Switch do %{consent_agreement: consent_agreement} = message ) do if assignment = - Assignment.Public.get_by_consent_agreement( + Assignment.Public.get_by( consent_agreement, Assignment.Model.preload_graph(:down) ) do diff --git a/core/systems/assignment/centerdata/fakeapi_controller.ex b/core/systems/assignment/centerdata/fakeapi_controller.ex new file mode 100644 index 000000000..afefd7efa --- /dev/null +++ b/core/systems/assignment/centerdata/fakeapi_controller.ex @@ -0,0 +1,11 @@ +defmodule Systems.Assignment.Centerdata.FakeApiController do + use CoreWeb, :controller + + def create( + conn, + params + ) do + path = Routes.live_path(conn, Systems.Assignment.Centerdata.FakeApiPage, params: params) + redirect(conn, to: path) + end +end diff --git a/core/systems/union/centerdata/fakeapi_page.ex b/core/systems/assignment/centerdata/fakeapi_page.ex similarity index 87% rename from core/systems/union/centerdata/fakeapi_page.ex rename to core/systems/assignment/centerdata/fakeapi_page.ex index 6ddfdfac1..00164a620 100644 --- a/core/systems/union/centerdata/fakeapi_page.ex +++ b/core/systems/assignment/centerdata/fakeapi_page.ex @@ -1,4 +1,4 @@ -defmodule Systems.Union.Centerdata.FakeApiPage do +defmodule Systems.Assignment.Centerdata.FakeApiPage do use CoreWeb, :live_view use CoreWeb.LiveLocale use CoreWeb.LiveAssignHelper @@ -6,13 +6,6 @@ defmodule Systems.Union.Centerdata.FakeApiPage do import Phoenix.Component - # data(page, :any) - # data(qu_1, :any) - # data(respondent, :any) - # data(token, :any) - # data(quest, :any) - # data(button_next, :any) - @impl true def mount( %{ diff --git a/core/systems/assignment/connection_view.ex b/core/systems/assignment/connection_view.ex new file mode 100644 index 000000000..9a317a821 --- /dev/null +++ b/core/systems/assignment/connection_view.ex @@ -0,0 +1,121 @@ +defmodule Systems.Assignment.ConnectionView do + use CoreWeb, :live_component + use Fabric.LiveComponent + + alias Frameworks.Pixel.Panel + + alias Systems.{ + Assignment + } + + @impl true + def update( + %{ + id: id, + type: type, + assignment: assignment, + connection: connection, + uri_origin: uri_origin + }, + socket + ) do + { + :ok, + socket + |> assign( + id: id, + type: type, + assignment: assignment, + connection: connection, + uri_origin: uri_origin + ) + |> update_title() + |> update_buttons() + |> update_special_view() + } + end + + defp update_title(%{assigns: %{type: type, assignment: assignment}} = socket) do + title = Assignment.Private.connection_title(type, assignment) + assign(socket, title: title) + end + + defp update_buttons(%{assigns: %{myself: myself}} = socket) do + disconnect_button = %{ + action: %{type: :send, target: myself, event: "disconnect"}, + face: %{ + type: :label, + icon: :disconnect_tertiary, + height: "h-4", + text_color: "text-tertiary", + label: dgettext("eyra-assignment", "disconnect.button") + } + } + + edit_button = %{ + action: %{type: :send, target: myself, event: "edit"}, + face: %{ + type: :label, + icon: :edit_tertiary, + height: "h-4", + text_color: "text-tertiary", + label: dgettext("eyra-ui", "edit.button") + } + } + + assign(socket, buttons: [edit_button, disconnect_button]) + end + + defp update_special_view( + %{assigns: %{type: type, assignment: assignment, myself: myself, uri_origin: uri_origin}} = + socket + ) do + special_view = %{ + id: "connection_view_#{type}", + module: Assignment.Private.connection_view_module(type), + assignment: assignment, + uri_origin: uri_origin, + parent: myself + } + + assign(socket, special_view: special_view) + end + + @impl true + def handle_event( + "disconnect", + _payload, + %{assigns: %{special_view: %{id: id, module: module}}} = socket + ) do + send_update(module, id: id, event: :disconnect) + {:noreply, socket} + end + + @impl true + def handle_event("edit", _payload, socket) do + {:noreply, socket |> send_event(:parent, "edit")} + end + + @impl true + def render(assigns) do + ~H""" +
+ + <:title> +
+
+ <%= @title %> +
+
+ <%= for button <- @buttons do %> + + <% end %> +
+ + <.spacing value="S" /> + <.live_component {@special_view} /> + +
+ """ + end +end diff --git a/core/systems/assignment/connection_view_panel.ex b/core/systems/assignment/connection_view_panel.ex new file mode 100644 index 000000000..27176856a --- /dev/null +++ b/core/systems/assignment/connection_view_panel.ex @@ -0,0 +1,100 @@ +defmodule Systems.Assignment.ConnectionViewPanel do + use CoreWeb.LiveForm + + alias Frameworks.Pixel.Annotation + + alias Systems.{ + Assignment + } + + @impl true + def update(%{event: :disconnect}, %{assigns: %{assignment: assignment}} = socket) do + changeset = Assignment.Model.changeset(assignment, %{external_panel: nil}) + + { + :ok, + socket + |> save(changeset) + } + end + + @impl true + def update(%{id: id, assignment: assignment, uri_origin: uri_origin}, socket) do + { + :ok, + socket + |> assign( + id: id, + assignment: assignment, + uri_origin: uri_origin + ) + |> update_annotation() + |> update_url() + } + end + + defp update_annotation(%{assigns: %{assignment: %{external_panel: nil}}} = socket) do + assign(socket, annotation: nil) + end + + defp update_annotation(%{assigns: %{assignment: %{external_panel: external_panel}}} = socket) do + annotation = + case external_panel do + :liss -> dgettext("eyra-assignment", "panel.liss.connection.annotation") + :ioresearch -> dgettext("eyra-assignment", "panel.ioresearch.connection.annotation") + :generic -> dgettext("eyra-assignment", "panel.generic.connection.annotation") + end + + assign(socket, annotation: annotation) + end + + defp update_url(%{assigns: %{assignment: %{external_panel: nil}}} = socket) do + assign(socket, url: nil) + end + + defp update_url( + %{ + assigns: %{ + assignment: %{id: id, external_panel: external_panel}, + uri_origin: uri_origin + } + } = socket + ) do + relative_url = + case external_panel do + :liss -> "/assignment/#{id}/liss" + :ioresearch -> "/assignment/#{id}/ioresearch?participant=&language=nl" + :generic -> "/assignment/#{id}/participate?participant=&language=nl" + end + + assign(socket, url: uri_origin <> relative_url) + end + + @impl true + def render(assigns) do + ~H""" +
+ <%= if @annotation do %> + + <% end %> + <%= if @url do %> + <.spacing value="S" /> +
+
+ <%= @url %> +
+
+
+ +
+
+
+ <% end %> +
+ """ + end +end diff --git a/core/systems/assignment/connection_view_storage.ex b/core/systems/assignment/connection_view_storage.ex new file mode 100644 index 000000000..054803713 --- /dev/null +++ b/core/systems/assignment/connection_view_storage.ex @@ -0,0 +1,35 @@ +defmodule Systems.Assignment.ConnectionViewStorage do + use CoreWeb, :live_component + + alias Systems.{ + Assignment + } + + @impl true + def update(%{event: :disconnect}, %{assigns: %{assignment: assignment}} = socket) do + Assignment.Public.delete_storage_endpoint!(assignment) + {:ok, socket} + end + + @impl true + def update(%{assignment: assignment}, socket) do + { + :ok, + socket + |> assign(assignment: assignment) + } + end + + @impl true + def handle_event("change", _payload, socket) do + {:noreply, socket} + end + + @impl true + def render(assigns) do + ~H""" +
+
+ """ + end +end diff --git a/core/systems/assignment/connector_popup_panel.ex b/core/systems/assignment/connector_popup_panel.ex new file mode 100644 index 000000000..dea885588 --- /dev/null +++ b/core/systems/assignment/connector_popup_panel.ex @@ -0,0 +1,169 @@ +defmodule Systems.Assignment.ConnectorPopupPanel do + use CoreWeb, :live_component + use Fabric.LiveComponent + + import CoreWeb.UI.Dialog + alias Frameworks.Pixel.Annotation + alias Frameworks.Pixel.Selector + alias Frameworks.Pixel.Panel + + alias Systems.{ + Assignment + } + + # Handle Selector Update + @impl true + def update( + %{active_item_id: panel_type, selector_id: :panel_type_selector}, + socket + ) do + { + :ok, + socket + |> assign(selected_type: panel_type) + |> update_type_selector() + |> update_annotation() + } + end + + @impl true + def update(%{id: id, entity: entity}, socket) do + { + :ok, + socket + |> assign( + id: id, + entity: entity + ) + |> update_title() + |> update_text() + |> update_buttons() + |> update_selected_type() + |> update_type_selector() + |> update_annotation() + } + end + + defp update_title(socket) do + title = dgettext("eyra-assignment", "connector.panel.title") + assign(socket, title: title) + end + + defp update_text(socket) do + text = dgettext("eyra-assignment", "connector.panel.text") + assign(socket, text: text) + end + + defp update_buttons(%{assigns: %{myself: myself}} = socket) do + buttons = [ + %{ + action: %{type: :send, target: myself, event: "connect"}, + face: %{type: :primary, label: dgettext("eyra-assignment", "connector.connect.button")} + }, + %{ + action: %{type: :send, target: myself, event: "cancel"}, + face: %{type: :label, label: dgettext("eyra-assignment", "connector.cancel.button")} + } + ] + + assign(socket, buttons: buttons) + end + + defp update_selected_type(%{assigns: %{entity: %{external_panel: external_panel}}} = socket) do + assign(socket, selected_type: external_panel) + end + + defp update_type_selector(%{assigns: %{id: id, selected_type: selected_type}} = socket) do + items = + Assignment.ExternalPanelIds.labels( + selected_type, + Assignment.Private.allowed_external_panel_ids() + ) + + type_selector = %{ + module: Selector, + id: :panel_type_selector, + grid_options: "flex flex-row gap-8", + items: items, + type: :radio, + parent: %{id: id, type: __MODULE__} + } + + assign(socket, type_selector: type_selector) + end + + defp update_annotation(%{assigns: %{selected_type: nil}} = socket) do + assign(socket, annotation: nil) + end + + defp update_annotation(%{assigns: %{selected_type: selected_type}} = socket) do + annotation = + case selected_type do + :liss -> dgettext("eyra-assignment", "panel.liss.connector.annotation") + :ioresearch -> dgettext("eyra-assignment", "panel.ioresearch.connector.annotation") + :generic -> dgettext("eyra-assignment", "panel.generic.connector.annotation") + end + + annotation_title = Assignment.ExternalPanelIds.translate(selected_type) + + socket + |> assign(annotation: annotation) + |> assign(annotation_title: annotation_title) + end + + @impl true + def handle_event("connect", _payload, socket) do + { + :noreply, + socket |> connect() + } + end + + @impl true + def handle_event("cancel", _payload, socket) do + { + :noreply, + socket |> cancel_popup() + } + end + + defp connect(%{assigns: %{selected_type: nil}} = socket) do + socket + end + + defp connect(%{assigns: %{selected_type: panel_type, entity: entity}} = socket) do + Assignment.Public.update(entity, %{external_panel: panel_type}) + socket |> send_event(:parent, "finish", %{connection: %{external_panel: panel_type}}) + end + + defp cancel_popup(socket) do + socket |> send_event(:parent, "cancel") + end + + @impl true + def render(assigns) do + ~H""" +
+ <.dialog {%{title: @title, text: @text, buttons: @buttons}}> + <%= dgettext("eyra-assignment", "panel_form.type.label") %> + <.spacing value="XS" /> +
+ <.live_component {@type_selector} /> +
+ <%= if @annotation do %> + <.spacing value="M" /> + + <:title> +
+ <%= @annotation_title %> +
+ + <.spacing value="XS" /> + +
+ <% end %> + +
+ """ + end +end diff --git a/core/systems/assignment/connector_popup_storage.ex b/core/systems/assignment/connector_popup_storage.ex new file mode 100644 index 000000000..925641c67 --- /dev/null +++ b/core/systems/assignment/connector_popup_storage.ex @@ -0,0 +1,133 @@ +defmodule Systems.Assignment.ConnectorPopupStorage do + use CoreWeb, :live_component + use Fabric.LiveComponent + + import CoreWeb.UI.Dialog + + alias Systems.{ + Storage + } + + @endpoint_form_key :endpoint_form + + @impl true + def update(%{id: id, entity: assignment}, socket) do + { + :ok, + socket + |> assign( + id: id, + entity: assignment + ) + |> update_title() + |> update_text() + |> update_buttons() + |> update_storage_endpoint() + |> update_storage_endpoint_form() + } + end + + defp update_title(socket) do + title = dgettext("eyra-assignment", "connector.storage.title") + assign(socket, title: title) + end + + defp update_text(socket) do + text = dgettext("eyra-assignment", "connector.storage.text") + assign(socket, text: text) + end + + defp update_buttons(%{assigns: %{myself: myself}} = socket) do + buttons = [ + %{ + action: %{type: :send, target: myself, event: "connect_storage"}, + face: %{type: :primary, label: dgettext("eyra-assignment", "connector.connect.button")} + }, + %{ + action: %{type: :send, target: myself, event: "cancel"}, + face: %{type: :label, label: dgettext("eyra-assignment", "connector.cancel.button")} + } + ] + + assign(socket, buttons: buttons) + end + + def update_storage_endpoint(%{assigns: %{entity: %{storage_endpoint_id: nil}}} = socket) do + assign(socket, storage_endpoint: %Storage.EndpointModel{}) + end + + def update_storage_endpoint( + %{assigns: %{entity: %{storage_endpoint: storage_endpoint}}} = socket + ) do + assign(socket, storage_endpoint: storage_endpoint) + end + + defp update_storage_endpoint_form(%{assigns: %{storage_endpoint: storage_endpoint}} = socket) do + child = + prepare_child(socket, @endpoint_form_key, Storage.EndpointForm, %{ + endpoint: storage_endpoint + }) + + show_child(socket, child) + end + + @impl true + def handle_event("connect_storage", _payload, socket) do + { + :noreply, + socket |> commit_form() + } + end + + @impl true + def handle_event("cancel", _payload, socket) do + { + :noreply, + socket |> cancel_popup() + } + end + + @impl true + def handle_event("update", %{source: %{id: @endpoint_form_key}, changeset: changeset}, socket) do + { + :noreply, + socket |> assign(endpoint_changeset: changeset) + } + end + + defp commit_form(%{assigns: %{endpoint_changeset: nil}} = socket) do + socket + end + + defp commit_form(%{assigns: %{endpoint_changeset: endpoint_changeset}} = socket) do + case Ecto.Changeset.apply_action(endpoint_changeset, :update) do + {:ok, endpoint} -> + socket + |> assign(endpoint: endpoint) + |> finish() + + {:error, _} -> + socket + |> send_event(@endpoint_form_key, "show_errors") + end + end + + defp cancel_popup(socket) do + socket |> send_event(:parent, "cancel") + end + + defp finish(%{assigns: %{endpoint: endpoint}} = socket) do + socket |> send_event(:parent, "finish", %{connection: endpoint}) + end + + @impl true + def render(assigns) do + ~H""" +
+ <.dialog {%{title: @title, text: @text, buttons: @buttons}}> + <.child id={:endpoint_form} fabric={@fabric}/> + +
+ """ + end +end diff --git a/core/systems/assignment/connector_view.ex b/core/systems/assignment/connector_view.ex new file mode 100644 index 000000000..5ff02d90f --- /dev/null +++ b/core/systems/assignment/connector_view.ex @@ -0,0 +1,126 @@ +defmodule Systems.Assignment.ConnectorView do + use CoreWeb, :live_component + use Fabric.LiveComponent + + alias Systems.{ + Assignment + } + + @impl true + def update( + %{ + id: id, + type: type, + assignment: assignment, + connection: connection, + uri_origin: uri_origin + }, + socket + ) do + { + :ok, + socket + |> assign( + id: id, + type: type, + assignment: assignment, + connection: connection, + uri_origin: uri_origin + ) + |> update_connect_button() + |> update_connection_view() + } + end + + defp update_connect_button(%{assigns: %{myself: myself}} = socket) do + connect_button = %{ + action: %{type: :send, target: myself, event: "connect"}, + face: %{type: :primary, label: dgettext("eyra-assignment", "connect.button")} + } + + assign(socket, connect_button: connect_button) + end + + defp update_connection_view(%{assigns: %{connection: nil}} = socket) do + assign(socket, connection_view: nil) + end + + defp update_connection_view( + %{ + assigns: %{ + id: id, + type: type, + connection: connection, + assignment: assignment, + uri_origin: uri_origin + } + } = socket + ) do + child = + prepare_child(socket, "#{id}_connection_view", Assignment.ConnectionView, %{ + assignment: assignment, + connection: connection, + type: type, + uri_origin: uri_origin + }) + + show_child(socket, child) + end + + @impl true + def handle_event( + "connect", + _payload, + %{assigns: %{type: type, assignment: assignment}} = socket + ) do + module = Assignment.Private.connector_popup_module(type) + + child = + prepare_child(socket, :connector_popup, module, %{ + entity: assignment + }) + + show_popup(socket, child) + {:noreply, socket} + end + + @impl true + def handle_event("edit", _payload, %{assigns: %{type: type, assignment: assignment}} = socket) do + module = Assignment.Private.connector_popup_module(type) + + child = + prepare_child(socket, :connector_popup, module, %{ + entity: assignment + }) + + show_popup(socket, child) + {:noreply, socket} + end + + @impl true + def handle_event("finish", %{source: %{id: :connector_popup}, connection: _connection}, socket) do + hide_popup(socket, :connector_popup) + {:noreply, socket} + end + + @impl true + def handle_event("cancel", %{source: %{id: :connector_popup}}, socket) do + hide_popup(socket, :connector_popup) + {:noreply, socket} + end + + @impl true + def render(assigns) do + ~H""" +
+ <%= if get_child(@fabric, "#{@id}_connection_view") do %> + <.child id={"#{@id}_connection_view"} fabric={@fabric}/> + <% else %> + <.wrap> + + + <% end %> +
+ """ + end +end diff --git a/core/systems/assignment/content_page.ex b/core/systems/assignment/content_page.ex index 1a02b5aec..1e4807770 100644 --- a/core/systems/assignment/content_page.ex +++ b/core/systems/assignment/content_page.ex @@ -1,5 +1,6 @@ defmodule Systems.Assignment.ContentPage do use Systems.Content.Page + use Fabric.LiveView alias Systems.{ Assignment, diff --git a/core/systems/assignment/content_page_builder.ex b/core/systems/assignment/content_page_builder.ex index 85773bf0c..a3a607f86 100644 --- a/core/systems/assignment/content_page_builder.ex +++ b/core/systems/assignment/content_page_builder.ex @@ -161,10 +161,16 @@ defmodule Systems.Assignment.ContentPageBuilder do :config, assignment, show_errors, - _assigns + %{fabric: fabric, uri_origin: uri_origin} = _assigns ) do ready? = false + child = + Fabric.prepare_child(fabric, :settings_form, Assignment.SettingsView, %{ + entity: assignment, + uri_origin: uri_origin + }) + %{ id: :settings_form, ready: ready?, @@ -172,10 +178,7 @@ defmodule Systems.Assignment.ContentPageBuilder do title: dgettext("eyra-project", "tabbar.item.settings"), forward_title: dgettext("eyra-project", "tabbar.item.settings.forward"), type: :fullpage, - live_component: Assignment.SettingsView, - props: %{ - entity: assignment - } + child: child } end @@ -285,23 +288,23 @@ defmodule Systems.Assignment.ContentPageBuilder do defp create_tab( :invite, - %{id: id}, + assignment, show_errors, %{uri_origin: uri_origin} ) do ready? = false - url = uri_origin <> "/assignment/#{id}" %{ - id: :invite_form, + id: :panel_form, ready: ready?, show_errors: show_errors, - title: dgettext("eyra-project", "tabbar.item.invite"), - forward_title: dgettext("eyra-project", "tabbar.item.invite.forward"), + title: dgettext("eyra-project", "tabbar.item.panel"), + forward_title: dgettext("eyra-project", "tabbar.item.panel.forward"), type: :fullpage, - live_component: Project.InviteForm, + live_component: Assignment.PanelForm, props: %{ - url: url + uri_origin: uri_origin, + entity: assignment } } end diff --git a/core/systems/assignment/controller.ex b/core/systems/assignment/controller.ex index 5642ee11c..af0879d98 100644 --- a/core/systems/assignment/controller.ex +++ b/core/systems/assignment/controller.ex @@ -9,7 +9,7 @@ defmodule Systems.Assignment.Controller do def callback(%{assigns: %{current_user: user}} = conn, %{"item" => item_id}) do %{workflow_id: workflow_id} = item = Workflow.Public.get_item!(String.to_integer(item_id)) - %{id: id, crew: crew} = assignment = Assignment.Public.get_by_workflow(workflow_id, [:crew]) + %{id: id, crew: crew} = assignment = Assignment.Public.get_by(workflow_id, [:crew]) Crew.Public.get_member(crew, user) |> then(&Assignment.Private.task_identifier(assignment, item, &1)) diff --git a/core/systems/assignment/crew_page.ex b/core/systems/assignment/crew_page.ex index 5ee2d8291..af3b88345 100644 --- a/core/systems/assignment/crew_page.ex +++ b/core/systems/assignment/crew_page.ex @@ -1,15 +1,18 @@ defmodule Systems.Assignment.CrewPage do use CoreWeb, :live_view use Systems.Observatory.Public + use CoreWeb.LiveRemoteIp use CoreWeb.Layouts.Stripped.Component, :projects + require Logger alias Frameworks.Concept alias Systems.{ Assignment, Project, Workflow, - Crew + Crew, + Storage } import Assignment.StartView @@ -23,7 +26,7 @@ defmodule Systems.Assignment.CrewPage do end @impl true - def mount(%{"id" => id}, _session, socket) do + def mount(%{"id" => id} = _params, _session, socket) do model = Assignment.Public.get!(id, Assignment.Model.preload_graph(:down)) { @@ -155,6 +158,24 @@ defmodule Systems.Assignment.CrewPage do defp task_status(%{status: status}), do: status defp task_status(_), do: :pending + @impl true + def handle_info( + {:store_participant_data, _data}, + %{assigns: %{model: %{storage_endpoint: nil}}} = socket + ) do + raise "Unable to store participant data: no storage endpoint configurated" + {:noreply, socket} + end + + @impl true + def handle_info( + {:store_participant_data, data}, + %{assigns: %{model: %{storage_endpoint: endpoint}, remote_ip: remote_ip}} = socket + ) do + Storage.Public.store(endpoint, data, remote_ip) + {:noreply, socket} + end + @impl true def handle_info( {:complete_task, _}, diff --git a/core/systems/assignment/external_panel_controller.ex b/core/systems/assignment/external_panel_controller.ex new file mode 100644 index 000000000..62ed3feee --- /dev/null +++ b/core/systems/assignment/external_panel_controller.ex @@ -0,0 +1,57 @@ +defmodule Systems.Assignment.ExternalPanelController do + use CoreWeb, :controller + + @supported_locales ~w(nl en) + @centerdata_callback_url "https://quest.centerdata.nl/eyra/dd.php" + + def create(conn, %{"id" => id, "panel" => panel} = params) do + [] + |> add_locale(params) + |> add_panel_info(String.to_existing_atom(panel), params) + |> start_assignment(conn, id) + end + + defp add_panel_info(opts, panel, params) do + panel_info = %{ + callback_url: get_callback_url(panel), + participant: get_participant(panel, params), + language: get_language(panel, params), + query_string: params + } + + Keyword.put(opts, :panel_info, panel_info) + end + + defp add_locale(opts, %{"locale" => locale}), do: add_locale(opts, locale) + defp add_locale(opts, %{"lang" => locale}), do: add_locale(opts, locale) + + defp add_locale(opts, locale) when is_binary(locale) do + if is_supported?(locale) do + Keyword.put(opts, :locale, locale) + else + opts + end + end + + defp add_locale(opts, _), do: opts + + defp is_supported?(locale) when is_binary(locale) do + locale in @supported_locales + end + + defp start_assignment(_opts, conn, id) do + path = ~p"/assignment/#{id}" + redirect(conn, to: path) + end + + # Param Mappings + + defp get_participant(:liss, %{"respondent" => respondent}), do: respondent + defp get_participant(_, %{"participant" => participant}), do: participant + + defp get_language(:liss, %{"lang" => lang}), do: lang + defp get_language(_, %{"language" => language}), do: language + + defp get_callback_url(:liss), do: @centerdata_callback_url + defp get_callback_url(_), do: nil +end diff --git a/core/systems/assignment/external_panel_ids.ex b/core/systems/assignment/external_panel_ids.ex new file mode 100644 index 000000000..2cd77a585 --- /dev/null +++ b/core/systems/assignment/external_panel_ids.ex @@ -0,0 +1,7 @@ +defmodule Systems.Assignment.ExternalPanelIds do + @moduledoc """ + Defines list of supported panel agencies + """ + use Core.Enums.Base, + {:external_panel_ids, [:liss, :ioresearch, :generic]} +end diff --git a/core/systems/assignment/model.ex b/core/systems/assignment/model.ex index b1ea7a0e1..dd60e27bf 100644 --- a/core/systems/assignment/model.ex +++ b/core/systems/assignment/model.ex @@ -18,6 +18,7 @@ defmodule Systems.Assignment.Model do schema "assignments" do field(:special, Ecto.Atom) field(:status, Ecto.Enum, values: Assignment.Status.values(), default: :concept) + field(:external_panel, Ecto.Enum, values: Assignment.ExternalPanelIds.values()) belongs_to(:consent_agreement, Consent.AgreementModel) belongs_to(:info, Assignment.InfoModel) @@ -40,7 +41,7 @@ defmodule Systems.Assignment.Model do timestamps() end - @fields ~w(special status)a + @fields ~w(special status external_panel)a defimpl Frameworks.GreenLight.AuthorizationNode do def id(assignment), do: assignment.auth_node_id diff --git a/core/systems/assignment/panel_form.ex b/core/systems/assignment/panel_form.ex new file mode 100644 index 000000000..876288db5 --- /dev/null +++ b/core/systems/assignment/panel_form.ex @@ -0,0 +1,104 @@ +defmodule Systems.Assignment.PanelForm do + use CoreWeb.LiveForm + + alias Frameworks.Pixel.Selector + + alias Systems.{ + Assignment + } + + # Handle Selector Update + @impl true + def update( + %{active_item_id: panel_id, selector_id: :panel_selector}, + %{assigns: %{entity: assignment}} = socket + ) do + changeset = Assignment.Model.changeset(assignment, %{external_panel: panel_id}) + + { + :ok, + socket + |> save(changeset) + |> update_panel_selector() + |> update_panel_view() + } + end + + @impl true + def update(%{id: id, uri_origin: uri_origin, entity: entity}, socket) do + { + :ok, + socket + |> assign( + id: id, + uri_origin: uri_origin, + entity: entity + ) + |> update_panel_selector() + |> update_panel_view() + } + end + + defp update_panel_selector( + %{assigns: %{id: id, entity: %{external_panel: external_panel}}} = socket + ) do + items = + Assignment.ExternalPanelIds.labels( + external_panel, + Assignment.Private.allowed_external_panel_ids() + ) + + panel_selector = %{ + module: Selector, + id: :panel_selector, + grid_options: "flex flex-col gap-3", + items: items, + type: :radio, + parent: %{type: __MODULE__, id: id} + } + + assign(socket, panel_selector: panel_selector) + end + + defp update_panel_view(%{assigns: %{entity: %{external_panel: nil}}} = socket) do + assign(socket, panel_view: nil) + end + + defp update_panel_view(%{assigns: %{entity: assignment, uri_origin: uri_origin}} = socket) do + panel_view = + if function = Assignment.Private.panel_function_component(assignment) do + %{ + function: function, + props: %{ + assignment: assignment, + uri_origin: uri_origin + } + } + else + nil + end + + assign(socket, panel_view: panel_view) + end + + @impl true + def render(assigns) do + ~H""" +
+ + + <%= dgettext("eyra-assignment", "panel.title") %> + <%= dgettext("eyra-assignment", "panel.label") %> + <.spacing value="XS" /> +
+ <.live_component {@panel_selector} /> +
+ <%= if @panel_view do %> + <.spacing value="XS" /> + <.function_component {@panel_view} /> + <% end %> +
+
+ """ + end +end diff --git a/core/systems/assignment/panel_views.ex b/core/systems/assignment/panel_views.ex new file mode 100644 index 000000000..cca3781a3 --- /dev/null +++ b/core/systems/assignment/panel_views.ex @@ -0,0 +1,47 @@ +defmodule Systems.Assignment.PanelViews do + use CoreWeb, :html + + attr(:id, :string, required: true) + attr(:uri_origin, :string, required: true) + + def default(%{assignment: %{id: id}, uri_origin: uri_origin} = assigns) do + url = uri_origin <> ~p"/assignment/#{id}" + assigns = assign(assigns, url: url) + + ~H""" + <.url_view url={@url} /> + """ + end + + attr(:id, :string, required: true) + attr(:uri_origin, :string, required: true) + + def liss(%{assignment: %{id: id}, uri_origin: uri_origin} = assigns) do + url = uri_origin <> ~p"/assignment/#{id}" + assigns = assign(assigns, url: url) + + ~H""" + <.url_view url={@url} /> + """ + end + + attr(:url, :string, required: true) + + defp url_view(assigns) do + ~H""" +
+
+ <%= @url %> +
+
+
+ +
+
+
+ """ + end +end diff --git a/core/systems/assignment/settings_view.ex b/core/systems/assignment/settings_view.ex index a63041c37..3881fd5ae 100644 --- a/core/systems/assignment/settings_view.ex +++ b/core/systems/assignment/settings_view.ex @@ -1,92 +1,55 @@ defmodule Systems.Assignment.SettingsView do use CoreWeb, :live_component - - alias Frameworks.Pixel.Selector + use Fabric.LiveComponent alias Systems.{ - Assignment, - Storage + Assignment } - # Handle Selector Update - @impl true - def update( - %{active_item_id: active_item_id, selector_id: :enable_storage_selector}, - socket - ) do - { - :ok, - socket - |> update_storage_endpoint(active_item_id) - |> update_enable_storage_selector() - |> update_storage_endpoint_form() - } - end - @impl true - def update(%{id: id, entity: assignment}, socket) do + def update(%{id: id, entity: assignment, uri_origin: uri_origin}, socket) do { :ok, socket |> assign( id: id, - entity: assignment + entity: assignment, + uri_origin: uri_origin ) - |> update_enable_storage_selector() - |> update_storage_endpoint_form() + |> update_storage_connector() + |> update_panel_connector() } end - def update_storage_endpoint(%{assigns: %{entity: assignment}} = socket, enabled) do - assignment = - if enabled == :no do - Assignment.Public.delete_storage_endpoint!(assignment) - else - Assignment.Public.create_storage_endpoint!(assignment) - end + defp update_storage_connector( + %{assigns: %{entity: assignment, uri_origin: uri_origin}} = socket + ) do + child = + prepare_child(socket, :storage_connector, Assignment.ConnectorView, %{ + assignment: assignment, + connection: assignment.storage_endpoint, + type: :storage, + uri_origin: uri_origin + }) - socket - |> assign(entity: assignment) - |> update_enable_storage_selector() - |> update_storage_endpoint_form() + show_child(socket, child) end - def update_enable_storage_selector( - %{assigns: %{id: id, entity: %{storage_endpoint_id: storage_endpoint_id}}} = socket - ) do - enabled = storage_endpoint_id != nil + defp update_panel_connector(%{assigns: %{entity: assignment, uri_origin: uri_origin}} = socket) do + child = + prepare_child(socket, :panel_connector, Assignment.ConnectorView, %{ + assignment: assignment, + connection: assignment.external_panel, + type: :panel, + uri_origin: uri_origin + }) - labels = [ - %{id: :no, value: "No, disable", active: not enabled}, - %{id: :yes, value: "Yes, enable", active: enabled} - ] - - enable_storage_selector = %{ - module: Selector, - id: :enable_storage_selector, - grid_options: "flex flex-row gap-8", - items: labels, - type: :radio, - parent: %{type: __MODULE__, id: id} - } - - assign(socket, enable_storage_selector: enable_storage_selector) - end - - def update_storage_endpoint_form(%{assigns: %{entity: %{storage_endpoint_id: nil}}} = socket) do - assign(socket, storage_form: nil) + show_child(socket, child) end - def update_storage_endpoint_form( - %{assigns: %{entity: %{storage_endpoint: storage_endpoint}}} = socket - ) do - storage_form = %{ - id: :storage_endpoint_revision, - module: Storage.EndpointForm, - entity: storage_endpoint - } - - assign(socket, storage_form: storage_form) + @impl true + def handle_event("finish", _payload, socket) do + {:noreply, socket} end @impl true @@ -96,13 +59,18 @@ defmodule Systems.Assignment.SettingsView do <%= dgettext("eyra-assignment", "settings.title") %> - <%= dgettext("eyra-assignment", "settings.data_storage.title") %> - <.spacing value="XS" /> - <.live_component {@enable_storage_selector} /> - <%= if @storage_form do %> - <.spacing value="M" /> - <.live_component {@storage_form} /> - <% end %> + + <%= dgettext("eyra-assignment", "settings.panel.title") %> + <%= dgettext("eyra-assignment", "settings.panel.body") %> + <.spacing value="M" /> + <.child id={:panel_connector} fabric={@fabric}/> + <.spacing value="L" /> + + <%= dgettext("eyra-assignment", "settings.data_storage.title") %> + <%= dgettext("eyra-assignment", "settings.data_storage.body") %> + <.spacing value="M" /> + <.child id={:storage_connector} fabric={@fabric}/> +
""" diff --git a/core/systems/benchmark/content_page_builder.ex b/core/systems/benchmark/content_page_builder.ex index 5c3b346b7..de3747bc8 100644 --- a/core/systems/benchmark/content_page_builder.ex +++ b/core/systems/benchmark/content_page_builder.ex @@ -218,12 +218,11 @@ defmodule Systems.Benchmark.ContentPageBuilder do defp create_tab( :invite, - %{id: id}, + assignment, show_errors, %{uri_origin: uri_origin} ) do ready? = false - url = uri_origin <> "/benchmark/#{id}" %{ id: :invite_form, @@ -232,9 +231,10 @@ defmodule Systems.Benchmark.ContentPageBuilder do title: dgettext("eyra-project", "tabbar.item.invite"), forward_title: dgettext("eyra-project", "tabbar.item.invite.forward"), type: :fullpage, - live_component: Project.InviteForm, + live_component: Assignment.PanelForm, props: %{ - url: url + uri_origin: uri_origin, + entity: assignment } } end diff --git a/core/systems/campaign/_public.ex b/core/systems/campaign/_public.ex index 8d502a26d..3a7035ff3 100644 --- a/core/systems/campaign/_public.ex +++ b/core/systems/campaign/_public.ex @@ -346,7 +346,7 @@ defmodule Systems.Campaign.Public do }) do # FIXME: budget change after pool update should be handled in student submission form budget = Directable.director(pool).resolve_budget(pool_id, nil) - Assignment.Public.update(assignment, budget) + Assignment.Public.update_budget(assignment, budget) end def submission_updated(_), do: nil diff --git a/core/systems/campaign/builders/assignment_landing_page.ex b/core/systems/campaign/builders/assignment_landing_page.ex index 62a084c70..73ba429cc 100644 --- a/core/systems/campaign/builders/assignment_landing_page.ex +++ b/core/systems/campaign/builders/assignment_landing_page.ex @@ -238,9 +238,9 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do # Alliance open button - defp open_action(user, tool, crew, panl_id) do + defp open_action(user, tool, crew, next_id) do label = Frameworks.Concept.ToolModel.open_label(tool) - path = Alliance.ToolModel.external_path(tool, panl_id) + path = Alliance.ToolModel.external_path(tool, next_id) %{ id: :open, @@ -340,7 +340,7 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do end defp contact_href(email, title, public_id) do - "mailto:#{email}?subject=[panl_id=#{public_id}] #{title}" + "mailto:#{email}?subject=[next_id=#{public_id}] #{title}" end # Optional behaviour diff --git a/core/systems/content/page.ex b/core/systems/content/page.ex index 3033549ed..929f26183 100644 --- a/core/systems/content/page.ex +++ b/core/systems/content/page.ex @@ -34,7 +34,7 @@ defmodule Systems.Content.Page do -
+
<%= if @popup do %> @@ -51,7 +51,9 @@ defmodule Systems.Content.Page do <% end %> - +
+ +
@@ -165,6 +167,22 @@ defmodule Systems.Content.Page do {:noreply, socket |> assign(dialog: nil)} end + @impl true + def handle_event("show_popup", %{ref: %{id: id, module: module}, params: params}, socket) do + popup = %{module: module, props: Map.put(params, :id, id)} + handle_event("show_popup", popup, socket) + end + + @impl true + def handle_event("show_popup", %{module: _, props: _} = popup, socket) do + {:noreply, socket |> assign(popup: popup)} + end + + @impl true + def handle_event("hide_popup", _, socket) do + {:noreply, socket |> assign(popup: nil)} + end + @impl true def handle_info({:handle_auto_save_done, _}, socket) do socket |> update_menus() diff --git a/core/systems/email/factory.ex b/core/systems/email/factory.ex index b62f9debd..bf94f627b 100644 --- a/core/systems/email/factory.ex +++ b/core/systems/email/factory.ex @@ -60,7 +60,7 @@ defmodule Systems.Email.Factory do html_message = message |> to_html() mail_user(to) - |> subject("Panl notification") + |> subject("Next notification") |> render(:notification, title: title, byline: byline, diff --git a/core/systems/feldspar/app_page.ex b/core/systems/feldspar/app_page.ex index 6106aeab8..8fd9479cf 100644 --- a/core/systems/feldspar/app_page.ex +++ b/core/systems/feldspar/app_page.ex @@ -43,7 +43,8 @@ defmodule Systems.Feldspar.AppPage do end defp handle(socket, "CommandSystemDonate", event) do - Frameworks.Pixel.Flash.put_error(socket, "Unsupported donation " <> event) + send(self(), {:store_participant_data, event}) + socket end defp handle(socket, _, event) do diff --git a/core/systems/project/invite_form.ex b/core/systems/project/invite_form.ex deleted file mode 100644 index 132fa6851..000000000 --- a/core/systems/project/invite_form.ex +++ /dev/null @@ -1,43 +0,0 @@ -defmodule Systems.Project.InviteForm do - use CoreWeb.LiveForm - - @impl true - def update(%{url: url}, socket) do - { - :ok, - socket - |> assign(url: url) - } - end - - @impl true - def render(assigns) do - ~H""" -
- - - <%= dgettext("eyra-project", "invite.title") %> - <%= dgettext("eyra-project", "direct.invite.title") %> - <%= dgettext("eyra-project", "direct.invite.description") %> - <.spacing value="XS" /> -
-
- <%= @url %> -
-
-
- -
-
-
- <.spacing value="M" /> - <%= dgettext("eyra-project", "campaign.invite.title") %> - TBD.. -
-
- """ - end -end diff --git a/core/systems/project/tool_ref_model.ex b/core/systems/project/tool_ref_model.ex index 705abef23..bf87337cf 100644 --- a/core/systems/project/tool_ref_model.ex +++ b/core/systems/project/tool_ref_model.ex @@ -69,8 +69,8 @@ defmodule Systems.Project.ToolRefModel do def flatten(item), do: tool(item) - def external_path(%{alliance_tool: alliance_tool}, panl_id) do - Alliance.ToolModel.external_path(alliance_tool, panl_id) + def external_path(%{alliance_tool: alliance_tool}, next_id) do + Alliance.ToolModel.external_path(alliance_tool, next_id) end def external_path(_, _), do: nil diff --git a/core/systems/storage/_assembly.ex b/core/systems/storage/_assembly.ex index 765971363..746e1d0bd 100644 --- a/core/systems/storage/_assembly.ex +++ b/core/systems/storage/_assembly.ex @@ -2,32 +2,6 @@ defmodule Systems.Storage.Assembly do alias Core.Repo alias Systems.Storage - alias Systems.Storage.AWS - alias Systems.Storage.Azure - alias Systems.Storage.Centerdata - alias Systems.Storage.Yoda - - def prepare_endpoint_special(nil), do: nil - - def prepare_endpoint_special(:aws) do - %AWS.EndpointModel{} - |> AWS.EndpointModel.changeset(%{}) - end - - def prepare_endpoint_special(:azure) do - %Azure.EndpointModel{} - |> Azure.EndpointModel.changeset(%{}) - end - - def prepare_endpoint_special(:centerdata) do - %Centerdata.EndpointModel{} - |> Centerdata.EndpointModel.changeset(%{}) - end - - def prepare_endpoint_special(:yoda) do - %Yoda.EndpointModel{} - |> Yoda.EndpointModel.changeset(%{}) - end def delete_endpoint_special(endpoint) do if special = Storage.EndpointModel.special(endpoint) do diff --git a/core/systems/storage/_private.ex b/core/systems/storage/_private.ex new file mode 100644 index 000000000..87917fd55 --- /dev/null +++ b/core/systems/storage/_private.ex @@ -0,0 +1,18 @@ +defmodule Systems.Storage.Private do + alias Systems.{ + Storage + } + + def allowed_service_ids() do + Keyword.get(config(), :services, []) + end + + defp config() do + Application.get_env(:core, :storage) + end + + def build_special(:aws), do: %Storage.AWS.EndpointModel{} + def build_special(:azure), do: %Storage.Azure.EndpointModel{} + def build_special(:centerdata), do: %Storage.Centerdata.EndpointModel{} + def build_special(:yoda), do: %Storage.Yoda.EndpointModel{} +end diff --git a/core/systems/storage/_public.ex b/core/systems/storage/_public.ex index aa339279e..dcfde7334 100644 --- a/core/systems/storage/_public.ex +++ b/core/systems/storage/_public.ex @@ -1,11 +1,33 @@ defmodule Systems.Storage.Public do alias Systems.{ + Rate, Storage } - def deliver(%Storage.EndpointModel{} = _endpoint, data) when is_binary(data) do - %{endpoint: %{type: :aws}, data: data} - |> Storage.Delivery.new() - |> Oban.insert() + def store(%Storage.EndpointModel{} = endpoint, data, remote_ip) do + storage = %{ + key: Storage.EndpointModel.special_field_id(endpoint), + endpoint: Storage.EndpointModel.special(endpoint) + } + + packet_size = String.length(data) + + with :granted <- Rate.Public.request_permission(storage.key, remote_ip, packet_size) do + %{ + storage: storage, + data: data + } + |> Storage.Delivery.new() + |> Oban.insert() + end + end +end + +defimpl Core.Persister, for: Systems.Storage.EndpointModel do + def save(_endpoint, changeset) do + case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :storage_endpoint) do + {:ok, %{storage_endpoint: storage_endpoint}} -> {:ok, storage_endpoint} + _ -> {:error, changeset} + end end end diff --git a/core/systems/storage/aws/endpoint_form.ex b/core/systems/storage/aws/endpoint_form.ex index 418152e6d..18949f35d 100644 --- a/core/systems/storage/aws/endpoint_form.ex +++ b/core/systems/storage/aws/endpoint_form.ex @@ -1,58 +1,11 @@ defmodule Systems.Storage.AWS.EndpointForm do - use CoreWeb.LiveForm - - alias Systems.Storage.AWS.EndpointModel - - # Handle initial update - @impl true - def update( - %{id: id, entity: endpoint}, - socket - ) do - changeset = EndpointModel.changeset(endpoint, %{}) - - { - :ok, - socket - |> assign( - id: id, - entity: endpoint, - changeset: changeset - ) - } - end - - # Handle Events - @impl true - def handle_event("save", %{"endpoint_model" => attrs}, socket) do - { - :noreply, - socket - |> save_entity(attrs) - } - end - - # Saving - def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do - changeset = EndpointModel.changeset(entity, attrs) - - socket - |> save(changeset) - |> validate(changeset) - end - - def validate(socket, changeset) do - changeset = EndpointModel.validate(changeset) - - socket - |> assign(changeset: changeset) - end + use Systems.Storage.EndpointForm.Helper, Systems.Storage.AWS.EndpointModel @impl true def render(assigns) do ~H""" -
- <.form id={"#{@id}_aws_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > +
+ <.form id={"#{@id}_aws_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself}> <.text_input form={form} field={:access_key_id} label_text={dgettext("eyra-storage", "aws.access_key_id.label")} /> <.password_input form={form} field={:secret_access_key} label_text={dgettext("eyra-storage", "aws.secret_access_key.label")} /> <.text_input form={form} field={:s3_bucket_name} label_text={dgettext("eyra-storage", "aws.s3_bucket_name.label")} /> diff --git a/core/systems/storage/azure/endpoint_form.ex b/core/systems/storage/azure/endpoint_form.ex index d1374ef1d..033b2f648 100644 --- a/core/systems/storage/azure/endpoint_form.ex +++ b/core/systems/storage/azure/endpoint_form.ex @@ -1,58 +1,11 @@ defmodule Systems.Storage.Azure.EndpointForm do - use CoreWeb.LiveForm - - alias Systems.Storage.Azure.EndpointModel - - # Handle initial update - @impl true - def update( - %{id: id, entity: endpoint}, - socket - ) do - changeset = EndpointModel.changeset(endpoint, %{}) - - { - :ok, - socket - |> assign( - id: id, - entity: endpoint, - changeset: changeset - ) - } - end - - # Handle Events - @impl true - def handle_event("save", %{"endpoint_model" => attrs}, socket) do - { - :noreply, - socket - |> save_entity(attrs) - } - end - - # Saving - def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do - changeset = EndpointModel.changeset(entity, attrs) - - socket - |> save(changeset) - |> validate(changeset) - end - - def validate(socket, changeset) do - changeset = EndpointModel.validate(changeset) - - socket - |> assign(changeset: changeset) - end + use Systems.Storage.EndpointForm.Helper, Systems.Storage.Azure.EndpointModel @impl true def render(assigns) do ~H""" -
- <.form id={"#{@id}_azure_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > +
+ <.form id={"#{@id}_azure_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself}> <.text_input form={form} field={:account_name} label_text={dgettext("eyra-storage", "azure.account_name.label")} /> <.text_input form={form} field={:container} label_text={dgettext("eyra-storage", "azure.container.label")} /> <.text_input form={form} field={:sas_token} label_text={dgettext("eyra-storage", "azure.sas_token.label")} /> diff --git a/core/systems/storage/backend_types.ex b/core/systems/storage/backend_types.ex deleted file mode 100644 index 80f17a0a4..000000000 --- a/core/systems/storage/backend_types.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule Systems.Storage.BackendTypes do - @moduledoc """ - Defines types of external data storage services - """ - use Core.Enums.Base, - {:storage_backend_types, [:aws, :azure, :centerdata, :yoda]} -end diff --git a/core/systems/storage/centerdata/backend.ex b/core/systems/storage/centerdata/backend.ex index 964b4cd60..d3315da72 100644 --- a/core/systems/storage/centerdata/backend.ex +++ b/core/systems/storage/centerdata/backend.ex @@ -36,7 +36,7 @@ defmodule Systems.Storage.Centerdata.Backend do Application.get_env( :core, :data_donation_http_client, - Systems.Union.Centerdata.HTTPClient + Frameworks.Utility.HTTPClient ) end end diff --git a/core/systems/storage/centerdata/endpoint_form.ex b/core/systems/storage/centerdata/endpoint_form.ex index ee1f84ef5..60ef0a2e9 100644 --- a/core/systems/storage/centerdata/endpoint_form.ex +++ b/core/systems/storage/centerdata/endpoint_form.ex @@ -1,58 +1,11 @@ defmodule Systems.Storage.Centerdata.EndpointForm do - use CoreWeb.LiveForm - - alias Systems.Storage.Centerdata.EndpointModel - - # Handle initial update - @impl true - def update( - %{id: id, entity: endpoint}, - socket - ) do - changeset = EndpointModel.changeset(endpoint, %{}) - - { - :ok, - socket - |> assign( - id: id, - entity: endpoint, - changeset: changeset - ) - } - end - - # Handle Events - @impl true - def handle_event("save", %{"endpoint_model" => attrs}, socket) do - { - :noreply, - socket - |> save_entity(attrs) - } - end - - # Saving - def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do - changeset = EndpointModel.changeset(entity, attrs) - - socket - |> save(changeset) - |> validate(changeset) - end - - def validate(socket, changeset) do - changeset = EndpointModel.validate(changeset) - - socket - |> assign(changeset: changeset) - end + use Systems.Storage.EndpointForm.Helper, Systems.Storage.Centerdata.EndpointModel @impl true def render(assigns) do ~H""" -
- <.form id={"#{@id}_centerdata_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > +
+ <.form id={"#{@id}_centerdata_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself}> <.url_input form={form} field={:url} diff --git a/core/systems/storage/endpoint_form.ex b/core/systems/storage/endpoint_form.ex index 044bc3377..8cd8b35d0 100644 --- a/core/systems/storage/endpoint_form.ex +++ b/core/systems/storage/endpoint_form.ex @@ -1,5 +1,6 @@ defmodule Systems.Storage.EndpointForm do use CoreWeb.LiveForm + use Fabric.LiveComponent alias Frameworks.Concept alias Frameworks.Pixel.Selector @@ -8,22 +9,24 @@ defmodule Systems.Storage.EndpointForm do Storage } + @special_form_key :storage_endpoint_special_form + # Handle Selector Update @impl true def update( - %{active_item_id: special_field, selector_id: :storage_type_selector}, - %{assigns: %{entity: endpoint}} = socket + %{active_item_id: special_type, selector_id: :special_type_selector}, + socket ) do - special = Storage.Assembly.prepare_endpoint_special(special_field) - - changeset = Storage.EndpointModel.reset_special(endpoint, special_field, special) + special = Storage.Private.build_special(special_type) { :ok, socket - |> save(changeset) - |> update_selected_type() - |> update_special() + |> assign( + special_type: special_type, + special_changeset: nil, + special: special + ) |> update_special_form() } end @@ -31,38 +34,35 @@ defmodule Systems.Storage.EndpointForm do # Handle initial update @impl true def update( - %{id: id, entity: endpoint}, + %{id: id, endpoint: endpoint}, socket ) do - changeset = Storage.EndpointModel.changeset(endpoint, %{}) - { :ok, socket |> assign( id: id, - entity: endpoint, - changeset: changeset + endpoint: endpoint ) - |> update_selected_type() + |> update_special_type() |> update_type_selector() |> update_special() |> update_special_form() } end - defp update_selected_type(%{assigns: %{entity: entity}} = socket) do - selected_type = Storage.EndpointModel.special_field(entity) - assign(socket, selected_type: selected_type) + defp update_special_type(%{assigns: %{endpoint: endpoint}} = socket) do + special_type = Storage.EndpointModel.special_field_id(endpoint) + assign(socket, special_type: special_type) end - defp update_type_selector(%{assigns: %{id: id, selected_type: selected_type}} = socket) do - items = Storage.BackendTypes.labels(selected_type) + defp update_type_selector(%{assigns: %{id: id, special_type: special_type}} = socket) do + items = Storage.ServiceIds.labels(special_type, Storage.Private.allowed_service_ids()) type_selector = %{ module: Selector, - id: :storage_type_selector, - grid_options: "grid gap-3 grid-cols-2", + id: :special_type_selector, + grid_options: "flex flex-row gap-4", items: items, type: :radio, parent: %{type: __MODULE__, id: id} @@ -71,25 +71,62 @@ defmodule Systems.Storage.EndpointForm do assign(socket, type_selector: type_selector) end - defp update_special(%{assigns: %{entity: entity}} = socket) do - special = Storage.EndpointModel.special(entity) + defp update_special(%{assigns: %{endpoint: endpoint}} = socket) do + special = Storage.EndpointModel.special(endpoint) assign(socket, special: special) end - defp update_special_form(%{assigns: %{selected_type: nil}} = socket) do - assign(socket, special_form: nil, special_form_title: nil) + defp update_special_form(%{assigns: %{special_type: nil}} = socket) do + socket + |> assign(@special_form_key, nil) + |> assign(special_form_title: nil) end - defp update_special_form(%{assigns: %{special: special, selected_type: selected_type}} = socket) do - special_form_title = Storage.BackendTypes.translate(selected_type) + defp update_special_form(%{assigns: %{special_type: special_type, special: special}} = socket) do + special_form_title = Storage.ServiceIds.translate(special_type) + + child = + prepare_child(socket, @special_form_key, Concept.ContentModel.form(special), %{ + model: special + }) + + socket + |> replace_child(child) + |> assign(special_form_title: special_form_title) + end - special_form = %{ - module: Concept.ContentModel.form(special), - id: :storage_endpoint_special_form, - entity: special + defp update_changeset(%{assigns: %{special_changeset: nil}} = socket) do + socket + end + + defp update_changeset( + %{ + assigns: %{ + endpoint: endpoint, + special_type: special_type, + special_changeset: special_changeset + } + } = socket + ) do + changeset = Storage.EndpointModel.reset_special(endpoint, special_type, special_changeset) + + socket + |> send_event(:parent, "update", %{changeset: changeset}) + end + + @impl true + def handle_event("update", %{source: %{id: @special_form_key}, changeset: changeset}, socket) do + { + :noreply, + socket + |> assign(special_changeset: changeset) + |> update_changeset() } + end - assign(socket, special_form: special_form, special_form_title: special_form_title) + @impl true + def handle_event("show_errors", _payload, socket) do + {:noreply, socket |> send_event(@special_form_key, "show_errors")} end @impl true @@ -98,14 +135,14 @@ defmodule Systems.Storage.EndpointForm do
<%= dgettext("eyra-storage", "endpoint_form.type.label") %> <.spacing value="XS" /> -
+
<.live_component {@type_selector} />
- <.spacing value="L" /> - <%= if @special_form do %> + <%= if get_child(@fabric, :storage_endpoint_special_form) do %> + <.spacing value="L" /> <%= @special_form_title %> <.spacing value="XS" /> - <.live_component {@special_form} /> + <.child id={:storage_endpoint_special_form} fabric={@fabric} /> <% end %>
""" diff --git a/core/systems/storage/endpoint_form_helper.ex b/core/systems/storage/endpoint_form_helper.ex new file mode 100644 index 000000000..9606cb8d7 --- /dev/null +++ b/core/systems/storage/endpoint_form_helper.ex @@ -0,0 +1,55 @@ +defmodule Systems.Storage.EndpointForm.Helper do + defmacro __using__(model) do + quote do + use CoreWeb.LiveForm + use Fabric.LiveComponent + + alias unquote(model), as: Model + + # Handle initial update + @impl true + def update( + %{id: id, model: model}, + socket + ) do + { + :ok, + socket + |> assign( + id: id, + model: model, + attrs: %{}, + show_errors: false + ) + |> update_changeset() + } + end + + @impl true + def handle_event("save", %{"endpoint_model" => attrs}, socket) do + { + :noreply, + socket + |> assign(attrs: attrs) + |> update_changeset() + } + end + + @impl true + def handle_event("show_errors", _payload, socket) do + {:noreply, socket |> assign(show_errors: true)} + end + + defp update_changeset(%{assigns: %{id: id, model: model, attrs: attrs}} = socket) do + changeset = + Model.changeset(model, attrs) + |> Model.validate() + |> Map.put(:action, :update) + + socket + |> assign(:changeset, changeset) + |> send_event(:parent, "update", %{changeset: changeset}) + end + end + end +end diff --git a/core/systems/storage/endpoint_model.ex b/core/systems/storage/endpoint_model.ex index e6b7bafac..1e15ac9dd 100644 --- a/core/systems/storage/endpoint_model.ex +++ b/core/systems/storage/endpoint_model.ex @@ -10,7 +10,7 @@ defmodule Systems.Storage.EndpointModel do Storage } - require Storage.BackendTypes + require Storage.ServiceIds schema "storage_endpoints" do belongs_to(:aws, Storage.AWS.EndpointModel, on_replace: :delete) @@ -58,18 +58,24 @@ defmodule Systems.Storage.EndpointModel do end def special(endpoint) do - Enum.reduce(@special_fields, nil, fn field, acc -> - if special = Map.get(endpoint, field) do - special - else - acc - end - end) + if field = special_field(endpoint) do + Map.get(endpoint, field) + else + nil + end + end + + def special_field_id(endpoint) do + if field = special_field(endpoint) do + map_to_field_id(field) + else + nil + end end def special_field(endpoint) do Enum.reduce(@special_fields, nil, fn field, acc -> - field_id = String.to_existing_atom("#{field}_id") + field_id = map_to_field_id(field) if Map.get(endpoint, field_id) != nil do field @@ -87,6 +93,8 @@ defmodule Systems.Storage.EndpointModel do end end + defp map_to_field_id(field), do: String.to_existing_atom("#{field}_id") + defimpl Frameworks.Concept.ContentModel do alias Systems.Storage def form(_), do: Storage.EndpointForm diff --git a/core/systems/storage/service_ids.ex b/core/systems/storage/service_ids.ex new file mode 100644 index 000000000..b72c2c46e --- /dev/null +++ b/core/systems/storage/service_ids.ex @@ -0,0 +1,7 @@ +defmodule Systems.Storage.ServiceIds do + @moduledoc """ + Defines list of supported storage services + """ + use Core.Enums.Base, + {:storage_service_ids, [:aws, :azure, :yoda]} +end diff --git a/core/systems/storage/yoda/endpoint_form.ex b/core/systems/storage/yoda/endpoint_form.ex index adeb6f81c..6b18e501c 100644 --- a/core/systems/storage/yoda/endpoint_form.ex +++ b/core/systems/storage/yoda/endpoint_form.ex @@ -1,58 +1,11 @@ defmodule Systems.Storage.Yoda.EndpointForm do - use CoreWeb.LiveForm - - alias Systems.Storage.Yoda.EndpointModel - - # Handle initial update - @impl true - def update( - %{id: id, entity: endpoint}, - socket - ) do - changeset = EndpointModel.changeset(endpoint, %{}) - - { - :ok, - socket - |> assign( - id: id, - entity: endpoint, - changeset: changeset - ) - } - end - - # Handle Events - @impl true - def handle_event("save", %{"endpoint_model" => attrs}, socket) do - { - :noreply, - socket - |> save_entity(attrs) - } - end - - # Saving - def save_entity(%{assigns: %{entity: entity}} = socket, attrs) do - changeset = EndpointModel.changeset(entity, attrs) - - socket - |> save(changeset) - |> validate(changeset) - end - - def validate(socket, changeset) do - changeset = EndpointModel.validate(changeset) - - socket - |> assign(changeset: changeset) - end + use Systems.Storage.EndpointForm.Helper, Systems.Storage.Yoda.EndpointModel @impl true def render(assigns) do ~H""" -
- <.form id={"#{@id}_yoda_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself} > +
+ <.form id={"#{@id}_yoda_endpoint_form"} :let={form} for={@changeset} phx-change="save" phx-target={@myself}> <.text_input form={form} field={:url} label_text={dgettext("eyra-storage", "yoda.url.label")} /> <.text_input form={form} field={:user} label_text={dgettext("eyra-storage", "yoda.user.label")} /> <.password_input form={form} field={:password} label_text={dgettext("eyra-storage", "yoda.password.label")} /> diff --git a/core/systems/union/_routes.ex b/core/systems/union/_routes.ex deleted file mode 100644 index 6b597481e..000000000 --- a/core/systems/union/_routes.ex +++ /dev/null @@ -1,16 +0,0 @@ -defmodule Systems.Union.Routes do - defmacro routes() do - quote do - scope "/centerdata", Systems.Union.Centerdata do - pipe_through([:browser]) - get("/:id", Controller, :create) - live("/fakeapi/page", FakeApiPage) - end - - scope "/centerdata", Systems.Union.Centerdata do - pipe_through([:browser_unprotected]) - post("/:id", Controller, :create) - end - end - end -end diff --git a/core/systems/union/centerdata/controller.ex b/core/systems/union/centerdata/controller.ex deleted file mode 100644 index 26fac244a..000000000 --- a/core/systems/union/centerdata/controller.ex +++ /dev/null @@ -1,66 +0,0 @@ -defmodule Systems.Union.Centerdata.Controller do - use CoreWeb, :controller - - @supported_locales ~w(nl en) - @url "https://quest.centerdata.nl/eyra/dd.php" - - def create(conn, %{"id" => id} = params) do - [] - |> add_locale(params) - |> add_session(params) - |> start_data_donation(conn, id) - end - - defp add_session( - opts, - %{ - "error1" => error1, - "lang" => lang, - "mobile" => mobile, - "page" => page, - "questiontext1" => questiontext1, - "respondent" => respondent, - "token" => token, - "varname1" => varname1, - "varvalue1" => varvalue1 - } - ) do - session = %{ - url: @url, - page: page, - varname1: varname1, - varvalue1: varvalue1, - questiontext1: questiontext1, - error1: error1, - respondent: respondent, - mobile: mobile, - lang: lang, - token: token - } - - Keyword.put(opts, :session, session) - end - - defp add_locale(opts, %{"locale" => locale}), do: add_locale(opts, locale) - defp add_locale(opts, %{"lang" => locale}), do: add_locale(opts, locale) - - defp add_locale(opts, locale) when is_binary(locale) do - if is_supported?(locale) do - Keyword.put(opts, :locale, locale) - else - opts - end - end - - defp add_locale(opts, _), do: opts - - defp is_supported?(locale) when is_binary(locale) do - locale in @supported_locales - end - - defp start_data_donation(_opts, conn, _id) do - # Routes.live_path(conn, Systems.DataDonation.FlowPage, id, opts) - path = "/" - redirect(conn, to: path) - end -end diff --git a/core/systems/union/centerdata/fakeapi_controller.ex b/core/systems/union/centerdata/fakeapi_controller.ex deleted file mode 100644 index 5c8537da3..000000000 --- a/core/systems/union/centerdata/fakeapi_controller.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule Systems.Union.Centerdata.FakeApiController do - use CoreWeb, :controller - - def create( - conn, - params - ) do - path = Routes.live_path(conn, Systems.Union.Centerdata.FakeApiPage, params: params) - redirect(conn, to: path) - end -end diff --git a/core/systems/workflow/item_model.ex b/core/systems/workflow/item_model.ex index b9a1c3a96..b613b09f4 100644 --- a/core/systems/workflow/item_model.ex +++ b/core/systems/workflow/item_model.ex @@ -64,8 +64,8 @@ defmodule Systems.Workflow.ItemModel do def preload_graph(:tool_ref), do: [tool_ref: Project.ToolRefModel.preload_graph(:down)] - def external_path(%{tool_ref: tool_ref}, panl_id), - do: Project.ToolRefModel.external_path(tool_ref, panl_id) + def external_path(%{tool_ref: tool_ref}, next_id), + do: Project.ToolRefModel.external_path(tool_ref, next_id) def flatten(%{tool_ref: tool_ref}), do: Project.ToolRefModel.flatten(tool_ref) end diff --git a/core/test/frameworks/fabric/factories.ex b/core/test/frameworks/fabric/factories.ex new file mode 100644 index 000000000..25b0b5e7e --- /dev/null +++ b/core/test/frameworks/fabric/factories.ex @@ -0,0 +1,20 @@ +defmodule Fabric.Factories do + def create_fabric() do + create_fabric(%Fabric.LiveView.RefModel{pid: self()}) + end + + def create_fabric(%Fabric.LiveView.RefModel{} = self) do + %Fabric.Model{parent: nil, self: self, children: []} + end + + def create_fabric(%Fabric.LiveComponent.RefModel{} = self) do + %Fabric.Model{parent: nil, self: self, children: []} + end + + def create_child(id, module \\ Fabric.TestLiveComponent, params \\ %{}) do + ref = %Fabric.LiveComponent.RefModel{id: id, module: module} + fabric = create_fabric(ref) + params = Map.put(params, :fabric, fabric) + %Fabric.LiveComponent.Model{ref: ref, params: params} + end +end diff --git a/core/test/frameworks/fabric/test.ex b/core/test/frameworks/fabric/test.ex new file mode 100644 index 000000000..878e15af1 --- /dev/null +++ b/core/test/frameworks/fabric/test.ex @@ -0,0 +1,446 @@ +defmodule Fabric.Test do + use ExUnit.Case, async: true + + import Fabric.Factories + + describe "new_fabric/1" do + test "socket" do + socket = + %Phoenix.LiveView.Socket{} + |> Fabric.new_fabric() + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + __changed__: %{fabric: true}, + fabric: %Fabric.Model{parent: nil, self: nil, children: []} + } + } = socket + end + end + + describe "new_fabric/0" do + test "default" do + fabric = Fabric.new_fabric() + assert %Fabric.Model{parent: nil, self: nil, children: []} = fabric + end + end + + describe "prepare_child/4" do + test "socket" do + fabric = %Fabric.Model{parent: nil, children: []} + + child = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.prepare_child(:child, Fabric.TestLiveComponent, %{}) + + assert %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } = child + end + + test "fabric" do + fabric = %Fabric.Model{parent: nil, children: []} + + child = Fabric.prepare_child(fabric, :child, Fabric.TestLiveComponent, %{}) + + assert %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } = child + end + end + + describe "get_child/2" do + test "socket" do + child = create_child(:child) + fabric = %Fabric.Model{parent: nil, children: [child]} + + child = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.get_child(:child) + + assert %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } = child + end + + test "fabric" do + child = + %Fabric.Model{parent: nil, children: [create_child(:child)]} + |> Fabric.get_child(:child) + + assert %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } = child + end + end + + describe "show_child/2" do + test "socket" do + child = create_child(:child) + fabric = %Fabric.Model{parent: nil, children: []} + + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.show_child(child) + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + fabric: %Fabric.Model{ + parent: nil, + self: nil, + children: [ + %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } + ] + } + } + } = socket + end + end + + describe "add_child/2" do + test "socket" do + child = create_child(:child) + fabric = %Fabric.Model{parent: nil, children: []} + + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.add_child(child) + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + fabric: %Fabric.Model{ + children: [ + %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } + ] + } + } + } = socket + end + end + + describe "replace_child/2" do + test "socket" do + child_1 = create_child(:child, Fabric.TestLiveComponent, %{some: :thing}) + child_2 = create_child(:child, Fabric.TestLiveComponent, %{another: :thing}) + + fabric = %Fabric.Model{parent: nil, children: [child_1]} + + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.replace_child(child_2) + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + fabric: %Fabric.Model{ + parent: nil, + self: nil, + children: [ + %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + params: %{ + another: :thing, + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } + ] + } + } + } = socket + end + end + + describe "hide_child/2" do + test "socket" do + child = create_child(:child, Fabric.TestLiveComponent) + + fabric = %Fabric.Model{parent: nil, children: [child]} + + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.hide_child(:child) + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + fabric: %Fabric.Model{ + parent: nil, + self: nil, + children: [] + } + } + } = socket + end + end + + describe "remove_child/2" do + test "socket" do + child = create_child(:child, Fabric.TestLiveComponent) + + fabric = %Fabric.Model{parent: nil, children: [child]} + + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.remove_child(:child) + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + fabric: %Fabric.Model{ + parent: nil, + self: nil, + children: [] + } + } + } = socket + end + end + + describe "show_popup/2" do + test "socket" do + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, create_fabric()) + |> Fabric.show_popup(create_child(:child)) + + assert_received %{ + fabric_event: %{ + name: "show_popup", + payload: %Fabric.LiveComponent.Model{ + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + children: [] + } + }, + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent} + } + } + } + + self = self() + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveView.RefModel{pid: ^self}, + children: [ + %Fabric.LiveComponent.Model{ + ref: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + params: %{ + fabric: %Fabric.Model{ + parent: nil, + self: %Fabric.LiveComponent.RefModel{ + id: :child, + module: Fabric.TestLiveComponent + }, + children: [] + } + } + } + ] + } + } + } = socket + end + end + + describe "hide_popup/2" do + test "socket" do + child = create_child(:child, Fabric.TestLiveComponent) + + fabric = %Fabric.Model{parent: nil, children: [child]} + + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, fabric) + |> Fabric.hide_popup(:child) + + assert_received %{fabric_event: %{name: "hide_popup", payload: %{}}} + + assert %Phoenix.LiveView.Socket{ + assigns: %{ + fabric: %Fabric.Model{ + parent: nil, + self: nil, + children: [] + } + } + } = socket + end + end + + describe "send_event/2" do + test "parent -> child" do + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, create_fabric()) + + child = Fabric.prepare_child(socket, :child, Fabric.TestLiveComponent, %{}) + socket = Fabric.show_child(socket, child) + + Fabric.send_event(socket, :child, "event", %{some: :message}) + + assert_received { + :phoenix, + :send_update, + { + {Fabric.TestLiveComponent, :child}, + %{ + fabric_event: %{ + name: "event", + payload: %{some: :message} + }, + id: :child + } + } + } + end + + test "child -> parent (child)" do + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, create_fabric()) + + child1 = Fabric.prepare_child(socket, :child1, Fabric.TestLiveComponent, %{}) + child2 = Fabric.prepare_child(child1.params.fabric, :child2, Fabric.TestLiveComponent, %{}) + + Fabric.send_event(child2.params.fabric, :parent, "event", %{some: :message}) + + assert_received { + :phoenix, + :send_update, + { + {Fabric.TestLiveComponent, :child1}, + %{ + fabric_event: %{ + name: "event", + payload: %{ + some: :message, + source: %Fabric.LiveComponent.RefModel{ + id: :child2, + module: Fabric.TestLiveComponent + } + } + }, + id: :child1 + } + } + } + end + + test "child -> root" do + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, create_fabric()) + + child1 = Fabric.prepare_child(socket, :child1, Fabric.TestLiveComponent, %{}) + child2 = Fabric.prepare_child(child1.params.fabric, :child2, Fabric.TestLiveComponent, %{}) + + Fabric.send_event(child2.params.fabric, :root, "event", %{some: :message}) + + assert_received %{fabric_event: %{name: "event", payload: %{some: :message}}} + end + + test "child -> parent (root)" do + socket = + %Phoenix.LiveView.Socket{} + |> Phoenix.Component.assign(:fabric, create_fabric()) + + child = Fabric.prepare_child(socket, :child, Fabric.TestLiveComponent, %{}) + + Fabric.send_event(child.params.fabric, :parent, "event", %{some: :message}) + + assert_received %{fabric_event: %{name: "event", payload: %{some: :message}}} + end + end +end diff --git a/core/test/frameworks/fabric/test_live_component.ex b/core/test/frameworks/fabric/test_live_component.ex new file mode 100644 index 000000000..eae0349b4 --- /dev/null +++ b/core/test/frameworks/fabric/test_live_component.ex @@ -0,0 +1,21 @@ +defmodule Fabric.TestLiveComponent do + use Phoenix.LiveComponent + use Fabric.LiveComponent + + @impl true + def update(%{"id" => id}, socket) do + {:ok, socket |> assign(id: id)} + end + + @impl true + def handle_event("event", _payload, socket) do + {:noreply, socket} + end + + @impl true + def render(assigns) do + ~H""" +
+ """ + end +end diff --git a/core/test/frameworks/fabric/test_live_view.ex b/core/test/frameworks/fabric/test_live_view.ex new file mode 100644 index 000000000..2bb3dc290 --- /dev/null +++ b/core/test/frameworks/fabric/test_live_view.ex @@ -0,0 +1,21 @@ +defmodule Fabric.TestLiveView do + use Phoenix.LiveView + use Fabric.LiveView + + @impl true + def mount(%{"id" => id}, _session, socket) do + {:ok, socket |> assign(id: id)} + end + + @impl true + def handle_event("event", _payload, socket) do + {:noreply, socket} + end + + @impl true + def render(assigns) do + ~H""" +
+ """ + end +end diff --git a/core/test/systems/assignment/landing_page_test.exs b/core/test/systems/assignment/landing_page_test.exs index 5ad88cbf5..4e950d23a 100644 --- a/core/test/systems/assignment/landing_page_test.exs +++ b/core/test/systems/assignment/landing_page_test.exs @@ -111,7 +111,7 @@ defmodule Systems.Assignment.LandingPageTest do |> element("[phx-click=\"open\"]") |> render_click() - assert {:error, {:redirect, %{to: "https://eyra.co/alliance/123?panl_id=1"}}} = html + assert {:error, {:redirect, %{to: "https://eyra.co/alliance/123?next_id=1"}}} = html task = Crew.Public.get_task!(task.id) assert %Systems.Crew.TaskModel{started_at: started_at, updated_at: updated_at} = task diff --git a/core/test/systems/campaign/view_model_builder.exs b/core/test/systems/campaign/view_model_builder.exs index 2570de5b2..38db84f4a 100644 --- a/core/test/systems/campaign/view_model_builder.exs +++ b/core/test/systems/campaign/view_model_builder.exs @@ -54,7 +54,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do assert %{ call_to_action: %{ label: "Naar vragenlijst", - path: "https://eyra.co/fake_alliance?panl_id=1", + path: "https://eyra.co/fake_alliance?next_id=1", target: %{type: :event, value: "open"} }, hero_title: "Online Studie", @@ -79,7 +79,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do assert %{ call_to_action: %{ label: "Naar vragenlijst", - path: "https://eyra.co/fake_alliance?panl_id=1", + path: "https://eyra.co/fake_alliance?next_id=1", target: %{type: :event, value: "open"} }, hero_title: "Online Studie", @@ -109,7 +109,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do assert %{ call_to_action: %{ label: "Naar vragenlijst", - path: "https://eyra.co/fake_alliance?panl_id=1", + path: "https://eyra.co/fake_alliance?next_id=1", target: %{type: :event, value: "open"} }, hero_title: "Online Studie", diff --git a/core/test/systems/campaign/x.html b/core/test/systems/campaign/x.html deleted file mode 100644 index 7f6ee7b31..000000000 --- a/core/test/systems/campaign/x.html +++ /dev/null @@ -1,201 +0,0 @@ -
next_wide
- Menu -

- content.title -

\ No newline at end of file From 0d728109a2cbfb65d81b294b800885d32e2cc457 Mon Sep 17 00:00:00 2001 From: emielvdveen Date: Thu, 16 Nov 2023 13:40:25 +0100 Subject: [PATCH 3/3] Added simple Fabric live view test case --- core/test/frameworks/fabric/factories.ex | 2 +- ...ve_component.ex => live_component_mock.ex} | 8 +-- core/test/frameworks/fabric/live_view_mock.ex | 32 +++++++++ .../test/frameworks/fabric/live_view_test.exs | 15 ++++ core/test/frameworks/fabric/test.ex | 68 +++++++++---------- core/test/frameworks/fabric/test_live_view.ex | 21 ------ 6 files changed, 86 insertions(+), 60 deletions(-) rename core/test/frameworks/fabric/{test_live_component.ex => live_component_mock.ex} (61%) create mode 100644 core/test/frameworks/fabric/live_view_mock.ex create mode 100644 core/test/frameworks/fabric/live_view_test.exs delete mode 100644 core/test/frameworks/fabric/test_live_view.ex diff --git a/core/test/frameworks/fabric/factories.ex b/core/test/frameworks/fabric/factories.ex index 25b0b5e7e..8cb0690f7 100644 --- a/core/test/frameworks/fabric/factories.ex +++ b/core/test/frameworks/fabric/factories.ex @@ -11,7 +11,7 @@ defmodule Fabric.Factories do %Fabric.Model{parent: nil, self: self, children: []} end - def create_child(id, module \\ Fabric.TestLiveComponent, params \\ %{}) do + def create_child(id, module \\ Fabric.LiveComponentMock, params \\ %{}) do ref = %Fabric.LiveComponent.RefModel{id: id, module: module} fabric = create_fabric(ref) params = Map.put(params, :fabric, fabric) diff --git a/core/test/frameworks/fabric/test_live_component.ex b/core/test/frameworks/fabric/live_component_mock.ex similarity index 61% rename from core/test/frameworks/fabric/test_live_component.ex rename to core/test/frameworks/fabric/live_component_mock.ex index eae0349b4..928ce5900 100644 --- a/core/test/frameworks/fabric/test_live_component.ex +++ b/core/test/frameworks/fabric/live_component_mock.ex @@ -1,10 +1,10 @@ -defmodule Fabric.TestLiveComponent do +defmodule Fabric.LiveComponentMock do use Phoenix.LiveComponent use Fabric.LiveComponent @impl true - def update(%{"id" => id}, socket) do - {:ok, socket |> assign(id: id)} + def update(%{text: text}, socket) do + {:ok, socket |> assign(text: text)} end @impl true @@ -15,7 +15,7 @@ defmodule Fabric.TestLiveComponent do @impl true def render(assigns) do ~H""" -
+
<%= @text %>
""" end end diff --git a/core/test/frameworks/fabric/live_view_mock.ex b/core/test/frameworks/fabric/live_view_mock.ex new file mode 100644 index 000000000..d01b49e69 --- /dev/null +++ b/core/test/frameworks/fabric/live_view_mock.ex @@ -0,0 +1,32 @@ +defmodule Fabric.LiveViewMock do + use Phoenix.LiveView + use Fabric.LiveView + + @impl true + def mount(:not_mounted_at_router, _session, socket) do + { + :ok, + socket + |> add_child(:child_a, "Child A") + |> add_child(:child_b, "Child B") + } + end + + defp add_child(socket, child_id, text) do + child = prepare_child(socket, child_id, Fabric.LiveComponentMock, %{text: text}) + socket |> show_child(child) + end + + @impl true + def handle_event("event", _payload, socket) do + {:noreply, socket} + end + + @impl true + def render(assigns) do + ~H""" + <.child id={:child_a} fabric={@fabric} /> + <.child id={:child_b} fabric={@fabric} /> + """ + end +end diff --git a/core/test/frameworks/fabric/live_view_test.exs b/core/test/frameworks/fabric/live_view_test.exs new file mode 100644 index 000000000..314e00010 --- /dev/null +++ b/core/test/frameworks/fabric/live_view_test.exs @@ -0,0 +1,15 @@ +defmodule Fabric.LiveViewTest do + use CoreWeb.ConnCase, async: true + import Phoenix.LiveViewTest + + test "launching live view mock", %{conn: conn} do + {:ok, _view, html} = + conn + |> Map.put(:request_path, "") + |> put_connect_params(%{"param" => "value"}) + |> live_isolated(Fabric.LiveViewMock) + + assert html =~ "Child A" + assert html =~ "Child B" + end +end diff --git a/core/test/frameworks/fabric/test.ex b/core/test/frameworks/fabric/test.ex index 878e15af1..49adc62e2 100644 --- a/core/test/frameworks/fabric/test.ex +++ b/core/test/frameworks/fabric/test.ex @@ -32,16 +32,16 @@ defmodule Fabric.Test do child = %Phoenix.LiveView.Socket{} |> Phoenix.Component.assign(:fabric, fabric) - |> Fabric.prepare_child(:child, Fabric.TestLiveComponent, %{}) + |> Fabric.prepare_child(:child, Fabric.LiveComponentMock, %{}) assert %Fabric.LiveComponent.Model{ - ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.LiveComponentMock}, params: %{ fabric: %Fabric.Model{ parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -52,16 +52,16 @@ defmodule Fabric.Test do test "fabric" do fabric = %Fabric.Model{parent: nil, children: []} - child = Fabric.prepare_child(fabric, :child, Fabric.TestLiveComponent, %{}) + child = Fabric.prepare_child(fabric, :child, Fabric.LiveComponentMock, %{}) assert %Fabric.LiveComponent.Model{ - ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.LiveComponentMock}, params: %{ fabric: %Fabric.Model{ parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -81,13 +81,13 @@ defmodule Fabric.Test do |> Fabric.get_child(:child) assert %Fabric.LiveComponent.Model{ - ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.LiveComponentMock}, params: %{ fabric: %Fabric.Model{ parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -101,13 +101,13 @@ defmodule Fabric.Test do |> Fabric.get_child(:child) assert %Fabric.LiveComponent.Model{ - ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.LiveComponentMock}, params: %{ fabric: %Fabric.Model{ parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -135,14 +135,14 @@ defmodule Fabric.Test do %Fabric.LiveComponent.Model{ ref: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, params: %{ fabric: %Fabric.Model{ parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -172,14 +172,14 @@ defmodule Fabric.Test do %Fabric.LiveComponent.Model{ ref: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, params: %{ fabric: %Fabric.Model{ parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -194,8 +194,8 @@ defmodule Fabric.Test do describe "replace_child/2" do test "socket" do - child_1 = create_child(:child, Fabric.TestLiveComponent, %{some: :thing}) - child_2 = create_child(:child, Fabric.TestLiveComponent, %{another: :thing}) + child_1 = create_child(:child, Fabric.LiveComponentMock, %{some: :thing}) + child_2 = create_child(:child, Fabric.LiveComponentMock, %{another: :thing}) fabric = %Fabric.Model{parent: nil, children: [child_1]} @@ -213,7 +213,7 @@ defmodule Fabric.Test do %Fabric.LiveComponent.Model{ ref: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, params: %{ another: :thing, @@ -221,7 +221,7 @@ defmodule Fabric.Test do parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -236,7 +236,7 @@ defmodule Fabric.Test do describe "hide_child/2" do test "socket" do - child = create_child(:child, Fabric.TestLiveComponent) + child = create_child(:child, Fabric.LiveComponentMock) fabric = %Fabric.Model{parent: nil, children: [child]} @@ -259,7 +259,7 @@ defmodule Fabric.Test do describe "remove_child/2" do test "socket" do - child = create_child(:child, Fabric.TestLiveComponent) + child = create_child(:child, Fabric.LiveComponentMock) fabric = %Fabric.Model{parent: nil, children: [child]} @@ -294,11 +294,11 @@ defmodule Fabric.Test do params: %{ fabric: %Fabric.Model{ parent: nil, - self: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent}, + self: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.LiveComponentMock}, children: [] } }, - ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.TestLiveComponent} + ref: %Fabric.LiveComponent.RefModel{id: :child, module: Fabric.LiveComponentMock} } } } @@ -314,14 +314,14 @@ defmodule Fabric.Test do %Fabric.LiveComponent.Model{ ref: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, params: %{ fabric: %Fabric.Model{ parent: nil, self: %Fabric.LiveComponent.RefModel{ id: :child, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock }, children: [] } @@ -336,7 +336,7 @@ defmodule Fabric.Test do describe "hide_popup/2" do test "socket" do - child = create_child(:child, Fabric.TestLiveComponent) + child = create_child(:child, Fabric.LiveComponentMock) fabric = %Fabric.Model{parent: nil, children: [child]} @@ -365,7 +365,7 @@ defmodule Fabric.Test do %Phoenix.LiveView.Socket{} |> Phoenix.Component.assign(:fabric, create_fabric()) - child = Fabric.prepare_child(socket, :child, Fabric.TestLiveComponent, %{}) + child = Fabric.prepare_child(socket, :child, Fabric.LiveComponentMock, %{}) socket = Fabric.show_child(socket, child) Fabric.send_event(socket, :child, "event", %{some: :message}) @@ -374,7 +374,7 @@ defmodule Fabric.Test do :phoenix, :send_update, { - {Fabric.TestLiveComponent, :child}, + {Fabric.LiveComponentMock, :child}, %{ fabric_event: %{ name: "event", @@ -391,8 +391,8 @@ defmodule Fabric.Test do %Phoenix.LiveView.Socket{} |> Phoenix.Component.assign(:fabric, create_fabric()) - child1 = Fabric.prepare_child(socket, :child1, Fabric.TestLiveComponent, %{}) - child2 = Fabric.prepare_child(child1.params.fabric, :child2, Fabric.TestLiveComponent, %{}) + child1 = Fabric.prepare_child(socket, :child1, Fabric.LiveComponentMock, %{}) + child2 = Fabric.prepare_child(child1.params.fabric, :child2, Fabric.LiveComponentMock, %{}) Fabric.send_event(child2.params.fabric, :parent, "event", %{some: :message}) @@ -400,7 +400,7 @@ defmodule Fabric.Test do :phoenix, :send_update, { - {Fabric.TestLiveComponent, :child1}, + {Fabric.LiveComponentMock, :child1}, %{ fabric_event: %{ name: "event", @@ -408,7 +408,7 @@ defmodule Fabric.Test do some: :message, source: %Fabric.LiveComponent.RefModel{ id: :child2, - module: Fabric.TestLiveComponent + module: Fabric.LiveComponentMock } } }, @@ -423,8 +423,8 @@ defmodule Fabric.Test do %Phoenix.LiveView.Socket{} |> Phoenix.Component.assign(:fabric, create_fabric()) - child1 = Fabric.prepare_child(socket, :child1, Fabric.TestLiveComponent, %{}) - child2 = Fabric.prepare_child(child1.params.fabric, :child2, Fabric.TestLiveComponent, %{}) + child1 = Fabric.prepare_child(socket, :child1, Fabric.LiveComponentMock, %{}) + child2 = Fabric.prepare_child(child1.params.fabric, :child2, Fabric.LiveComponentMock, %{}) Fabric.send_event(child2.params.fabric, :root, "event", %{some: :message}) @@ -436,7 +436,7 @@ defmodule Fabric.Test do %Phoenix.LiveView.Socket{} |> Phoenix.Component.assign(:fabric, create_fabric()) - child = Fabric.prepare_child(socket, :child, Fabric.TestLiveComponent, %{}) + child = Fabric.prepare_child(socket, :child, Fabric.LiveComponentMock, %{}) Fabric.send_event(child.params.fabric, :parent, "event", %{some: :message}) diff --git a/core/test/frameworks/fabric/test_live_view.ex b/core/test/frameworks/fabric/test_live_view.ex deleted file mode 100644 index 2bb3dc290..000000000 --- a/core/test/frameworks/fabric/test_live_view.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Fabric.TestLiveView do - use Phoenix.LiveView - use Fabric.LiveView - - @impl true - def mount(%{"id" => id}, _session, socket) do - {:ok, socket |> assign(id: id)} - end - - @impl true - def handle_event("event", _payload, socket) do - {:noreply, socket} - end - - @impl true - def render(assigns) do - ~H""" -
- """ - end -end