+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= live_flash(@flash, :error) %>
+
<% end %>
-
+
-
<%= render_slot(@inner_block) %>
@@ -79,9 +81,11 @@ defmodule CoreWeb.Layouts.Stripped.Component do
- <.content_footer />
-
+ <%= if @footer? do %>
+
+ <.content_footer />
+
+ <% end %>
diff --git a/core/lib/core_web/controllers/layouts/workspace/component.ex b/core/lib/core_web/controllers/layouts/workspace/component.ex
index 3b7ddcb07..f4dee1843 100644
--- a/core/lib/core_web/controllers/layouts/workspace/component.ex
+++ b/core/lib/core_web/controllers/layouts/workspace/component.ex
@@ -58,50 +58,54 @@ defmodule CoreWeb.Layouts.Workspace.Component do
~H"""
-
-
-
-
-
-
-
-
-
-
-
-
- <%= if @title do %>
-
-
-
- <% end %>
- <%= if @top_bar do %>
-
- <%= render_slot(@top_bar) %>
+
+
-
+
+
+
+
+
+
diff --git a/core/lib/core_web/endpoint.ex b/core/lib/core_web/endpoint.ex
index 80779e6b9..2c7a050f8 100644
--- a/core/lib/core_web/endpoint.ex
+++ b/core/lib/core_web/endpoint.ex
@@ -1,5 +1,6 @@
defmodule CoreWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :core
+ require Systems.Feldspar.Plug
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
@@ -33,6 +34,8 @@ defmodule CoreWeb.Endpoint do
)
end
+ Systems.Feldspar.Plug.setup()
+
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phx.digest
diff --git a/core/lib/core_web/file_uploader.ex b/core/lib/core_web/file_uploader.ex
index f2f05a4f8..938a41589 100644
--- a/core/lib/core_web/file_uploader.ex
+++ b/core/lib/core_web/file_uploader.ex
@@ -22,18 +22,17 @@ defmodule CoreWeb.FileUploader do
# Skip init if it already has been called
def init_file_uploader(%{assigns: %{uploads: _uploads}} = socket, _key), do: socket
- def init_file_uploader(socket, key) do
+ def init_file_uploader(socket, key, max_file_size \\ 20_000_000) do
socket
|> allow_upload(key,
accept: unquote(accept),
progress: &handle_progress/3,
+ max_file_size: max_file_size,
auto_upload: true
)
end
def handle_progress(_key, entry, socket) do
- IO.puts("handle_progress")
-
if entry.done? do
upload_result = consume_file(socket, entry)
{:noreply, socket |> process_file(upload_result)}
@@ -43,8 +42,6 @@ defmodule CoreWeb.FileUploader do
end
def consume_file(socket, entry) do
- IO.puts("consume_file")
-
consume_uploaded_entry(socket, entry, fn %{path: tmp_path} ->
file = "#{entry.uuid}.#{ext(entry)}"
local_full_path = CoreWeb.FileUploader.get_static_path(file)
diff --git a/core/lib/core_web/live/fake_qualtrics.ex b/core/lib/core_web/live/fake_qualtrics.ex
new file mode 100644
index 000000000..64dc07287
--- /dev/null
+++ b/core/lib/core_web/live/fake_qualtrics.ex
@@ -0,0 +1,37 @@
+defmodule CoreWeb.FakeQualtrics do
+ @moduledoc """
+ The home screen.
+ """
+ use CoreWeb, :live_view
+ alias Frameworks.Pixel.Text
+ alias Frameworks.Pixel.Button
+
+ def mount(%{"re" => redirect_url}, _session, socket) do
+ socket =
+ socket
+ |> assign(redirect_url: redirect_url)
+
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_uri(socket), do: socket
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+
+
+
+
+
-
+
-
+
- <%= if @footer do %>
-
+ <%= if @title do %>
+
+ <%= if @footer do %>
+
+
+
+ <% end %>
+ <%= if @top_bar do %>
+
+ <%= render_slot(@top_bar) %>
+
+ <% end %>
+
+ <%= render_slot(@inner_block) %>
+
- <% end %>
-
- <%= render_slot(@inner_block) %>
-
+ <.content_footer />
+
+ <% end %>
- <.content_footer />
-
- <% end %>
- <.platform_footer />
+
+ <.platform_footer />
+
+
+
+ Questionnaire
+ This page is used to validate the questionnaire roundtrip.
+ <.spacing value="M" />
+
+
+
+ """
+ end
+end
diff --git a/core/lib/core_web/live/fake_survey.ex b/core/lib/core_web/live/fake_survey.ex
deleted file mode 100644
index 2520728f0..000000000
--- a/core/lib/core_web/live/fake_survey.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule CoreWeb.FakeSurvey do
- @moduledoc """
- The home screen.
- """
- use CoreWeb, :live_view
- alias Frameworks.Pixel.Hero
- alias Frameworks.Pixel.Text
- alias Frameworks.Pixel.Button
-
- def mount(%{"id" => id}, _session, socket) do
- redirect_url = "/task/#{id}/callback"
-
- socket =
- socket
- |> assign(redirect_url: redirect_url)
-
- {:ok, socket}
- end
-
- @impl true
- def handle_uri(socket), do: socket
-
- # data(redirect_url, :string)
- @impl true
- def render(assigns) do
- ~H"""
-
+ Qualtrics emulator
+
+
-
-
-
-
- Fake survey
- This fake survey is used to validate the survey tool flow with an external tool.
- <.spacing value="M" />
-
-
-
- """
- end
-end
diff --git a/core/lib/core_web/live/routes.ex b/core/lib/core_web/live/routes.ex
index 96c9ecfba..9ef290c2b 100644
--- a/core/lib/core_web/live/routes.ex
+++ b/core/lib/core_web/live/routes.ex
@@ -8,7 +8,7 @@ defmodule CoreWeb.Live.Routes do
scope "/", CoreWeb do
pipe_through(:browser)
get("/switch-language/:locale", LanguageSwitchController, :index)
- live("/fake_survey/:id", FakeSurvey)
+ live("/fake_qualtrics", FakeQualtrics)
end
if Mix.env() in [:test] do
diff --git a/core/lib/core_web/loaders.ex b/core/lib/core_web/loaders.ex
index ef6943aa7..2dbf0aafb 100644
--- a/core/lib/core_web/loaders.ex
+++ b/core/lib/core_web/loaders.ex
@@ -8,6 +8,6 @@ defmodule CoreWeb.Loaders do
defloader(:campaign, &Systems.Campaign.Public.get!/1)
defloader(:promotion, &Systems.Promotion.Public.get!/1)
defloader(:assignment, &Systems.Assignment.Public.get!/1)
- defloader(:survey_tool, &Systems.Survey.Public.get_survey_tool!/1)
+ defloader(:alliance_tool, &Systems.Alliance.Public.get_tool!/1)
defloader(:user_profile, &Core.Accounts.get_profile/1)
end
diff --git a/core/lib/core_web/ui/navigation.ex b/core/lib/core_web/ui/navigation.ex
index 0de5602cb..c9c6671cf 100644
--- a/core/lib/core_web/ui/navigation.ex
+++ b/core/lib/core_web/ui/navigation.ex
@@ -68,7 +68,7 @@ defmodule CoreWeb.UI.Navigation do
+
<%= if @centralize do %>
@@ -136,7 +136,7 @@ defmodule CoreWeb.UI.Navigation do
def desktop_menu(assigns) do
~H"""
-
+
"""
@@ -148,7 +148,7 @@ defmodule CoreWeb.UI.Navigation do
def tablet_menu(assigns) do
~H"""
-
+
"""
diff --git a/core/lib/core_web/ui/step_indicator.ex b/core/lib/core_web/ui/step_indicator.ex
index 7bc1b314b..ea6c9bbd0 100644
--- a/core/lib/core_web/ui/step_indicator.ex
+++ b/core/lib/core_web/ui/step_indicator.ex
@@ -4,8 +4,8 @@ defmodule CoreWeb.UI.StepIndicator do
"""
use CoreWeb, :html
- defp center_correction_for_number(1), do: "mr-1px"
- defp center_correction_for_number(4), do: "mr-1px"
+ defp center_correction_for_number(1), do: "mr-px"
+ defp center_correction_for_number(4), do: "mr-px"
defp center_correction_for_number(_), do: ""
attr(:text, :string, required: true)
@@ -15,7 +15,7 @@ defmodule CoreWeb.UI.StepIndicator do
def step_indicator(assigns) do
~H"""
)|()|' \
- r'((afbeelding|GIF|video|image|audio|(s|S)ticker|.*document.*) (weggelaten|omitted))'
-FILE_RE = re.compile(r".*.txt$")
-HIDDEN_FILE_RE = re.compile(r".*__MACOSX*")
-
-
-SYSTEM_MESSAGES = ['end-to-end', 'WhatsApp']
-hformats = ['%m/%d/%y, %H:%M - %name:', '[%d/%m/%y, %H:%M:%S] %name:', '%d-%m-%y %H:%M - %name:',
- '[%d-%m-%y %H:%M:%S] %name:', '[%m/%d/%y, %H:%M:%S] %name:', '%d/%m/%y, %H:%M – %name:',
- '%d/%m/%y, %H:%M - %name:', '%d.%m.%y, %H:%M – %name:', '%d.%m.%y, %H:%M - %name:',
- '%m.%d.%y, %H:%M - %name:', '%m.%d.%y %H:%M - %name:',
- '[%d/%m/%y, %H:%M:%S %P] %name:', '[%m/%d/%y, %H:%M:%S %P] %name:',
- '[%d.%m.%y, %H:%M:%S] %name:', '[%m/%d/%y %H:%M:%S] %name:',
- '[%m-%d-%y, %H:%M:%S] %name:',
- '[%m-%d-%y %H:%M:%S] %name:', '%m-%d-%y %H:%M - %name:', '%m-%d-%y, %H:%M - %name:',
- '%m-%d-%y, %H:%M , %name:', '%m/%d/%y, %H:%M , %name:', '%d-%m-%y, %H:%M , %name:',
- '%d/%m/%y, %H:%M , %name:', '%d.%m.%y %H:%M – %name:', '%m.%d.%y, %H:%M – %name:',
- '%m.%d.%y %H:%M – %name:', '[%d.%m.%y %H:%M:%S] %name:', '[%m.%d.%y, %H:%M:%S] %name:',
- '[%m.%d.%y %H:%M:%S] %name:']
-
-
-class ColnamesDf: # pylint: disable=R0903
- """Access class constants using variable ``COLNAMES_DF``."""
-
- DATE = 'date'
- """Date column"""
-
- USERNAME = 'username'
- """Username column"""
-
- MESSAGE = 'message'
- """Message column"""
-
- MESSAGE_LENGTH = 'Lengte bericht'
- """Message length column"""
-
- FirstMessage = 'Datum eerste bericht'
- """Date of first message column"""
-
- LastMessage = 'Datum laatste bericht'
- """Date of last message column"""
-
- MESSAGE_NO = 'Aantal berichten'
- """Number of Message column"""
-
- WORDS_NO = 'Aantal woorden'
- """Total number of words column"""
-
- REPLY_2USER = 'Wie reageert het meest op deze deelnemer?'
- """Who replies to the user the most column"""
-
- USER_REPLY2 = 'Op wie reageert deze deelnemer het meest?'
- """User replies to who the most column"""
-
- URL_NO = 'Aantal websites'
- """Number of URLs column"""
-
- LOCATION_NO = 'Aantal locaties'
- """Number of locations column"""
-
- FILE_NO = 'Aantal foto’s en bestanden'
- """Number of files column"""
-
- EMOJI_NO = 'emoji_no'
- """Total number of emojies column"""
-
- EMOJI_Fav = 'emoji_fav'
- """Favorite emojies column"""
-
- DESCRIPTION = 'Omschrijving'
- """Variable column in melted dataframe"""
-
- VALUE = 'Gegevens'
- """Value column in melted dataframe"""
-
-
-COLNAMES_DF = ColnamesDf()
-
-
-class DutchConst: # pylint: disable=R0903
- """Access class constants using variable ``DUTCH_CONST``."""
-
- YOU = 'u'
- """Refer to the data donor in dutch"""
- PRE_MESSAGE = ''
- #POST_MESSAGE = 'Dit is voor ons nog steeds waardevolle informatie' \
- # ' en u kunt dit resultaat ook doneren.'
-
-
-DUTCH_CONST = DutchConst()
-
-# *** parsing functions ***
-regex_simplifier = {
- '%Y': r'(?P\d{2,4})',
- '%y': r'(?P\d{2,4})',
- '%m': r'(?P\d{1,2})',
- '%d': r'(?P\d{1,2})',
- '%H': r'(?P\d{1,2})',
- '%I': r'(?P\d{1,2})',
- '%M': r'(?P\d{2})',
- '%S': r'(?P\d{2})',
- '%P': r'(?P[AaPp].? ?[Mm].?)',
- '%p': r'(?P[AaPp].? ?[Mm].?)',
- '%name': fr'(?P<{COLNAMES_DF.USERNAME}>[^:]*)'
-}
-
-
-def generate_regex(log_error, hformat):
- r"""Generate regular expression from hformat.
- Parameters
- ----------
- log_error : list
- List of error messages
- hformat :str
- Simplified syntax for the header, e.g. ``'%y-%m-%d, %H:%M:%S - %name:'``.
- Returns
- -------
- str
- Regular expression corresponding to the specified syntax
- """
- items = re.findall(r'\%\w*', hformat)
-
- for i in items:
- try:
- hformat = hformat.replace(i, regex_simplifier[i])
- except KeyError:
- log_error(f"Could find regular expression for : {i}")
-
- hformat = hformat + ' '
- hformat_x = hformat.split('(?P[^:]*)')[0]
- return hformat, hformat_x
-
-
-def add_schema(df_chat):
- """Add default chat schema to df.
- Parameters
- ----------
- df_chat : pandas.DataFrame
- Chat DataFrame.
- Returns
- -------
- pandas.DataFrame
- Chat DataFrame with correct dtypes
- """
- df_chat = df_chat.astype({
- COLNAMES_DF.DATE: pd.StringDtype(),
- COLNAMES_DF.USERNAME: pd.StringDtype(),
- COLNAMES_DF.MESSAGE: pd.StringDtype()
- })
- return df_chat
-
-
-def parse_line(text, headers, i):
- """Get date, username and message from the i:th intervention.
- Parameters
- ----------
- text : str
- Whole log chat text
- headers : list
- All headers.
- i : int
- Index denoting the message number
- Returns
- -------
- dict
- ith date, username and message.
- """
- result_ = headers[i].groupdict()
- if 'ampm' in result_:
- hour = int(result_['hour'])
- mode = result_.get('ampm').lower()
- if hour == 12 and mode == 'am':
- hour = 0
- elif hour != 12 and mode == 'pm':
- hour += 12
- else:
- hour = int(result_['hour'])
-
- # Check format of year. If year is 2-digit represented we add 2000
- if len(result_['year']) == 2:
- year = int(result_['year']) + 2000
- else:
- year = int(result_['year'])
-
- if 'seconds' not in result_:
- date = datetime(year, int(result_['month']), int(result_['day']), hour,
- int(result_['minutes']))
- else:
- date = datetime(year, int(result_['month']), int(result_['day']), hour,
- int(result_['minutes']), int(result_['seconds']))
- username = result_[COLNAMES_DF.USERNAME]
- message = get_message(text, headers, i)
- line_dict = {
- COLNAMES_DF.DATE: date,
- COLNAMES_DF.USERNAME: username,
- COLNAMES_DF.MESSAGE: message
- }
- return line_dict
-
-
-def remove_alerts_from_df(r_x, df_chat):
- """Try to get rid of alert/notification messages.
- Parameters
- ----------
- r_x : str
- Regular expression to detect whatsapp warnings
- df_chat : pandas.DataFrame
- pandas.DataFrame with all interventions
- Returns
- -------
- pandas.DataFrame
- Fixed version of input DataFrame
- """
-
- alerts_no = count_alerts(r_x, df_chat)
- df_new = df_chat.copy()
- df_new.loc[:, COLNAMES_DF.MESSAGE] = df_new[COLNAMES_DF.MESSAGE].apply(
- lambda x: remove_alerts_from_line(r_x, x))
- return df_new, alerts_no
-
-
-def remove_alerts_from_line(r_x, line_df):
- """Remove line content that is not desirable (automatic alerts etc.).
- Parameters
- ----------
- r_x : str
- Regula expression to detect WhatsApp warnings
- line_df : str
- Message sent as string
- Returns
- -------
- str
- Cleaned message string
- """
- if re.search(r_x, line_df):
- return line_df[:re.search(r_x, line_df).start()]
-
- return line_df
-
-
-def count_alerts(r_x, df_chat):
- """Count line content that is not desirable (automatic alerts etc.).
- Parameters
- ----------
- r_x : str
- Regula expression to detect WhatsApp warnings
- df_chat : pandas.DataFrame
- pandas.DataFrame with all interventions
-
- Returns
- -------
- int
- Number of line contents that is not desirable
- """
-
- # alerts_count = df[COLNAMES_DF.MESSAGE].apply(lambda x: (re.search(r_x, x) is not None))
- alerts_count = df_chat[COLNAMES_DF.MESSAGE].apply(lambda x: re.findall(r_x, x))
- return alerts_count.str.len().sum()
-
-
-def get_message(text, headers, i):
- """Get i:th message from text.
- Parameters
- ----------
- text : str
- Whole log chat text
- headers : list
- All headers
- i : int
- Index denoting the message number
- Returns
- -------
- str
- ith message.
- """
- msg_start = headers[i].end()
- msg_end = headers[i + 1].start() if i < len(headers) - 1 else headers[i].endpos
- msg = text[msg_start:msg_end].strip()
- return msg
-
-
-def parse_text(text, regex):
- """Parse chat using given regex.
- Parameters
- ----------
- text : str
- Whole log chat text
- regex : str
- Regular expression
- Returns
- -------
- pandas.DataFrame
- pandas.DataFrame with messages sent by users
-
- Raises
- ------
- RegexError
- When provided regex could not match the text
- """
- result = []
- headers = list(re.finditer(regex, text))
- try:
- for i in range(len(headers)):
- line_dict = parse_line(text, headers, i)
- result.append(line_dict)
- except KeyError:
- print("Could not match the provided regex with provided text. No match was found.")
- return None
-
- df_chat = pd.DataFrame.from_records(result)
- df_chat = df_chat[[COLNAMES_DF.DATE, COLNAMES_DF.USERNAME, COLNAMES_DF.MESSAGE]]
-
- # clean username
- df_chat[COLNAMES_DF.USERNAME] = df_chat[COLNAMES_DF.USERNAME].apply(lambda u: u.strip('\u202c'))
-
- return df_chat
-
-
-def make_df_general_regx(log_error, text):
- """Use a general regex to load chat as a DataFrame.
- Parameters
- ----------
- log_error : list
- List of error messages
- text : str
- Text of the chat
-
- Returns
- -------
- pandas.DataFrame
- pandas.DataFrame with messages sent by users
-
- """
-
- expr_to_test = re.compile(r"^(.*?)(?:\] | - )(.*?): (.*?$)", flags=re.DOTALL)
- lines = text.split("\n")
- result = []
- line_counts = len(lines)
- for line in lines:
- try:
-
- res = expr_to_test.match(line)
-
- timestamp = res.group(1)
- username = res.group(2)
- message = res.group(3)
-
- # clean timestamp
- timestamp = timestamp.replace('[', '').replace(',', '')
- timestamp = timestamp.strip('\u202c')
- timestamp = timestamp.strip('\u200e')
-
- timestamp = pd.to_datetime(timestamp)
-
- line_dict = {
- COLNAMES_DF.DATE: timestamp,
- COLNAMES_DF.USERNAME: username,
- COLNAMES_DF.MESSAGE: message
- }
-
- result.append(line_dict)
- except AttributeError:
- pass
-
- if len(result) == 0:
- log_error("Failed to read the Chat file.")
- return None
- df_chat = pd.DataFrame.from_records(result)
- df_chat = df_chat[[COLNAMES_DF.DATE, COLNAMES_DF.USERNAME, COLNAMES_DF.MESSAGE]]
-
- # clean username
- df_chat[COLNAMES_DF.USERNAME] = df_chat[COLNAMES_DF.USERNAME].apply(
- lambda u: u.strip('\u202c'))
-
- # log unprocessed_line_no= the number of lines in multiline messages +
- # the number of system messages
- unprocessed_line_no = line_counts - df_chat.shape[0]
-
- if unprocessed_line_no > 0:
- log_error("Number of unprocessed lines: " + str(unprocessed_line_no))
-
- return df_chat
-
-
-def make_chat_df(log_error, text, hformat):
- """Load chat as a DataFrame.
- Parameters
- ----------
- log_error : list
- List of error messages
- text : str
- Text of the chat
- hformat : str
- Simplified syntax for the header, e.g. ``'%y-%m-%d, %H:%M:%S - %name:'``
-
- Returns
- -------
- pandas.DataFrame
- A pandas.DataFrame with three columns, i.e. 'date', 'username', and 'message'
- """
- # Bracket is reserved character in RegEx, add backslash before them.
- hformat = hformat.replace('[', r'\[').replace(']', r'\]')
-
- # Generate regex for given hformat
- reg, r_x = generate_regex(log_error, hformat=hformat)
-
- # Parse chat to DataFrame
- try:
- df_chat = parse_text(text, reg)
- df_chat, alerts_no = remove_alerts_from_df(r_x, df_chat)
- df_chat = add_schema(df_chat)
-
- if alerts_no > 0:
- log_error("Number of unprocessed system messages: " + str(alerts_no))
-
- return df_chat
- except (KeyError, AttributeError, ValueError):
- print(f"hformat : {hformat} is not match with the given text")
- return None
-
-
-def parse_chat(log_error, data):
- """Parse chat and test it with defined hformats.
- Parameters
- ----------
- log_error : list
- List of error messages.
- data : str
- Data read from the chat file
- Returns
- -------
- pandas.dataframe
- A pandas.DataFrame with three columns, i.e. 'date', 'username', and 'message'
- """
- for hformat in hformats:
- # Build DataFrame
- df_chat = make_chat_df(log_error, data, hformat)
- if df_chat is not None:
- return df_chat
- log_error("hformats did not match the provided text. We try to use a general regex"
- " to read the chat file. ")
- # If header format is unknown to our script we use a loose regular expression to detect
- df_chat = make_df_general_regx(log_error, data)
- # if df_chat.shape[0] > 0:
- # return df_chat
- # log_error("Failed to read the Chat file.")
- # return None
- return df_chat
-
-
-def decode_chat(log_error, file_chat, filename):
- """Parse the given zip file.
- Parameters
- ----------
- log_error : list
- List of error messages.
- f : bytes
- bytes of the file name in the zip file
- filename : str
- Name of a compressed file in the zip file.
- Returns
- -------
- pandas.DataFrame
- A pandas.DataFrame which includes the content of the given chat file.
- """
- try:
- data = file_chat.decode("utf-8")
- except UnicodeEncodeError:
- log_error(f"Could not decode to utf-8: {filename}")
- return None
- else:
- return parse_chat(log_error, data)
-
-
-def parse_zipfile(log_error, zfile):
- """Parse the given zip file.
- Parameters
- ----------
- log_error : list
- List of error messages
- zfile : ZipFile object
- Regular expression
- Returns
- -------
- pandas.DataFrame
- A pandas.DataFrames which include the content of the chat file.
- """
- for name in zfile.namelist():
- if HIDDEN_FILE_RE.match(name):
- continue
- if not FILE_RE.match(name):
- continue
- chat = decode_chat(log_error, zfile.read(name), name)
-
- if chat is None:
- log_error("No valid chat file is available")
-
- return chat
-
-# *** test related function ***
-
-
-def input_df(data_path):
- """Create inputs df_chats and df_participants, used for test purposes.
- Parameters
- ----------
- data_path : str
- File path of zip file
- Returns
- -------
- pandas.DataFrame
- df_chats and df_participants
- """
- errors = []
- log_error = errors.append
- username = 'Deelnemer 1'
- fp_chat = os.path.join(data_path, "whatsapp_chat.zip")
- chat_df = parse_chat_file(log_error, str(fp_chat))
- if chat_df is not None:
- chat_df = remove_system_messages(chat_df)
- participants_df = get_participants_features(chat_df)
- results = extract_results(participants_df, username, anonymize=False)
- return chat_df, results
- return None
-
-# *** analysis functions ***
-
-
-def get_response_matrix(df_chat):
- """Create a response matrix for the usernames mentioned in the given DataFrame.
- Parameters
- ----------
- df_chat: padas.DataFrame
- A DataFrame including chat data
- Returns
- -------
- pandas.DataFrame
- A DataFrame with senders in the rows and receivers in the columns
- """
- users = set(df_chat[COLNAMES_DF.USERNAME])
- users = sorted(users)
-
- # Get list of username transitions and initialize dicitonary with counts
- user_transitions = df_chat[COLNAMES_DF.USERNAME].tolist()
- responses = {user: dict(zip(users, [0] * len(users))) for user in users}
- # Fill count dictionary
- for i in range(1, len(user_transitions)):
- sender = user_transitions[i]
- receiver = user_transitions[i - 1]
- if sender != receiver:
- responses[sender][receiver] += 1
-
- responses = pd.DataFrame.from_dict(responses, orient='index')
- return responses
-
-
-# def make_salt():
-# """Return an string as salt for anonym_txt function.
-# Returns
-# -------
-# str
-# The salt value is deliberately set to be a fixed value for all the usernames,
-# because then we can generate the
-# same hashed value for the same value in the UERNAME, REPLY_2USER, and USER_REPLY2 columns.
-# """
-# return str.encode('WhatsAppProject@2022')
-
-
-def anonymize_participants(df_participants, donor_user_name):
- """Anonymize text data.
- Anonymize USERNAME, REPLY_2USER, and USER_REPLY2 columns of the given DataFrame.
- Parameters
- ----------
- df_participants : pandas.DataFrame
- A DataFrame including participants data
- Returns
- -------
- pandas.DataFrame
- An anonymized DataFrame
- """
- # salt = make_salt()
- # df_participants[COLNAMES_DF.USERNAME] = df_participants[COLNAMES_DF.USERNAME].apply(
- # lambda u: anonym_txt(u, salt))
- # df_participants[COLNAMES_DF.REPLY_2USER] = df_participants
- # [COLNAMES_DF.REPLY_2USER].apply(lambda u:
- # anonym_txt(u,salt))
- # df_participants[COLNAMES_DF.USER_REPLY2] = df_participants[COLNAMES_DF.USER_REPLY2].
- # apply(lambda u:
- # anonym_txt(u,salt))
- # df_participants[['username', 'user_reply2']] = df_participants[['username',
- # 'user_reply2']].stack().rank(
- # method='dense').unstack()
-
- stacked = df_participants[[COLNAMES_DF.USERNAME,
- COLNAMES_DF.USER_REPLY2,
- COLNAMES_DF.REPLY_2USER]].stack()
- df_participants[[COLNAMES_DF.USERNAME,
- COLNAMES_DF.USER_REPLY2,
- COLNAMES_DF.REPLY_2USER]] = \
- pd.Series(stacked.factorize()[0], index=stacked.index).unstack()
- df_participants[[COLNAMES_DF.USERNAME,
- COLNAMES_DF.USER_REPLY2,
- COLNAMES_DF.REPLY_2USER]] = \
- 'Deelnemer ' + df_participants[[COLNAMES_DF.USERNAME,
- COLNAMES_DF.USER_REPLY2,
- COLNAMES_DF.REPLY_2USER]].astype(str)
-
- # replace donor_user_name with word 'you'
- fact_index_bool = (stacked.factorize()[1] == donor_user_name)
- you_index = np.where(fact_index_bool)[0][0]
- you_username = 'Deelnemer ' + str(you_index)
-
- df_participants[[COLNAMES_DF.USERNAME,
- COLNAMES_DF.USER_REPLY2,
- COLNAMES_DF.REPLY_2USER]] = \
- df_participants[[COLNAMES_DF.USERNAME,
- COLNAMES_DF.USER_REPLY2,
- COLNAMES_DF.REPLY_2USER]].replace(you_username, DUTCH_CONST.YOU)
-
- return df_participants
-
-
-def get_wide_to_long_participant(df_participants):
- """Generate one dataframe for each participant .
- Parameter
- ----------
- df_participants : pandas.DataFrame
- A DataFrame which includes participants and their features
-
- anonymize : bool
- Indicates if usernames should be anonymized
- Returns
- -------
- list pandas.DataFrame
- A list of pandas.DataFrame. Each data frame includes the description of features and
- their values extracted from a specific participant
- """
- results = []
- df_melt = pd.melt(df_participants, id_vars=[COLNAMES_DF.USERNAME],
- value_vars=[COLNAMES_DF.WORDS_NO,
- COLNAMES_DF.MESSAGE_NO,
- COLNAMES_DF.FirstMessage,
- COLNAMES_DF.LastMessage,
- COLNAMES_DF.URL_NO,
- COLNAMES_DF.FILE_NO,
- COLNAMES_DF.LOCATION_NO,
- COLNAMES_DF.REPLY_2USER,
- COLNAMES_DF.USER_REPLY2],
- var_name=COLNAMES_DF.DESCRIPTION, value_name=COLNAMES_DF.VALUE)
-
- usernames = sorted(set(df_melt[COLNAMES_DF.USERNAME]))
-
- # bring donator username to the top of the list
- usernames.insert(0, usernames.pop(usernames.index(DUTCH_CONST.YOU)))
-
- for user_name in usernames:
- df_user = df_melt[(df_melt[COLNAMES_DF.USERNAME] == user_name) &
- df_melt[COLNAMES_DF.VALUE] != 0]
-
- results.append(df_user)
-
- return results
-
-
-def get_participants_features(df_chat):
- """Calculate participant features from the given chat.
- Parameter
- ----------
- df_chat : pandas.DataFrame
- A DataFrame including chat data
- Returns
- -------
- pandas.DataFrame
- A DataFrame which includes participants and their features
- """
- # Calculate first message date
- df_chat[COLNAMES_DF.FirstMessage] = df_chat[COLNAMES_DF.DATE].astype('datetime64[ns]')
- df_chat[COLNAMES_DF.FirstMessage] = df_chat[COLNAMES_DF.FirstMessage].dt.floor('Min')
- # Calculate last message date
- df_chat[COLNAMES_DF.LastMessage] = df_chat[COLNAMES_DF.DATE].astype('datetime64[ns]')
- df_chat[COLNAMES_DF.LastMessage] = df_chat[COLNAMES_DF.LastMessage].dt.floor('Min')
- # Calculate the number of words in messages
- df_chat[COLNAMES_DF.WORDS_NO] = df_chat['message'].apply(lambda x: len(x.split()))
- # number of ulrs
- df_chat[COLNAMES_DF.URL_NO] = df_chat["message"].apply(
- lambda x: len(re.findall(URL_PATTERN, x))).astype(int)
- # number of locations
- df_chat[COLNAMES_DF.LOCATION_NO] = df_chat["message"].apply(
- lambda x: len(re.findall(LOCATION_PATTERN, x))).astype(int)
- # number of files
- df_chat[COLNAMES_DF.FILE_NO] = df_chat["message"].apply(
- lambda x: len(re.findall(ATTACH_FILE_PATTERN, x))).astype(
- int)
- # number of messages
- df_chat[COLNAMES_DF.MESSAGE_NO] = 1
-
- df_participants = df_chat.groupby(COLNAMES_DF.USERNAME).agg({
- COLNAMES_DF.WORDS_NO: 'sum',
- COLNAMES_DF.URL_NO: 'sum',
- COLNAMES_DF.LOCATION_NO: 'sum',
- COLNAMES_DF.FILE_NO: 'sum',
- COLNAMES_DF.MESSAGE_NO: 'sum',
- COLNAMES_DF.FirstMessage: 'min',
- COLNAMES_DF.LastMessage: 'max'
- }).reset_index()
-
- response_matrix = get_response_matrix(df_chat)
- user_reply2 = response_matrix.idxmax(axis=1)
- reply2_user = response_matrix.T.idxmax(axis=1)
-
- response_matrix[COLNAMES_DF.USER_REPLY2] = user_reply2
- response_matrix[COLNAMES_DF.REPLY_2USER] = reply2_user
- response_matrix.index.name = COLNAMES_DF.USERNAME
- response_matrix = response_matrix.loc[:, [COLNAMES_DF.USER_REPLY2, COLNAMES_DF.REPLY_2USER]]
- response_matrix = response_matrix.reset_index()
-
- df_participants = pd.merge(df_participants, response_matrix, how="left",
- on=COLNAMES_DF.USERNAME, validate="1:1")
-
- return df_participants
-
-
-def remove_system_messages(chat):
- """Removes system messages from chat
- Parameters
- ----------
- chat : pandas.DataFrame
- A DataFrame that includes chat data
- Returns
- -------
- pandas.DataFrame
- A filtered dataframe
- """
-
- message0 = chat.loc[0, COLNAMES_DF.MESSAGE]
- is_system_message = bool(all(s in message0 for s in SYSTEM_MESSAGES))
- if is_system_message:
- group_name = chat.loc[0, COLNAMES_DF.USERNAME]
- chat = chat.loc[chat[COLNAMES_DF.USERNAME] != group_name, ]
-
- return chat
-
-
-def extract_results(participants_df, donor_user_name, anonymize=True):
- """Parse the given zip file.
- Parameters
- ----------
- participants_df : pandas.DataFrame
- A DataFrame that includes participants data
- donor_user_name : str
-
- anonymize : bool
- Indicates if usernames should be anonymized
- Returns
- -------
- list
- A list of DataFrames which include participant features
- """
- if anonymize:
- participants_df = anonymize_participants(participants_df, donor_user_name)
-
- results = get_wide_to_long_participant(participants_df)
- return results
-
-# ***** end of analysis functions *****
-
-def format_results_no_information():
- """
- In case the selected options from the radio button was:
- "Mijn naam of telefoonnummer staat er niet tussen"
- """
- res = {
- "id": "Wij konden geen gegevens over u vinden",
- "title": "",
- "data_frame": pd.DataFrame(["Wij konden geen gegevens over u vinden"], columns=["Omschrijving"])
- }
- return {"cmd": "result", "result": [res]}
-
-
-def format_results(df_list, error):
- """Format results to the standard format.
- Parameters
- ----------
- df_list: pandas.dataframe
- Returns
- -------
- pandas.dataframe
- """
- results = []
- for df_item in df_list:
- user_name = pd.unique(df_item[COLNAMES_DF.USERNAME])[0]
- if user_name == "u":
- title = "Dit bent u"
- else:
- title = user_name
-
- results.append(
- {
- "id": user_name,
- "title": title,
- "data_frame": df_item[[COLNAMES_DF.DESCRIPTION, COLNAMES_DF.VALUE]].reset_index(
- drop=True)
- }
- )
- if len(error) > 0:
- results = results+error
- return {"cmd": "result", "result": results}
-
-
-def format_errors(errors):
- """Return errors in the format of dataframe.
- Parameters
- ----------
- errors: str
- Returns
- -------
- pandas.dataframe
- """
- if len(errors) == 0:
- return []
- data_frame = pd.DataFrame()
- data_frame[COLNAMES_DF.DESCRIPTION] = pd.Series(errors, name=COLNAMES_DF.DESCRIPTION)
- return [{"id": "extraction_log", "title": DUTCH_CONST.PRE_MESSAGE, "data_frame": data_frame}]
-
-
-def parse_chat_file(log_error, chat_file_name):
- """Read whatsapp chat file and return chat data in the format of a dataframe.
- Parameters
- ----------
- chat_file_name : str
- The path of the chat file. It can be in zip or txt format.
- log_error : list
- List of error messages.
- Returns
- -------
- pandas.dataframe
- Extracted data from the chat file
- """
-
- try:
- zfile = zipfile.ZipFile(chat_file_name) # pylint: disable=R1732
-
- except BadZipFile:
-
- if FILE_RE.match(chat_file_name):
- try:
- with open(chat_file_name, encoding="utf8") as tfile:
- chat = parse_chat(log_error, tfile.read())
- except:
- log_error("Could not read .txt file")
- return None
- else:
- log_error("There is not a valid input file format.")
- return None
- else:
- chat = parse_zipfile(log_error, zfile)
-
- return chat
-
-
-def process():
- """Convert whatsapp chat file to participant dataframes.
- This is the main function which extracts the participants
- information from the row chat file provided by data-donors.
- Parameters
- ----------
- chat_file_name : str
- The path of the chat file. It can be in zip or txt format.
- Returns
- -------
- pandas.dataframe
- Extracted data from the chat file
- """
- errors = []
- log_error = errors.append
- chat_df = None
-
- chat_file_name = yield prompt_file()
- try:
- chat_df = parse_chat_file(log_error, chat_file_name)
- except:
- log_error("Could not extract data from this chat file")
-
- if chat_df is not None:
- chat_df = remove_system_messages(chat_df)
- participants_df = get_participants_features(chat_df)
- usernames = extract_usernames(participants_df)
- username = yield prompt_radio(usernames)
- if username == "Mijn naam of telefoonnummer staat er niet tussen":
- yield format_results_no_information()
-
- results = extract_results(participants_df, username)
- yield format_results(results, format_errors(errors))
- else:
- yield format_results([], format_errors(errors))
-
-
-
-def prompt_file():
- """Promt a file selection window in Eyra system
- Parameters
- ----------
-
- Returns
- -------
- Dictionary
- a prompt event - file type
- """
- return {
- "cmd": "prompt",
- "prompt": {
- "type": "file",
- "file": {
- "title": {
- "en": "Select the chat file",
- "nl": "Kies het chatbestand"
- },
- "description": {
- "en": "We previously asked you to export a chat file from WhatsApp and save it on your phone. "
- "Please select this file so we can extract relevant information for our research.",
- "nl": "We hebben u gevraagd een chatbestand te exporteren uit WhatsApp en op uw telefoon op te slaan. "
- "U kunt dit bestand nu kiezen zodat wij er relevante informatie uit kunnen halen voor ons onderzoek."
- },
- "extensions": "application/zip, text/plain",
- }
- }
- }
-
-
-def prompt_radio(usernames):
- """Promt a list of items(usernames here) in Eyra system
- This function shows a list of radio-buttons
- Parameters
- ----------
- usernames: pandas.Series
- Extracted usernames from the chat file
- Returns
- -------
- Dictionary
- a prompt event - radio type
- """
- usernames.append("Mijn naam of telefoonnummer staat er niet tussen")
- return {
- "cmd": "prompt",
- "prompt": {
- "type": "radio",
- "radio": {
- "title": {
- "en": "Select username",
- "nl": "Kies gebruikersnaam"
- },
- "description": {
- "en": "Please indicate which username is yours. Note that names "
- "and phone numbers are not stored, but only used to extract "
- "relevant information from the chat file.",
- "nl": "Geef hieronder aan welke gebruikersnaam van u is. "
- "Namen en telefoonnummers worden niet opgeslagen, "
- "maar alleen gebruikt om de juiste informatie uit "
- "het bestand te kunnen halen."
- },
- "items": usernames,
- }
- }
- }
-
-
-def extract_usernames(participants_df):
- """Extract username from the given dataframe.
- This function is used by Eyra system to show the list of extracted usernames
- to the donor.
- Parameters
- ----------
- participants_df: pandas.dataframe
- Participants in the chat and their features
- Returns
- -------
- list
- Extracted usernames
- """
- return participants_df[COLNAMES_DF.USERNAME].tolist() # pylint: disable=C0302
diff --git a/core/priv/repo/zip_contents.py b/core/priv/repo/zip_contents.py
deleted file mode 100644
index 63f5f3182..000000000
--- a/core/priv/repo/zip_contents.py
+++ /dev/null
@@ -1,20 +0,0 @@
-__version__ = '0.2.0'
-
-import zipfile
-import pandas as pd
-
-
-def process(file_data):
- names = []
- zfile = zipfile.ZipFile(file_data)
- data = []
- for name in zfile.namelist():
- names.append(name)
- info = zfile.getinfo(name)
- data.append((name, info.compress_size, info.file_size))
-
- return [{
- "id": "overview",
- "title": "The following files where read:",
- "data_frame": pd.DataFrame(data, columns=["filename", "compressed size", "size"])
- }]
diff --git a/core/systems/alliance/_presenter.ex b/core/systems/alliance/_presenter.ex
new file mode 100644
index 000000000..7c29dfbd4
--- /dev/null
+++ b/core/systems/alliance/_presenter.ex
@@ -0,0 +1,10 @@
+defmodule Systems.Alliance.Presenter do
+ @behaviour Frameworks.Concept.Presenter
+
+ alias Systems.Alliance
+
+ @impl true
+ def view_model(%Alliance.ToolModel{director: director} = tool, page, assigns) do
+ Frameworks.Utility.Module.get(director, "Presenter").view_model(tool, page, assigns)
+ end
+end
diff --git a/core/systems/alliance/_public.ex b/core/systems/alliance/_public.ex
new file mode 100644
index 000000000..6585a5fef
--- /dev/null
+++ b/core/systems/alliance/_public.ex
@@ -0,0 +1,134 @@
+defmodule Systems.Alliance.Public do
+ @moduledoc """
+
+ Alliance tools allow a researcher to setup a link to an external web
+ tool. The participant goes through the flow described below:
+
+ - Receive invitation to start a alliance (mail, push etc.).
+ - Open alliance tool, this opens it on the platform and requires authentication.
+ - The participant is then redirected to the alliance at a 3rd party web-application.
+ - After completion the user is redirect back to the platform.
+ - The platform registers the completion of this alliance for the participant.
+
+
+ A researcher is required to configure the 3rd party application with a redirect
+ link. The redirect link to be used is show on the alliance tool configuration
+ screen (with copy button).
+
+ IDEA: The tool requires a sucessful round-trip with a verify flow to ensure
+ that everything is configured correctly.
+
+ Participants need to be invited to a particular alliance explicitly. This avoids
+ the situation where a new user joins a study and then can immediately complete
+ previous alliances.
+
+ Once a participant has completed a alliance they are no longer allowed to enter it
+ a second time. The status is clearly shown when the attempt to do so.
+
+ IDEA: A list of alliances can be access by the notification icon which is shown
+ on all screens.
+ """
+
+ import Ecto.Query, warn: false
+ alias Ecto.Multi
+ alias Core.Repo
+ alias Core.Authorization
+
+ alias Frameworks.{
+ Signal
+ }
+
+ alias Systems.{
+ Alliance
+ }
+
+ @doc """
+ Returns the list of alliance_tools.
+ """
+ def list_tools do
+ Repo.all(Alliance.ToolModel)
+ end
+
+ @doc """
+ Gets a single alliance_tool.
+
+ Raises `Ecto.NoResultsError` if the Alliance tool does not exist.
+ """
+ def get_tool!(id), do: Repo.get!(Alliance.ToolModel, id)
+ def get_tool(id), do: Repo.get(Alliance.ToolModel, id)
+
+ @doc """
+ Creates a alliance_tool.
+ """
+ def prepare_tool(attrs, auth_node \\ Authorization.prepare_node()) do
+ %Alliance.ToolModel{}
+ |> Alliance.ToolModel.changeset(:mount, attrs)
+ |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
+ end
+
+ @doc """
+ Updates a alliance_tool.
+ """
+ def update_tool(%Alliance.ToolModel{} = alliance_tool, type, attrs) do
+ alliance_tool
+ |> Alliance.ToolModel.changeset(type, attrs)
+ |> update_tool()
+ end
+
+ def update_tool(_, _, _), do: {:error, nil}
+
+ def update_tool(changeset) do
+ result =
+ Multi.new()
+ |> Repo.multi_update(:tool, changeset)
+ |> Repo.transaction()
+
+ with {:ok, %{tool: tool}} <- result do
+ Signal.Public.dispatch!(:alliance_tool, %{tool: tool})
+ end
+
+ result
+ end
+
+ @doc """
+ Deletes a alliance_tool.
+ """
+ def delete_tool(%Alliance.ToolModel{} = alliance_tool) do
+ Repo.delete(alliance_tool)
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking alliance_tool changes.
+ """
+ def change_tool(
+ %Alliance.ToolModel{} = alliance_tool,
+ type,
+ attrs \\ %{}
+ ) do
+ Alliance.ToolModel.changeset(alliance_tool, type, attrs)
+ end
+
+ def copy(%Alliance.ToolModel{} = tool, auth_node) do
+ %Alliance.ToolModel{}
+ |> Alliance.ToolModel.changeset(:copy, Map.from_struct(tool))
+ |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
+ |> Repo.insert!()
+ end
+
+ def ready?(%Alliance.ToolModel{} = alliance_tool) do
+ changeset =
+ %Alliance.ToolModel{}
+ |> Alliance.ToolModel.operational_changeset(Map.from_struct(alliance_tool))
+
+ changeset.valid?
+ end
+end
+
+defimpl Core.Persister, for: Systems.Alliance.ToolModel do
+ def save(_tool, changeset) do
+ case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :alliance_tool) do
+ {:ok, %{alliance_tool: alliance_tool}} -> {:ok, alliance_tool}
+ _ -> {:error, changeset}
+ end
+ end
+end
diff --git a/core/systems/alliance/_routes.ex b/core/systems/alliance/_routes.ex
new file mode 100644
index 000000000..a5743544a
--- /dev/null
+++ b/core/systems/alliance/_routes.ex
@@ -0,0 +1,10 @@
+defmodule Systems.Alliance.Routes do
+ defmacro routes() do
+ quote do
+ scope "/", Systems.Alliance do
+ pipe_through([:browser, :require_authenticated_user])
+ live("/alliance/:id/callback", CallbackPage)
+ end
+ end
+ end
+end
diff --git a/core/systems/assignment/callback_page.ex b/core/systems/alliance/callback_page.ex
similarity index 85%
rename from core/systems/assignment/callback_page.ex
rename to core/systems/alliance/callback_page.ex
index 2def382fd..c6589ba61 100644
--- a/core/systems/assignment/callback_page.ex
+++ b/core/systems/alliance/callback_page.ex
@@ -1,25 +1,29 @@
-defmodule Systems.Assignment.CallbackPage do
+defmodule Systems.Alliance.CallbackPage do
@moduledoc """
The redirect page to complete a task
"""
use CoreWeb, :live_view
- use CoreWeb.Layouts.Workspace.Component, :survey
+ use CoreWeb.Layouts.Workspace.Component, :alliance
+ use Systems.Observatory.Public
alias Frameworks.Pixel.Text
alias Frameworks.Pixel.Button
+ alias Frameworks.Concept.Directable
alias Systems.{
- Assignment
+ Assignment,
+ Alliance
}
@impl true
- def get_authorization_context(%{"id" => id}, _session, _socket) do
- Assignment.Public.get!(id, [:crew]).crew
+ def get_authorization_context(%{"id" => id}, _session, %{assigns: %{current_user: user}}) do
+ tool = Alliance.Public.get_tool!(id)
+ Directable.director(tool).authorization_context(tool, user)
end
@impl true
def mount(%{"id" => id}, _session, socket) do
- model = Assignment.Public.get!(id, [:crew])
+ model = Alliance.Public.get_tool!(id)
{
:ok,
diff --git a/core/systems/alliance/content_page.ex b/core/systems/alliance/content_page.ex
new file mode 100644
index 000000000..9337e10b3
--- /dev/null
+++ b/core/systems/alliance/content_page.ex
@@ -0,0 +1,43 @@
+defmodule Systems.Alliance.ContentPage do
+ use Systems.Content.Page
+
+ alias Systems.{
+ Alliance
+ }
+
+ @impl true
+ def get_authorization_context(%{"id" => id}, _session, _socket) do
+ Alliance.Public.get_tool!(id)
+ end
+
+ @impl true
+ def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
+ model = Alliance.Public.get_tool!(String.to_integer(id))
+ tabbar_id = "alliance_content/#{id}"
+
+ {
+ :ok,
+ socket |> initialize(id, model, tabbar_id, initial_tab, locale)
+ }
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.content_page
+ title={@vm.title}
+ menus={@menus}
+ tabs={@vm.tabs}
+ actions={@actions}
+ more_actions={@more_actions}
+ initial_tab={@initial_tab}
+ tabbar_id={@tabbar_id}
+ tabbar_size={@tabbar_size}
+ breakpoint={@breakpoint}
+ popup={@popup}
+ dialog={@dialog}
+ show_errors={@show_errors}
+ />
+ """
+ end
+end
diff --git a/core/systems/alliance/content_page_builder.ex b/core/systems/alliance/content_page_builder.ex
new file mode 100644
index 000000000..506f5b9ad
--- /dev/null
+++ b/core/systems/alliance/content_page_builder.ex
@@ -0,0 +1,20 @@
+defmodule Systems.Alliance.ContentPageBuilder do
+ import CoreWeb.Gettext
+
+ alias Systems.{
+ Alliance
+ }
+
+ def view_model(
+ %Alliance.ToolModel{id: id},
+ _assigns
+ ) do
+ %{
+ id: id,
+ title: dgettext("eyra-alliance", "content.title"),
+ tabs: [],
+ actions: [],
+ show_errors: false
+ }
+ end
+end
diff --git a/core/systems/alliance/controller.ex b/core/systems/alliance/controller.ex
new file mode 100644
index 000000000..fab592c9d
--- /dev/null
+++ b/core/systems/alliance/controller.ex
@@ -0,0 +1,8 @@
+defmodule Systems.Alliance.Controller do
+ use CoreWeb, :controller
+
+ def callback(conn, %{"id" => _id}) do
+ conn
+ |> redirect(to: "/assignment/#{1}")
+ end
+end
diff --git a/core/systems/survey/experiment_task_view.ex b/core/systems/alliance/task_view.ex
similarity index 92%
rename from core/systems/survey/experiment_task_view.ex
rename to core/systems/alliance/task_view.ex
index 4002c81a3..0a300b8f6 100644
--- a/core/systems/survey/experiment_task_view.ex
+++ b/core/systems/alliance/task_view.ex
@@ -1,4 +1,4 @@
-defmodule Systems.Survey.ExperimentTaskView do
+defmodule Systems.Alliance.TaskView do
use CoreWeb, :live_component
alias Frameworks.Utility.LiveCommand
diff --git a/core/systems/survey/tool_form.ex b/core/systems/alliance/tool_form.ex
similarity index 61%
rename from core/systems/survey/tool_form.ex
rename to core/systems/alliance/tool_form.ex
index 379c3eef0..cb997e225 100644
--- a/core/systems/survey/tool_form.ex
+++ b/core/systems/alliance/tool_form.ex
@@ -1,17 +1,15 @@
-defmodule Systems.Survey.ToolForm do
+defmodule Systems.Alliance.ToolForm do
use CoreWeb.LiveForm
import CoreWeb.UI.StepIndicator
- alias Phoenix.LiveView
alias Frameworks.Pixel.Panel
alias Frameworks.Pixel.Text
import Frameworks.Pixel.Form
alias Frameworks.Pixel.Button
alias Systems.{
- Director,
- Survey
+ Alliance
}
# Handle initial update
@@ -19,22 +17,19 @@ defmodule Systems.Survey.ToolForm do
def update(
%{
id: id,
- entity_id: entity_id,
+ entity: entity,
callback_url: callback_url,
user: user
},
socket
) do
- entity = Survey.Public.get_survey_tool!(entity_id)
-
- changeset = Survey.ToolModel.changeset(entity, :create, %{})
+ changeset = Alliance.ToolModel.changeset(entity, :create, %{})
{
:ok,
socket
|> assign(
id: id,
- entity_id: entity_id,
entity: entity,
callback_url: callback_url,
changeset: changeset,
@@ -46,25 +41,25 @@ defmodule Systems.Survey.ToolForm do
# Handle Events
- @impl true
- def handle_event(
- "test-roundtrip",
- _params,
- %{assigns: %{user: user, changeset: changeset, entity: entity}} = socket
- ) do
- changeset = Survey.ToolModel.validate(changeset, :roundtrip)
+ # @impl true
+ # def handle_event(
+ # "test-roundtrip",
+ # _params,
+ # %{assigns: %{user: user, changeset: changeset, entity: entity}} = socket
+ # ) do
+ # changeset = Alliance.ToolModel.validate(changeset, :roundtrip)
- if changeset.valid? do
- Director.public(entity).assign_tester_role(entity, user)
+ # if changeset.valid? do
+ # Directable.director(entity).assign_tester_role(entity, user)
- fake_panl_id = "TEST-" <> Faker.UUID.v4()
- external_path = Survey.ToolModel.external_path(entity, fake_panl_id)
+ # fake_panl_id = "TEST-" <> Faker.UUID.v4()
+ # external_path = Alliance.ToolModel.external_path(entity, fake_panl_id)
- {:noreply, LiveView.redirect(socket, external: external_path)}
- else
- {:noreply, socket |> assign(changeset: changeset)}
- end
- end
+ # {:noreply, LiveView.redirect(socket, external: external_path)}
+ # else
+ # {:noreply, socket |> assign(changeset: changeset)}
+ # end
+ # end
@impl true
def handle_event("save", %{"tool_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
@@ -75,10 +70,18 @@ defmodule Systems.Survey.ToolForm do
}
end
+ @impl true
+ def handle_event("change", _, socket) do
+ {
+ :noreply,
+ socket
+ }
+ end
+
# Saving
def save(socket, entity, type, attrs) do
- changeset = Survey.ToolModel.changeset(entity, type, attrs)
+ changeset = Alliance.ToolModel.changeset(entity, type, attrs)
socket
|> save(changeset)
@@ -89,7 +92,7 @@ defmodule Systems.Survey.ToolForm do
def validate_for_publish(%{assigns: %{id: id, entity: entity}} = socket) do
changeset =
- Survey.ToolModel.operational_changeset(entity, %{})
+ Alliance.ToolModel.operational_changeset(entity, %{})
|> Map.put(:action, :validate_for_publish)
send(self(), %{id: id, ready?: changeset.valid?})
@@ -100,21 +103,21 @@ defmodule Systems.Survey.ToolForm do
defp redirect_instructions_link() do
link_as_string(
- dgettext("link-survey", "redirect.instructions.link"),
+ dgettext("eyra-alliance", "redirect.instructions.link"),
"https://www.qualtrics.com/support/survey-platform/survey-module/survey-options/survey-termination/#RedirectingRespondentsToAUrl"
)
end
defp panlid_instructions_link() do
link_as_string(
- dgettext("link-survey", "panlid.instructions.link"),
- "https://www.qualtrics.com/support/survey-platform/survey-module/survey-flow/standard-elements/passing-information-through-query-strings/?parent=p001135#PassingInformationIntoASurvey"
+ dgettext("eyra-alliance", "panlid.instructions.link"),
+ "https://www.qualtrics.com/support/survey-platform/survey-module/survey-flow/standard-elements/passing-information-through-query-strings/?parent=p001135#PassingInformationIntoAQuestionnaire"
)
end
defp study_instructions_link() do
link_as_string(
- dgettext("link-survey", "study.instructions.link"),
+ dgettext("eyra-alliance", "study.instructions.link"),
"https://www.qualtrics.com/support/survey-platform/distributions-module/web-distribution/anonymous-link/#ObtainingTheAnonymousLink"
)
end
@@ -134,12 +137,8 @@ defmodule Systems.Survey.ToolForm do
~H"""
- <%= @text %>
+ <%= @text %>
"""
end
diff --git a/core/lib/google_sign_in.ex b/core/lib/google_sign_in.ex
index a4b8d1613..e212d7a8a 100644
--- a/core/lib/google_sign_in.ex
+++ b/core/lib/google_sign_in.ex
@@ -38,7 +38,7 @@ defmodule GoogleSignIn do
|> GoogleSignIn.User.changeset(attrs)
|> Ecto.Changeset.put_assoc(:user, user)
|> Repo.insert() do
- Signal.Public.dispatch!(:user_created, %{user: google_user.user})
+ Signal.Public.dispatch!({:user, :created}, %{user: google_user.user})
{:ok, google_user}
end
end
diff --git a/core/lib/sign_in_with_apple.ex b/core/lib/sign_in_with_apple.ex
index f8abfd009..a5c299068 100644
--- a/core/lib/sign_in_with_apple.ex
+++ b/core/lib/sign_in_with_apple.ex
@@ -35,7 +35,7 @@ defmodule SignInWithApple do
|> SignInWithApple.User.changeset(attrs)
|> Ecto.Changeset.put_assoc(:user, user)
|> Repo.insert() do
- Signal.Public.dispatch!(:user_created, %{user: apple_user.user})
+ Signal.Public.dispatch!({:user, :created}, %{user: apple_user.user})
{:ok, apple_user}
end
end
diff --git a/core/mix.exs b/core/mix.exs
index 658d71d87..bab7e8928 100644
--- a/core/mix.exs
+++ b/core/mix.exs
@@ -44,7 +44,7 @@ defmodule Core.MixProject do
def application do
[
mod: {Core.Application, []},
- extra_applications: [:logger, :runtime_tools, :csv]
+ extra_applications: [:logger, :runtime_tools, :csv, :ssl]
]
end
@@ -67,7 +67,7 @@ defmodule Core.MixProject do
# Deps
{:assent, "~> 0.2.3"},
{:bcrypt_elixir, "~> 2.0"},
- {:ex_aws_s3, "~> 2.0"},
+ {:ex_aws_s3, "~> 2.5"},
{:phoenix, "1.7.2"},
{:phoenix_view, "~> 2.0"},
{:phoenix_ecto, "~> 4.4"},
@@ -99,6 +99,7 @@ defmodule Core.MixProject do
{:logger_json, "~> 4.3"},
{:statistics, "~> 0.6.2"},
{:csv, "~> 2.4"},
+ {:sentry, "~> 8.0"},
# i18n
{:ex_cldr, "~> 2.25"},
{:ex_cldr_numbers, "~> 2.23"},
diff --git a/core/mix.lock b/core/mix.lock
index 38c5ccb7c..9fdd36c73 100644
--- a/core/mix.lock
+++ b/core/mix.lock
@@ -10,7 +10,7 @@
"burnex": {:hex, :burnex, "3.1.0", "1c1ffaab0dccd4efe80f3c3d0de61e9bb4e622fd0c52b0fccea693095e7c30b2", [:mix], [{:dns, "~> 2.2.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm", "611af3dd131c1a5e75b367c75641c9104b0a942dfdd9767e69fbe8be883d536d"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
"castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"},
- "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
+ "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"cldr_utils": {:hex, :cldr_utils, "2.22.0", "5df60df2603dfeeffe26e40ab1ee565df34da288a53bb2db0d0dbd243fd646ef", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "ea14e8a6aa89ffd59a5d49baebe7ebf852cc024ac50dc2b3dabcd3786eeed657"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
@@ -22,7 +22,7 @@
"csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"},
"currency_formatter": {:hex, :currency_formatter, "0.8.1", "ab410d520713ba3a5b56eaf4ef944471df67c870dd0e5cd5a9f7e22f9b634777", [:mix], [{:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:poison, "~> 3.1.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "e1aafb0c2eba8d63d0d1055e118a674f8b60bd1095d86a701a9732803e410214"},
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
- "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
+ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
"digital_token": {:hex, :digital_token, "0.4.0", "2ad6894d4a40be8b2890aad286ecd5745fa473fa5699d80361a8c94428edcd1f", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a178edf61d1fee5bb3c34e14b0f4ee21809ee87cade8738f87337e59e5e66e26"},
"dns": {:hex, :dns, "2.2.0", "4721a79c2bccc25481930dffbfd06f40851321c3d679986af307111214bf124c", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm", "13ed1ef36ce896211ec6ce5e02709dbfb12aa61d6255bda8d531577a0a5a56e0"},
@@ -34,8 +34,8 @@
"elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"esbuild": {:hex, :esbuild, "0.7.0", "ce3afb13cd2c5fd63e13c0e2d0e0831487a97a7696cfa563707342bb825d122a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4ae9f4f237c5ebcb001390b8ada65a12fb2bb04f3fe3d1f1692b7a06fbfe8752"},
- "ex_aws": {:hex, :ex_aws, "2.4.2", "d2686c34b69287cc8dd7629e70131aec05fef3cd3eae13698c9422933f7bc9ee", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0a2c07bd1541b0bef315f67e050d3cb9f947ab1a281896a8c35e3ee4976889f6"},
- "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
+ "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"},
+ "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.0", "9fab3312a58edcfe5392928349605be03414d024a2e530c1202c22ffe77e5352", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "e6928abe9e04224293c1d7d3eef8df901f0ef4cf6e3125aa1f2b0d2911022ba6"},
"ex_cldr": {:hex, :ex_cldr, "2.36.0", "ccd7c61c4126aa42d3cd9d93097642930f1b8a10c9c8351fc58bb2c627bad839", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "5a56c66cd61ebde42277baa828cd1587959b781ac7e2aee135328a78a4de3fe9"},
"ex_cldr_calendars": {:hex, :ex_cldr_calendars, "1.21.0", "5f3c71a145926cfa7f8691aa3566921957543f635ec345e52282c613e8985ba8", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: true]}, {:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_cldr_units, "~> 3.12", [hex: :ex_cldr_units, repo: "hexpm", optional: true]}, {:ex_doc, "~> 0.21", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8ce8737b70f9c36828324995f96464a1a4c87cf2f76e5868e20662ca31c4098f"},
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.0", "aadd34e91cfac7ef6b03fe8f47f8c6fa8c5daf3f89b5d9fee64ec545ded839cf", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0521316396c66877a2d636219767560bb2397c583341fcb154ecf9f3000e6ff8"},
@@ -51,13 +51,13 @@
"floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
"gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"},
"gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"},
- "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
+ "hackney": {:hex, :hackney, "1.19.1", "59de4716e985dd2b5cbd4954fa1ae187e2b610a9c4520ffcb0b1653c3d6e5559", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "8aa08234bdefc269995c63c2282cf3cd0e36febe3a6bfab11b610572fdd1cad0"},
"hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm", "06f580167c4b8b8a6429040df36cc93bba6d571faeaec1b28816523379cbb23a"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
- "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
+ "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
"kadabra": {:hex, :kadabra, "0.6.0", "8d8de886802f38d86d2c250eb9416e3208b5e4b78ce8409b40b4d57f21d21fc9", [:mix], [{:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:hpack, "~> 0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "0cdaf72fc6205cba62da9e49ee6b24b7c50adb6d9f8b0e92b4b1847959371403"},
@@ -68,7 +68,7 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
- "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
+ "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
"mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"},
@@ -77,7 +77,7 @@
"oban": {:hex, :oban, "2.13.6", "a0cb1bce3bd393770512231fb5a3695fa19fd3af10d7575bf73f837aee7abf43", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c1c5eb16f377b3cbbf2ea14be24d20e3d91285af9d1ac86260b7c2af5464887"},
"parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"},
"parent": {:hex, :parent, "0.12.1", "495c4386f06de0df492e0a7a7199c10323a55e9e933b27222060dd86dccd6d62", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2ab589ef1f37bfcedbfb5ecfbab93354972fb7391201b8907a866dadd20b39d1"},
- "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+ "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
@@ -96,14 +96,16 @@
"progress_bar": {:hex, :progress_bar, "2.0.1", "7b40200112ae533d5adceb80ff75fbe66dc753bca5f6c55c073bfc122d71896d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "2519eb58a2f149a3a094e729378256d8cb6d96a259ec94841bd69fdc71f18f87"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
+ "sentry": {:hex, :sentry, "8.1.0", "8d235b62fce5f8e067ea1644e30939405b71a5e1599d9529ff82899d11d03f2b", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f9fc7641ef61e885510f5e5963c2948b9de1de597c63f781e9d3d6c9c8681ab4"},
"socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm", "f82ea9833ef49dde272e6568ab8aac657a636acb4cf44a7de8a935acb8957c2e"},
"sourceror": {:hex, :sourceror, "0.12.2", "2ae55efd149193572e0eb723df7c7a1bda9ab33c43373c82642931dbb2f4e428", [:mix], [], "hexpm", "7ad74ade6fb079c71f29fae10c34bcf2323542d8c51ee1bcd77a546cfa89d59c"},
- "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"statistics": {:hex, :statistics, "0.6.2", "213dcedc2b3ae7fb775b5510ea9630c66d3c0019ea2f86d5096559853623a60d", [:mix], [], "hexpm", "329f1008dc4ad24430d94c04b52ff09d5fb435ab11f34360831f11eb0c391c17"},
"surface": {:hex, :surface, "0.10.0", "54eeb1bc7d3e411e3cbd1074237f4369afb24c4e3177ba26f6aaf62336533197", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.18", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.11", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "2cbf3217c1184980a058edc33f9fd8e47c67ebe0c46c2c6448e663eee095dc82"},
"surface_catalogue": {:hex, :surface_catalogue, "0.5.2", "2ca7179bc157910dd76e6167489f8186b1ec36aeba3ec1606fb8c85e7c75ff20", [:mix], [{:earmark, "~>1.4.21", [hex: :earmark, repo: "hexpm", optional: false]}, {:esbuild, "~> 0.2", [hex: :esbuild, repo: "hexpm", optional: false]}, {:html_entities, "~> 0.4", [hex: :html_entities, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.16.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:surface, "~>0.8.0 or ~>0.9.0", [hex: :surface, repo: "hexpm", optional: false]}], "hexpm", "53ca6fa8e5fc6f375ca9ab422ac8ac507b942681bd3c44defbfd20ecae968723"},
- "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
+ "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
"table_rex": {:hex, :table_rex, "3.0.0", "5189b71b3b92ed461358f40f7b7b630dc37716bf6c8ab3e934b2bc63a99028bd", [:mix], [], "hexpm", "582776d24cbe6a4d30a39a7f02035b1bc979b6cd64923d7234dd2f0ad21a18c7"},
+ "tailwind": {:hex, :tailwind, "0.2.1", "83d8eadbe71a8e8f67861fe7f8d51658ecfb258387123afe4d9dc194eddc36b0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e8a13f6107c95f73e58ed1b4221744e1eb5a093cd1da244432067e19c8c9a277"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
diff --git a/core/priv/gettext/en/LC_MESSAGES/link-survey.po b/core/priv/gettext/en/LC_MESSAGES/eyra-alliance.po
similarity index 69%
rename from core/priv/gettext/en/LC_MESSAGES/link-survey.po
rename to core/priv/gettext/en/LC_MESSAGES/eyra-alliance.po
index c4b247598..525e694f8 100644
--- a/core/priv/gettext/en/LC_MESSAGES/link-survey.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-alliance.po
@@ -10,17 +10,9 @@ msgid ""
msgstr ""
"Language: en\n"
-#, elixir-autogen, elixir-format
-msgid "config.nrofsubjects.label"
-msgstr "How many participants do you need?"
-
#, elixir-autogen, elixir-format
msgid "config.url.label"
-msgstr "Qualtrics link"
-
-#, elixir-autogen, elixir-format
-msgid "duration.label"
-msgstr "How many minutes do you estimate participants will need?"
+msgstr "Questionnaire link"
#, elixir-autogen, elixir-format
msgid "by.author.label"
@@ -34,25 +26,17 @@ msgstr "Reward"
msgid "apply.cta.title"
msgstr "Participate"
-#, elixir-autogen, elixir-format
-msgid "devices.label"
-msgstr "Which devices can participants use?"
-
#, elixir-autogen, elixir-format
msgid "open.cta.title"
msgstr "Proceed"
#, elixir-autogen, elixir-format
msgid "redirect.description"
-msgstr "To redirect participants back to Panl at the end of the survey, copy the redirect URL below and paste it as \"Website URL\" in Qualtrics as %{link}."
+msgstr "To redirect participants back to Next at the end of the questionnaire, copy the redirect URL below and paste it as \"Website URL\" in Qualtrics as %{link}."
#, elixir-autogen, elixir-format
msgid "redirect.title"
-msgstr "Redirect URL (End of Survey)"
-
-#, elixir-autogen, elixir-format
-msgid "devices.title"
-msgstr "Devices"
+msgstr "Redirect URL"
#, elixir-autogen, elixir-format
msgid "pending.label"
@@ -90,18 +74,6 @@ msgstr "Here you can set-up and follow the status of your campaigns. A campaign
msgid "empty.title"
msgstr "Recruit participants for your study"
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.monitor"
-msgstr "Dashboard"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.promotion"
-msgstr "Promotion"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.assignment"
-msgstr "Study"
-
#, elixir-autogen, elixir-format
msgid "monitor.empty.description"
msgstr "Submit your campaign and wait for the pool administrator to publish your campaign. Check out the preview to see what your campaign will look like for participants. Once your campaign is online, you can use this dashboard to manage your participants."
@@ -110,42 +82,10 @@ msgstr "Submit your campaign and wait for the pool administrator to publish your
msgid "monitor.empty.title"
msgstr "Campaign submitted?"
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.promotion.forward"
-msgstr "Create promotion"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.assignment.forward"
-msgstr "Your study"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.monitor.forward"
-msgstr "Go to dashboard"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.submission"
-msgstr "Sample"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.submission.forward"
-msgstr "Sample"
-
-#, elixir-autogen, elixir-format
-msgid "form.title"
-msgstr "Survey"
-
#, elixir-autogen, elixir-format
msgid "campaign.overview.title"
msgstr "Campaigns"
-#, elixir-autogen, elixir-format
-msgid "language.title"
-msgstr "Language"
-
-#, elixir-autogen, elixir-format
-msgid "languages.label"
-msgstr "In what language is your study written?"
-
#, elixir-autogen, elixir-format
msgid "ethical.label"
msgstr "I hereby state that my study conforms to all ethical standards."
@@ -156,7 +96,7 @@ msgstr "Ethical review"
#, elixir-autogen, elixir-format
msgid "panlid.description"
-msgstr "Panl automatically adds the participant ID to your Qualtrics survey URL. To use this ID in Qualtrics, add an embedded data element field with the name \"panl_id\", as %{link} in \"PART 2\"."
+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\"."
#, elixir-autogen, elixir-format
msgid "panlid.title"
@@ -164,7 +104,7 @@ msgstr "Panl ID"
#, elixir-autogen, elixir-format
msgid "setup.title"
-msgstr "How to set up your survey on Qualtrics?"
+msgstr "How to set up your questionnaire on Qualtrics?"
#, elixir-autogen, elixir-format
msgid "redirect.copy.button"
@@ -176,11 +116,11 @@ msgstr "Make sure that you completed the %{link} and that you have approval from
#, elixir-autogen, elixir-format
msgid "study.link.description"
-msgstr "To obtain the link to your Qualtrics survey, follow %{link}. Paste your link in the \"Qualtrics link\" field in the form below."
+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 Survey Link"
+msgstr "Anonymous Questionnaire Link"
#, elixir-autogen, elixir-format
msgid "panlid.instructions.link"
@@ -194,10 +134,6 @@ msgstr "instructed here"
msgid "study.instructions.link"
msgstr "these instructions"
-#, elixir-autogen, elixir-format
-msgid "form.description"
-msgstr "Follow the steps in the black frame to make sure that participants can start your online study on Panl and are redirected back to Panl after they finish."
-
#, elixir-autogen, elixir-format
msgid "duration.highlight.text"
msgstr "%{duration} minutes"
@@ -272,16 +208,8 @@ msgstr "Test set-up"
#, elixir-autogen, elixir-format, ex-autogen
msgid "test.roundtrip.text"
-msgstr "Test your survey set-up and check if you end up on the \"Round trip confirmed\" screen."
+msgstr "Test your questionnaire set-up and check if you end up on the \"Round trip confirmed\" screen."
#, elixir-autogen, elixir-format, ex-autogen
msgid "test.roundtrip.title"
-msgstr "Is your survey set up correctly?"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.funding"
-msgstr "Funding"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.funding.forward"
-msgstr "Fund your study"
+msgstr "Is your questionnaire set up correctly?"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po
index 50fd5247b..229e9341d 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po
@@ -32,7 +32,7 @@ msgstr "Contact researcher"
#, elixir-autogen, elixir-format
msgid "form.title"
-msgstr "Study"
+msgstr "Settings"
#, elixir-autogen, elixir-format, ex-autogen
msgid "ticket.title"
@@ -53,3 +53,51 @@ msgstr "%{amount} to go"
#, elixir-autogen, elixir-format, ex-autogen
msgid "pool.target"
msgstr "%{target} credits required"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "close.button"
+msgstr "Finish up"
+
+#, elixir-autogen, elixir-format
+msgid "config.nrofsubjects.label"
+msgstr "How many participants do you need?"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "content.title"
+msgstr "Assignment"
+
+#, elixir-autogen, elixir-format
+msgid "devices.label"
+msgstr "Which devices can participants use?"
+
+#, elixir-autogen, elixir-format
+msgid "devices.title"
+msgstr "Devices"
+
+#, elixir-autogen, elixir-format
+msgid "duration.label"
+msgstr "How man minutes do you estimate participants will need?"
+
+#, elixir-autogen, elixir-format
+msgid "language.title"
+msgstr "Language"
+
+#, elixir-autogen, elixir-format
+msgid "languages.label"
+msgstr "In what language is the assignment written?"
+
+#, elixir-autogen, elixir-format
+msgid "open.button"
+msgstr "Open"
+
+#, elixir-autogen, elixir-format
+msgid "preview.button"
+msgstr "Preview"
+
+#, elixir-autogen, elixir-format
+msgid "publish.button"
+msgstr "Publish"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "retract.button"
+msgstr "Contact researcher"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-benchmark.po b/core/priv/gettext/en/LC_MESSAGES/eyra-benchmark.po
index 1a1f9d056..459092138 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-benchmark.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-benchmark.po
@@ -21,7 +21,7 @@ msgstr "Leaderboard"
#, elixir-autogen, elixir-format
msgid "tabbar.item.leaderboard.forward"
-msgstr "Leaderboard"
+msgstr "Go to Leaderboard"
#, elixir-autogen, elixir-format
msgid "form.data_set.label"
@@ -163,7 +163,7 @@ msgstr "Submissions"
#, elixir-autogen, elixir-format
msgid "tabbar.item.submissions.forward"
-msgstr "Submissions"
+msgstr "Go to Submissions"
#, elixir-autogen, elixir-format
msgid "import.leaderboard.title"
@@ -212,3 +212,43 @@ msgstr "Score"
#, elixir-autogen, elixir-format
msgid "leaderboard.team.label"
msgstr "Team"
+
+#, elixir-autogen, elixir-format
+msgid "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "assignment.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "close.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "open.cta.title"
+msgstr "Challenge"
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "preview.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "publish.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "retract.button"
+msgstr "Github example repository"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-campaign.po b/core/priv/gettext/en/LC_MESSAGES/eyra-campaign.po
index 85e36f710..02ade5791 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-campaign.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-campaign.po
@@ -39,7 +39,7 @@ msgid "campaign.create.title"
msgstr "New campaign"
#, elixir-autogen, elixir-format
-msgid "campaign.create.tooltype.label"
+msgid "campaign.create.template.label"
msgstr "Study type"
#, elixir-autogen, elixir-format
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-data-donation.po b/core/priv/gettext/en/LC_MESSAGES/eyra-data-donation.po
index 3bd777f5f..006fdad74 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-data-donation.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-data-donation.po
@@ -9,91 +9,3 @@
msgid ""
msgstr ""
"Language: en\n"
-
-#, elixir-autogen, elixir-format
-msgid "config.nrofsubjects.label"
-msgstr "How many participants do you need?"
-
-#, elixir-autogen, elixir-format
-msgid "platforms.title"
-msgstr "Platforms"
-
-#, elixir-autogen, elixir-format
-msgid "content.title"
-msgstr "Data Donation"
-
-#, elixir-autogen, elixir-format
-msgid "task.donate.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.donate.title"
-msgstr "Donate"
-
-#, elixir-autogen, elixir-format
-msgid "task.download.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.download.title"
-msgstr "Download DDP"
-
-#, elixir-autogen, elixir-format
-msgid "task.library.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.library.title"
-msgstr "Task Library"
-
-#, elixir-autogen, elixir-format
-msgid "task.list.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.list.title"
-msgstr "Tasks"
-
-#, elixir-autogen, elixir-format
-msgid "task.request.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.request.title"
-msgstr "Request DDP"
-
-#, elixir-autogen, elixir-format
-msgid "task.survey.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.survey.title"
-msgstr "Survey"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "task.description.label"
-msgstr "Description"
-
-#, elixir-autogen, elixir-format
-msgid "task.title.label"
-msgstr "Title"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "task.list.hint"
-msgstr "Change order with the arrows"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "task.platform.label"
-msgstr "Platform"
-
-#, elixir-autogen, elixir-format
-msgid "pdf-replace-file-button"
-msgstr "Replace PDF"
-
-#, elixir-autogen, elixir-format
-msgid "pdf-select-file-button"
-msgstr "Select PDF"
-
-#, elixir-autogen, elixir-format
-msgid "pdf-select-placeholder"
-msgstr "Select a pdf file"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-document.po b/core/priv/gettext/en/LC_MESSAGES/eyra-document.po
new file mode 100644
index 000000000..2612fca7d
--- /dev/null
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-document.po
@@ -0,0 +1,52 @@
+## "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 "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pdf-replace-file-button"
+msgstr "Replace PDF"
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-file-button"
+msgstr "Select PDF"
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-label"
+msgstr "Manual"
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-placeholder"
+msgstr "Select a pdf file"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "component.title"
+msgstr "Manual"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po b/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po
index afff9ba8d..53bcc4717 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-enums.po
@@ -152,7 +152,7 @@ msgstr "Attention checks failed"
#, elixir-autogen, elixir-format
msgid "reject_catagories.not_completed"
-msgstr "Survey not completed"
+msgstr "Questionnaire not completed"
#, elixir-autogen, elixir-format
msgid "reject_catagories.other"
@@ -179,11 +179,11 @@ msgid "ticket_status.open"
msgstr "Open"
#, elixir-autogen, elixir-format
-msgid "tool_types.lab"
+msgid "templates.lab"
msgstr "Lab study"
#, elixir-autogen, elixir-format
-msgid "tool_types.online"
+msgid "templates.online"
msgstr "Online study"
#, elixir-autogen, elixir-format, ex-autogen
@@ -328,16 +328,24 @@ msgstr "Benchmark"
#, elixir-autogen, elixir-format
msgid "project_templates.data_donation"
-msgstr "Data Donation Study"
+msgstr "Data Donation"
#, elixir-autogen, elixir-format, fuzzy
msgid "project_templates.empty"
msgstr "Empty"
#, elixir-autogen, elixir-format, fuzzy
-msgid "project_tools.benchmark"
-msgstr "Benchmark"
+msgid "templates.data_donation"
+msgstr "Online study"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "platforms.apple"
+msgstr "Apple"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "platforms.samsung"
+msgstr "Samsung"
#, elixir-autogen, elixir-format, fuzzy
-msgid "project_tools.data_donation"
-msgstr "Data Donation Study"
+msgid "platforms.tiktok"
+msgstr "TikTok"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-feldspar.po b/core/priv/gettext/en/LC_MESSAGES/eyra-feldspar.po
new file mode 100644
index 000000000..fd87f2edb
--- /dev/null
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-feldspar.po
@@ -0,0 +1,52 @@
+## "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 "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "zip-replace-file-button"
+msgstr "Replace file"
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-file-button"
+msgstr "Select file"
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-label"
+msgstr "Flow application"
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-placeholder"
+msgstr "Select a zip file"
+
+#, elixir-autogen, elixir-format
+msgid "zip-too-large"
+msgstr "The selected file is too large (max 20MB)"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-marketplace.po b/core/priv/gettext/en/LC_MESSAGES/eyra-marketplace.po
index d2ce7e26c..d0e1c2253 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-marketplace.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-marketplace.po
@@ -18,10 +18,6 @@ msgstr "There are currently no research studies available to participate in. As
msgid "empty.title"
msgstr "Looking for a study?"
-#, elixir-autogen, elixir-format
-msgid "reward.label"
-msgstr "Reward: %{value} credits"
-
#, elixir-autogen, elixir-format
msgid "assignment.status.complete.label"
msgstr "Closed"
@@ -30,10 +26,6 @@ msgstr "Closed"
msgid "assignment.status.completed.label"
msgstr "Finished"
-#, elixir-autogen, elixir-format
-msgid "assignment.status.expired.label"
-msgstr "Expired"
-
#, elixir-autogen, elixir-format
msgid "assignment.status.pending.label"
msgstr "Enrolled"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-project.po b/core/priv/gettext/en/LC_MESSAGES/eyra-project.po
index cb8ee9e2b..c90bf1fd1 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-project.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-project.po
@@ -11,10 +11,6 @@ msgstr ""
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#, elixir-autogen, elixir-format
-msgid "preview.button"
-msgstr "Preview"
-
#, elixir-autogen, elixir-format
msgid "delete.confirm"
msgstr "project"
@@ -25,7 +21,7 @@ msgstr "Settings"
#, elixir-autogen, elixir-format
msgid "tabbar.item.config.forward"
-msgstr "Settings"
+msgstr "Go to Settings"
#, elixir-autogen, elixir-format
msgid "tabbar.item.invite"
@@ -33,7 +29,7 @@ msgstr "Invite"
#, elixir-autogen, elixir-format
msgid "tabbar.item.invite.forward"
-msgstr "Invite"
+msgstr "Go to Invite"
#, elixir-autogen, elixir-format
msgid "tabbar.item.monitor"
@@ -41,7 +37,7 @@ msgstr "Monitor"
#, elixir-autogen, elixir-format
msgid "tabbar.item.monitor.forward"
-msgstr "Monitor"
+msgstr "Go to Monitor"
#, elixir-autogen, elixir-format
msgid "tabbar.item.privacy"
@@ -49,15 +45,7 @@ msgstr "Privacy"
#, elixir-autogen, elixir-format
msgid "tabbar.item.privacy.forward"
-msgstr "Privacy"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.tasks"
-msgstr "Tasks"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.tasks.forward"
-msgstr "Tasks"
+msgstr "Go to Privacy"
#, elixir-autogen, elixir-format
msgid "add.first.button"
@@ -115,10 +103,6 @@ msgstr "Overview"
msgid "node.items.title"
msgstr "Items"
-#, elixir-autogen, elixir-format, fuzzy
-msgid "publish.button"
-msgstr "Publish"
-
#, elixir-autogen, elixir-format
msgid "form.title"
msgstr "Project settings"
@@ -135,10 +119,6 @@ msgstr "Add new item"
msgid "add.new.item.button.short"
msgstr "New item"
-#, elixir-autogen, elixir-format
-msgid "retract.button"
-msgstr "Retract"
-
#, elixir-autogen, elixir-format
msgid "campaign.invite.title"
msgstr "Invite by campaign"
@@ -151,14 +131,6 @@ msgstr "You can invite people directly using the url below."
msgid "direct.invite.title"
msgstr "Invite by url"
-#, elixir-autogen, elixir-format
-msgid "close.button"
-msgstr "Beëindig"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "open.button"
-msgstr "Heropen"
-
#, elixir-autogen, elixir-format
msgid "label.concept"
msgstr "Concept"
@@ -190,3 +162,23 @@ msgstr "Retract"
#, elixir-autogen, elixir-format
msgid "create.item.button.short"
msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "create.item.title"
+msgstr "Select item type"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "item.form.name.label"
+msgstr "Name"
+
+#, elixir-autogen, elixir-format
+msgid "item.form.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "tabbar.item.support"
+msgstr "Support"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "tabbar.item.support.forward"
+msgstr "Go to Support"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-support.po b/core/priv/gettext/en/LC_MESSAGES/eyra-support.po
index 9982a211c..7d2740ef4 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-support.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-support.po
@@ -37,3 +37,7 @@ msgstr "Report is received"
#, elixir-autogen, elixir-format
msgid "ticket.type"
msgstr "Form"
+
+#, elixir-autogen, elixir-format
+msgid "content.form.title"
+msgstr "Support"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-survey.po b/core/priv/gettext/en/LC_MESSAGES/eyra-survey.po
deleted file mode 100644
index 006fdad74..000000000
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-survey.po
+++ /dev/null
@@ -1,11 +0,0 @@
-## "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"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-workflow.po b/core/priv/gettext/en/LC_MESSAGES/eyra-workflow.po
new file mode 100644
index 000000000..7a1604593
--- /dev/null
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-workflow.po
@@ -0,0 +1,88 @@
+## "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 "add.to.button"
+msgstr "Add"
+
+#, elixir-autogen, elixir-format
+msgid "item.description.label"
+msgstr "Description"
+
+#, elixir-autogen, elixir-format
+msgid "item.donate.description"
+msgstr "Enables participants to donate data."
+
+#, elixir-autogen, elixir-format
+msgid "item.donate.title"
+msgstr "Donate"
+
+#, elixir-autogen, elixir-format
+msgid "item.download.description"
+msgstr "Instructs participants on how to download digital trace data."
+
+#, elixir-autogen, elixir-format
+msgid "item.download.title"
+msgstr "Download manual"
+
+#, elixir-autogen, elixir-format
+msgid "item.group.label"
+msgstr "Group"
+
+#, elixir-autogen, elixir-format
+msgid "item.library.description"
+msgstr "Choose which items to add to your workflow."
+
+#, elixir-autogen, elixir-format
+msgid "item.library.title"
+msgstr "Library"
+
+#, elixir-autogen, elixir-format
+msgid "item.list.description"
+msgstr "Add items from the library to build a custom workflow for your participants."
+
+#, elixir-autogen, elixir-format
+msgid "item.list.hint"
+msgstr "Use the arrows to order the items"
+
+#, elixir-autogen, elixir-format
+msgid "item.list.title"
+msgstr "Workflow"
+
+#, elixir-autogen, elixir-format
+msgid "item.request.description"
+msgstr "Instructs participants on how to request digital trace data."
+
+#, elixir-autogen, elixir-format
+msgid "item.request.title"
+msgstr "Request manual"
+
+#, elixir-autogen, elixir-format
+msgid "item.title.label"
+msgstr "Title"
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.workflow"
+msgstr "Workflow"
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.workflow.forward"
+msgstr "Go to Workflow"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "item.questionnaire.description"
+msgstr "Redirects participants to an online questionnaire."
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "item.questionnaire.title"
+msgstr "Questionnaire"
diff --git a/core/priv/gettext/en/LC_MESSAGES/link-campaign.po b/core/priv/gettext/en/LC_MESSAGES/link-campaign.po
index f000e2533..11ab0e583 100644
--- a/core/priv/gettext/en/LC_MESSAGES/link-campaign.po
+++ b/core/priv/gettext/en/LC_MESSAGES/link-campaign.po
@@ -53,3 +53,47 @@ msgstr "Based on the selected criteria, %{sample} out of %{total} people in the
#, elixir-autogen, elixir-format, ex-autogen
msgid "submission.criteria.title"
msgstr "Criteria"
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.assignment"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.assignment.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.funding"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.funding.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.monitor"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.monitor.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.promotion"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.promotion.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.submission"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.submission.forward"
+msgstr ""
diff --git a/core/priv/gettext/en/LC_MESSAGES/link-lab.po b/core/priv/gettext/en/LC_MESSAGES/link-lab.po
index 5c0572404..79cfa654d 100644
--- a/core/priv/gettext/en/LC_MESSAGES/link-lab.po
+++ b/core/priv/gettext/en/LC_MESSAGES/link-lab.po
@@ -133,7 +133,7 @@ msgid "search.subject.not.found"
msgstr "No results"
#, elixir-autogen, elixir-format, ex-autogen
-msgid "experiment.checkin.label"
+msgid "inquiry.checkin.label"
msgstr "Check in with Panl ID:"
#, elixir-autogen, elixir-format, ex-autogen, fuzzy
@@ -171,3 +171,7 @@ msgstr "Reservation found"
#, elixir-autogen, elixir-format, ex-autogen
msgid "date.location.error"
msgstr "Location already booked on the selected date"
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
diff --git a/core/priv/gettext/en/LC_MESSAGES/link-questionnaire.po b/core/priv/gettext/en/LC_MESSAGES/link-questionnaire.po
new file mode 100644
index 000000000..279742595
--- /dev/null
+++ b/core/priv/gettext/en/LC_MESSAGES/link-questionnaire.po
@@ -0,0 +1,16 @@
+## "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 "task.hero.title"
+msgstr ""
diff --git a/core/priv/gettext/link-survey.pot b/core/priv/gettext/eyra-alliance.pot
similarity index 75%
rename from core/priv/gettext/link-survey.pot
rename to core/priv/gettext/eyra-alliance.pot
index 5ddb95225..721c0e5e9 100644
--- a/core/priv/gettext/link-survey.pot
+++ b/core/priv/gettext/eyra-alliance.pot
@@ -10,18 +10,10 @@
msgid ""
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "config.nrofsubjects.label"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "config.url.label"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "duration.label"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "by.author.label"
msgstr ""
@@ -34,10 +26,6 @@ msgstr ""
msgid "apply.cta.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "devices.label"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "open.cta.title"
msgstr ""
@@ -50,10 +38,6 @@ msgstr ""
msgid "redirect.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "devices.title"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "pending.label"
msgstr ""
@@ -90,18 +74,6 @@ msgstr ""
msgid "empty.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.monitor"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.promotion"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.assignment"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "monitor.empty.description"
msgstr ""
@@ -110,42 +82,10 @@ msgstr ""
msgid "monitor.empty.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.promotion.forward"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.assignment.forward"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.monitor.forward"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.submission"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.submission.forward"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "form.title"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "campaign.overview.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "language.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "languages.label"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "ethical.label"
msgstr ""
@@ -194,10 +134,6 @@ msgstr ""
msgid "study.instructions.link"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "form.description"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "duration.highlight.text"
msgstr ""
@@ -277,11 +213,3 @@ msgstr ""
#, elixir-autogen, elixir-format, ex-autogen
msgid "test.roundtrip.title"
msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.funding"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.funding.forward"
-msgstr ""
diff --git a/core/priv/gettext/eyra-assignment.pot b/core/priv/gettext/eyra-assignment.pot
index 2df788fcf..ad180794a 100644
--- a/core/priv/gettext/eyra-assignment.pot
+++ b/core/priv/gettext/eyra-assignment.pot
@@ -53,3 +53,51 @@ msgstr ""
#, elixir-autogen, elixir-format, ex-autogen
msgid "pool.target"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "close.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "config.nrofsubjects.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "devices.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "devices.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "duration.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "language.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "languages.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "preview.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "publish.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "retract.button"
+msgstr ""
diff --git a/core/priv/gettext/eyra-benchmark.pot b/core/priv/gettext/eyra-benchmark.pot
index 7f969c708..d7dadbdae 100644
--- a/core/priv/gettext/eyra-benchmark.pot
+++ b/core/priv/gettext/eyra-benchmark.pot
@@ -212,3 +212,43 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "leaderboard.team.label"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "assignment.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "close.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "preview.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "publish.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "retract.button"
+msgstr ""
diff --git a/core/priv/gettext/eyra-campaign.pot b/core/priv/gettext/eyra-campaign.pot
index c8930701a..dc522ad46 100644
--- a/core/priv/gettext/eyra-campaign.pot
+++ b/core/priv/gettext/eyra-campaign.pot
@@ -39,7 +39,7 @@ msgid "campaign.create.title"
msgstr ""
#, elixir-autogen, elixir-format
-msgid "campaign.create.tooltype.label"
+msgid "campaign.create.template.label"
msgstr ""
#, elixir-autogen, elixir-format
diff --git a/core/priv/gettext/eyra-data-donation.pot b/core/priv/gettext/eyra-data-donation.pot
index 73aedaf4e..d231fbf6d 100644
--- a/core/priv/gettext/eyra-data-donation.pot
+++ b/core/priv/gettext/eyra-data-donation.pot
@@ -9,91 +9,3 @@
## effect: edit them in PO (.po) files instead.
msgid ""
msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "config.nrofsubjects.label"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "platforms.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "content.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.donate.description"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.donate.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.download.description"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.download.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.library.description"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.library.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.list.description"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.list.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.request.description"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.request.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.survey.description"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.survey.title"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.description.label"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.title.label"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.list.hint"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "task.platform.label"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "pdf-replace-file-button"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "pdf-select-file-button"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "pdf-select-placeholder"
-msgstr ""
diff --git a/core/priv/gettext/eyra-document.pot b/core/priv/gettext/eyra-document.pot
new file mode 100644
index 000000000..c505de8d9
--- /dev/null
+++ b/core/priv/gettext/eyra-document.pot
@@ -0,0 +1,52 @@
+## 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 "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pdf-replace-file-button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-file-button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-placeholder"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "component.title"
+msgstr ""
diff --git a/core/priv/gettext/eyra-enums.pot b/core/priv/gettext/eyra-enums.pot
index b74a54417..34a208b63 100644
--- a/core/priv/gettext/eyra-enums.pot
+++ b/core/priv/gettext/eyra-enums.pot
@@ -179,11 +179,11 @@ msgid "ticket_status.open"
msgstr ""
#, elixir-autogen, elixir-format
-msgid "tool_types.lab"
+msgid "templates.lab"
msgstr ""
#, elixir-autogen, elixir-format
-msgid "tool_types.online"
+msgid "templates.online"
msgstr ""
#, elixir-autogen, elixir-format, ex-autogen
@@ -335,9 +335,17 @@ msgid "project_templates.empty"
msgstr ""
#, elixir-autogen, elixir-format
-msgid "project_tools.benchmark"
+msgid "templates.data_donation"
msgstr ""
#, elixir-autogen, elixir-format
-msgid "project_tools.data_donation"
+msgid "platforms.apple"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "platforms.samsung"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "platforms.tiktok"
msgstr ""
diff --git a/core/priv/gettext/eyra-feldspar.pot b/core/priv/gettext/eyra-feldspar.pot
new file mode 100644
index 000000000..63533b13e
--- /dev/null
+++ b/core/priv/gettext/eyra-feldspar.pot
@@ -0,0 +1,52 @@
+## 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 "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "zip-replace-file-button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-file-button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-placeholder"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "zip-too-large"
+msgstr ""
diff --git a/core/priv/gettext/eyra-marketplace.pot b/core/priv/gettext/eyra-marketplace.pot
index e062666ec..0bc8967a2 100644
--- a/core/priv/gettext/eyra-marketplace.pot
+++ b/core/priv/gettext/eyra-marketplace.pot
@@ -18,10 +18,6 @@ msgstr ""
msgid "empty.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "reward.label"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "assignment.status.complete.label"
msgstr ""
@@ -30,10 +26,6 @@ msgstr ""
msgid "assignment.status.completed.label"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "assignment.status.expired.label"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "assignment.status.pending.label"
msgstr ""
diff --git a/core/priv/gettext/eyra-project.pot b/core/priv/gettext/eyra-project.pot
index 06f26afc1..524fa319b 100644
--- a/core/priv/gettext/eyra-project.pot
+++ b/core/priv/gettext/eyra-project.pot
@@ -11,10 +11,6 @@
msgid ""
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "preview.button"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "delete.confirm"
msgstr ""
@@ -51,14 +47,6 @@ msgstr ""
msgid "tabbar.item.privacy.forward"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.tasks"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.tasks.forward"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "add.first.button"
msgstr ""
@@ -115,10 +103,6 @@ msgstr ""
msgid "node.items.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "publish.button"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "form.title"
msgstr ""
@@ -135,10 +119,6 @@ msgstr ""
msgid "add.new.item.button.short"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "retract.button"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "campaign.invite.title"
msgstr ""
@@ -151,14 +131,6 @@ msgstr ""
msgid "direct.invite.title"
msgstr ""
-#, elixir-autogen, elixir-format
-msgid "close.button"
-msgstr ""
-
-#, elixir-autogen, elixir-format
-msgid "open.button"
-msgstr ""
-
#, elixir-autogen, elixir-format
msgid "label.concept"
msgstr ""
@@ -190,3 +162,23 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "create.item.button.short"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "create.item.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.form.name.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.form.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.support"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.support.forward"
+msgstr ""
diff --git a/core/priv/gettext/eyra-support.pot b/core/priv/gettext/eyra-support.pot
index cf5326a67..abe6fae88 100644
--- a/core/priv/gettext/eyra-support.pot
+++ b/core/priv/gettext/eyra-support.pot
@@ -37,3 +37,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "ticket.type"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.form.title"
+msgstr ""
diff --git a/core/priv/gettext/eyra-survey.pot b/core/priv/gettext/eyra-survey.pot
deleted file mode 100644
index d231fbf6d..000000000
--- a/core/priv/gettext/eyra-survey.pot
+++ /dev/null
@@ -1,11 +0,0 @@
-## This file is a PO Template file.
-##
-## "msgid"s here are often extracted from source code.
-## Add new translations manually only if they're dynamic
-## translations 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 as no
-## effect: edit them in PO (.po) files instead.
-msgid ""
-msgstr ""
diff --git a/core/priv/gettext/eyra-workflow.pot b/core/priv/gettext/eyra-workflow.pot
new file mode 100644
index 000000000..01294f7cd
--- /dev/null
+++ b/core/priv/gettext/eyra-workflow.pot
@@ -0,0 +1,88 @@
+## 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 "add.to.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.description.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.donate.description"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.donate.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.download.description"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.download.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.group.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.library.description"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.library.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.list.description"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.list.hint"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.list.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.request.description"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.request.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.title.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.workflow"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.workflow.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.questionnaire.description"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "item.questionnaire.title"
+msgstr ""
diff --git a/core/priv/gettext/link-campaign.pot b/core/priv/gettext/link-campaign.pot
index a480d0ac9..0d3833c0c 100644
--- a/core/priv/gettext/link-campaign.pot
+++ b/core/priv/gettext/link-campaign.pot
@@ -53,3 +53,47 @@ msgstr ""
#, elixir-autogen, elixir-format, ex-autogen
msgid "submission.criteria.title"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.assignment"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.assignment.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.funding"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.funding.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.monitor"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.monitor.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.promotion"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.promotion.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.submission"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.submission.forward"
+msgstr ""
diff --git a/core/priv/gettext/link-lab.pot b/core/priv/gettext/link-lab.pot
index 661d7bac5..c2e9b0ef3 100644
--- a/core/priv/gettext/link-lab.pot
+++ b/core/priv/gettext/link-lab.pot
@@ -133,7 +133,7 @@ msgid "search.subject.not.found"
msgstr ""
#, elixir-autogen, elixir-format, ex-autogen
-msgid "experiment.checkin.label"
+msgid "inquiry.checkin.label"
msgstr ""
#, elixir-autogen, elixir-format, ex-autogen
@@ -171,3 +171,7 @@ msgstr ""
#, elixir-autogen, elixir-format, ex-autogen
msgid "date.location.error"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
diff --git a/core/priv/gettext/link-questionnaire.pot b/core/priv/gettext/link-questionnaire.pot
new file mode 100644
index 000000000..dc2998e69
--- /dev/null
+++ b/core/priv/gettext/link-questionnaire.pot
@@ -0,0 +1,16 @@
+## 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 "task.hero.title"
+msgstr ""
diff --git a/core/priv/gettext/nl/LC_MESSAGES/link-survey.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-alliance.po
similarity index 75%
rename from core/priv/gettext/nl/LC_MESSAGES/link-survey.po
rename to core/priv/gettext/nl/LC_MESSAGES/eyra-alliance.po
index 04511388e..1e78b16c8 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/link-survey.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-alliance.po
@@ -10,17 +10,9 @@ msgid ""
msgstr ""
"Language: nl\n"
-#, elixir-autogen, elixir-format
-msgid "config.nrofsubjects.label"
-msgstr "Hoeveel deelnemers heb je nodig?"
-
#, elixir-autogen, elixir-format
msgid "config.url.label"
-msgstr "Qualtrics link"
-
-#, elixir-autogen, elixir-format
-msgid "duration.label"
-msgstr "Hoeveel minuten schat je in dat deelnemers nodig hebben?"
+msgstr "Vragenlijst link"
#, elixir-autogen, elixir-format
msgid "by.author.label"
@@ -34,25 +26,17 @@ msgstr "Beloning"
msgid "apply.cta.title"
msgstr "Ik doe mee"
-#, elixir-autogen, elixir-format
-msgid "devices.label"
-msgstr "Welke apparaten kunnen door deelnemers gebruikt worden?"
-
#, elixir-autogen, elixir-format
msgid "open.cta.title"
msgstr "Doorgaan"
#, elixir-autogen, elixir-format
msgid "redirect.description"
-msgstr "De redirect URL zorgt ervoor dat deelnemers teruggestuurd worden naar Panl, aan het einde van de vragenlijst. Kopieer de redirect URL hieronder en plak deze als \"Website URL\" in Qualtrics zoals %{link}."
+msgstr "De redirect URL zorgt ervoor dat deelnemers teruggestuurd worden naar Next, aan het einde van de vragenlijst. Kopieer de redirect URL hieronder en plak deze als \"Website URL\" in Qualtrics zoals %{link}."
#, elixir-autogen, elixir-format
msgid "redirect.title"
-msgstr "Redirect URL (End of Survey)"
-
-#, elixir-autogen, elixir-format
-msgid "devices.title"
-msgstr "Apparaten"
+msgstr "Redirect URL"
#, elixir-autogen, elixir-format
msgid "pending.label"
@@ -90,18 +74,6 @@ msgstr "Hier kun je campagnes opzetten en hun status volgen. Een campagne is een
msgid "empty.title"
msgstr "Werf deelnemers voor jouw studie"
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.monitor"
-msgstr "Dashboard"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.promotion"
-msgstr "Advertentie"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.assignment"
-msgstr "Studie"
-
#, elixir-autogen, elixir-format
msgid "monitor.empty.description"
msgstr "Dien je campagne in en wacht tot de poolbeheerder je campagne heeft gepubliceerd. Bekijk het voorbeeld om te zien hoe je campagne er uit komt te zien voor de deelnemers. Zodra je campagne online staat, kun je dit dashboard gebruiken om je deelnemers te beheren."
@@ -110,42 +82,10 @@ msgstr "Dien je campagne in en wacht tot de poolbeheerder je campagne heeft gepu
msgid "monitor.empty.title"
msgstr "Campagne ingediend?"
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.promotion.forward"
-msgstr "Maak advertentie aan"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.assignment.forward"
-msgstr "Jouw studie"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.monitor.forward"
-msgstr "Naar dashboard"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.submission"
-msgstr "Sample"
-
-#, elixir-autogen, elixir-format
-msgid "tabbar.item.submission.forward"
-msgstr "Sample"
-
-#, elixir-autogen, elixir-format
-msgid "form.title"
-msgstr "Vragenlijst"
-
#, elixir-autogen, elixir-format
msgid "campaign.overview.title"
msgstr "Campagnes"
-#, elixir-autogen, elixir-format
-msgid "language.title"
-msgstr "Taal"
-
-#, elixir-autogen, elixir-format
-msgid "languages.label"
-msgstr "In welke taal is jouw studie geschreven?"
-
#, elixir-autogen, elixir-format
msgid "ethical.label"
msgstr "Hierbij verklaar ik dat mijn studie voldoet aan alle ethische standaarden."
@@ -176,11 +116,11 @@ msgstr "Zorg ervoor dat je de %{link} doorlopen hebt en dat je goedkeuring hebt
#, elixir-autogen, elixir-format
msgid "study.link.description"
-msgstr "Volg %{link} om jouw Qualtrics vragenlijst link aan te maken op te halen. Plak deze link in het \"Qualtrics link\" veld van het formulier hieronder."
+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 Survey Link"
+msgstr "Anonymous Questionnaire Link"
#, elixir-autogen, elixir-format
msgid "panlid.instructions.link"
@@ -194,10 +134,6 @@ msgstr "hier beschreven"
msgid "study.instructions.link"
msgstr "deze instructies"
-#, elixir-autogen, elixir-format
-msgid "form.description"
-msgstr "Doorloop eerst de stappen in het zwarte kader. Zo zorg je ervoor dat deelnemers vanuit Panl deel kunnen nemen aan jouw online studie en na afloop weer teruggestuurd worden naar Panl."
-
#, elixir-autogen, elixir-format
msgid "duration.highlight.text"
msgstr "%{duration} minuten"
@@ -277,11 +213,3 @@ msgstr "Test of jouw vragenlijst goed opgezet is en controleer of je terugkomt o
#, elixir-autogen, elixir-format, ex-autogen
msgid "test.roundtrip.title"
msgstr "Is je vragenlijst goed opgezet?"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.funding"
-msgstr "Financiering"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.funding.forward"
-msgstr "Financier je studie"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po
index ee3b4cd55..0fddc3e12 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po
@@ -32,7 +32,7 @@ msgstr "E-mail onderzoeker"
#, elixir-autogen, elixir-format
msgid "form.title"
-msgstr "Studie"
+msgstr "Opdracht"
#, elixir-autogen, elixir-format, ex-autogen
msgid "ticket.title"
@@ -53,3 +53,51 @@ msgstr "%{amount} te gaan"
#, elixir-autogen, elixir-format, ex-autogen
msgid "pool.target"
msgstr "%{target} credits vereist"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "close.button"
+msgstr "Afronden"
+
+#, elixir-autogen, elixir-format
+msgid "config.nrofsubjects.label"
+msgstr "Hoeveel deelnemers heb je nodig?"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "content.title"
+msgstr "Jouw Panl ID"
+
+#, elixir-autogen, elixir-format
+msgid "devices.label"
+msgstr "Welke apparaten mogen deelnemers gebruiken?"
+
+#, elixir-autogen, elixir-format
+msgid "devices.title"
+msgstr "Apparaten"
+
+#, elixir-autogen, elixir-format
+msgid "duration.label"
+msgstr "Hoeveel minuten schat je in dat deelnemers nodig hebben?"
+
+#, elixir-autogen, elixir-format
+msgid "language.title"
+msgstr "Taal"
+
+#, elixir-autogen, elixir-format
+msgid "languages.label"
+msgstr "In welke taal is de opdracht geschreven?"
+
+#, elixir-autogen, elixir-format
+msgid "open.button"
+msgstr "Open"
+
+#, elixir-autogen, elixir-format
+msgid "preview.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "publish.button"
+msgstr "Publiceren"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "retract.button"
+msgstr "Intrekken"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-benchmark.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-benchmark.po
index 51860f748..39cec4dec 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-benchmark.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-benchmark.po
@@ -212,3 +212,43 @@ msgstr "Score"
#, elixir-autogen, elixir-format
msgid "leaderboard.team.label"
msgstr "Team"
+
+#, elixir-autogen, elixir-format
+msgid "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "assignment.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "close.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "open.cta.title"
+msgstr "Challenge"
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "preview.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "publish.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "retract.button"
+msgstr "Github voorbeeld repository"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-campaign.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-campaign.po
index 8229b3115..d7874b4dc 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-campaign.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-campaign.po
@@ -39,7 +39,7 @@ msgid "campaign.create.title"
msgstr "Nieuwe campagne"
#, elixir-autogen, elixir-format
-msgid "campaign.create.tooltype.label"
+msgid "campaign.create.template.label"
msgstr "Type studie"
#, elixir-autogen, elixir-format
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-data-donation.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-data-donation.po
index 47232a765..ebb7d6837 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-data-donation.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-data-donation.po
@@ -9,91 +9,3 @@
msgid ""
msgstr ""
"Language: nl\n"
-
-#, elixir-autogen, elixir-format
-msgid "config.nrofsubjects.label"
-msgstr "Aantal gewenste deelnemers"
-
-#, elixir-autogen, elixir-format
-msgid "platforms.title"
-msgstr "Platformen"
-
-#, elixir-autogen, elixir-format
-msgid "content.title"
-msgstr "Data Donatie"
-
-#, elixir-autogen, elixir-format
-msgid "task.donate.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.donate.title"
-msgstr "Donatie"
-
-#, elixir-autogen, elixir-format
-msgid "task.download.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.download.title"
-msgstr "Downloaden DDP"
-
-#, elixir-autogen, elixir-format
-msgid "task.library.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.library.title"
-msgstr "Bibliotheek"
-
-#, elixir-autogen, elixir-format
-msgid "task.list.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.list.title"
-msgstr "Taken"
-
-#, elixir-autogen, elixir-format
-msgid "task.request.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.request.title"
-msgstr "Aanvragen DDP"
-
-#, elixir-autogen, elixir-format
-msgid "task.survey.description"
-msgstr "Lorem ipsum dolor sit amet."
-
-#, elixir-autogen, elixir-format
-msgid "task.survey.title"
-msgstr "Vragenlijst"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "task.description.label"
-msgstr "Beschrijving"
-
-#, elixir-autogen, elixir-format
-msgid "task.title.label"
-msgstr "Titel"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "task.list.hint"
-msgstr "Verander volgorde met de pijltjes"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "task.platform.label"
-msgstr "Platform"
-
-#, elixir-autogen, elixir-format
-msgid "pdf-replace-file-button"
-msgstr "Vervang PDF"
-
-#, elixir-autogen, elixir-format
-msgid "pdf-select-file-button"
-msgstr "Kies PDF"
-
-#, elixir-autogen, elixir-format
-msgid "pdf-select-placeholder"
-msgstr "Kies een pdf bestand"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-document.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-document.po
new file mode 100644
index 000000000..e387cf81a
--- /dev/null
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-document.po
@@ -0,0 +1,52 @@
+## "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 "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pdf-replace-file-button"
+msgstr "Vervang PDF"
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-file-button"
+msgstr "Kies PDF"
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-label"
+msgstr "Handleiding"
+
+#, elixir-autogen, elixir-format
+msgid "pdf-select-placeholder"
+msgstr "Kies een pdf bestand"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "component.title"
+msgstr "Handleiding"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po
index 75a753519..714599c21 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-enums.po
@@ -179,11 +179,11 @@ msgid "ticket_status.open"
msgstr "Open"
#, elixir-autogen, elixir-format
-msgid "tool_types.lab"
+msgid "templates.lab"
msgstr "Lab studie"
#, elixir-autogen, elixir-format
-msgid "tool_types.online"
+msgid "templates.online"
msgstr "Online studie"
#, elixir-autogen, elixir-format, ex-autogen
@@ -335,9 +335,17 @@ msgid "project_templates.empty"
msgstr "Leeg"
#, elixir-autogen, elixir-format, fuzzy
-msgid "project_tools.benchmark"
-msgstr "Benchmark"
+msgid "templates.data_donation"
+msgstr "Online studie"
#, elixir-autogen, elixir-format, fuzzy
-msgid "project_tools.data_donation"
-msgstr "Data Donatie Studie"
+msgid "platforms.apple"
+msgstr "Apple"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "platforms.samsung"
+msgstr "Samsung"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "platforms.tiktok"
+msgstr "TikTok"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-feldspar.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-feldspar.po
new file mode 100644
index 000000000..d5aebc9c2
--- /dev/null
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-feldspar.po
@@ -0,0 +1,52 @@
+## "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 "apply.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "open.cta.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "participated.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "pending.label"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "zip-replace-file-button"
+msgstr "Vervang bestand"
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-file-button"
+msgstr "Kies bestand"
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-label"
+msgstr "Flow applicatie"
+
+#, elixir-autogen, elixir-format
+msgid "zip-select-placeholder"
+msgstr "Kies een zip bestand"
+
+#, elixir-autogen, elixir-format
+msgid "zip-too-large"
+msgstr "Het geselecteerde bestand is te groot (max 20MB)"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-marketplace.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-marketplace.po
index 575be82f4..3012b5c24 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-marketplace.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-marketplace.po
@@ -18,10 +18,6 @@ msgstr "Op dit moment zijn er geen onderzoeksstudies beschikbaar. Zodra er een s
msgid "empty.title"
msgstr "Op zoek naar een studie?"
-#, elixir-autogen, elixir-format
-msgid "reward.label"
-msgstr "Beloning: %{value} credits"
-
#, elixir-autogen, elixir-format
msgid "assignment.status.complete.label"
msgstr "Gesloten"
@@ -30,10 +26,6 @@ msgstr "Gesloten"
msgid "assignment.status.completed.label"
msgstr "Afgerond"
-#, elixir-autogen, elixir-format
-msgid "assignment.status.expired.label"
-msgstr "Verlopen"
-
#, elixir-autogen, elixir-format
msgid "assignment.status.pending.label"
msgstr "Aangemeld"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po
index 07a565320..bfa7228f1 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-project.po
@@ -11,10 +11,6 @@ msgstr ""
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#, elixir-autogen, elixir-format
-msgid "preview.button"
-msgstr "Voorbeeld"
-
#, elixir-autogen, elixir-format
msgid "delete.confirm"
msgstr "project"
@@ -25,7 +21,7 @@ msgstr "Instellingen"
#, elixir-autogen, elixir-format
msgid "tabbar.item.config.forward"
-msgstr "Instellingen"
+msgstr "Ga naar Instellingen"
#, elixir-autogen, elixir-format
msgid "tabbar.item.invite"
@@ -33,7 +29,7 @@ msgstr "Uitnodigen"
#, elixir-autogen, elixir-format
msgid "tabbar.item.invite.forward"
-msgstr "Uitnodigen"
+msgstr "Ga naar Uitnodigen"
#, elixir-autogen, elixir-format
msgid "tabbar.item.monitor"
@@ -41,7 +37,7 @@ msgstr "Monitor"
#, elixir-autogen, elixir-format
msgid "tabbar.item.monitor.forward"
-msgstr "Monitor"
+msgstr "Ga naar Monitor"
#, elixir-autogen, elixir-format
msgid "tabbar.item.privacy"
@@ -49,15 +45,7 @@ msgstr "Privacy"
#, elixir-autogen, elixir-format
msgid "tabbar.item.privacy.forward"
-msgstr "Privacy"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.tasks"
-msgstr "Taken"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "tabbar.item.tasks.forward"
-msgstr "Taken"
+msgstr "Ga naar Privacy"
#, elixir-autogen, elixir-format
msgid "add.first.button"
@@ -115,10 +103,6 @@ msgstr "Overzicht"
msgid "node.items.title"
msgstr "Items"
-#, elixir-autogen, elixir-format, fuzzy
-msgid "publish.button"
-msgstr "Publiceren"
-
#, elixir-autogen, elixir-format
msgid "form.title"
msgstr "Project instellingen"
@@ -135,10 +119,6 @@ msgstr "Voeg nieuw item toe"
msgid "add.new.item.button.short"
msgstr "Nieuw item"
-#, elixir-autogen, elixir-format
-msgid "retract.button"
-msgstr "Terugtrekken"
-
#, elixir-autogen, elixir-format
msgid "campaign.invite.title"
msgstr "Nodig mensen uit met een campagne"
@@ -151,14 +131,6 @@ msgstr "Je kan mensen direct uitnodigigen op de onderstaande url."
msgid "direct.invite.title"
msgstr "Nodig mensen uit met een url"
-#, elixir-autogen, elixir-format
-msgid "close.button"
-msgstr "Finish"
-
-#, elixir-autogen, elixir-format, fuzzy
-msgid "open.button"
-msgstr "Resume"
-
#, elixir-autogen, elixir-format
msgid "label.concept"
msgstr "Concept"
@@ -190,3 +162,23 @@ msgstr "Terugtrekken"
#, elixir-autogen, elixir-format
msgid "create.item.button.short"
msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "create.item.title"
+msgstr "Kies type item"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "item.form.name.label"
+msgstr "Naam"
+
+#, elixir-autogen, elixir-format
+msgid "item.form.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "tabbar.item.support"
+msgstr "Support"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "tabbar.item.support.forward"
+msgstr "Ga naar Support"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-support.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-support.po
index a1def3115..55679c378 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-support.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-support.po
@@ -37,3 +37,7 @@ msgstr "Melding ontvangen"
#, elixir-autogen, elixir-format
msgid "ticket.type"
msgstr "Formulier"
+
+#, elixir-autogen, elixir-format
+msgid "content.form.title"
+msgstr "Support"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-survey.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-survey.po
deleted file mode 100644
index ebb7d6837..000000000
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-survey.po
+++ /dev/null
@@ -1,11 +0,0 @@
-## "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"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-workflow.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-workflow.po
new file mode 100644
index 000000000..320598f43
--- /dev/null
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-workflow.po
@@ -0,0 +1,88 @@
+## "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 "add.to.button"
+msgstr "Voeg toe"
+
+#, elixir-autogen, elixir-format
+msgid "item.description.label"
+msgstr "Beschrijving"
+
+#, elixir-autogen, elixir-format
+msgid "item.donate.description"
+msgstr "Biedt deelnemers de mogelijkheid om te doneren."
+
+#, elixir-autogen, elixir-format
+msgid "item.donate.title"
+msgstr "Donatie"
+
+#, elixir-autogen, elixir-format
+msgid "item.download.description"
+msgstr "Instructs participants on how to download digital trace data."
+
+#, elixir-autogen, elixir-format
+msgid "item.download.title"
+msgstr "Download handleiding"
+
+#, elixir-autogen, elixir-format
+msgid "item.group.label"
+msgstr "Group"
+
+#, elixir-autogen, elixir-format
+msgid "item.library.description"
+msgstr "Kies items die je aan de werklijst wilt toevoegen."
+
+#, elixir-autogen, elixir-format
+msgid "item.library.title"
+msgstr "Bibliotheek"
+
+#, elixir-autogen, elixir-format
+msgid "item.list.description"
+msgstr "Voeg items uit de bibliotheek toe om een werklijst te bouwen voor jouw deelnemers."
+
+#, elixir-autogen, elixir-format
+msgid "item.list.hint"
+msgstr "Verander volgorde met de pijltjes"
+
+#, elixir-autogen, elixir-format
+msgid "item.list.title"
+msgstr "Werklijst"
+
+#, elixir-autogen, elixir-format
+msgid "item.request.description"
+msgstr "Legt deelnemers uit hoe ze digital trace data kunnen aanvragen."
+
+#, elixir-autogen, elixir-format
+msgid "item.request.title"
+msgstr "Aanvraag handleiding"
+
+#, elixir-autogen, elixir-format
+msgid "item.title.label"
+msgstr "Titel"
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.workflow"
+msgstr "Werklijst"
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.workflow.forward"
+msgstr "Ga naar Werklijst"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "item.questionnaire.description"
+msgstr "Stuurt deelnemers naar een online vragenlijst."
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "item.questionnaire.title"
+msgstr "Vragenlijst"
diff --git a/core/priv/gettext/nl/LC_MESSAGES/link-campaign.po b/core/priv/gettext/nl/LC_MESSAGES/link-campaign.po
index 4e6776df8..40262319a 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/link-campaign.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/link-campaign.po
@@ -53,3 +53,47 @@ msgstr "Op basis van de geselecteerde criteria, kunnen %{sample} van de %{total}
#, elixir-autogen, elixir-format, ex-autogen
msgid "submission.criteria.title"
msgstr "Criteria"
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.assignment"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.assignment.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.funding"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.funding.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.monitor"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.monitor.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.promotion"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.promotion.forward"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.submission"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "tabbar.item.submission.forward"
+msgstr ""
diff --git a/core/priv/gettext/nl/LC_MESSAGES/link-lab.po b/core/priv/gettext/nl/LC_MESSAGES/link-lab.po
index 3b5e9995b..9138d1356 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/link-lab.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/link-lab.po
@@ -133,7 +133,7 @@ msgid "search.subject.not.found"
msgstr "Geen resultaten"
#, elixir-autogen, elixir-format, ex-autogen
-msgid "experiment.checkin.label"
+msgid "inquiry.checkin.label"
msgstr "Inchecken met Panl ID:"
#, elixir-autogen, elixir-format, ex-autogen, fuzzy
@@ -171,3 +171,7 @@ msgstr "Reserving gevonden"
#, elixir-autogen, elixir-format, ex-autogen
msgid "date.location.error"
msgstr "Deze locatie is al geboekt op de geselecteerde datum"
+
+#, elixir-autogen, elixir-format
+msgid "content.title"
+msgstr ""
diff --git a/core/priv/gettext/nl/LC_MESSAGES/link-questionnaire.po b/core/priv/gettext/nl/LC_MESSAGES/link-questionnaire.po
new file mode 100644
index 000000000..895b8bc4a
--- /dev/null
+++ b/core/priv/gettext/nl/LC_MESSAGES/link-questionnaire.po
@@ -0,0 +1,16 @@
+## "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 "task.hero.title"
+msgstr ""
diff --git a/core/priv/repo/generator_test.py b/core/priv/repo/generator_test.py
deleted file mode 100644
index 9bfc8b58f..000000000
--- a/core/priv/repo/generator_test.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import pandas as pd
-
-
-def process():
- chat_file_name = yield prompt_file()
- usernames = extract_usernames(chat_file_name)
- username = yield prompt_radio(usernames)
- yield result(usernames, username)
-
-
-def prompt_file():
- return {
- "cmd": "prompt",
- "prompt": {
- "type": "file",
- "file": {
- "title": {
- "en": "Step 1: Select the chat file",
- "nl": "Stap 1: Selecteer het chat file"
- },
- "description": {
- "en": "We previously asked you to export a chat file from WhatsApp. Please select this file so we can extract relevant information for our research.",
- "nl": "We hebben je gevraagd een chat bestand te exporteren uit WhatsApp. Je kan deze file nu selecteren zodat wij er relevante informatie uit kunnen halen voor ons onderzoek."
- },
- "extensions": "application/zip, text/plain",
- }
- }
- }
-
-
-def prompt_radio(usernames):
- return {
- "cmd": "prompt",
- "prompt": {
- "type": "radio",
- "radio": {
- "title": {
- "en": "Step 2: Select your username",
- "nl": "Stap 2: Selecteer je gebruikersnaam"
- },
- "description": {
- "en": "The following users are extracted from the chat file. Which one are you?",
- "nl": "De volgende gebruikers hebben we uit de chat file gehaald. Welke ben jij?"
- },
- "items": usernames,
- }
- }
- }
-
-
-def extract_usernames(chat_file_name):
- print(f"filename: {chat_file_name}")
-
- with open(chat_file_name) as chat_file:
- while (line := chat_file.readline().rstrip()):
- print(line)
-
- return ["emielvdveen", "a.m.mendrik", "9bitcat"]
-
-
-def result(usernames, selected_username):
- data = []
- for username in usernames:
- description = "you" if username == selected_username else "-"
- data.append((username, description))
-
- data_frame = pd.DataFrame(data, columns=["username", "description"])
-
- print(data_frame)
-
- result = [{
- "id": "overview",
- "title": "The following usernames where extracted:",
- "data_frame": data_frame
- }]
- return {"cmd": "result", "result": result}
diff --git a/core/priv/repo/migrations/20211026184309_refactor_campaign_part_1.exs b/core/priv/repo/migrations/20211026184309_refactor_campaign_part_1.exs
index 2a06b76c4..e9d5a1dd6 100644
--- a/core/priv/repo/migrations/20211026184309_refactor_campaign_part_1.exs
+++ b/core/priv/repo/migrations/20211026184309_refactor_campaign_part_1.exs
@@ -4,7 +4,7 @@ defmodule Core.Repo.Migrations.RefactorCampaignPromotionAssignment do
import Ecto.Query, warn: false
def up do
- #Rename Studies -> Campaigns
+ # Rename Studies -> Campaigns
rename_pkey(from: :studies, to: :campaigns)
rename_fkey(from: :studies, to: :campaigns, field: :auth_node_id)
rename_fkey(from: :study_id, to: :campaign_id, table: :authors)
@@ -14,7 +14,7 @@ defmodule Core.Repo.Migrations.RefactorCampaignPromotionAssignment do
rename_fkey(from: :study_id, to: :campaign_id, table: :survey_tools)
rename_table(from: :studies, to: :campaigns)
- #Create Assignment
+ # Create Assignment
create_assignment_table()
# Add director field
@@ -25,10 +25,10 @@ defmodule Core.Repo.Migrations.RefactorCampaignPromotionAssignment do
# Drop director field
drop_directors()
- #Drop Assignment
+ # Drop Assignment
drop_assignment_table()
- #Rename Campaigns -> Studies
+ # Rename Campaigns -> Studies
rename_pkey(from: :campaigns, to: :studies)
rename_fkey(from: :campaigns, to: :studies, field: :auth_node_id)
rename_fkey(from: :campaign_id, to: :study_id, table: :authors)
@@ -76,29 +76,47 @@ defmodule Core.Repo.Migrations.RefactorCampaignPromotionAssignment do
defp create_assignment_table do
create table(:assignments) do
add(:director, :string)
- add(:assignable_survey_tool_id, references(:survey_tools, on_delete: :delete_all), null: true)
+
+ add(:assignable_survey_tool_id, references(:survey_tools, on_delete: :delete_all),
+ null: true
+ )
+
add(:assignable_lab_tool_id, references(:lab_tools, on_delete: :delete_all), null: true)
- add(:assignable_data_donation_tool_id, references(:data_donation_tools, on_delete: :delete_all), null: true)
+
+ add(
+ :assignable_data_donation_tool_id,
+ references(:data_donation_tools, on_delete: :delete_all),
+ null: true
+ )
+
add(:crew_id, references(:crews, on_delete: :delete_all), null: true)
add(:auth_node_id, references(:authorization_nodes), null: false)
timestamps()
end
- create constraint(:assignments, :must_have_at_least_one_assignable, check:
- """
- assignable_survey_tool_id != null or
- assignable_lab_tool_id != null or
- assignable_data_donation_tool_id != null
- """
+
+ create(
+ constraint(:assignments, :must_have_at_least_one_assignable,
+ check: """
+ assignable_survey_tool_id != null or
+ assignable_lab_tool_id != null or
+ assignable_data_donation_tool_id != null
+ """
+ )
)
+
flush()
+
alter table(:campaigns) do
add(:promotion_id, references(:promotions, on_delete: :delete_all), null: true)
add(:promotable_assignment_id, references(:assignments, on_delete: :delete_all), null: true)
end
- create constraint(:campaigns, :must_have_at_least_one_promotable, check:
- """
- promotable_assignment_id != null
- """
+
+ create(
+ constraint(:campaigns, :must_have_at_least_one_promotable,
+ check: """
+ promotable_assignment_id != null
+ """
+ )
)
alter table(:promotions) do
@@ -113,13 +131,15 @@ defmodule Core.Repo.Migrations.RefactorCampaignPromotionAssignment do
modify(:plugin, :string, null: false)
end
- drop constraint(:campaigns, :must_have_at_least_one_promotable)
+ drop(constraint(:campaigns, :must_have_at_least_one_promotable))
+
alter table(:campaigns) do
remove(:promotion_id)
remove(:promotable_assignment_id)
end
- drop constraint(:assignments, :must_have_at_least_one_assignable)
- drop table(:assignments)
+
+ drop(constraint(:assignments, :must_have_at_least_one_assignable))
+ drop(table(:assignments))
end
defp rename_fkey(from: from_field, to: to_field, table: table_name) do
@@ -145,28 +165,22 @@ defmodule Core.Repo.Migrations.RefactorCampaignPromotionAssignment do
end
defp rename_table(from: from_table_name, to: to_table_name) do
- execute(
- """
- ALTER TABLE #{from_table_name} RENAME TO #{to_table_name};
- """
- )
+ execute("""
+ ALTER TABLE #{from_table_name} RENAME TO #{to_table_name};
+ """)
end
defp rename_constraint(table, from, to) do
- execute(
- """
- ALTER TABLE #{table} RENAME CONSTRAINT "#{from}" TO "#{to}";
- """
- )
+ execute("""
+ ALTER TABLE #{table} RENAME CONSTRAINT "#{from}" TO "#{to}";
+ """)
end
defp rename_field(table, from, to) do
- execute(
- """
- ALTER TABLE #{table}
- RENAME COLUMN #{from} TO #{to};
- """
- )
+ execute("""
+ ALTER TABLE #{table}
+ RENAME COLUMN #{from} TO #{to};
+ """)
end
@max_identifier_length 63
@@ -175,5 +189,4 @@ defmodule Core.Repo.Migrations.RefactorCampaignPromotionAssignment do
|> Enum.join("_")
|> String.slice(0, @max_identifier_length)
end
-
end
diff --git a/core/priv/repo/migrations/20230707084349_rename_survey.exs b/core/priv/repo/migrations/20230707084349_rename_survey.exs
new file mode 100644
index 000000000..17bd14f56
--- /dev/null
+++ b/core/priv/repo/migrations/20230707084349_rename_survey.exs
@@ -0,0 +1,25 @@
+defmodule Core.Repo.Migrations.RenameSurvey do
+ use Ecto.Migration
+
+ def up do
+ rename(table(:survey_tools), :survey_url, to: :questionnaire_url)
+ rename(table(:survey_tools), to: table(:questionnaire_tools))
+
+ rename(table(:experiments), :survey_tool_id, to: :questionnaire_tool_id)
+
+ rename(table(:tool_refs), :survey_tool_id, to: :questionnaire_tool_id)
+ rename(table(:data_donation_tasks), :survey_task_id, to: :questionnaire_task_id)
+ rename(table(:data_donation_survey_tasks), to: table(:data_donation_questionnaire_tasks))
+ end
+
+ def down do
+ rename(table(:data_donation_tasks), :questionnaire_task_id, to: :survey_task_id)
+ rename(table(:data_donation_questionnaire_tasks), to: table(:data_donation_survey_tasks))
+ rename(table(:tool_refs), :questionnaire_tool_id, to: :survey_tool_id)
+
+ rename(table(:experiments), :questionnaire_tool_id, to: :survey_tool_id)
+
+ rename(table(:questionnaire_tools), to: table(:survey_tools))
+ rename(table(:survey_tools), :questionnaire_url, to: :survey_url)
+ end
+end
diff --git a/core/priv/repo/migrations/20230711100350_refactor_assignment_part_1.exs b/core/priv/repo/migrations/20230711100350_refactor_assignment_part_1.exs
new file mode 100644
index 000000000..6d2f46d47
--- /dev/null
+++ b/core/priv/repo/migrations/20230711100350_refactor_assignment_part_1.exs
@@ -0,0 +1,295 @@
+defmodule Core.Repo.Migrations.RefactorAssignmentPart1 do
+ use Ecto.Migration
+
+ def up do
+ drop(constraint(:authorization_nodes, "authorization_nodes_parent_id_fkey"))
+
+ alter table(:authorization_nodes) do
+ modify(:parent_id, references(:authorization_nodes, on_delete: :nilify_all))
+ end
+
+ drop(constraint(:campaigns, "campaigns_promotion_id_fkey"))
+ drop(constraint(:campaigns, "campaigns_promotable_assignment_id_fkey"))
+
+ alter table(:campaigns) do
+ modify(:promotion_id, references(:promotions, on_delete: :nothing))
+ modify(:promotable_assignment_id, references(:assignments, on_delete: :nothing))
+ end
+
+ alter table(:crew_tasks) do
+ remove(:member_id)
+ add(:identifier, {:array, :string}, null: false)
+ add(:auth_node_id, references(:authorization_nodes), null: false)
+ end
+
+ create(unique_index(:crew_tasks, :identifier))
+
+ alter table(:users) do
+ add(:type, :string, null: false, default: "identified")
+ add(:external_id, :string, null: true)
+ end
+
+ create table(:feldspar_tools) do
+ add(:archive_name, :string)
+ add(:archive_ref, :string)
+ add(:director, :string, null: true)
+ add(:auth_node_id, references(:authorization_nodes), null: false)
+ timestamps()
+ end
+
+ create table(:document_tools) do
+ add(:name, :string)
+ add(:ref, :string)
+ add(:director, :string, null: true)
+ add(:auth_node_id, references(:authorization_nodes), null: false)
+ timestamps()
+ end
+
+ create table(:alliance_tools) do
+ add(:url, :string)
+ add(:next_participant_id, :bigint, default: 0)
+ add(:director, :string, null: true)
+ add(:auth_node_id, references(:authorization_nodes), null: false)
+ timestamps()
+ end
+
+ create table(:alliance_tool_participants) do
+ add(:participant_id, :bigint)
+ add(:alliance_tool_id, references(:alliance_tools, on_delete: :delete_all))
+ add(:user_id, references(:users, on_delete: :delete_all))
+ timestamps()
+ end
+
+ execute("""
+ CREATE OR REPLACE FUNCTION set_alliance_tool_participants_participant_id()
+ RETURNS TRIGGER AS $$
+ BEGIN
+ UPDATE alliance_tools INTO NEW.participant_id
+ SET next_participant_id=next_participant_id+1 WHERE alliance_tools.id=NEW.alliance_tool_id RETURNING next_participant_id;
+
+ RETURN NEW;
+ END;
+ $$ LANGUAGE plpgsql;
+ """)
+
+ execute("""
+ CREATE TRIGGER alliance_tool_participants_participant_id
+ BEFORE INSERT ON alliance_tool_participants
+ FOR EACH ROW EXECUTE FUNCTION set_alliance_tool_participants_participant_id();
+ """)
+
+ create(unique_index(:alliance_tool_participants, [:alliance_tool_id, :user_id]))
+
+ create table(:workflows) do
+ add(:type, :string)
+ timestamps()
+ end
+
+ create table(:workflow_items) do
+ add(:group, :string)
+ add(:position, :integer)
+ add(:title, :string)
+ add(:description, :string)
+
+ add(:workflow_id, references(:workflows, on_delete: :nothing), null: false)
+ add(:tool_ref_id, references(:tool_refs, on_delete: :nothing), null: false)
+
+ timestamps()
+ end
+
+ drop(constraint(:tool_refs, :must_have_at_least_one_tool))
+
+ alter table(:tool_refs) do
+ remove(:data_donation_tool_id)
+ remove(:questionnaire_tool_id)
+
+ add(:special, :string)
+
+ add(:feldspar_tool_id, references(:feldspar_tools, on_delete: :delete_all), null: true)
+ add(:document_tool_id, references(:document_tools, on_delete: :delete_all), null: true)
+ add(:alliance_tool_id, references(:alliance_tools, on_delete: :delete_all), null: true)
+ end
+
+ create(
+ constraint(:tool_refs, :must_have_at_least_one_tool,
+ check: """
+ alliance_tool_id != null or
+ feldspar_tool_id != null or
+ document_tool_id != null or
+ lab_tool_id != null or
+ benchmark_tool_id != null
+ """
+ )
+ )
+
+ create table(:assignment_info) do
+ add(:subject_count, :integer)
+ add(:duration, :string)
+ add(:language, :string)
+ add(:devices, {:array, :string})
+ add(:ethical_approval, :boolean)
+ add(:ethical_code, :string)
+
+ timestamps()
+ end
+
+ drop(constraint(:assignments, "assignments_crew_id_fkey"))
+ drop(constraint(:assignments, "assignments_budget_id_fkey"))
+
+ alter table(:assignments) do
+ modify(:budget_id, references(:budgets, on_delete: :nothing))
+ modify(:crew_id, references(:crews, on_delete: :nothing))
+ add(:info_id, references(:assignment_info, on_delete: :nothing), null: true)
+ add(:workflow_id, references(:workflows, on_delete: :nothing), null: true)
+ add(:special, :string)
+ add(:status, :string)
+
+ remove(:assignable_survey_tool_id)
+ remove(:assignable_lab_tool_id)
+ remove(:assignable_data_donation_tool_id)
+ remove(:assignable_experiment_id)
+ end
+
+ drop(constraint(:project_items, :project_items_tool_ref_id_fkey))
+
+ alter table(:project_items) do
+ add(:assignment_id, references(:assignments, on_delete: :delete_all), null: true)
+ modify(:tool_ref_id, references(:tool_refs, on_delete: :delete_all), null: true)
+ end
+
+ create(
+ constraint(:project_items, :must_have_at_least_one_reference,
+ check: """
+ tool_ref_id != null or
+ assignment_id != null
+ """
+ )
+ )
+
+ # drop table(:data_donation_user_data)
+ # drop table(:data_donation_participants)
+ # drop table(:data_donation_tasks)
+ # drop table(:data_donation_questionnaire_tasks)
+ # drop table(:data_donation_document_tasks)
+ # drop table(:data_donation_donation_tasks)
+ # drop table(:experiments)
+ # drop table(:survey_tools)
+ # drop table(:survey_tool_tasks)
+ # drop table(:survey_tool_participants)
+
+ # execute(
+ # """
+ # DROP FUNCTION public.set_survey_tool_participants_participant_id();
+ # DROP FUNCTION public.set_survey_tool_current_subject_count();
+ # """
+ # )
+ end
+
+ def down do
+ drop(constraint(:project_items, :must_have_at_least_one_reference))
+ drop(constraint(:project_items, :project_items_tool_ref_id_fkey))
+
+ alter table(:project_items) do
+ remove(:assignment_id)
+ modify(:tool_ref_id, references(:tool_refs, on_delete: :delete_all), null: true)
+ end
+
+ drop(constraint(:assignments, "assignments_crew_id_fkey"))
+ drop(constraint(:assignments, "assignments_budget_id_fkey"))
+
+ alter table(:assignments) do
+ modify(:budget_id, references(:budgets, on_delete: :delete_all))
+ modify(:crew_id, references(:crews, on_delete: :delete_all))
+
+ remove(:status)
+ remove(:info_id)
+ remove(:workflow_id)
+ remove(:special)
+
+ add(:assignable_survey_tool_id, references(:questionnaire_tools, on_delete: :delete_all),
+ null: true
+ )
+
+ add(:assignable_lab_tool_id, references(:lab_tools, on_delete: :delete_all), null: true)
+
+ add(
+ :assignable_data_donation_tool_id,
+ references(:data_donation_tools, on_delete: :delete_all),
+ null: true
+ )
+
+ add(:assignable_experiment_id, references(:experiments, on_delete: :delete_all), null: true)
+ end
+
+ drop(table(:assignment_info))
+
+ drop(constraint(:tool_refs, :must_have_at_least_one_tool))
+
+ alter table(:tool_refs) do
+ remove(:special)
+
+ add(:data_donation_tool_id, references(:data_donation_tools, on_delete: :delete_all),
+ null: true
+ )
+
+ add(:questionnaire_tool_id, references(:questionnaire_tools, on_delete: :delete_all),
+ null: true
+ )
+
+ remove(:feldspar_tool_id)
+ remove(:document_tool_id)
+ remove(:alliance_tool_id)
+ end
+
+ create(
+ constraint(:tool_refs, :must_have_at_least_one_tool,
+ check: """
+ questionnaire_tool_id != null or
+ lab_tool_id != null or
+ data_donation_tool_id != null or
+ benchmark_tool_id != null
+ """
+ )
+ )
+
+ drop(table(:alliance_tool_participants))
+
+ drop(table(:workflow_items))
+ drop(table(:workflows))
+
+ execute("""
+ DROP FUNCTION public.set_alliance_tool_participants_participant_id();
+ """)
+
+ drop(table(:alliance_tools))
+ drop(table(:document_tools))
+ drop(table(:feldspar_tools))
+
+ alter table(:users) do
+ remove(:type)
+ remove(:external_id)
+ end
+
+ drop(index(:crew_tasks, :identifier))
+
+ alter table(:crew_tasks) do
+ remove(:identifier)
+ remove(:auth_node_id)
+ add(:member_id, references(:crew_members, on_delete: :delete_all), null: true)
+ end
+
+ drop(constraint(:campaigns, "campaigns_promotion_id_fkey"))
+ drop(constraint(:campaigns, "campaigns_promotable_assignment_id_fkey"))
+
+ alter table(:campaigns) do
+ modify(:promotion_id, references(:promotions, on_delete: :delete_all))
+ modify(:promotable_assignment_id, references(:assignments, on_delete: :delete_all))
+ end
+
+ drop(constraint(:authorization_nodes, "authorization_nodes_parent_id_fkey"))
+
+ alter table(:authorization_nodes) do
+ modify(:parent_id, references(:authorization_nodes, on_delete: :delete_all))
+ end
+ end
+end
diff --git a/core/priv/repo/script.py b/core/priv/repo/script.py
deleted file mode 100644
index d1f1c4b9d..000000000
--- a/core/priv/repo/script.py
+++ /dev/null
@@ -1,156 +0,0 @@
-"""Script to extract data from Google Semantic History Location zipfile"""
-__version__ = "0.1.0"
-
-import json
-import re
-import zipfile
-from datetime import datetime, timedelta
-
-import numpy as np
-import pandas as pd
-
-file_re = re.compile(r"^(?:.*\/)?\d+_[A-Z]+.json$")
-
-
-def parse_datetime(string):
- return datetime.fromisoformat(string.split(".")[0].strip("Z"))
-
-
-def parse_unix(string):
- return datetime.fromtimestamp(int(string) / 1_000)
-
-
-def parse_duration(duration):
- try:
- return (
- parse_datetime(duration["startTimestamp"]),
- parse_datetime(duration["endTimestamp"]),
- )
- except KeyError:
- return (
- parse_unix(duration["startTimestampMs"]),
- parse_unix(duration["endTimestampMs"]),
- )
-
-
-def parse_activity_segment(segment):
- distance = segment.get("distance")
- # Ignore activities without distance
- if distance is None:
- return
- return (
- segment.get("activityType") or "NO_ACTIVITY_TYPE_DETECTED",
- distance,
- ) + parse_duration(segment["duration"])
-
-
-def parse_timeline_objects(data):
- for item in data["timelineObjects"]:
- # Ignore placeVisits and other types
- segment = item.get("activitySegment")
- if segment:
- parsed = parse_activity_segment(segment)
- # Skip unparsable activities
- if parsed is not None:
- yield parsed
-
-
-def parse_records(log_error, f):
- try:
- data = json.load(f)
- except json.JSONDecodeError:
- log_error(f"Could not parse: {f.name}")
- else:
- yield from parse_timeline_objects(data)
-
-
-def parse_zipfile(log_error, zfile):
- for name in zfile.namelist():
- if not file_re.match(name):
- continue
-
- yield from parse_records(log_error, zfile.open(name))
-
-
-def add_duration(df):
- df["duration"] = round(
- (df["end_timestamp"] - df["start_timestamp"]) / timedelta(hours=1), 2
- )
-
-
-def sum_totals(df):
- return pd.Series(
- {
- "Duration (hours)": df["duration"].sum(),
- "Distance (km)": round(df["distance"].sum() / 1000, 2),
- }
- )
-
-
-def format_results(df):
- results = []
- for activity, activity_df in df.groupby("activity_type"):
- activity_df.index = pd.MultiIndex.from_arrays(
- [
- activity_df.index.map(lambda item: item[0].year),
- activity_df.index.map(lambda item: item[0].month),
- ],
- names=["Year", "Month"],
- )
- results.append(
- {
- "id": activity,
- "title": activity.title().replace("_", " "),
- "data_frame": activity_df,
- }
- )
- return results
-
-
-def format_errors(errors):
- data_frame = pd.DataFrame()
- data_frame["Messages"] = pd.Series(errors, name="Messages")
- return {"id": "extraction_log", "title": "Extraction log", "data_frame": data_frame}
-
-
-def process(file_data):
- """Return relevant data from zipfile for years and months"""
-
- errors = []
- log_error = errors.append
-
- with zipfile.ZipFile(file_data) as zfile:
- records = parse_zipfile(log_error, zfile)
- df = pd.DataFrame.from_records(
- records,
- columns=["activity_type", "distance", "start_timestamp", "end_timestamp"],
- )
-
- df = df[
- (df["start_timestamp"] >= np.datetime64("2016-01-01"))
- & (df["start_timestamp"] < np.datetime64("2022-01-01"))
- ]
-
- add_duration(df)
-
- df.sort_values(
- ["activity_type", "start_timestamp"], ignore_index=True, ascending=True
- )
-
- if df.empty:
- return []
-
- # Create the totals we are after
- df = df.groupby(
- [pd.Grouper(key="start_timestamp", freq="1M"), "activity_type"],
- ).apply(sum_totals)
-
- # Filter out months without data
- df = df[(df["Duration (hours)"] > 0) & (df["Distance (km)"] > 0)]
-
- formatted_results = format_results(df)
- # Rename to nice names
-
- if errors:
- return formatted_results + [format_errors(errors)]
- return formatted_results
diff --git a/core/priv/repo/seeds.exs b/core/priv/repo/seeds.exs
index 6afff9d93..2d2c2c0bd 100644
--- a/core/priv/repo/seeds.exs
+++ b/core/priv/repo/seeds.exs
@@ -12,7 +12,7 @@
#
#
-_survey_url = "https://vuamsterdam.eu.qualtrics.com/jfe/form/SV_4Po8iTxbvcxtuaW"
+_alliance_url = "https://vuamsterdam.eu.qualtrics.com/jfe/form/SV_4Po8iTxbvcxtuaW"
images = [
"raw_url=https%3A%2F%2Fimages.unsplash.com%2Fphoto-1498462440456-0dba182e775b%3Fixid%3DMnwyMTY0MzZ8MHwxfHNlYXJjaHw5fHx3YXRlcnxlbnwwfHx8fDE2MjE3NzY0MjA%26ixlib%3Drb-1.2.1&username=samaradoole&name=Samara+Doole&blur_hash=LtI~0%3Ft7aeof~qofazayt6f6j%5Bf6",
@@ -24,7 +24,7 @@ images = [
"raw_url=https%3A%2F%2Fimages.unsplash.com%2Fphoto-1515378791036-0648a3ef77b2%3Fixid%3DMnwyMTY0MzZ8MHwxfHNlYXJjaHw4fHx3b3JrfGVufDB8fHx8MTYyMTc3NjgwOQ%26ixlib%3Drb-1.2.1&username=christinhumephoto&name=Christin+Hume&blur_hash=LMF%3B%3Dw0LAJR%25~A9uT0nNRjxaW%3DIo"
]
-data_donation_promotions =
+_data_donation_promotions =
Enum.map(images, fn image ->
%{
title: Faker.Lorem.sentence(),
@@ -52,15 +52,15 @@ data_donation_promotions =
}
end)
-Enum.each(data_donation_promotions, fn promotion ->
- data = %{
- script: File.read!(Path.join([:code.priv_dir(:core), "repo", "script.py"])),
- subject_count: 400
- # promotion: promotion
- }
+# Enum.each(data_donation_promotions, fn promotion ->
+# data = %{
+# script: File.read!(Path.join([:code.priv_dir(:core), "repo", "script.py"])),
+# subject_count: 400
+# # promotion: promotion
+# }
- Core.Factories.insert!(:data_donation_tool, data)
-end)
+# Core.Factories.insert!(:data_donation_tool, data)
+# end)
# campaigns =
# Enum.map(data_donation_tools, fn data_donation_tool ->
diff --git a/core/priv/repo/whatsapp_account.py b/core/priv/repo/whatsapp_account.py
deleted file mode 100644
index b16a8c483..000000000
--- a/core/priv/repo/whatsapp_account.py
+++ /dev/null
@@ -1,294 +0,0 @@
-# pylint: disable=R0801
-"""Parse account_info"""
-__version__ = '0.2.0'
-
-import zipfile
-import re
-import json
-import pandas as pd
-from zipfile import BadZipFile
-
-
-HIDDEN_FILE_RE = re.compile(r".*__MACOSX*")
-FILE_RE = re.compile(r".*.json$")
-
-
-class ColnamesDf: # pylint: disable=R0903
- """Class to define column names"""
- GROUPS = 'wa_groups'
- """Groups column"""
-
- CONTACTS = 'wa_contacts'
- """Contacts column"""
-
- GROUPS_OLD = 'groups'
- """Groups column"""
-
- CONTACTS_OLD = 'contacts'
- """Contacts column"""
-
- GROUPS_OUTPUT = 'Aantal groepen'
- """Groups column"""
-
- CONTACTS_OUTPUT = 'Aantal contacten'
- """Contacts column"""
-
-
-COLNAMES_DF = ColnamesDf()
-
-
-class DutchConst: # pylint: disable=R0903
- """Access class constants using variable ``DUTCH_CONST``."""
-
- LOG_TITLE = 'Wij ontvingen de volgende waarschuwing: '
- OUTPUT_TITLE = "Het account informatie bestand bestaat uit:"
- DESCRIPTION = "Omschrijving"
-
-
-DUTCH_CONST = DutchConst()
-
-
-def format_results(dataframe, error):
- """Format results to the standard format.
- Parameters
- ----------
- dataframe: pandas.dataframe
- error : list
- Returns
- -------
- pandas.dataframe
- """
-
- results = []
- if not isinstance(dataframe, pd.DataFrame):
- dataframe = pd.DataFrame(["We konden geen account informatie van u vinden"], columns=[DUTCH_CONST.DESCRIPTION])
-
- results.append(
- {
- "id": "WhatsApp account info",
- "title": DUTCH_CONST.OUTPUT_TITLE,
- "data_frame": dataframe
- }
- )
- if len(error) > 0:
- results = results+error
- return {"cmd": "result", "result": results}
-
-
-def format_errors(errors):
- """Return errors in the format of dataframe.
- Parameters
- ----------
- errors: list
- Returns
- -------
- pandas.dataframe
- """
- if len(errors) == 0:
- return []
- data_frame = pd.DataFrame()
- data_frame[DUTCH_CONST.DESCRIPTION] = pd.Series(errors, name=DUTCH_CONST.DESCRIPTION)
- return [{"id": "extraction_log", "title": "", "data_frame": data_frame}]
-
-
-def extract_groups(log_error, data):
- """Function for extracting the number of groups"""
-
- groups_no = 0
- try:
- groups_no = len(data[COLNAMES_DF.GROUPS])
- except (TypeError, KeyError):
- print("No group is available")
-
- if groups_no == 0:
- log_error("No group is available")
-
- return groups_no
-
-
-def extract_contacts(log_error, data):
- """Function for extracting the number of contacts"""
-
- contacts_no = 0
-
- try:
- contacts_no = len(data[COLNAMES_DF.CONTACTS])
- except (TypeError, KeyError):
- print("No contact is available")
-
- if contacts_no == 0:
- log_error("No contact is available")
- return contacts_no
-
-
-def extract_data(log_error, data):
- """Function to extract group_no and contact_no fields - Support for the old format"""
- groups_no = 0
- contacts_no = 0
- try:
- groups_no = len(data[COLNAMES_DF.GROUPS_OLD])
- except (TypeError, KeyError):
- print("No group is available")
- try:
- contacts_no = len(data[COLNAMES_DF.CONTACTS_OLD])
- except (TypeError, KeyError):
- print("No contact is available")
-
- if (groups_no == 0) and (contacts_no == 0):
- log_error("Neither group nor contact is available")
- return groups_no, contacts_no
-
-
-def parse_records(log_error, file): # pylint: disable=R1710
- """Function for loading json files content"""
- try:
- data = json.load(file)
- except json.JSONDecodeError:
- log_error(f"Could not parse: {file.name}")
- else:
- return data
-
-
-def parse_zipfile(log_error, zfile):
- """Function for extracting input zipfile"""
- data_groups = None
- data_contacts = None
-
- for name in zfile.namelist():
- if name == 'whatsapp_connections/groups.json':
- if HIDDEN_FILE_RE.match(name):
- continue
- if not FILE_RE.match(name):
- continue
- data_groups = parse_records(log_error, zfile.open(name))
- elif name == 'whatsapp_connections/contacts.json':
- if HIDDEN_FILE_RE.match(name):
- continue
- if not FILE_RE.match(name):
- continue
- data_contacts = parse_records(log_error, zfile.open(name))
- # log_error("No Json file is available")
- return data_groups, data_contacts
-
-
-def parse_zipfile_old_format(log_error, zfile):
- """Function for extracting input zipfile"""
- for name in zfile.namelist():
- if HIDDEN_FILE_RE.match(name):
- continue
- if not FILE_RE.match(name):
- continue
- return parse_records(log_error, zfile.open(name))
- log_error("No Json file is available")
-
-
-def prompt_file():
- """Promt a file selection window in Eyra system
- Parameters
- ----------
-
- Returns
- -------
- Dictionary
- a prompt event - file type
- """
- return {
- "cmd": "prompt",
- "prompt": {
- "type": "file",
- "file": {
- "title": {
- "en": "Select the account information file",
- "nl": "Kies het account informatiebestand"
- },
- "description": {
- "en": "We previously asked you to export an account information file "
- "from WhatsApp and save it on your phone. Please select this "
- "file so we can extract relevant information for our research.",
- "nl": "We hebben u gevraagd een account informatiebestand te exporteren "
- "uit WhatsApp en op uw telefoon op te slaan. U kunt dit bestand nu "
- "kiezen zodat wij er relevante informatie uit kunnen halen voor "
- "ons onderzoek."
- },
- "extensions": "application/zip",
- }
- }
- }
-
-
-def prompt_radio(usernames):
- """Promt a list of items(usernames here) in Eyra system
- This function shows a list of radio-buttons
- Parameters
- ----------
- usernames: pandas.Series
- Extracted usernames from the chat file
- Returns
- -------
- Dictionary
- a prompt event - radio type
- """
- return {
- "cmd": "prompt",
- "prompt": {
- "type": "radio",
- "radio": {
- "title": {
- "en": "Step 2: Select your username",
- "nl": "Stap 2: Selecteer je gebruikersnaam"
- },
- "description": {
- "en": "Please indicate which username is yours. Note that "
- "names and phone numbers are not stored, but only used "
- "to extract relevant information from the chat file.",
- "nl": "Geef hieronder aan welke gebruikersnaam van u is. "
- "Deze data wordt niet opgeslagen, maar alleen gebruikt om de juiste "
- "informatie uit uw data te kunnen halen."
-
- },
- "items": usernames,
- }
- }
- }
-
-
-def process():
- """Main function for extracting account information"""
- errors = []
- log_error = errors.append
-
- file_data = yield prompt_file()
- try:
- zfile = zipfile.ZipFile(file_data) # pylint: disable=R1732
- except BadZipFile:
- log_error("We could not read the zipfile")
- yield format_results([], format_errors(errors))
-
- try:
- data_groups, data_contacts = parse_zipfile(log_error, zfile)
-
- if data_groups is None and data_contacts is None:
- log_error("We could not extract your account information")
- yield format_results([], format_errors(errors))
-
- if data_groups is not None:
- groups_no = extract_groups(log_error, data_groups)
-
- if data_contacts is not None:
- contacts_no = extract_contacts(log_error, data_contacts)
-
- if errors:
- yield format_results([], format_errors(errors))
-
- # Support old format of the account_info data package
- except UnboundLocalError:
- data = parse_zipfile_old_format(log_error, zfile)
- if data is not None:
- groups_no, contacts_no = extract_data(log_error, data)
-
- if errors:
- yield format_results([], format_errors(errors))
-
- data_info = {COLNAMES_DF.GROUPS_OUTPUT: [groups_no], COLNAMES_DF.CONTACTS_OUTPUT: [contacts_no]}
- results = pd.DataFrame(data=data_info)
- yield format_results(results, format_errors(errors))
diff --git a/core/priv/repo/whatsapp_chat.py b/core/priv/repo/whatsapp_chat.py
deleted file mode 100644
index 2074503cf..000000000
--- a/core/priv/repo/whatsapp_chat.py
+++ /dev/null
@@ -1,1001 +0,0 @@
-"""Parser utils.
-The main part is extracted from https://github.com/lucasrodes/whatstk.git
-"""
-__version__ = '0.2.0'
-
-import os
-import re
-from datetime import datetime
-import zipfile
-from zipfile import BadZipFile
-import pandas as pd
-import numpy as np
-
-
-URL_PATTERN = r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)" \
- r"(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|" \
- r"(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))"
-LOCATION_PATTERN = r'((L|l)ocation: https?://\S+)|((l|L)ocatie: https?://\S+)|' \
- r'(.*(l|L)ive locatie gedeeld.*)|(.*(l|L)ive location shared.*)'
-
-
-ATTACH_FILE_PATTERN = r'(
<.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself}>
- <%= dgettext("link-survey", "form.title") %>
- <%= dgettext("link-survey", "form.description") %>
- <.spacing value="M" />
-
- <%= dgettext("link-survey", "setup.title") %>
+ <%= dgettext("eyra-alliance", "setup.title") %>
<.spacing value="M" />
@@ -159,9 +158,9 @@ defmodule Systems.Survey.ToolForm do
<.step_indicator text="2" bg_color="bg-tertiary" text_color="text-grey1" />
@@ -148,9 +147,9 @@ defmodule Systems.Survey.ToolForm do
<.step_indicator text="1" bg_color="bg-tertiary" text_color="text-grey1" />
- <%= dgettext("link-survey", "panlid.title") %>
+ <%= dgettext("eyra-alliance", "panlid.title") %>
<.spacing value="XS" />
- <%= raw(dgettext("link-survey", "panlid.description", link: panlid_instructions_link())) %>
+ <%= raw(dgettext("eyra-alliance", "panlid.description", link: panlid_instructions_link())) %>
- <%= dgettext("link-survey", "redirect.title") %>
+ <%= dgettext("eyra-alliance", "redirect.title") %>
<.spacing value="XS" />
- <%= raw(dgettext("link-survey", "redirect.description", link: redirect_instructions_link())) %>
+ <%= raw(dgettext("eyra-alliance", "redirect.description", link: redirect_instructions_link())) %>
<.spacing value="XS" />
- <%= dgettext("link-survey", "test.roundtrip.title") %>
- <.spacing value="M" />
- <%= dgettext("link-survey", "test.roundtrip.text") %>
- <.spacing value="M" />
- <.wrap>
-
-
-
+ <.url_input form={form} field={:url} label_text={dgettext("eyra-alliance", "config.url.label")} />
<.spacing value="XL" />
diff --git a/core/systems/survey/tool_model.ex b/core/systems/alliance/tool_model.ex
similarity index 64%
rename from core/systems/survey/tool_model.ex
rename to core/systems/alliance/tool_model.ex
index fb871f5c1..a20234c9e 100644
--- a/core/systems/survey/tool_model.ex
+++ b/core/systems/alliance/tool_model.ex
@@ -1,31 +1,35 @@
-defmodule Systems.Survey.ToolModel do
+defmodule Systems.Alliance.ToolModel do
@moduledoc """
- The survey tool schema.
+ The alliance tool schema.
"""
use Ecto.Schema
use Frameworks.Utility.Model
use Frameworks.Utility.Schema
import NimbleParsec
+ import Ecto.Changeset
+ import CoreWeb.Gettext
require Core.Enums.Devices
- import Ecto.Changeset
-
- schema "survey_tools" do
+ schema "alliance_tools" do
belongs_to(:auth_node, Core.Authorization.Node)
- field(:survey_url, :string)
- field(:director, Ecto.Enum, values: [:campaign])
+ field(:url, :string)
+ field(:director, Ecto.Enum, values: [:assignment])
timestamps()
end
defimpl Frameworks.GreenLight.AuthorizationNode do
- def id(survey_tool), do: survey_tool.auth_node_id
+ def id(tool), do: tool.auth_node_id
+ end
+
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Concept.System.director(director)
end
- @operational_fields ~w(survey_url)a
+ @operational_fields ~w(url)a
@fields @operational_fields
@required_fields ~w()a
@@ -35,7 +39,7 @@ defmodule Systems.Survey.ToolModel do
@impl true
def operational_validation(changeset), do: changeset
- def preload_graph(:full),
+ def preload_graph(:down),
do:
preload_graph([
:auth_node
@@ -47,7 +51,7 @@ defmodule Systems.Survey.ToolModel do
tool
|> cast(params, @fields)
|> validate_required(@required_fields)
- |> validate_url(:survey_url)
+ |> validate_url(:url)
end
def changeset(tool, _, params) do
@@ -56,14 +60,32 @@ defmodule Systems.Survey.ToolModel do
|> cast(params, @fields)
end
+ def changeset(tool, params) do
+ tool
+ |> cast(params, @fields)
+ end
+
+ def validate(changeset) do
+ changeset
+ |> validate_required(@operational_fields)
+ end
+
def validate(changeset, :roundtrip) do
changeset =
changeset
- |> Ecto.Changeset.validate_required([:survey_url])
+ |> Ecto.Changeset.validate_required([:url])
%{changeset | action: :validate_roundtrip}
end
+ def ready?(tool) do
+ changeset =
+ changeset(tool, %{})
+ |> validate()
+
+ changeset.valid?()
+ end
+
variable =
string("<")
|> ignore()
@@ -126,8 +148,9 @@ defmodule Systems.Survey.ToolModel do
|> to_string()
end
- def external_path(%{survey_url: survey_url}, panl_id) when not is_nil(survey_url) do
- url_components = URI.parse(survey_url)
+ def external_path(%{url: url}, panl_id)
+ when not is_nil(url) do
+ url_components = URI.parse(url)
query =
url_components.query
@@ -144,4 +167,25 @@ defmodule Systems.Survey.ToolModel do
defp decode_query(nil), do: %{}
defp decode_query(query), do: URI.decode_query(query)
+
+ defimpl Frameworks.Concept.ToolModel do
+ alias Systems.Alliance
+ def key(_), do: :alliance
+ def auth_tree(%{auth_node: auth_node}), do: auth_node
+ def apply_label(_), do: dgettext("eyra-alliance", "apply.cta.title")
+ def open_label(_), do: dgettext("eyra-alliance", "open.cta.title")
+ def ready?(tool), do: Alliance.ToolModel.ready?(tool)
+ def form(_), do: Alliance.ToolForm
+ def launcher(%{url: url}), do: %{url: url}
+
+ def task_labels(_) do
+ %{
+ pending: dgettext("eyra-alliance", "pending.label"),
+ participated: dgettext("eyra-alliance", "participated.label")
+ }
+ end
+
+ def attention_list_enabled?(_t), do: true
+ def group_enabled?(_t), do: false
+ end
end
diff --git a/core/systems/alliance/work_view.ex b/core/systems/alliance/work_view.ex
new file mode 100644
index 000000000..9fd103d6f
--- /dev/null
+++ b/core/systems/alliance/work_view.ex
@@ -0,0 +1,15 @@
+defmodule Systems.Alliance.WorkView do
+ use CoreWeb, :html
+
+ attr(:url, :string, required: true)
+
+ def work_view(assigns) do
+ ~H"""
+
@@ -170,7 +169,7 @@ defmodule Systems.Survey.ToolForm do
@@ -185,9 +184,9 @@ defmodule Systems.Survey.ToolForm do
<.step_indicator text="3" bg_color="bg-tertiary" text_color="text-grey1" />
@@ -195,25 +194,7 @@ defmodule Systems.Survey.ToolForm do
<.spacing value="L" />
- <.url_input form={form} field={:survey_url} label_text={dgettext("link-survey", "config.url.label")} />
- <.spacing value="S" />
-
- <%= dgettext("link-survey", "study.link.title") %>
+ <%= dgettext("eyra-alliance", "study.link.title") %>
<.spacing value="XS" />
- <%= raw(dgettext("link-survey", "study.link.description", link: study_instructions_link())) %>
+ <%= raw(dgettext("eyra-alliance", "study.link.description", link: study_instructions_link())) %>
+
+ """
+ end
+end
diff --git a/core/systems/assignment/_assembly.ex b/core/systems/assignment/_assembly.ex
new file mode 100644
index 000000000..a481f38ff
--- /dev/null
+++ b/core/systems/assignment/_assembly.ex
@@ -0,0 +1,76 @@
+defmodule Systems.Assignment.Assembly do
+ alias Core.Repo
+ alias Core.Authorization
+
+ alias Systems.{
+ Project,
+ Assignment,
+ Crew,
+ Alliance,
+ Lab
+ }
+
+ def create(template, director, budget \\ nil, auth_node \\ Authorization.prepare_node()) do
+ prepare(template, director, budget, auth_node)
+ |> Repo.insert()
+ end
+
+ def prepare(template, director, budget \\ nil, auth_node \\ Authorization.prepare_node()) do
+ crew_auth_node = Authorization.prepare_node(auth_node)
+ crew = Crew.Public.prepare(crew_auth_node)
+
+ info = Assignment.Public.prepare_info(info_attrs(template, director))
+
+ workflow = prepare_workflow(template, auth_node)
+
+ Assignment.Public.prepare(
+ %{special: template},
+ crew,
+ info,
+ workflow,
+ budget,
+ auth_node
+ )
+ end
+
+ defp prepare_workflow(:data_donation, _) do
+ Assignment.Public.prepare_workflow(:data_donation, [])
+ end
+
+ defp prepare_workflow(:online = template, %Authorization.Node{} = auth_node) do
+ tool_auth_node = Authorization.create_node!(auth_node)
+ tool = Alliance.Public.prepare_tool(%{director: :assignment}, tool_auth_node)
+ prepare_workflow(template, tool)
+ end
+
+ defp prepare_workflow(:lab = template, %Authorization.Node{} = auth_node) do
+ tool_auth_node = Authorization.create_node!(auth_node)
+ tool = Lab.Public.prepare_tool(%{director: :assignment}, tool_auth_node)
+ prepare_workflow(template, tool)
+ end
+
+ defp prepare_workflow(template, %{} = tool) do
+ tool_ref = prepare_tool_ref(template, tool)
+ item = Assignment.Public.prepare_workflow_item(tool_ref)
+ Assignment.Public.prepare_workflow(template, [item])
+ end
+
+ defp prepare_tool_ref(special, tool) do
+ field_name = Project.ToolRefModel.tool_field(tool)
+ Project.Public.prepare_tool_ref(special, field_name, tool)
+ end
+
+ defp info_attrs(:lab, director) do
+ %{
+ director: director,
+ devices: []
+ }
+ end
+
+ defp info_attrs(_, director) do
+ %{
+ director: director,
+ devices: [:phone, :tablet, :desktop]
+ }
+ end
+end
diff --git a/core/systems/assignment/_director.ex b/core/systems/assignment/_director.ex
new file mode 100644
index 000000000..6c05577ab
--- /dev/null
+++ b/core/systems/assignment/_director.ex
@@ -0,0 +1,43 @@
+defmodule Systems.Assignment.Director do
+ @behaviour Frameworks.Concept.ToolDirector
+
+ alias Frameworks.Concept.Directable
+
+ alias Systems.{
+ Assignment,
+ Project,
+ Workflow
+ }
+
+ @impl true
+ def apply_member_and_activate_task(tool, user) do
+ identifier = Assignment.Private.task_identifier(tool, user)
+ assignment = Assignment.Public.get_by_tool(tool, Assignment.Model.preload_graph(:down))
+ reward_value = Directable.director(assignment).reward_value(assignment)
+ Assignment.Public.apply_member_and_activate_task(assignment, user, identifier, reward_value)
+ end
+
+ @impl true
+ def search_subject(tool, user_ref) do
+ Assignment.Public.search_subject(tool, user_ref)
+ end
+
+ @impl true
+ def assign_tester_role(tool, user) do
+ Assignment.Public.assign_tester_role(tool, user)
+ end
+
+ @impl true
+ def authorization_context(tool, user) do
+ {member, _} = search_subject(tool, user)
+ tool_ref = Project.Public.get_tool_ref_by_tool(tool)
+ item = Workflow.Public.get_item_by_tool_ref(tool_ref)
+ assignment = Assignment.Public.get_by_tool(tool)
+
+ task =
+ Assignment.Private.task_identifier(assignment, item, member)
+ |> then(&Assignment.Public.get_task(tool, &1))
+
+ task
+ end
+end
diff --git a/core/systems/assignment/_presenter.ex b/core/systems/assignment/_presenter.ex
new file mode 100644
index 000000000..777558830
--- /dev/null
+++ b/core/systems/assignment/_presenter.ex
@@ -0,0 +1,33 @@
+defmodule Systems.Assignment.Presenter do
+ @behaviour Frameworks.Concept.Presenter
+
+ alias Frameworks.Utility.Module
+
+ alias Systems.{
+ Assignment,
+ Alliance
+ }
+
+ @impl true
+ def view_model(%Alliance.ToolModel{} = tool, Alliance.CallbackPage = page, assigns) do
+ builder(page).view_model(tool, assigns)
+ end
+
+ @impl true
+ def view_model(
+ %Assignment.Model{director: director} = assignment,
+ Assignment.LandingPage = page,
+ assigns
+ ) do
+ Module.get(director, "Presenter").view_model(assignment, page, assigns)
+ end
+
+ @impl true
+ def view_model(%Assignment.Model{} = assignment, page, assigns) do
+ builder(page).view_model(assignment, assigns)
+ end
+
+ def builder(Assignment.CrewPage), do: Assignment.CrewPageBuilder
+ def builder(Assignment.ContentPage), do: Assignment.ContentPageBuilder
+ def builder(Alliance.CallbackPage), do: Assignment.AllianceCallbackPageBuilder
+end
diff --git a/core/systems/assignment/_private.ex b/core/systems/assignment/_private.ex
new file mode 100644
index 000000000..4e1f87f9e
--- /dev/null
+++ b/core/systems/assignment/_private.ex
@@ -0,0 +1,33 @@
+defmodule Systems.Assignment.Private do
+ require Logger
+
+ alias Systems.{
+ Workflow,
+ Crew
+ }
+
+ def task_template(%{special: :data_donation}, %Workflow.ItemModel{id: item_id}) do
+ ["item=#{item_id}"]
+ end
+
+ def task_identifier(
+ %{special: :data_donation},
+ %Workflow.ItemModel{id: item_id},
+ %Crew.MemberModel{id: member_id}
+ ) do
+ ["item=#{item_id}", "member=#{member_id}"]
+ end
+
+ # Depricated
+ def task_identifier(tool, user) do
+ Logger.warn(
+ "`Systems.Assignment.Private.task_identifier/2` is deprecated; call `task_identifier/3` instead."
+ )
+
+ [
+ Atom.to_string(Frameworks.Concept.ToolModel.key(tool)),
+ Integer.to_string(tool.id),
+ Integer.to_string(user.id)
+ ]
+ end
+end
diff --git a/core/systems/assignment/_public.ex b/core/systems/assignment/_public.ex
index 80e0ed9ea..9f239ca96 100644
--- a/core/systems/assignment/_public.ex
+++ b/core/systems/assignment/_public.ex
@@ -3,7 +3,6 @@ defmodule Systems.Assignment.Public do
The assignment context.
"""
import Ecto.Query, warn: false
- import CoreWeb.Gettext
require Logger
@@ -12,18 +11,14 @@ defmodule Systems.Assignment.Public do
alias CoreWeb.UI.Timestamp
alias Core.Authorization
alias Core.Accounts.User
-
- alias Frameworks.{
- Signal,
- Utility
- }
+ alias Frameworks.Concept
alias Systems.{
- Budget,
+ Project,
Assignment,
- Crew,
- Survey,
- Lab
+ Budget,
+ Workflow,
+ Crew
}
@min_expiration_timeout 30
@@ -33,54 +28,92 @@ defmodule Systems.Assignment.Public do
|> Repo.get!(id)
end
- def get_by_crew!(crew, preload \\ [])
+ def get_workflow!(id, preload \\ []) do
+ from(a in Workflow.Model, preload: ^preload)
+ |> Repo.get!(id)
+ end
- def get_by_crew!(%{id: crew_id}, preload), do: get_by_crew!(crew_id, preload)
+ def list_by_crew(crew, preload \\ [])
- def get_by_crew!(crew_id, preload) when is_number(crew_id) do
+ def list_by_crew(%{id: crew_id}, preload), do: list_by_crew(crew_id, preload)
+
+ def list_by_crew(crew_id, preload) when is_number(crew_id) do
from(a in Assignment.Model, where: a.crew_id == ^crew_id, preload: ^preload)
|> Repo.all()
end
- def get_by_assignable(assignable, preload \\ [])
+ def list_by_workflow(workflow, preload \\ [])
- def get_by_assignable(%Assignment.ExperimentModel{id: id}, preload) do
- from(a in Assignment.Model,
- where: a.assignable_experiment_id == ^id,
+ def list_by_workflow(%{id: workflow_id}, preload), do: list_by_workflow(workflow_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()
+ |> Repo.one!()
end
- def get_by_experiment!(experiment, preload \\ [])
+ def get_by_workflow(workflow, preload \\ [])
- def get_by_experiment!(%{id: experiment_id}, preload),
- do: get_by_experiment!(experiment_id, preload)
+ def get_by_workflow(%Workflow.Model{id: id}, preload), do: get_by_workflow(id, preload)
- def get_by_experiment!(experiment_id, preload) when is_number(experiment_id) do
- from(a in Assignment.Model,
- where: a.assignable_experiment_id == ^experiment_id,
+ def get_by_workflow(workflow_id, preload) do
+ from(assignment in Assignment.Model,
+ where: assignment.workflow_id == ^workflow_id,
preload: ^preload
)
|> Repo.one()
end
- def get_by_tool(%Survey.ToolModel{id: id}, preload) do
- query_by_tool(preload)
- |> where([assignment, experiment], experiment.survey_tool_id == ^id)
+ def get_by_tool_ref(workflow, preload \\ [])
+
+ def get_by_tool_ref(%Project.ToolRefModel{id: id}, preload), do: get_by_tool_ref(id, preload)
+
+ def get_by_tool_ref(tool_ref_id, preload) do
+ query_by_tool_ref(tool_ref_id, preload)
|> Repo.one()
end
- def get_by_tool(%Lab.ToolModel{id: id}, preload) do
- query_by_tool(preload)
- |> where([assignment, experiment], experiment.lab_tool_id == ^id)
+ def get_by_tool(tool, preload \\ [])
+
+ def get_by_tool(%{id: id} = tool, preload) do
+ field_name = Project.ToolRefModel.tool_id_field(tool)
+
+ query_by_tool(field_name, id, preload)
|> Repo.one()
end
- defp query_by_tool(preload) do
+ def query_by_tool(field_name, id, preload) do
from(assignment in Assignment.Model,
- join: experiment in Assignment.ExperimentModel,
- on: assignment.assignable_experiment_id == experiment.id,
+ join: workflow in Workflow.Model,
+ on: workflow.id == assignment.workflow_id,
+ join: workflow_item in Workflow.ItemModel,
+ on: workflow_item.workflow_id == workflow.id,
+ join: tool_ref in Project.ToolRefModel,
+ on: tool_ref.id == workflow_item.tool_ref_id,
+ where: field(tool_ref, ^field_name) == ^id,
+ preload: ^preload
+ )
+ end
+
+ def query_by_tool_ref(tool_ref_id, preload) do
+ from(assignment in Assignment.Model,
+ join: workflow in Workflow.Model,
+ on: workflow.id == assignment.workflow_id,
+ join: workflow_item in Workflow.ItemModel,
+ on: workflow_item.workflow_id == workflow.id,
+ join: tool_ref in Project.ToolRefModel,
+ on: tool_ref.id == ^tool_ref_id,
preload: ^preload
)
end
@@ -97,36 +130,65 @@ defmodule Systems.Assignment.Public do
|> Repo.all()
end
- def create(%{} = attrs, crew, experiment, budget, auth_node) do
- assignable_field = assignable_field(experiment)
-
+ def prepare(%{} = attrs, crew, info, workflow, budget, auth_node) do
%Assignment.Model{}
|> Assignment.Model.changeset(attrs)
+ |> Ecto.Changeset.put_assoc(:info, info)
+ |> Ecto.Changeset.put_assoc(:workflow, workflow)
|> Ecto.Changeset.put_assoc(:crew, crew)
- |> Ecto.Changeset.put_assoc(assignable_field, experiment)
|> Ecto.Changeset.put_assoc(:budget, budget)
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert()
+ end
+
+ def prepare_info(%{} = attrs) do
+ %Assignment.InfoModel{}
+ |> Assignment.InfoModel.changeset(:create, attrs)
+ end
+
+ def prepare_workflow(special, items, type \\ :single_task) do
+ %Workflow.Model{}
+ |> Workflow.Model.changeset(%{type: type, special: special})
+ |> Ecto.Changeset.put_assoc(:items, items)
+ end
+
+ def prepare_workflow_item(tool_ref) do
+ %Workflow.ItemModel{}
+ |> Workflow.ItemModel.changeset(%{})
+ |> Ecto.Changeset.put_assoc(:tool_ref, tool_ref)
end
def copy(
%Assignment.Model{} = assignment,
+ %Assignment.InfoModel{} = info,
+ %Workflow.Model{} = workflow,
%Budget.Model{} = budget,
- %Assignment.ExperimentModel{} = experiment,
auth_node
) do
# don't copy crew, just create a new one
- {:ok, crew} = Crew.Public.create(auth_node)
+ crew = Crew.Public.prepare(auth_node)
%Assignment.Model{}
|> Assignment.Model.changeset(Map.from_struct(assignment))
+ |> Ecto.Changeset.put_assoc(:info, info)
+ |> Ecto.Changeset.put_assoc(:workflow, workflow)
|> Ecto.Changeset.put_assoc(:budget, budget)
|> Ecto.Changeset.put_assoc(:crew, crew)
- |> Ecto.Changeset.put_assoc(:assignable_experiment, experiment)
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
|> Repo.insert!()
end
+ def copy_info(%Assignment.InfoModel{} = info) do
+ %Assignment.InfoModel{}
+ |> Assignment.InfoModel.changeset(:copy, Map.from_struct(info))
+ |> Repo.insert!()
+ end
+
+ def copy_workflow(%Workflow.Model{} = workflow) do
+ %Workflow.Model{}
+ |> Workflow.Model.changeset(Map.from_struct(workflow))
+ |> Repo.insert!()
+ end
+
def exclude(%Assignment.Model{} = assignment1, %Assignment.Model{} = assignment2) do
Multi.new()
|> Assignment.ExcludeModel.exclude(assignment1, assignment2)
@@ -147,78 +209,17 @@ defmodule Systems.Assignment.Public do
|> Repo.update!()
end
- def update_experiment(changeset) do
- Multi.new()
- |> Repo.multi_update(:experiment, changeset)
- |> Multi.run(:dispatch, fn _, %{experiment: experiment} ->
- Signal.Public.dispatch!(:experiment_updated, experiment)
- {:ok, true}
- end)
- |> Repo.transaction()
+ def is_owner?(assignment, user) do
+ Core.Authorization.user_has_role?(user, assignment, :owner)
end
- def create_experiment(%{} = attrs, tool, auth_node) do
- tool_field = Assignment.ExperimentModel.tool_field(tool)
-
- %Assignment.ExperimentModel{}
- |> Assignment.ExperimentModel.changeset(:create, attrs)
- |> Ecto.Changeset.put_assoc(tool_field, tool)
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert()
- end
-
- def copy_experiment(
- %Assignment.ExperimentModel{} = experiment,
- %Survey.ToolModel{} = tool,
- auth_node
- ) do
- %Assignment.ExperimentModel{}
- |> Assignment.ExperimentModel.changeset(:copy, Map.from_struct(experiment))
- |> Ecto.Changeset.put_assoc(:survey_tool, tool)
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert!()
- end
-
- def copy_experiment(
- %Assignment.ExperimentModel{} = experiment,
- %Lab.ToolModel{} = tool,
- auth_node
- ) do
- %Assignment.ExperimentModel{}
- |> Assignment.ExperimentModel.changeset(:copy, Map.from_struct(experiment))
- |> Ecto.Changeset.put_assoc(:lab_tool, tool)
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert!()
- end
-
- def copy_tool(
- %Assignment.ExperimentModel{survey_tool: %{auth_node: tool_auth_node} = tool},
- experiment_auth_node
- ) do
- tool_auth_node = Authorization.copy(tool_auth_node, experiment_auth_node)
- Survey.Public.copy(tool, tool_auth_node)
- end
-
- def copy_tool(
- %Assignment.ExperimentModel{lab_tool: %{auth_node: tool_auth_node} = tool},
- experiment_auth_node
- ) do
- tool_auth_node = Authorization.copy(tool_auth_node, experiment_auth_node)
- Lab.Public.copy(tool, tool_auth_node)
- end
-
- def delete_tool(multi, %{survey_tool: tool}) when not is_nil(tool) do
- multi |> Utility.EctoHelper.delete(:survey_tool, tool)
- end
-
- def delete_tool(multi, %{lab_tool: %{time_slots: time_slots} = tool}) when not is_nil(tool) do
- multi
- |> Utility.EctoHelper.delete(:lab_time_slots, Lab.TimeSlotModel, time_slots)
- |> Utility.EctoHelper.delete(:lab_tool, tool)
+ def add_owner!(assignment, user) do
+ :ok = Core.Authorization.assign_role(user, assignment, :owner)
end
def owner!(%Assignment.Model{} = assignment), do: parent_owner!(assignment)
- def owner!(%Assignment.ExperimentModel{} = experiment), do: parent_owner!(experiment)
+ def owner!(%Workflow.Model{} = workflow), do: parent_owner!(workflow)
+ def owner!(%Workflow.ItemModel{} = item), do: parent_owner!(item)
def assign_tester_role(tool, user) do
%{crew: crew} = get_by_tool(tool, [:crew])
@@ -241,25 +242,48 @@ defmodule Systems.Assignment.Public do
|> Authorization.first_user_with_role(:owner, [])
end
- def expiration_timestamp(%{assignable_experiment: experiment}) do
- duration = Assignment.ExperimentModel.duration(experiment)
+ def expiration_timestamp(%{info: info}) do
+ duration = Assignment.InfoModel.duration(info)
timeout = max(@min_expiration_timeout, duration)
Timestamp.naive_from_now(timeout)
end
+ def status(%{crew: crew}, user) do
+ statuses =
+ Crew.Public.list_tasks_for_user(crew, user)
+ |> Enum.map(& &1.status)
+
+ cond do
+ Enum.member?(statuses, :rejected) -> :rejected
+ Enum.member?(statuses, :pending) -> :pending
+ Enum.member?(statuses, :completed) -> :completed
+ true -> :accepted
+ end
+ end
+
+ def timestamp(%{crew: crew}, user) do
+ crew
+ |> Crew.Public.list_tasks_for_user(user)
+ |> List.first()
+ |> timestamp()
+ end
+
+ def timestamp(%{updated_at: updated_at}), do: updated_at
+ def timestamp(_), do: nil
+
def member?(%{crew: crew}, user) do
Crew.Public.member?(crew, user)
end
- def apply_member(id, user, reward_amount) when is_number(id) do
+ def apply_member(id, user, identifier, reward_amount) when is_number(id) do
get!(id, [:crew])
- |> apply_member(user, reward_amount)
+ |> apply_member(user, identifier, reward_amount)
end
- def apply_member(%{crew: crew} = assignment, user, reward_amount) do
+ def apply_member(%{crew: crew} = assignment, user, identifier, reward_amount) do
if Crew.Public.member?(crew, user) do
- Crew.Public.get_member!(crew, user)
+ Crew.Public.get_member(crew, user)
else
expire_at = expiration_timestamp(assignment)
@@ -268,7 +292,7 @@ defmodule Systems.Assignment.Public do
run_create_reward(assignment, user, reward_amount)
end)
|> Multi.run(:member, fn _, _ ->
- run_apply_member(crew, user, expire_at)
+ run_apply_member(crew, user, identifier, expire_at)
end)
|> Repo.transaction()
end
@@ -283,8 +307,8 @@ defmodule Systems.Assignment.Public do
end
end
- def run_apply_member(%Crew.Model{} = crew, user, expire_at) do
- case Crew.Public.apply_member(crew, user, expire_at) do
+ def run_apply_member(%Crew.Model{} = crew, user, identifier, expire_at) do
+ case Crew.Public.apply_member(crew, user, identifier, expire_at) do
{:ok, %{member: member}} -> {:ok, member}
{:error, error} -> {:error, error}
end
@@ -294,7 +318,7 @@ defmodule Systems.Assignment.Public do
if Crew.Public.member?(crew, user) do
expire_at = expiration_timestamp(assignment)
- Crew.Public.get_member!(crew, user)
+ Crew.Public.get_member(crew, user)
|> Crew.Public.reset_member(expire_at)
else
Logger.warn("Unable to reset, user #{user.id} is not a member on crew #{crew.id}")
@@ -303,18 +327,18 @@ defmodule Systems.Assignment.Public do
def reject_task(
%Assignment.Model{} = assignment,
- %Crew.TaskModel{member: %{user: user}} = task,
+ %Crew.TaskModel{} = task,
rejection
) do
+ [user] = Authorization.users_with_role(assignment, :owner)
+
Multi.new()
|> Crew.Public.reject_task(task, rejection)
|> rollback_deposit(assignment, user)
|> Repo.transaction()
end
- def cancel(%Assignment.Model{} = assignment, user) do
- crew = get_crew(assignment)
-
+ def cancel(%Assignment.Model{crew: crew} = assignment, user) do
Multi.new()
|> Crew.Public.cancel(crew, user)
|> rollback_deposit(assignment, user)
@@ -325,50 +349,38 @@ defmodule Systems.Assignment.Public do
get!(id) |> cancel(user)
end
- def lock_task(%{crew: crew} = _assignment, user) do
- if Crew.Public.member?(crew, user) do
- member = Crew.Public.get_member!(crew, user)
- task = Crew.Public.get_task(crew, member)
+ def get_task(tool, identifier) do
+ %{crew: crew} = Assignment.Public.get_by_tool(tool, [:crew])
+ Crew.Public.get_task(crew, identifier)
+ end
+
+ def lock_task(tool, identifier) do
+ if task = get_task(tool, identifier) do
Crew.Public.lock_task(task)
else
- Logger.warn("Can not lock task for non member")
+ Logger.warn("Can not lock task")
end
end
def apply_member_and_activate_task(
%Assignment.Model{crew: crew} = assignment,
%User{} = user,
+ [_ | _] = identifier,
reward_amount
)
when is_integer(reward_amount) do
- member =
- if Crew.Public.member?(crew, user) do
- Crew.Public.get_member!(crew, user)
- else
- case apply_member(assignment, user, reward_amount) do
- {:ok, %{member: member}} -> member
- _ -> nil
- end
- end
-
- _activate_task(crew, member)
- end
-
- def activate_task(%Assignment.Model{crew: crew}, %User{} = user), do: activate_task(crew, user)
-
- def activate_task(%Crew.Model{} = crew, %User{} = user) do
- if Crew.Public.member?(crew, user) do
- member = Crew.Public.get_member!(crew, user)
- _activate_task(crew, member)
- else
- raise "Can not complete task for non member"
+ if not Crew.Public.member?(crew, user) do
+ apply_member(assignment, user, identifier, reward_amount)
end
+
+ activate_task(crew, identifier)
end
- defp _activate_task(%Crew.Model{} = _crew, nil), do: nil
+ def activate_task(%Assignment.Model{crew: crew}, [_ | _] = identifier),
+ do: activate_task(crew, identifier)
- defp _activate_task(%Crew.Model{} = crew, %Crew.MemberModel{} = member) do
- Crew.Public.get_task(crew, member)
+ def activate_task(%Crew.Model{} = crew, [_ | _] = identifier) do
+ Crew.Public.get_task(crew, identifier)
|> Crew.Public.activate_task!()
end
@@ -391,6 +403,16 @@ defmodule Systems.Assignment.Public do
|> Repo.exists?()
end
+ def attention_list_enabled?(%{workflow: workflow}) do
+ [tool] = Workflow.Model.flatten(workflow)
+ Concept.ToolModel.attention_list_enabled?(tool)
+ end
+
+ def task_labels(%{workflow: workflow}) do
+ [tool] = Workflow.Model.flatten(workflow)
+ Concept.ToolModel.task_labels(tool)
+ end
+
@doc """
Is assignment open for new members?
"""
@@ -408,24 +430,14 @@ defmodule Systems.Assignment.Public do
open_spot_count(assignment, type)
end
- defp open_spot_count(%{crew: crew, assignable_experiment: experiment}, :one_task) do
- target = Assignment.ExperimentModel.spot_count(experiment)
+ defp open_spot_count(%{crew: crew, info: %{subject_count: subject_count}}, :single_task) do
all_non_expired_tasks = Crew.Public.count_tasks(crew, Crew.TaskStatus.values())
-
- max(0, target - all_non_expired_tasks)
+ max(0, subject_count - all_non_expired_tasks)
end
- defp assignment_type(_assignment) do
- # Some logic (eg: open?) is depending on the type of assignment.
- # Currently we only support the 1-task assignment: a member has one task todo.
- # Other types will be:
- # N-tasks: a member can voluntaraly pick one or more tasks
- # all-tasks: a member has a batch of tasks todo
-
- :one_task
- end
+ defp assignment_type(%{workflow: %{type: type}}), do: type
- def mark_expired_debug(%{assignable_experiment: %{duration: duration}} = assignment, force) do
+ def mark_expired_debug(%{info: %{duration: duration}} = assignment, force) do
mark_expired_debug(assignment, duration, force)
end
@@ -439,8 +451,8 @@ defmodule Systems.Assignment.Public do
expired_pending_tasks_query(crew_id, expiration_timeout)
end
- member_ids = from(t in task_query, select: t.member_id)
- member_query = from(m in Crew.MemberModel, where: m.id in subquery(member_ids))
+ task_ids = from(t in task_query, select: t.id)
+ member_query = Crew.Public.member_query(task_ids)
Multi.new()
|> Multi.update_all(:members, member_query, set: [expired: true])
@@ -488,83 +500,27 @@ defmodule Systems.Assignment.Public do
# Assignable
- def ready?(%{assignable_experiment: experiment}) do
- ready?(experiment)
- end
-
- def ready?(%Assignment.ExperimentModel{} = experiment) do
- changeset =
- %Assignment.ExperimentModel{}
- |> Assignment.ExperimentModel.operational_changeset(Map.from_struct(experiment))
-
- changeset.valid? && tool_ready?(experiment)
- end
-
- def tool_ready?(%{survey_tool: tool}) when not is_nil(tool), do: Survey.Public.ready?(tool)
- def tool_ready?(%{lab_tool: tool}) when not is_nil(tool), do: Lab.Public.ready?(tool)
-
- defp assignable_field(%Assignment.ExperimentModel{}), do: :assignable_experiment
-
- # Experiment
-
- def get_experiment!(id, preload \\ []) do
- from(a in Assignment.ExperimentModel, preload: ^preload)
- |> Repo.get!(id)
- end
-
- def get_experiment_by_tool(%{id: tool_id} = tool, preload \\ []) do
- tool_id_field = Assignment.ExperimentModel.tool_id_field(tool)
- where = [{tool_id_field, tool_id}]
-
- from(a in Assignment.ExperimentModel,
- where: ^where,
- preload: ^preload
- )
- |> Repo.one()
+ def ready?(%{workflow: workflow}) do
+ Workflow.Model.ready?(workflow)
end
- def attention_list_enabled?(%{assignable_experiment: %{survey_tool: tool}})
- when not is_nil(tool),
- do: true
-
- def attention_list_enabled?(%{assignable_experiment: %{lab_tool: tool}}) when not is_nil(tool),
- do: false
-
- def task_labels(%{assignable_experiment: %{lab_tool: tool}}) when not is_nil(tool) do
- %{
- pending: dgettext("link-lab", "pending.label"),
- participated: dgettext("link-lab", "participated.label")
- }
+ def search_subject(%Assignment.Model{crew: crew}, %User{} = user) do
+ member = Crew.Public.get_member(crew, user)
+ tasks = Crew.Public.list_tasks_for_user(crew, member.user_id)
+ {member, tasks}
end
- def task_labels(%{assignable_experiment: %{survey_tool: tool}}) when not is_nil(tool) do
- %{
- pending: dgettext("link-survey", "pending.label"),
- participated: dgettext("link-survey", "participated.label")
- }
+ def search_subject(%Assignment.Model{crew: crew}, public_id) do
+ member = Crew.Public.subject(crew, public_id)
+ tasks = Crew.Public.list_tasks_for_user(crew, member.user_id)
+ {member, tasks}
end
- def search_subject(tool, %User{} = user) do
- if experiment = get_experiment_by_tool(tool) do
- %{crew: crew} = get_by_experiment!(experiment, [:crew])
- member = Crew.Public.get_member!(crew, user)
- task = Crew.Public.get_task(crew, member)
- {member, task}
- else
- nil
- end
+ def search_subject(%{} = tool, user) do
+ search_subject(get_by_tool(tool, [:crew]), user)
end
- def search_subject(tool, public_id) do
- if experiment = get_experiment_by_tool(tool) do
- %{crew: crew} = get_by_experiment!(experiment, [:crew])
- member = Crew.Public.subject(crew, public_id)
- task = Crew.Public.get_task(crew, member)
- {member, task}
- else
- nil
- end
- end
+ def search_subject(nil, _), do: nil
def expired_user_assignments(%NaiveDateTime{} = from) do
from(a in Assignment.Model,
@@ -609,7 +565,8 @@ defmodule Systems.Assignment.Public do
idempotence_key(assignment_id, user_id)
end
- def idempotence_key(assignment_id, user_id) do
+ def idempotence_key(assignment_id, user_id)
+ when is_integer(assignment_id) and is_integer(user_id) do
"assignment=#{assignment_id},user=#{user_id}"
end
@@ -623,12 +580,3 @@ defmodule Systems.Assignment.Public do
Budget.Public.rewarded_amount(idempotence_key)
end
end
-
-defimpl Core.Persister, for: Systems.Assignment.ExperimentModel do
- def save(_model, changeset) do
- case Systems.Assignment.Public.update_experiment(changeset) do
- {:ok, %{experiment: experiment}} -> {:ok, experiment}
- _ -> {:error, changeset}
- end
- end
-end
diff --git a/core/systems/assignment/_routes.ex b/core/systems/assignment/_routes.ex
index 2e81d5582..b136b28ce 100644
--- a/core/systems/assignment/_routes.ex
+++ b/core/systems/assignment/_routes.ex
@@ -3,13 +3,10 @@ defmodule Systems.Assignment.Routes do
quote do
scope "/", Systems.Assignment do
pipe_through([:browser, :require_authenticated_user])
- live("/assignment/:id", LandingPage)
- live("/assignment/:id/callback", CallbackPage)
- end
-
- scope "/", Frameworks.Utility do
- pipe_through([:browser, :require_authenticated_user])
- get("/task/:type/:id/callback", LegacyRoutesController, :task_callback)
+ live("/assignment/:id", CrewPage)
+ live("/assignment/:id/landing", LandingPage)
+ live("/assignment/:id/content", ContentPage)
+ get("/assignment/callback/:item", Controller, :callback)
end
end
end
diff --git a/core/systems/assignment/_switch.ex b/core/systems/assignment/_switch.ex
index ef59eabcb..6400c1123 100644
--- a/core/systems/assignment/_switch.ex
+++ b/core/systems/assignment/_switch.ex
@@ -1,7 +1,8 @@
defmodule Systems.Assignment.Switch do
use Frameworks.Signal.Handler
require Logger
- alias Core.Accounts
+
+ alias Core.Authorization
alias Frameworks.{
Signal
@@ -9,116 +10,140 @@ defmodule Systems.Assignment.Switch do
alias Systems.{
Assignment,
+ Workflow,
Crew,
NextAction
}
- def dispatch(
- :crew_task_updated,
- %{
- data: %{status: old_status, member_id: member_id, crew_id: crew_id},
- changes: %{status: new_status}
- }
- ) do
- # crew does not have a director (yet), so check if assignment is available to handle signal
- with [%{director: director} = assignment | _] <-
- Assignment.Public.get_by_crew!(crew_id, budget: [:fund, :reserve, :currency]) do
- %{user_id: user_id} = Crew.Public.get_member!(member_id)
- user = Accounts.get_user!(user_id)
-
- handle_next_action_check_rejection(old_status, new_status, assignment, user)
-
- case new_status do
- :accepted ->
- Signal.Public.dispatch!(:assignment_accepted, %{
- director: director,
- assignment: assignment,
- user: user
- })
-
- :rejected ->
- Signal.Public.dispatch!(:assignment_rejected, %{
- director: director,
- assignment: assignment,
- user: user
- })
-
- :pending ->
- nil
-
- :completed ->
- Signal.Public.dispatch!(:assignment_completed, %{
- director: director,
- assignment: assignment,
- user: user
- })
-
- _ ->
- Logger.warning("Unknown crew task status: #{new_status}")
- end
+ @impl true
+ def intercept({:workflow, _} = signal, %{workflow: workflow} = message) do
+ if assignment =
+ Assignment.Public.get_by_workflow(workflow, Assignment.Model.preload_graph(:down)) do
+ dispatch!(
+ {:assignment, signal},
+ Map.merge(message, %{assignment: assignment})
+ )
end
end
- def dispatch(:crew_task_updated, _task_changeset), do: :noop
+ @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
+ handle(
+ {:assignment, signal},
+ Map.merge(message, %{assignment: assignment})
+ )
+ end
+ end
- def dispatch(:lab_reservations_cancelled, %{tool: tool, user: user}) do
- # reset the membership (with new expiration time), so user has time to reserve a spot on a different time slot
- if experiment = Assignment.Public.get_experiment_by_tool(tool) do
- experiment
- |> Assignment.Public.get_by_assignable([:crew])
- |> Assignment.Public.reset_member(user)
+ @impl true
+ def intercept({:assignment, _} = signal, message) do
+ handle(signal, message)
+ end
- handle(:lab_tool_updated, tool)
- end
+ def intercept({:crew_task, _} = signal, %{crew_task: %{crew_id: crew_id}} = message) do
+ Assignment.Public.list_by_crew(crew_id, Assignment.Model.preload_graph(:down))
+ |> Enum.each(
+ &dispatch!(
+ {:assignment, signal},
+ Map.merge(message, %{assignment: &1})
+ )
+ )
end
- def dispatch(:lab_reservation_created, %{tool: tool, user: user}) do
- if experiment = Assignment.Public.get_experiment_by_tool(tool) do
- experiment
- |> Assignment.Public.get_by_assignable([:crew])
- |> Assignment.Public.lock_task(user)
+ def intercept({:lab_tool, :reservations_cancelled}, %{tool: tool, user: user}) do
+ # reset the membership (with new expiration time), so user has time to reserve a spot on a different time slot
+ if assignment = Assignment.Public.get_by_tool(tool, [:crew]) do
+ Assignment.Public.reset_member(assignment, user)
+ end
+ end
- handle(:lab_tool_updated, tool)
+ def intercept({:lab_tool, :reservation_created}, %{tool: tool, user: user}) do
+ if Assignment.Public.get_by_tool(tool) do
+ Assignment.Public.lock_task(tool, user)
end
end
- def dispatch(signal, %{director: :assignment} = object) do
+ def intercept(signal, %{director: :assignment} = object) do
handle(signal, object)
end
- def handle(:survey_tool_updated, tool), do: handle(:tool_updated, tool)
- def handle(:lab_tool_updated, tool), do: handle(:tool_updated, tool)
- def handle(:data_donation_tool_updated, tool), do: handle(:tool_updated, tool)
+ defp handle({:assignment, event}, %{assignment: assignment} = message) do
+ with {:workflow_item, :deleted} <- event do
+ delete_crew_tasks(message)
+ end
+
+ with {:crew_task, :accepted} <- event do
+ payout_participants(message)
+ end
- def handle(:tool_updated, tool) do
- experiment = Assignment.Public.get_experiment_by_tool(tool)
- handle(:experiment_updated, experiment)
+ with {:crew_task, _} <- event do
+ update_crew_task_next_action(message)
+ end
+
+ update_pages(assignment)
+ end
+
+ defp delete_crew_tasks(%{
+ assignment: %Assignment.Model{crew: crew} = assignment,
+ workflow_item: %Workflow.ItemModel{} = workflow_item
+ }) do
+ Assignment.Private.task_template(assignment, workflow_item)
+ |> then(&Crew.Public.list_tasks_by_template(crew, &1))
+ |> delete_crew_tasks()
end
- def handle(:experiment_updated, experiment), do: handle(:assignable_updated, experiment)
+ defp delete_crew_tasks([_ | _] = tasks) do
+ Enum.each(tasks, &Crew.Public.delete_task/1)
+ end
+
+ defp delete_crew_tasks(_), do: nil
+
+ defp update_pages(%Assignment.Model{} = assignment) do
+ [
+ Assignment.CrewPage,
+ Assignment.ContentPage
+ ]
+ |> Enum.each(&update_page(&1, assignment))
+ end
- def handle(:assignable_updated, assignable) do
- assignment = Assignment.Public.get_by_assignable(assignable)
- Signal.Public.dispatch!(:assignment_updated, assignment)
+ defp update_page(page, model) do
+ dispatch!({:page, page}, %{id: model.id, model: model})
end
- defp handle_next_action_check_rejection(
- old_status,
- new_status,
- %{id: assignment_id} = _assignment,
- user
- ) do
+ defp update_crew_task_next_action(%{
+ assignment: %{id: assignment_id},
+ changeset: %{
+ data: %{status: old_status, auth_node_id: auth_node_id},
+ changes: %{status: new_status}
+ }
+ }) do
+ users = Authorization.users_with_role(auth_node_id, :owner)
+
opts = [key: "#{assignment_id}", params: %{id: assignment_id}]
case {old_status, new_status} do
{_, :rejected} ->
- NextAction.Public.create_next_action(user, Assignment.CheckRejection, opts)
+ NextAction.Public.create_next_action(users, Assignment.CheckRejection, opts)
{:rejected, _} ->
- NextAction.Public.clear_next_action(user, Assignment.CheckRejection, opts)
+ NextAction.Public.clear_next_action(users, Assignment.CheckRejection, opts)
_ ->
nil
end
end
+
+ defp update_crew_task_next_action(_), do: nil
+
+ defp payout_participants(%{
+ assignment: assignment,
+ crew_task: crew_task,
+ changeset: %{data: %{status: old_status}}
+ }) do
+ if old_status != :accepted do
+ participants = Core.Authorization.users_with_role(crew_task, :owner)
+ Enum.each(participants, &Assignment.Public.payout_participant(assignment, &1))
+ end
+ end
end
diff --git a/core/systems/campaign/builders/assignment_callback_page.ex b/core/systems/assignment/alliance_callback_page_builder.ex
similarity index 67%
rename from core/systems/campaign/builders/assignment_callback_page.ex
rename to core/systems/assignment/alliance_callback_page_builder.ex
index e9809643e..4c17765b7 100644
--- a/core/systems/campaign/builders/assignment_callback_page.ex
+++ b/core/systems/assignment/alliance_callback_page_builder.ex
@@ -1,4 +1,4 @@
-defmodule Systems.Campaign.Builders.AssignmentCallbackPage do
+defmodule Systems.Assignment.AllianceCallbackPageBuilder do
import CoreWeb.Gettext
alias Core.Accounts
@@ -6,34 +6,23 @@ defmodule Systems.Campaign.Builders.AssignmentCallbackPage do
alias Phoenix.LiveView
alias Systems.{
- Campaign,
- Assignment
+ Assignment,
+ Workflow,
+ Alliance
}
def view_model(
- %Campaign.Model{} = campaign,
- assigns
- ) do
- campaign
- |> Campaign.Model.flatten()
- |> view_model(assigns)
- end
-
- def view_model(
- %{
- id: id,
- promotion: %{
- title: title
- },
- promotable: assignment
- },
+ %Alliance.ToolModel{id: id} = tool,
%{current_user: user} = _assigns
) do
+ %{title: title} = Workflow.Public.get_item_by_tool!(:alliance_tool_id, tool.id)
+ assignment = Assignment.Public.get_by_tool(tool)
+
%{
id: id,
title: title,
state: state(assignment, user),
- hero_title: dgettext("link-survey", "task.hero.title"),
+ hero_title: dgettext("link-questionnaire", "task.hero.title"),
call_to_action: forward_call_to_action(user)
}
end
diff --git a/core/systems/assignment/assignment_form.ex b/core/systems/assignment/assignment_form.ex
index 5eca6fc68..a613ee097 100644
--- a/core/systems/assignment/assignment_form.ex
+++ b/core/systems/assignment/assignment_form.ex
@@ -2,7 +2,8 @@ defmodule Systems.Assignment.AssignmentForm do
use CoreWeb.LiveForm
alias Systems.{
- Assignment
+ Assignment,
+ Workflow
}
# Handle initial update
@@ -10,19 +11,14 @@ defmodule Systems.Assignment.AssignmentForm do
def update(
%{
id: id,
- entity: %{id: entity_id, assignable_experiment: experiment} = entity,
+ entity: %{info: info, workflow: workflow} = entity,
user: user,
uri_origin: uri_origin
},
socket
) do
- callback_path =
- CoreWeb.Router.Helpers.live_path(socket, Systems.Assignment.CallbackPage, entity_id)
-
- callback_url = uri_origin <> callback_path
-
- tool_id = Assignment.ExperimentModel.tool_id(experiment)
- tool_form = Assignment.ExperimentModel.tool_form(experiment)
+ [tool | _] = Workflow.Model.flatten(workflow)
+ tool_form = Frameworks.Concept.ToolModel.form(tool)
{
:ok,
@@ -30,35 +26,38 @@ defmodule Systems.Assignment.AssignmentForm do
|> assign(
id: id,
entity: entity,
- experiment: experiment,
- tool_id: tool_id,
+ info: info,
+ tool: tool,
tool_form: tool_form,
user: user,
- callback_url: callback_url
+ uri_origin: uri_origin
)
}
end
defp forms(%{
tool_form: tool_form,
- tool_id: tool_id,
- experiment: experiment,
- callback_url: callback_url,
+ tool: tool,
+ info: info,
+ uri_origin: uri_origin,
user: user
}) do
+ callback_path = ~p"/assignment/callback/#{tool.id}"
+ callback_url = uri_origin <> callback_path
+
[
%{
- live_component: Assignment.ExperimentForm,
+ live_component: Assignment.InfoForm,
props: %{
- id: :experiment_form,
- entity: experiment
+ id: :info_form,
+ entity: info
}
},
%{
live_component: tool_form,
props: %{
id: :tool_form,
- entity_id: tool_id,
+ entity: tool,
callback_url: callback_url,
user: user
}
@@ -67,7 +66,7 @@ defmodule Systems.Assignment.AssignmentForm do
live_component: Assignment.EthicalForm,
props: %{
id: :ethical_form,
- entity: experiment
+ entity: info
}
}
]
diff --git a/core/systems/assignment/check_rejection.ex b/core/systems/assignment/check_rejection.ex
index 135d14ce5..4322fe44a 100644
--- a/core/systems/assignment/check_rejection.ex
+++ b/core/systems/assignment/check_rejection.ex
@@ -10,7 +10,7 @@ defmodule Systems.Assignment.CheckRejection do
title: dgettext("eyra-nextaction", "assignment.check.rejection.title"),
description: dgettext("eyra-nextaction", "assignment.check.rejection.description"),
cta_label: dgettext("eyra-nextaction", "assignment.check.rejection.cta"),
- cta_action: %{type: :redirect, to: ~p"/assignment/#{id}"}
+ cta_action: %{type: :redirect, to: ~p"/assignment/#{id}/landing"}
}
end
end
diff --git a/core/systems/assignment/content_page.ex b/core/systems/assignment/content_page.ex
new file mode 100644
index 000000000..1a02b5aec
--- /dev/null
+++ b/core/systems/assignment/content_page.ex
@@ -0,0 +1,59 @@
+defmodule Systems.Assignment.ContentPage do
+ use Systems.Content.Page
+
+ alias Systems.{
+ Assignment,
+ Crew
+ }
+
+ @impl true
+ def get_authorization_context(%{"id" => id}, _session, _socket) do
+ Assignment.Public.get!(String.to_integer(id))
+ end
+
+ @impl true
+ def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
+ model = Assignment.Public.get!(String.to_integer(id), Assignment.Model.preload_graph(:down))
+ tabbar_id = "assignment_content/#{id}"
+
+ {
+ :ok,
+ socket
+ |> initialize(id, model, tabbar_id, initial_tab, locale)
+ |> ensure_tester_role()
+ }
+ end
+
+ @impl true
+ def mount(params, session, socket) do
+ mount(Map.put(params, "tab", nil), session, socket)
+ end
+
+ defp ensure_tester_role(%{assigns: %{current_user: user, model: %{crew: crew}}} = socket) do
+ if Crew.Public.get_member(crew, user) == nil do
+ Crew.Public.apply_member_with_role(crew, user, :tester)
+ end
+
+ socket
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.content_page
+ title={@vm.title}
+ show_errors={@vm.show_errors}
+ tabs={@vm.tabs}
+ menus={@menus}
+ actions={@actions}
+ more_actions={@more_actions}
+ initial_tab={@initial_tab}
+ tabbar_id={@tabbar_id}
+ tabbar_size={@tabbar_size}
+ breakpoint={@breakpoint}
+ popup={@popup}
+ dialog={@dialog}
+ />
+ """
+ end
+end
diff --git a/core/systems/assignment/content_page_builder.ex b/core/systems/assignment/content_page_builder.ex
new file mode 100644
index 000000000..9327551dc
--- /dev/null
+++ b/core/systems/assignment/content_page_builder.ex
@@ -0,0 +1,327 @@
+defmodule Systems.Assignment.ContentPageBuilder do
+ use CoreWeb, :verified_routes
+
+ import CoreWeb.Gettext
+
+ alias Systems.{
+ Assignment,
+ Project,
+ Workflow,
+ Privacy,
+ Support
+ }
+
+ def view_model(
+ %{id: id} = assignment,
+ assigns
+ ) do
+ show_errors = show_errors(assignment, assigns)
+ tabs = create_tabs(assignment, show_errors, assigns)
+ action_map = action_map(assignment)
+ actions = actions(assignment, action_map)
+
+ %{
+ id: id,
+ title: dgettext("eyra-assignment", "content.title"),
+ tabs: tabs,
+ actions: actions,
+ show_errors: show_errors
+ }
+ end
+
+ defp show_errors(_, _) do
+ # concept? = status == :concept
+ # publish_clicked or not concept?
+ false
+ end
+
+ defp action_map(%{id: id}) do
+ preview_action = %{type: :http_get, to: ~p"/assignment/#{id}", target: "_blank"}
+ publish_action = %{type: :send, event: "action_click", item: :publish}
+ retract_action = %{type: :send, event: "action_click", item: :retract}
+ close_action = %{type: :send, event: "action_click", item: :close}
+ open_action = %{type: :send, event: "action_click", item: :open}
+
+ %{
+ preview: %{
+ label: %{
+ action: preview_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-assignment", "preview.button"),
+ bg_color: "bg-primary"
+ }
+ },
+ icon: %{
+ action: preview_action,
+ face: %{type: :icon, icon: :preview, alt: dgettext("eyra-assignment", "preview.button")}
+ }
+ },
+ publish: %{
+ label: %{
+ action: publish_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-assignment", "publish.button"),
+ bg_color: "bg-success"
+ }
+ },
+ icon: %{
+ action: publish_action,
+ face: %{type: :icon, icon: :preview, alt: dgettext("eyra-assignment", "preview.button")}
+ },
+ handle_click: &handle_publish/1
+ },
+ retract: %{
+ label: %{
+ action: retract_action,
+ face: %{
+ type: :secondary,
+ label: dgettext("eyra-assignment", "retract.button"),
+ text_color: "text-error",
+ border_color: "border-error"
+ }
+ },
+ icon: %{
+ action: retract_action,
+ face: %{
+ type: :icon,
+ icon: :retract,
+ alt: dgettext("eyra-benchmark", "assignment.button")
+ }
+ },
+ handle_click: &handle_retract/1
+ },
+ close: %{
+ label: %{
+ action: close_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-assignment", "close.button")
+ }
+ },
+ icon: %{
+ action: close_action,
+ face: %{type: :icon, icon: :close, alt: dgettext("eyra-assignment", "close.button")}
+ },
+ handle_click: &handle_close/1
+ },
+ open: %{
+ label: %{
+ action: open_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-assignment", "open.button")
+ }
+ },
+ icon: %{
+ action: open_action,
+ face: %{type: :icon, icon: :open, alt: dgettext("eyra-assignment", "open.button")}
+ },
+ handle_click: &handle_open/1
+ }
+ }
+ end
+
+ defp actions(%{status: :online}, %{retract: retract}), do: [retract: retract]
+
+ defp actions(%{status: :offline}, %{publish: publish, close: close}),
+ do: [publish: publish, close: close]
+
+ defp actions(%{status: :idle}, %{open: open}), do: [open: open]
+
+ defp actions(%{status: _concept}, %{publish: publish, preview: preview}),
+ do: [publish: publish, preview: preview]
+
+ defp handle_publish(socket) do
+ socket
+ end
+
+ defp handle_retract(socket) do
+ socket
+ end
+
+ defp handle_close(socket) do
+ socket
+ end
+
+ defp handle_open(socket) do
+ socket
+ end
+
+ defp create_tabs(assignment, show_errors, assigns) do
+ get_tab_keys()
+ |> Enum.map(&create_tab(&1, assignment, show_errors, assigns))
+ end
+
+ defp get_tab_keys() do
+ [:config, :privacy, :items, :support, :invite, :monitor]
+ end
+
+ defp create_tab(
+ :config,
+ %{info: info},
+ show_errors,
+ _assigns
+ ) do
+ ready? = false
+
+ %{
+ id: :config_form,
+ ready: ready?,
+ show_errors: show_errors,
+ title: dgettext("eyra-project", "tabbar.item.config"),
+ forward_title: dgettext("eyra-project", "tabbar.item.config.forward"),
+ type: :fullpage,
+ live_component: Assignment.InfoForm,
+ props: %{
+ entity: info
+ }
+ }
+ end
+
+ defp create_tab(
+ :items,
+ %{workflow: workflow},
+ show_errors,
+ %{
+ current_user: user,
+ uri_origin: uri_origin
+ }
+ ) do
+ ready? = false
+
+ %{
+ id: :workflow_form,
+ ready: ready?,
+ show_errors: show_errors,
+ title: dgettext("eyra-workflow", "tabbar.item.workflow"),
+ forward_title: dgettext("eyra-workflow", "tabbar.item.workflow.forward"),
+ type: :fullpage,
+ live_component: Workflow.BuilderView,
+ props: %{
+ user: user,
+ uri_origin: uri_origin,
+ workflow: workflow,
+ config: %{
+ director: :assignment,
+ list: %{
+ title: dgettext("eyra-workflow", "item.list.title"),
+ description: dgettext("eyra-workflow", "item.list.description")
+ },
+ library: %{
+ title: dgettext("eyra-workflow", "item.library.title"),
+ description: dgettext("eyra-workflow", "item.library.description"),
+ items: [
+ %{
+ id: :questionnaire,
+ type: :alliance_tool,
+ title: dgettext("eyra-workflow", "item.questionnaire.title"),
+ description: dgettext("eyra-workflow", "item.questionnaire.description")
+ },
+ %{
+ id: :request,
+ type: :document_tool,
+ title: dgettext("eyra-workflow", "item.request.title"),
+ description: dgettext("eyra-workflow", "item.request.description")
+ },
+ %{
+ id: :download,
+ type: :document_tool,
+ title: dgettext("eyra-workflow", "item.download.title"),
+ description: dgettext("eyra-workflow", "item.download.description")
+ },
+ %{
+ id: :donate,
+ type: :feldspar_tool,
+ title: dgettext("eyra-workflow", "item.donate.title"),
+ description: dgettext("eyra-workflow", "item.donate.description")
+ }
+ ]
+ }
+ }
+ }
+ }
+ end
+
+ defp create_tab(
+ :privacy,
+ _assignment,
+ show_errors,
+ _assigns
+ ) do
+ ready? = false
+
+ %{
+ id: :privacy_form,
+ ready: ready?,
+ show_errors: show_errors,
+ title: dgettext("eyra-project", "tabbar.item.privacy"),
+ forward_title: dgettext("eyra-project", "tabbar.item.privacy.forward"),
+ type: :fullpage,
+ live_component: Privacy.Form,
+ props: %{
+ entity: %{}
+ }
+ }
+ end
+
+ defp create_tab(
+ :support,
+ assignment,
+ _show_errors,
+ _assigns
+ ) do
+ %{
+ id: :support,
+ title: dgettext("eyra-project", "tabbar.item.support"),
+ forward_title: dgettext("eyra-project", "tabbar.item.support.forward"),
+ type: :fullpage,
+ live_component: Support.Form,
+ props: %{
+ entity: assignment
+ }
+ }
+ end
+
+ defp create_tab(
+ :invite,
+ %{id: id},
+ show_errors,
+ %{uri_origin: uri_origin}
+ ) do
+ ready? = false
+ url = uri_origin <> "/assignment/#{id}"
+
+ %{
+ id: :invite_form,
+ ready: ready?,
+ show_errors: show_errors,
+ title: dgettext("eyra-project", "tabbar.item.invite"),
+ forward_title: dgettext("eyra-project", "tabbar.item.invite.forward"),
+ type: :fullpage,
+ live_component: Project.InviteForm,
+ props: %{
+ url: url
+ }
+ }
+ end
+
+ defp create_tab(
+ :monitor,
+ assignment,
+ _show_errors,
+ _assigns
+ ) do
+ %{
+ id: :monitor,
+ title: dgettext("eyra-project", "tabbar.item.monitor"),
+ forward_title: dgettext("eyra-project", "tabbar.item.monitor.forward"),
+ type: :fullpage,
+ live_component: Project.ItemMonitorView,
+ props: %{
+ entity: assignment
+ }
+ }
+ end
+end
diff --git a/core/systems/assignment/controller.ex b/core/systems/assignment/controller.ex
new file mode 100644
index 000000000..5642ee11c
--- /dev/null
+++ b/core/systems/assignment/controller.ex
@@ -0,0 +1,22 @@
+defmodule Systems.Assignment.Controller do
+ use CoreWeb, :controller
+
+ alias Systems.{
+ Assignment,
+ Workflow,
+ Crew
+ }
+
+ 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])
+
+ Crew.Public.get_member(crew, user)
+ |> then(&Assignment.Private.task_identifier(assignment, item, &1))
+ |> then(&Crew.Public.get_task(crew, &1))
+ |> Crew.Public.activate_task!()
+
+ conn
+ |> redirect(to: ~p"/assignment/#{id}")
+ end
+end
diff --git a/core/systems/assignment/crew_page.ex b/core/systems/assignment/crew_page.ex
new file mode 100644
index 000000000..47aaef810
--- /dev/null
+++ b/core/systems/assignment/crew_page.ex
@@ -0,0 +1,247 @@
+defmodule Systems.Assignment.CrewPage do
+ use CoreWeb, :live_view
+ use Systems.Observatory.Public
+ use CoreWeb.Layouts.Stripped.Component, :projects
+
+ alias Frameworks.Concept
+
+ alias Systems.{
+ Assignment,
+ Project,
+ Workflow,
+ Crew
+ }
+
+ import Assignment.StartView
+ import Project.ToolRefView
+ import Workflow.ItemViews, only: [work_list: 1]
+
+ @impl true
+ def get_authorization_context(%{"id" => id}, _session, _socket) do
+ %{crew: crew} = Assignment.Public.get!(String.to_integer(id), [:crew])
+ crew
+ end
+
+ @impl true
+ def mount(%{"id" => id}, _session, socket) do
+ model = Assignment.Public.get!(id, Assignment.Model.preload_graph(:down))
+
+ {
+ :ok,
+ socket
+ |> assign(
+ id: id,
+ model: model,
+ tool_ref_view: nil
+ )
+ |> observe_view_model()
+ |> update_selected_item()
+ |> update_start_view()
+ |> update_work_list()
+ |> update_menus()
+ }
+ end
+
+ defoverridable handle_view_model_updated: 1
+
+ def handle_view_model_updated(socket) do
+ socket
+ |> update_selected_item()
+ |> update_start_view()
+ |> update_work_list()
+ |> update_menus()
+ end
+
+ defp update_selected_item(%{assigns: %{selected_item: {_, _}}} = socket) do
+ socket
+ end
+
+ defp update_selected_item(%{assigns: %{vm: %{items: []}}} = socket) do
+ socket |> assign(selected_item: nil)
+ end
+
+ defp update_selected_item(%{assigns: %{vm: %{items: [item]}}} = socket) do
+ socket |> assign(selected_item: item)
+ end
+
+ defp update_selected_item(%{assigns: %{vm: %{items: items}}} = socket) do
+ selected_item =
+ Enum.find(items, List.first(items), fn {_, %{status: status}} -> status == :pending end)
+
+ socket |> assign(selected_item: selected_item)
+ end
+
+ defp update_start_view(
+ %{
+ assigns: %{
+ selected_item:
+ {%{title: title, description: description, group: group}, _task} = selected_item
+ }
+ } = socket
+ ) do
+ button = %{
+ action: start_action(selected_item),
+ face: %{type: :primary, label: "Start"}
+ }
+
+ start_view = %{
+ title: title,
+ description: description,
+ icon: group,
+ button: button
+ }
+
+ socket |> assign(start_view: start_view)
+ end
+
+ defp update_start_view(socket), do: socket |> assign(start_view: nil)
+
+ defp start_action({%{tool_ref: tool_ref}, _task} = item) do
+ Project.ToolRefModel.tool(tool_ref)
+ |> Concept.ToolModel.launcher()
+ |> start_action(item)
+ end
+
+ defp start_action(%{function: _, props: _}, {%{id: id}, _}) do
+ %{type: :send, event: "start", item: id}
+ end
+
+ defp start_action(%{url: url}, _) do
+ %{type: :http_get, to: url, target: "_blank"}
+ end
+
+ defp start_action(_, {%{id: id}, _}) do
+ %{type: :send, event: "start", item: id}
+ end
+
+ defp update_work_list(
+ %{assigns: %{vm: %{items: items}, selected_item: {%{id: selected_item_id}, _}}} = socket
+ ) do
+ work_list = %{
+ items: Enum.map(items, &map_item/1),
+ selected_item_id: selected_item_id
+ }
+
+ socket |> assign(work_list: work_list)
+ end
+
+ defp map_item({%{id: id, title: title, group: group}, task}) do
+ %{id: id, title: title, icon: group, status: task_status(task)}
+ end
+
+ defp task_status(%{status: status}), do: status
+ defp task_status(_), do: :pending
+
+ @impl true
+ def handle_info(
+ {:complete_task, _},
+ %{assigns: %{vm: %{items: items}, selected_item: {%{id: selected_item_id}, _}}} = socket
+ ) do
+ {_, task} = Enum.find(items, fn {%{id: id}, _} -> id == selected_item_id end)
+
+ Crew.Public.activate_task(task)
+
+ {:noreply, socket}
+ end
+
+ @impl true
+ def handle_event(
+ "work_item_selected",
+ %{"item" => item_id},
+ %{assigns: %{vm: %{items: items}}} = socket
+ ) do
+ item_id = String.to_integer(item_id)
+ item = Enum.find(items, fn {%{id: id}, _} -> id == item_id end)
+
+ {
+ :noreply,
+ socket
+ |> assign(
+ selected_item: item,
+ tool_ref_view: nil
+ )
+ |> update_start_view()
+ |> update_work_list()
+ }
+ end
+
+ @impl true
+ def handle_event("start", %{"item" => item_id}, %{assigns: %{vm: %{items: items}}} = socket) do
+ item_id = String.to_integer(item_id)
+ {%{tool_ref: tool_ref}, task} = Enum.find(items, fn {%{id: id}, _} -> id == item_id end)
+
+ tool_ref_view = %{
+ tool_ref: tool_ref,
+ task: task
+ }
+
+ Crew.Public.lock_task(task)
+
+ {
+ :noreply,
+ socket |> assign(tool_ref_view: tool_ref_view, start_view: nil)
+ }
+ end
+
+ @impl true
+ def handle_event("app_event", event, socket) do
+ {
+ :noreply,
+ socket |> handle_app_event(event)
+ }
+ end
+
+ defp handle_app_event(%{assigns: %{selected_item: {_, task}}} = socket, %{
+ "__type__" => "CommandSystemExit",
+ "code" => code,
+ "info" => _info
+ }) do
+ if code == 0 do
+ Crew.Public.activate_task(task)
+ socket
+ else
+ Frameworks.Pixel.Flash.put_error(socket, "Application stopped")
+ end
+ end
+
+ defp handle_app_event(socket, %{
+ "__type__" => "CommandSystemDonate",
+ "json_string" => _json_string
+ }) do
+ socket |> Frameworks.Pixel.Flash.put_info("Donation received")
+ end
+
+ defp handle_app_event(socket, %{"__type__" => type}) do
+ socket |> Frameworks.Pixel.Flash.put_error("Unsupported event " <> type)
+ end
+
+ defp handle_app_event(socket, _) do
+ socket |> Frameworks.Pixel.Flash.put_error("Unsupported event")
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.stripped menus={@menus} footer?={false}>
+
+
+
+
+
+
+ """
+ end
+end
diff --git a/core/systems/assignment/crew_page_builder.ex b/core/systems/assignment/crew_page_builder.ex
new file mode 100644
index 000000000..8f7387f1c
--- /dev/null
+++ b/core/systems/assignment/crew_page_builder.ex
@@ -0,0 +1,43 @@
+defmodule Systems.Assignment.CrewPageBuilder do
+ alias Systems.{
+ Assignment,
+ Crew,
+ Workflow
+ }
+
+ def view_model(
+ %{crew: crew, status: status} = assignment,
+ %{current_user: user} = _assigns
+ ) do
+ member = Crew.Public.get_member(crew, user)
+
+ items =
+ if status == :online or Core.Authorization.user_has_role?(user, crew, :tester) do
+ items(assignment, member)
+ else
+ # offline mode
+ []
+ end
+
+ %{
+ items: items
+ }
+ end
+
+ defp items(%{workflow: workflow} = assignment, member) do
+ ordered_items = Workflow.Model.ordered_items(workflow)
+ Enum.map(ordered_items, &{&1, get_or_create_task(&1, assignment, member)})
+ end
+
+ defp items(_assignment, nil), do: []
+
+ defp get_or_create_task(item, %{crew: crew} = assignment, member) do
+ identifier = Assignment.Private.task_identifier(assignment, item, member)
+
+ if task = Crew.Public.get_task(crew, identifier) do
+ task
+ else
+ Crew.Public.create_task(crew, [member], identifier)
+ end
+ end
+end
diff --git a/core/systems/assignment/ethical_form.ex b/core/systems/assignment/ethical_form.ex
index 9bc7d6f44..73c7c5e1c 100644
--- a/core/systems/assignment/ethical_form.ex
+++ b/core/systems/assignment/ethical_form.ex
@@ -6,10 +6,7 @@ defmodule Systems.Assignment.EthicalForm do
alias Frameworks.Pixel.Panel
alias Frameworks.Pixel.Text
-
- alias Systems.{
- Assignment
- }
+ alias Systems.Assignment
# Handle selector update
@@ -31,11 +28,11 @@ defmodule Systems.Assignment.EthicalForm do
%{id: id, entity: entity},
socket
) do
- changeset = Assignment.ExperimentModel.changeset(entity, :create, %{})
+ changeset = Assignment.InfoModel.changeset(entity, :create, %{})
ethical_label = %{
id: :statement,
- value: dgettext("link-survey", "ethical.label"),
+ value: dgettext("eyra-alliance", "ethical.label"),
accent: :tertiary,
active: entity.ethical_approval
}
@@ -56,7 +53,7 @@ defmodule Systems.Assignment.EthicalForm do
# Handle Events
@impl true
- def handle_event("save", %{"experiment_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
+ def handle_event("save", %{"info_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
{
:noreply,
socket
@@ -67,7 +64,7 @@ defmodule Systems.Assignment.EthicalForm do
# Saving
def save(socket, entity, type, attrs) do
- changeset = Assignment.ExperimentModel.changeset(entity, type, attrs)
+ changeset = Assignment.InfoModel.changeset(entity, type, attrs)
socket
|> save(changeset)
@@ -78,7 +75,7 @@ defmodule Systems.Assignment.EthicalForm do
def validate_for_publish(%{assigns: %{id: id, entity: entity}} = socket) do
changeset =
- Assignment.ExperimentModel.operational_changeset(entity, %{})
+ Assignment.InfoModel.operational_changeset(entity, %{})
|> Map.put(:action, :validate_for_publish)
send(self(), %{id: id, ready?: changeset.valid?})
@@ -89,7 +86,7 @@ defmodule Systems.Assignment.EthicalForm do
defp ethical_review_link() do
link_as_string(
- dgettext("link-survey", "ethical.review.link"),
+ dgettext("eyra-alliance", "ethical.review.link"),
"https://vueconomics.eu.qualtrics.com/jfe/form/SV_1SKjMzceWRZIk9D"
)
end
@@ -109,8 +106,8 @@ defmodule Systems.Assignment.EthicalForm do
~H"""
+ <.work_list {@work_list} />
+
+
+
+
+ <%= if @tool_ref_view do %>
+ <.tool_ref_view {@tool_ref_view}/>
+ <% else %>
+ <%= if @start_view do %>
+ <.start_view {@start_view} />
+ <% else %>
+ Empty
+ <% end %>
+ <% end %>
+
+
<.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself} >
- <%= dgettext("link-survey", "ethical.title") %>
- <%= raw(dgettext("link-survey", "ethical.description", link: ethical_review_link())) %>
+ <%= dgettext("eyra-alliance", "ethical.title") %>
+ <%= raw(dgettext("eyra-alliance", "ethical.description", link: ethical_review_link())) %>
<.spacing value="M" />
@@ -125,7 +122,7 @@ defmodule Systems.Assignment.EthicalForm do
<.checkbox
form={form}
field={:ethical_approval}
- label_text={dgettext("link-survey", "ethical.label")}
+ label_text={dgettext("eyra-alliance", "ethical.label")}
label_color="text-white"
accent={:tertiary}
background={:dark}
diff --git a/core/systems/assignment/experiment_model.ex b/core/systems/assignment/experiment_model.ex
deleted file mode 100644
index b5a542a42..000000000
--- a/core/systems/assignment/experiment_model.ex
+++ /dev/null
@@ -1,128 +0,0 @@
-defmodule Systems.Assignment.ExperimentModel do
- @moduledoc """
- The survey tool schema.
- """
- use Ecto.Schema
- use Frameworks.Utility.Model
-
- require Core.Enums.Devices
-
- import CoreWeb.Gettext
- import Ecto.Changeset
-
- alias Systems.{
- Survey,
- Lab
- }
-
- schema "experiments" do
- belongs_to(:auth_node, Core.Authorization.Node)
-
- belongs_to(:survey_tool, Systems.Survey.ToolModel)
- belongs_to(:lab_tool, Systems.Lab.ToolModel)
-
- field(:subject_count, :integer)
- field(:duration, :string)
- field(:language, :string)
- field(:devices, {:array, Ecto.Enum}, values: Core.Enums.Devices.schema_values())
- field(:ethical_approval, :boolean)
- field(:ethical_code, :string)
-
- field(:director, Ecto.Enum, values: [:campaign, :assignment])
-
- timestamps()
- end
-
- defimpl Frameworks.GreenLight.AuthorizationNode do
- def id(experiment), do: experiment.auth_node_id
- end
-
- @operational_fields ~w(subject_count duration ethical_code ethical_approval devices)a
- @fields @operational_fields ++ ~w(language)a
-
- @required_fields ~w()a
-
- @impl true
- def operational_fields, do: @operational_fields
-
- @impl true
- def operational_validation(changeset) do
- validate_true(changeset, :ethical_approval)
- end
-
- defp validate_true(changeset, field) do
- case get_field(changeset, field) do
- true -> changeset
- _ -> add_error(changeset, field, "is not true")
- end
- end
-
- def changeset(tool, :auto_save, params) do
- tool
- |> cast(params, @fields)
- |> validate_required(@required_fields)
- end
-
- def changeset(tool, _, params) do
- tool
- |> cast(params, [:director])
- |> cast(params, @fields)
- end
-
- def languages(%{language: language}) when not is_nil(language), do: [language]
- def languages(_), do: []
-
- def devices(%{devices: devices}) when not is_nil(devices), do: devices
- def devices(_), do: []
-
- def spot_count(%{subject_count: subject_count}) when not is_nil(subject_count),
- do: subject_count
-
- def spot_count(_), do: 0
-
- def duration(%{duration: duration}) when not is_nil(duration) do
- case Integer.parse(duration) do
- :error -> 0
- {duration, _} -> duration
- end
- end
-
- def duration(_), do: 0
-
- def apply_label(%{survey_tool: tool}) when not is_nil(tool),
- do: dgettext("link-survey", "apply.cta.title")
-
- def apply_label(%{lab_tool: tool}) when not is_nil(tool),
- do: dgettext("link-lab", "apply.cta.title")
-
- def apply_label(_), do: ""
-
- def open_label(%{survey_tool: tool}) when not is_nil(tool),
- do: dgettext("link-survey", "open.cta.title")
-
- def open_label(%{lab_tool: tool}) when not is_nil(tool),
- do: dgettext("link-lab", "open.cta.title")
-
- def open_label(_), do: ""
-
- def ready?(%{survey_tool: tool}) when not is_nil(tool), do: Systems.Survey.Public.ready?(tool)
- def ready?(%{lab_tool: tool}) when not is_nil(tool), do: Systems.Lab.Public.ready?(tool)
-
- def external_path(%{survey_tool: survey_tool}, panl_id) do
- Survey.ToolModel.external_path(survey_tool, panl_id)
- end
-
- def external_path(_, _), do: nil
-
- def tool_id(%{survey_tool_id: tool_id}) when not is_nil(tool_id), do: tool_id
- def tool_id(%{lab_tool_id: tool_id}) when not is_nil(tool_id), do: tool_id
-
- def tool_form(%{survey_tool_id: tool_id}) when not is_nil(tool_id), do: Survey.ToolForm
- def tool_form(%{lab_tool_id: tool_id}) when not is_nil(tool_id), do: Lab.ToolForm
-
- def tool_field(%Survey.ToolModel{}), do: :survey_tool
- def tool_field(%Lab.ToolModel{}), do: :lab_tool
-
- def tool_id_field(%Survey.ToolModel{}), do: :survey_tool_id
- def tool_id_field(%Lab.ToolModel{}), do: :lab_tool_id
-end
diff --git a/core/systems/assignment/experiment_form.ex b/core/systems/assignment/info_form.ex
similarity index 54%
rename from core/systems/assignment/experiment_form.ex
rename to core/systems/assignment/info_form.ex
index 05715826a..e3a6e726d 100644
--- a/core/systems/assignment/experiment_form.ex
+++ b/core/systems/assignment/info_form.ex
@@ -1,12 +1,11 @@
-defmodule Systems.Assignment.ExperimentForm do
+defmodule Systems.Assignment.InfoForm do
use CoreWeb.LiveForm
use Frameworks.Pixel.Form.CheckboxHelpers
alias Core.Enums.Devices
- import Frameworks.Pixel.Form
-
- alias Frameworks.Pixel.Selector
+ # import Frameworks.Pixel.Form
+ # alias Frameworks.Pixel.Selector
alias Frameworks.Pixel.Text
alias Systems.{
@@ -52,7 +51,7 @@ defmodule Systems.Assignment.ExperimentForm do
%{id: id, entity: entity},
socket
) do
- changeset = Assignment.ExperimentModel.changeset(entity, :create, %{})
+ changeset = Assignment.InfoModel.changeset(entity, :create, %{})
{
:ok,
@@ -80,7 +79,7 @@ defmodule Systems.Assignment.ExperimentForm do
# Handle Events
@impl true
- def handle_event("save", %{"experiment_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
+ def handle_event("save", %{"info_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
{
:noreply,
socket
@@ -91,7 +90,7 @@ defmodule Systems.Assignment.ExperimentForm do
# Saving
def save(socket, entity, type, attrs) do
- changeset = Assignment.ExperimentModel.changeset(entity, type, attrs)
+ changeset = Assignment.InfoModel.changeset(entity, type, attrs)
socket
|> save(changeset)
@@ -104,7 +103,7 @@ defmodule Systems.Assignment.ExperimentForm do
def validate_for_publish(%{assigns: %{id: id, entity: entity}} = socket) do
changeset =
- Assignment.ExperimentModel.operational_changeset(entity, %{})
+ Assignment.InfoModel.operational_changeset(entity, %{})
|> Map.put(:action, :validate_for_publish)
send(self(), %{id: id, ready?: changeset.valid?})
@@ -117,43 +116,48 @@ defmodule Systems.Assignment.ExperimentForm do
def render(assigns) do
~H"""
<%= @vm.text %>
<.spacing value="L" />
- <%= if @experiment do %>
+ <%= if @task do %>
<.live_component
- id={@experiment.id}
- module={@experiment.view}
- {@experiment.model}
+ id={@task.id}
+ module={@task.view}
+ {@task.model}
/>
<% end %>
diff --git a/core/systems/assignment/model.ex b/core/systems/assignment/model.ex
index 6e071ea05..cb72b8ee5 100644
--- a/core/systems/assignment/model.ex
+++ b/core/systems/assignment/model.ex
@@ -3,15 +3,22 @@ defmodule Systems.Assignment.Model do
The assignment type.
"""
use Ecto.Schema
+ use Frameworks.Utility.Schema
+
import Ecto.Changeset
alias Systems.{
Assignment,
+ Workflow,
Budget
}
schema "assignments" do
- belongs_to(:assignable_experiment, Assignment.ExperimentModel)
+ field(:special, Ecto.Atom)
+ field(:status, Ecto.Enum, values: Assignment.Status.values(), default: :concept)
+
+ belongs_to(:info, Assignment.InfoModel)
+ belongs_to(:workflow, Workflow.Model)
belongs_to(:crew, Systems.Crew.Model)
belongs_to(:budget, Budget.Model, on_replace: :update)
belongs_to(:auth_node, Core.Authorization.Node)
@@ -29,12 +36,18 @@ defmodule Systems.Assignment.Model do
timestamps()
end
- @fields ~w()a
+ @fields ~w(special status)a
defimpl Frameworks.GreenLight.AuthorizationNode do
def id(assignment), do: assignment.auth_node_id
end
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Concept.System.director(director)
+ end
+
+ def auth_tree(%Assignment.Model{auth_node: auth_node}), do: auth_node
+
def changeset(assignment, nil), do: changeset(assignment, %{})
def changeset(assignment, %Budget.Model{id: budget_id}) do
@@ -50,26 +63,23 @@ defmodule Systems.Assignment.Model do
def flatten(assignment) do
assignment
- |> Map.take([:id, :crew, :excluded, :director])
- |> Map.put(:assignable, assignable(assignment))
+ |> Map.take([:id, :info, :workflow, :crew, :budget, :excluded, :director])
+ |> Map.put(:tool, tool(assignment))
end
- def assignable(%{assignable: assignable}) when not is_nil(assignable), do: assignable
- def assignable(%{assignable_experiment: assignable}) when not is_nil(assignable), do: assignable
-
- def assignable(%{id: id}) do
- raise "no assignable object available for assignment #{id}"
+ def tool(%{workflow: workflow}) when not is_nil(workflow) do
+ [tool | _] = Workflow.Model.flatten(workflow)
+ tool
end
- def preload_graph(:full) do
+ def tool(_), do: nil
+
+ def preload_graph(:down) do
[
- :crew,
:excluded,
- assignable_experiment: [
- auth_node: [:role_assignments],
- lab_tool: [:time_slots],
- survey_tool: [auth_node: [:role_assignments]]
- ],
+ info: [],
+ crew: [:tasks, :members, :auth_node],
+ workflow: Workflow.Model.preload_graph(:down),
budget: [:currency, :fund, :reserve],
auth_node: [:role_assignments]
]
diff --git a/core/systems/assignment/start_view.ex b/core/systems/assignment/start_view.ex
new file mode 100644
index 000000000..26d12a84c
--- /dev/null
+++ b/core/systems/assignment/start_view.ex
@@ -0,0 +1,37 @@
+defmodule Systems.Assignment.StartView do
+ use CoreWeb, :html
+
+ alias Frameworks.Pixel.Align
+ alias Frameworks.Pixel.Button
+ alias Frameworks.Pixel.Text
+
+ attr(:id, :any, required: true)
+ attr(:title, :string, required: true)
+ attr(:icon, :string, required: true)
+ attr(:description, :string, required: true)
+ attr(:button, :map, required: true)
+
+ def start_view(assigns) do
+ ~H"""
+ <%= @title %>
<.spacing value="XS" />
-
- <%= dgettext("eyra-campaign", "campaign.create.tooltype.label") %>
+
+ <%= dgettext("eyra-campaign", "campaign.create.template.label") %>
<.spacing value="XXS" />
<.live_component
module={Selector}
- id={:tool_type_selector}
- items={@tool_type_labels}
+ id={:template_selector}
+ items={@template_labels}
type={:radio}
optional?={false}
parent={%{type: __MODULE__, id: @id}}
diff --git a/core/systems/campaign/funding_view.ex b/core/systems/campaign/funding_view.ex
index 1edb04ab4..5bd2884ed 100644
--- a/core/systems/campaign/funding_view.ex
+++ b/core/systems/campaign/funding_view.ex
@@ -140,7 +140,7 @@ defmodule Systems.Campaign.FundingView do
%{
assigns: %{
submission: %{pool: %{currency: currency}},
- assignment: %{assignable_experiment: %{duration: duration}},
+ assignment: %{assignable_inquiry: %{duration: duration}},
locale: locale
}
} = socket
diff --git a/core/systems/campaign/model.ex b/core/systems/campaign/model.ex
index b78fbbfd2..d275931d2 100644
--- a/core/systems/campaign/model.ex
+++ b/core/systems/campaign/model.ex
@@ -58,7 +58,7 @@ defmodule Systems.Campaign.Model do
def submission(%{submissions: []}), do: nil
def submission(_), do: raise("No support for multiple submissions yet")
- def preload_graph(:full) do
+ def preload_graph(:down) do
[
promotion: [auth_node: [:role_assignments]],
auth_node: [:role_assignments],
@@ -67,7 +67,7 @@ defmodule Systems.Campaign.Model do
:criteria,
pool: Pool.Model.preload_graph([:currency, :org, :auth_node, :participants])
],
- promotable_assignment: Assignment.Model.preload_graph(:full)
+ promotable_assignment: Assignment.Model.preload_graph(:down)
]
end
@@ -94,7 +94,6 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
Campaign,
Promotion,
Assignment,
- Crew,
Pool,
Budget
}
@@ -120,7 +119,7 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
},
promotable:
%{
- assignable_experiment: %{
+ info: %{
duration: duration,
language: language
}
@@ -189,7 +188,7 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
},
promotable:
%{
- assignable_experiment: %{
+ info: %{
duration: duration,
language: language
}
@@ -291,35 +290,24 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
title: title,
image_id: image_id
},
- promotable:
- %{
- crew: crew
- } = assignment
+ promotable: assignment
},
{Link.Marketplace, _},
%{current_user: user}
) do
- task = task(crew, user)
- tag = tag(task)
- subtitle = subtitle(task, user, assignment)
-
- quick_summary =
- case task do
- %{updated_at: updated_at} ->
- updated_at
- |> CoreWeb.UI.Timestamp.apply_timezone()
- |> CoreWeb.UI.Timestamp.humanize()
-
- _ ->
- "?"
- end
+ status = Assignment.Public.status(assignment, user)
+ tag = tag(status)
+ subtitle = subtitle(status, user, assignment)
+
+ timestamp = Assignment.Public.timestamp(assignment, user)
+ quick_summary = get_quick_summary(timestamp)
image_info = ImageHelpers.get_image_info(image_id, 120, 115)
image = %{type: :catalog, info: image_info}
%{
id: id,
- path: ~p"/assignment/#{assignment.id}",
+ path: ~p"/assignment/#{assignment.id}/landing",
title: title,
subtitle: subtitle,
tag: tag,
@@ -341,7 +329,7 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
} = promotion,
promotable:
%{
- assignable_experiment: %{
+ info: %{
subject_count: target_subject_count
}
} = assignment
@@ -420,7 +408,7 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
{Link.Console.Page, :contribution},
user
) do
- path = ~p"/assignment/#{assignment.id}"
+ path = ~p"/assignment/#{assignment.id}/landing"
vm(campaign, :contribution, user, path)
end
@@ -440,19 +428,17 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
title: title,
image_id: image_id
},
- promotable:
- %{
- crew: crew
- } = assignment
+ promotable: assignment
},
:contribution,
user,
path
) do
- %{updated_at: updated_at} = task = task(crew, user)
- tag = tag(task)
- subtitle = subtitle(task, user, assignment)
- quick_summary = get_quick_summary(updated_at)
+ status = Assignment.Public.status(assignment, user)
+ tag = tag(status)
+ subtitle = subtitle(status, user, assignment)
+ timestamp = Assignment.Public.timestamp(assignment, user)
+ quick_summary = get_quick_summary(timestamp)
image_info = ImageHelpers.get_image_info(image_id, 120, 115)
image = %{type: :catalog, info: image_info}
@@ -469,7 +455,7 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
defp required_funding(%{
submission: %{reward_value: reward_value, pool: %{currency: currency}},
- promotable: %{assignable_experiment: %{subject_count: subject_count}}
+ promotable: %{assignable_inquiry: %{subject_count: subject_count}}
}) do
reward_value = guard_nil(reward_value, :integer)
subject_count = guard_nil(subject_count, :integer)
@@ -482,7 +468,7 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
defp funding_tag(%{
submission: %{reward_value: reward_value},
- promotable: %{budget: budget, assignable_experiment: %{subject_count: subject_count}}
+ promotable: %{budget: budget, assignable_inquiry: %{subject_count: subject_count}}
}) do
reward_value = guard_nil(reward_value, :integer)
subject_count = guard_nil(subject_count, :integer)
@@ -500,19 +486,7 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
end
end
- defp task(crew, user) do
- case Crew.Public.get_member!(crew, user) do
- nil -> nil
- member -> Crew.Public.get_task(crew, member)
- end
- end
-
- defp tag(nil),
- do: %{text: dgettext("eyra-marketplace", "assignment.status.expired.label"), type: :disabled}
-
- # defp tag(%{expired: true} = _task), do: %{text: dgettext("eyra-marketplace", "assignment.status.expired.label"), type: :disabled}
-
- defp tag(%{status: status} = _task) do
+ defp tag(status) do
case status do
:pending ->
%{text: dgettext("eyra-marketplace", "assignment.status.pending.label"), type: :warning}
@@ -528,16 +502,11 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
:rejected ->
%{text: dgettext("eyra-marketplace", "assignment.status.rejected.label"), type: :delete}
-
- _ ->
- %{text: "?", type: :disabled}
end
end
- defp subtitle(nil, _, _), do: "?"
-
defp subtitle(
- %{status: status} = _task,
+ status,
user,
assignment
) do
@@ -560,12 +529,11 @@ defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Campaign.Model do
:rejected ->
dgettext("eyra-marketplace", "assignment.status.rejected.subtitle")
-
- _ ->
- dgettext("eyra-marketplace", "reward.label", value: 0)
end
end
+ defp get_quick_summary(nil), do: "?"
+
defp get_quick_summary(updated_at) do
updated_at
|> CoreWeb.UI.Timestamp.apply_timezone()
diff --git a/core/systems/campaign/monitor_view.ex b/core/systems/campaign/monitor_view.ex
index 77e6775c0..d6818d86f 100644
--- a/core/systems/campaign/monitor_view.ex
+++ b/core/systems/campaign/monitor_view.ex
@@ -5,13 +5,15 @@ defmodule Systems.Campaign.MonitorView do
import CoreWeb.UI.Popup
import CoreWeb.UI.ProgressBar
alias CoreWeb.UI.Timestamp
+ alias Core.Authorization
import Systems.Campaign.MonitorTableView
alias Systems.{
Pool,
Crew,
Campaign,
- Lab
+ Lab,
+ Workflow
}
alias Frameworks.Pixel.Text
@@ -50,7 +52,7 @@ defmodule Systems.Campaign.MonitorView do
%{
id: id,
entity: entity,
- attention_list_enabled?: attention_list_enabled?,
+ attention_list_enabled?: true,
labels: labels
},
socket
@@ -61,7 +63,7 @@ defmodule Systems.Campaign.MonitorView do
|> assign(
id: id,
entity: entity,
- attention_list_enabled?: attention_list_enabled?,
+ attention_list_enabled?: true,
labels: labels,
reject_task: nil
)
@@ -81,7 +83,7 @@ defmodule Systems.Campaign.MonitorView do
defp update_entity(%{assigns: %{entity: %{id: id}}} = socket) do
entity =
- Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
+ Campaign.Public.get!(id, Campaign.Model.preload_graph(:down))
|> Campaign.Model.flatten()
socket |> assign(entity: entity)
@@ -150,22 +152,22 @@ defmodule Systems.Campaign.MonitorView do
<%= if not @vm.active? do %>
<.empty
- title={dgettext("link-survey", "monitor.empty.title")}
- body={dgettext("link-survey", "monitor.empty.description")}
+ title={dgettext("eyra-alliance", "monitor.empty.title")}
+ body={dgettext("eyra-alliance", "monitor.empty.description")}
illustration="members"
/>
<% else %>
- <%= if lab_tool(@vm.experiment) do %>
+ <%= if lab_tool(@vm.tool) do %>
<.live_component module={Lab.CheckInView}
id={:search_subject_view}
- tool={lab_tool(@vm.experiment)}
+ tool={lab_tool(@vm.tool)}
parent={%{type: __MODULE__, id: @id}}
/>
<.spacing value="XL" />
<% end %>
<%= dgettext("link-monitor", "phase1.title") %>
- <%= dgettext("link-survey", "status.title") %>
+ <%= dgettext("eyra-alliance", "status.title") %>
<%= @vm.participated_count %>/<%= @vm.progress.size %>
<.spacing value="M" />
@@ -268,13 +268,13 @@ defmodule Systems.Campaign.OverviewPage do
<.spacing value="L" />
<% else %>
<.empty
- title={dgettext("link-survey", "empty.title")}
- body={dgettext("link-survey", "empty.description")}
+ title={dgettext("eyra-alliance", "empty.title")}
+ body={dgettext("eyra-alliance", "empty.description")}
illustration="cards"
/>
<.spacing value="L" />
<% end %>
diff --git a/core/systems/citizen/_director.ex b/core/systems/citizen/_director.ex
index b7a2ee770..5c3b3d39f 100644
--- a/core/systems/citizen/_director.ex
+++ b/core/systems/citizen/_director.ex
@@ -4,7 +4,7 @@ defmodule Systems.Citizen.Director do
defexception [:message]
end
- @behaviour Systems.Pool.External
+ @behaviour Frameworks.Concept.PoolDirector
alias CoreWeb.UI.Timestamp
diff --git a/core/systems/citizen/_presenter.ex b/core/systems/citizen/_presenter.ex
index 718f11453..b4d7c512d 100644
--- a/core/systems/citizen/_presenter.ex
+++ b/core/systems/citizen/_presenter.ex
@@ -1,25 +1,18 @@
defmodule Systems.Citizen.Presenter do
- use Systems.Presenter
+ @behaviour Frameworks.Concept.Presenter
alias Systems.{
Citizen,
Pool
}
- @impl true
- def view_model(id, Pool.SubmissionPage = page, assigns) when is_integer(id) do
- Pool.Public.get_submission!(id, pool: Pool.Model.preload_graph([:org, :currency]))
- |> view_model(page, assigns)
- end
-
@impl true
def view_model(%Pool.SubmissionModel{} = submission, Pool.SubmissionPage, assigns) do
Citizen.Pool.SubmissionPageBuilder.view_model(submission, assigns)
end
@impl true
- def view_model(id, Pool.DetailPage, assigns) do
- pool = Pool.Public.get!(id, Pool.Model.preload_graph([:org, :currency, :participants]))
+ def view_model(%Pool.Model{} = pool, Pool.DetailPage, assigns) do
Citizen.Pool.DetailPageBuilder.view_model(pool, assigns)
end
end
diff --git a/core/systems/citizen/pool/detail_page_builder.ex b/core/systems/citizen/pool/detail_page_builder.ex
index 9d83ed30e..41c8d3c0c 100644
--- a/core/systems/citizen/pool/detail_page_builder.ex
+++ b/core/systems/citizen/pool/detail_page_builder.ex
@@ -41,7 +41,7 @@ defmodule Systems.Citizen.Pool.DetailPageBuilder do
end
defp load_campaigns(pool) do
- preload = Campaign.Model.preload_graph(:full)
+ preload = Campaign.Model.preload_graph(:down)
Campaign.Public.list_submitted(pool, preload: preload)
|> Enum.map(&Campaign.Model.flatten(&1))
diff --git a/core/systems/citizen/pool/form.ex b/core/systems/citizen/pool/form.ex
index 113a14481..c743cc160 100644
--- a/core/systems/citizen/pool/form.ex
+++ b/core/systems/citizen/pool/form.ex
@@ -112,7 +112,7 @@ defmodule Systems.Citizen.Pool.Form do
%{assigns: %{id: id, locale: locale, currencies: currencies}} = socket
) do
{currency_selector, selected_currency} =
- Budget.Presenter.init_currency_selector(currencies, locale, %{type: __MODULE__, id: id})
+ Budget.CurrencySelector.init(currencies, locale, %{type: __MODULE__, id: id})
socket |> assign(currency_selector: currency_selector, selected_currency: selected_currency)
end
diff --git a/core/systems/citizen/pool/submission_page_builder.ex b/core/systems/citizen/pool/submission_page_builder.ex
index fe706888a..a6229e74b 100644
--- a/core/systems/citizen/pool/submission_page_builder.ex
+++ b/core/systems/citizen/pool/submission_page_builder.ex
@@ -30,7 +30,7 @@ defmodule Systems.Citizen.Pool.SubmissionPageBuilder do
preview_path = ~p"/promotion/#{promotion.id}?preview=true"
excluded_campaigns =
- Campaign.Public.list_excluded_campaigns([campaign], Campaign.Model.preload_graph(:full))
+ Campaign.Public.list_excluded_campaigns([campaign], Campaign.Model.preload_graph(:down))
|> Enum.map(&Campaign.Model.flatten(&1))
|> Enum.map(&Pool.Builders.CampaignItem.view_model(&1))
diff --git a/core/systems/content/page.ex b/core/systems/content/page.ex
new file mode 100644
index 000000000..3033549ed
--- /dev/null
+++ b/core/systems/content/page.ex
@@ -0,0 +1,221 @@
+defmodule Systems.Content.Page do
+ use CoreWeb, :html
+
+ import CoreWeb.Layouts.Workspace.Component
+ import CoreWeb.UI.PlainDialog
+ import CoreWeb.UI.Popup
+
+ alias CoreWeb.UI.Tabbar
+ alias CoreWeb.UI.Navigation
+ alias CoreWeb.UI.Responsive.Breakpoint
+
+ defp margin_x(:mobile), do: "mx-6"
+ defp margin_x(_), do: "mx-10"
+
+ attr(:title, :string, required: true)
+ attr(:menus, :map, required: true)
+ attr(:actions, :list, default: [])
+ attr(:more_actions, :list, default: [])
+ attr(:tabs, :list, default: [])
+ attr(:tabbar_id, :atom, required: true)
+ attr(:tabbar_size, :any, required: true)
+ attr(:initial_tab, :atom, default: nil)
+ attr(:show_errors, :boolean, default: false)
+ attr(:popup, :map, default: nil)
+ attr(:dialog, :map, default: nil)
+ attr(:breakpoint, :atom, default: nil)
+
+ def content_page(assigns) do
+ ~H"""
+ <.workspace title={@title} menus={@menus} >
+ <:top_bar>
+
+
+
+
+
+
- <.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself} >
- <.number_input
- form={form}
- field={:duration}
- label_text={dgettext("link-survey", "duration.label")}
- />
- <.spacing value="M" />
-
- <.number_input
- form={form}
- field={:subject_count}
- label_text={dgettext("link-survey", "config.nrofsubjects.label")}
- />
- <.spacing value="M" />
-
- <%= dgettext("link-survey", "language.title") %>
- <%= dgettext("link-survey", "languages.label") %>
- <.spacing value="S" />
- <.live_component
- module={Selector}
- id={:language}
- items={@language_labels}
- type={:radio}
- parent={%{type: __MODULE__, id: @id}}
- />
- <.spacing value="XL" />
-
- <%= dgettext("link-survey", "devices.title") %>
- <%= dgettext("link-survey", "devices.label") %>
- <.spacing value="S" />
- <.live_component
+
+
+ <%= dgettext("eyra-assignment", "form.title") %>
+
+ <%!-- <.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself} >
+ <.number_input
+ form={form}
+ field={:duration}
+ label_text={dgettext("eyra-assignment", "duration.label")}
+ />
+ <.spacing value="M" />
+
+ <.number_input
+ form={form}
+ field={:subject_count}
+ label_text={dgettext("eyra-assignment", "config.nrofsubjects.label")}
+ />
+ <.spacing value="M" />
+
+ <%= dgettext("eyra-assignment", "language.title") %>
+ <%= dgettext("eyra-assignment", "languages.label") %>
+ <.spacing value="S" />
+ <.live_component
module={Selector}
- id={:devices}
- type={:label}
- items={@device_labels}
- parent={%{type: __MODULE__, id: @id}} />
-
+ id={:language}
+ items={@language_labels}
+ type={:radio}
+ parent={%{type: __MODULE__, id: @id}}
+ />
+ <.spacing value="XL" />
+
+ <%= dgettext("eyra-assignment", "devices.title") %>
+ <%= dgettext("eyra-assignment", "devices.label") %>
+ <.spacing value="S" />
+ <.live_component
+ module={Selector}
+ id={:devices}
+ type={:label}
+ items={@device_labels}
+ parent={%{type: __MODULE__, id: @id}} />
+ --%>
+
"""
end
diff --git a/core/systems/assignment/info_model.ex b/core/systems/assignment/info_model.ex
new file mode 100644
index 000000000..f7329db04
--- /dev/null
+++ b/core/systems/assignment/info_model.ex
@@ -0,0 +1,76 @@
+defmodule Systems.Assignment.InfoModel do
+ use Ecto.Schema
+ use Frameworks.Utility.Schema
+ use Frameworks.Utility.Model
+
+ require Core.Enums.Devices
+ import Ecto.Changeset
+
+ alias Systems.{
+ Assignment
+ }
+
+ schema "assignment_info" do
+ field(:subject_count, :integer)
+ field(:duration, :string)
+ field(:language, :string)
+ field(:devices, {:array, Ecto.Enum}, values: Core.Enums.Devices.schema_values())
+ field(:ethical_approval, :boolean)
+ field(:ethical_code, :string)
+
+ has_one(:assignment, Assignment.Model, foreign_key: :id)
+
+ timestamps()
+ end
+
+ @operational_fields ~w(subject_count duration ethical_code ethical_approval devices)a
+ @fields @operational_fields ++ ~w(language)a
+
+ @required_fields ~w()a
+
+ @impl true
+ def operational_fields, do: @operational_fields
+
+ @impl true
+ def operational_validation(changeset) do
+ validate_true(changeset, :ethical_approval)
+ end
+
+ defp validate_true(changeset, field) do
+ case get_field(changeset, field) do
+ true -> changeset
+ _ -> add_error(changeset, field, "is not true")
+ end
+ end
+
+ def changeset(tool, :auto_save, params) do
+ tool
+ |> cast(params, @fields)
+ |> validate_required(@required_fields)
+ end
+
+ def changeset(tool, _, params) do
+ tool
+ |> cast(params, @fields)
+ end
+
+ def languages(%{language: language}) when not is_nil(language), do: [language]
+ def languages(_), do: []
+
+ def devices(%{devices: devices}) when not is_nil(devices), do: devices
+ def devices(_), do: []
+
+ def spot_count(%{subject_count: subject_count}) when not is_nil(subject_count),
+ do: subject_count
+
+ def spot_count(_), do: 0
+
+ def duration(%{duration: duration}) when not is_nil(duration) do
+ case Integer.parse(duration) do
+ :error -> 0
+ {duration, _} -> duration
+ end
+ end
+
+ def duration(_), do: 0
+end
diff --git a/core/systems/assignment/landing_page.ex b/core/systems/assignment/landing_page.ex
index b232c60cf..7009deefa 100644
--- a/core/systems/assignment/landing_page.ex
+++ b/core/systems/assignment/landing_page.ex
@@ -5,6 +5,7 @@ defmodule Systems.Assignment.LandingPage do
use CoreWeb, :live_view
use CoreWeb.UI.PlainDialog
use CoreWeb.Layouts.Workspace.Component, :assignment
+ use Systems.Observatory.Public
alias Frameworks.Pixel.Text
alias Frameworks.Pixel.Card
@@ -38,7 +39,7 @@ defmodule Systems.Assignment.LandingPage do
dialog: nil
)
|> observe_view_model()
- |> update_experiment_view()
+ |> update_task_view()
|> update_menus()
}
end
@@ -47,32 +48,32 @@ defmodule Systems.Assignment.LandingPage do
def handle_view_model_updated(socket) do
socket
- |> update_experiment_view()
+ |> update_task_view()
|> update_menus()
end
- defp update_experiment_view(
+ defp update_task_view(
%{
assigns: %{
- vm: %{experiment: %{view: view, id: id, model: model}},
- experiment: experiment
+ vm: %{task: %{view: view, id: id, model: model}},
+ task: task
}
} = socket
)
- when not is_nil(experiment) do
- # send update message to existing experiment view
+ when not is_nil(task) do
+ # send update message to existing task view
send_update(view, id: id, model: model)
socket
end
- defp update_experiment_view(%{assigns: %{vm: %{experiment: experiment}}} = socket) do
- # initialize experiment view
- socket |> assign(experiment: experiment)
+ defp update_task_view(%{assigns: %{vm: %{task: task}}} = socket) do
+ # initialize task view
+ socket |> assign(task: task)
end
- defp update_experiment_view(socket) do
- # disable experiment view
- socket |> assign(experiment: nil)
+ defp update_task_view(socket) do
+ # disable task view
+ socket |> assign(task: nil)
end
defp cancel(socket) do
@@ -160,11 +161,11 @@ defmodule Systems.Assignment.LandingPage do
+
+
+
+
+
+ """
+ end
+end
diff --git a/core/systems/data_donation/tool_status.ex b/core/systems/assignment/status.ex
similarity index 56%
rename from core/systems/data_donation/tool_status.ex
rename to core/systems/assignment/status.ex
index 6f0d999ee..bd26bf2a9 100644
--- a/core/systems/data_donation/tool_status.ex
+++ b/core/systems/assignment/status.ex
@@ -1,3 +1,3 @@
-defmodule Systems.DataDonation.ToolStatus do
+defmodule Systems.Assignment.Status do
def values, do: [:concept, :online, :offline, :idle]
end
diff --git a/core/systems/assignment/templates.ex b/core/systems/assignment/templates.ex
new file mode 100644
index 000000000..e6a611439
--- /dev/null
+++ b/core/systems/assignment/templates.ex
@@ -0,0 +1,7 @@
+defmodule Systems.Assignment.Templates do
+ @moduledoc """
+ Defines different templates used by Systems.Assignment.Assembly to initialize specials.
+ """
+ use Core.Enums.Base,
+ {:templates, [:online, :lab, :data_donation]}
+end
diff --git a/core/systems/assignment/tool_types.ex b/core/systems/assignment/tool_types.ex
deleted file mode 100644
index 7a8ae5388..000000000
--- a/core/systems/assignment/tool_types.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Systems.Assignment.ToolTypes do
- @moduledoc """
- Defines types of assignment.
- """
- use Core.Enums.Base,
- {:tool_types, [:online, :lab]}
-end
diff --git a/core/systems/benchmark/_presenter.ex b/core/systems/benchmark/_presenter.ex
index 1151f47d5..51f3f8d91 100644
--- a/core/systems/benchmark/_presenter.ex
+++ b/core/systems/benchmark/_presenter.ex
@@ -1,16 +1,10 @@
defmodule Systems.Benchmark.Presenter do
- use Systems.Presenter
+ @behaviour Frameworks.Concept.Presenter
alias Systems.{
Benchmark
}
- @impl true
- def view_model(id, Benchmark.ToolPage = page, assigns) when is_number(id) do
- Benchmark.Public.get_tool!(id, Benchmark.ToolModel.preload_graph(:down))
- |> view_model(page, assigns)
- end
-
@impl true
def view_model(%Benchmark.ToolModel{} = tool, page, assigns) do
builder(page).view_model(tool, assigns)
diff --git a/core/systems/benchmark/_public.ex b/core/systems/benchmark/_public.ex
index 6ddd673ef..a5ed64563 100644
--- a/core/systems/benchmark/_public.ex
+++ b/core/systems/benchmark/_public.ex
@@ -49,10 +49,7 @@ defmodule Systems.Benchmark.Public do
|> set_tool_status(status)
end
- def create(
- %{title: _, director: _} = attrs,
- %Authorization.Node{} = auth_node
- ) do
+ def prepare_tool(%{} = attrs, auth_node \\ Core.Authorization.prepare_node()) do
attrs = Map.put(attrs, :status, :concept)
%Benchmark.ToolModel{}
@@ -64,7 +61,7 @@ defmodule Systems.Benchmark.Public do
tool = Benchmark.Public.get_tool!(tool_id)
Multi.new()
- |> Multi.insert(:auth_node, Authorization.make_node())
+ |> Multi.insert(:auth_node, Authorization.prepare_node())
|> Multi.insert(:spot, fn %{auth_node: auth_node} ->
%Benchmark.SpotModel{}
|> Benchmark.SpotModel.changeset(%{name: displayname})
diff --git a/core/systems/benchmark/_routes.ex b/core/systems/benchmark/_routes.ex
index d6539f61e..9dafc506c 100644
--- a/core/systems/benchmark/_routes.ex
+++ b/core/systems/benchmark/_routes.ex
@@ -4,8 +4,10 @@ defmodule Systems.Benchmark.Routes do
scope "/benchmark", Systems.Benchmark do
pipe_through([:browser, :require_authenticated_user])
- get("/:id", ToolController, :ensure_spot)
+ live("/:id/content", ContentPage)
live("/:id/:spot", ToolPage)
+
+ get("/:id", ToolController, :ensure_spot)
get("/:id/export/submissions", ExportController, :submissions)
end
diff --git a/core/systems/benchmark/content_page.ex b/core/systems/benchmark/content_page.ex
new file mode 100644
index 000000000..27dbd907c
--- /dev/null
+++ b/core/systems/benchmark/content_page.ex
@@ -0,0 +1,50 @@
+defmodule Systems.Benchmark.ContentPage do
+ use Systems.Content.Page
+
+ alias Systems.{
+ Benchmark
+ }
+
+ @impl true
+ def get_authorization_context(%{"id" => id}, _session, _socket) do
+ Benchmark.Public.get_tool!(id)
+ end
+
+ @impl true
+ def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
+ model =
+ Benchmark.Public.get_tool!(String.to_integer(id), Benchmark.ToolModel.preload_graph(:down))
+
+ tabbar_id = "benchmark_content/#{id}"
+
+ {
+ :ok,
+ socket |> initialize(id, model, tabbar_id, initial_tab, locale)
+ }
+ end
+
+ @impl true
+ def mount(params, session, socket) do
+ mount(Map.put(params, "tab", nil), session, socket)
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.content_page
+ title={@vm.title}
+ menus={@menus}
+ tabs={@vm.tabs}
+ actions={@actions}
+ more_actions={@more_actions}
+ initial_tab={@initial_tab}
+ tabbar_id={@tabbar_id}
+ tabbar_size={@tabbar_size}
+ breakpoint={@breakpoint}
+ popup={@popup}
+ dialog={@dialog}
+ show_errors={@show_errors}
+ />
+ """
+ end
+end
diff --git a/core/systems/project/content_page_builder/item_benchmark.ex b/core/systems/benchmark/content_page_builder.ex
similarity index 55%
rename from core/systems/project/content_page_builder/item_benchmark.ex
rename to core/systems/benchmark/content_page_builder.ex
index 1fba95aa7..5c3b346b7 100644
--- a/core/systems/project/content_page_builder/item_benchmark.ex
+++ b/core/systems/benchmark/content_page_builder.ex
@@ -1,30 +1,23 @@
-defmodule Systems.Project.ContentPageBuilder.ItemBenchmark do
+defmodule Systems.Benchmark.ContentPageBuilder do
import CoreWeb.Gettext
alias Systems.{
- Project,
Benchmark
}
@tabs [:config, :invite, :submissions, :leaderboard]
def view_model(
- %{
- id: id,
- tool_ref: %{
- benchmark_tool: tool
- }
- } = item,
+ %{id: id} = tool,
assigns
) do
show_errors = show_errors(tool, assigns)
- tabs = create_tabs(item, show_errors, assigns)
+ tabs = create_tabs(tool, show_errors, assigns)
action_map = action_map(tool)
actions = actions(tool, action_map)
%{
id: id,
- tool_id: tool.id,
title: dgettext("eyra-benchmark", "content.title"),
tabs: tabs,
actions: actions,
@@ -38,25 +31,85 @@ defmodule Systems.Project.ContentPageBuilder.ItemBenchmark do
false
end
- defp action_map(%{id: tool_id}) do
+ defp action_map(%{id: id}) do
+ preview_action = %{type: :http_get, to: "/benchmark/#{id}", target: "_blank"}
+ publish_action = %{type: :send, event: "action_click", item: :publish}
+ retract_action = %{type: :send, event: "action_click", item: :retract}
+ close_action = %{type: :send, event: "action_click", item: :close}
+ open_action = %{type: :send, event: "action_click", item: :open}
+
%{
preview: %{
- action: %{type: :http_get, to: "/benchmark/#{tool_id}", target: "_blank"}
+ label: %{
+ action: preview_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-benchmark", "preview.button"),
+ bg_color: "bg-primary"
+ }
+ },
+ icon: %{
+ action: preview_action,
+ face: %{type: :icon, icon: :preview, alt: dgettext("eyra-benchmark", "preview.button")}
+ }
},
publish: %{
- action: %{type: :send, event: "action_click", item: :publish},
+ label: %{
+ action: publish_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-benchmark", "publish.button"),
+ bg_color: "bg-success"
+ }
+ },
+ icon: %{
+ action: publish_action,
+ face: %{type: :icon, icon: :publish, alt: dgettext("eyra-benchmark", "publish.button")}
+ },
handle_click: &handle_publish/1
},
retract: %{
- action: %{type: :send, event: "action_click", item: :retract},
+ label: %{
+ action: retract_action,
+ face: %{
+ type: :secondary,
+ label: dgettext("eyra-benchmark", "retract.button"),
+ text_color: "text-error",
+ border_color: "border-error"
+ }
+ },
+ icon: %{
+ action: retract_action,
+ face: %{type: :icon, icon: :retract, alt: dgettext("eyra-benchmark", "retract.button")}
+ },
handle_click: &handle_retract/1
},
close: %{
- action: %{type: :send, event: "action_click", item: :close},
+ label: %{
+ action: close_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-benchmark", "close.button")
+ }
+ },
+ icon: %{
+ action: close_action,
+ face: %{type: :icon, icon: :close, alt: dgettext("eyra-benchmark", "close.button")}
+ },
handle_click: &handle_close/1
},
open: %{
- action: %{type: :send, event: "action_click", item: :open},
+ label: %{
+ action: open_action,
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-benchmark", "open.button")
+ }
+ },
+ icon: %{
+ action: open_action,
+ face: %{type: :icon, icon: :open, alt: dgettext("eyra-benchmark", "open.button")}
+ },
handle_click: &handle_open/1
}
}
@@ -88,18 +141,18 @@ defmodule Systems.Project.ContentPageBuilder.ItemBenchmark do
socket |> set_tool_status(:concept)
end
- defp set_tool_status(%{assigns: %{vm: %{tool_id: tool_id}}} = socket, status) do
- Benchmark.Public.set_tool_status(tool_id, status)
+ defp set_tool_status(%{assigns: %{vm: %{id: id}}} = socket, status) do
+ Benchmark.Public.set_tool_status(id, status)
socket
end
- defp create_tabs(item, show_errors, assigns) do
- Enum.map(@tabs, &create_tab(&1, item, show_errors, assigns))
+ defp create_tabs(tool, show_errors, assigns) do
+ Enum.map(@tabs, &create_tab(&1, tool, show_errors, assigns))
end
defp create_tab(
:config,
- %{tool_ref: %{benchmark_tool: %{id: _} = tool}} = item,
+ tool,
show_errors,
_assigns
) do
@@ -112,21 +165,16 @@ defmodule Systems.Project.ContentPageBuilder.ItemBenchmark do
title: dgettext("eyra-project", "tabbar.item.config"),
forward_title: dgettext("eyra-project", "tabbar.item.config.forward"),
type: :fullpage,
- live_component: Project.ItemConfigForm,
+ live_component: Benchmark.ToolForm,
props: %{
- entity: item,
- sub_form: %{
- id: :tool_form,
- module: Benchmark.ToolForm,
- entity: tool
- }
+ entity: tool
}
}
end
defp create_tab(
:submissions,
- %{tool_ref: %{benchmark_tool: benchmark_tool}},
+ tool,
show_errors,
_assigns
) do
@@ -141,14 +189,14 @@ defmodule Systems.Project.ContentPageBuilder.ItemBenchmark do
type: :fullpage,
live_component: Benchmark.SubmissionOverview,
props: %{
- entity: benchmark_tool
+ entity: tool
}
}
end
defp create_tab(
:leaderboard,
- %{tool_ref: %{benchmark_tool: benchmark_tool}},
+ tool,
show_errors,
_assigns
) do
@@ -163,19 +211,19 @@ defmodule Systems.Project.ContentPageBuilder.ItemBenchmark do
type: :fullpage,
live_component: Benchmark.LeaderboardOverview,
props: %{
- entity: benchmark_tool
+ entity: tool
}
}
end
defp create_tab(
:invite,
- %{tool_ref: %{benchmark_tool: %{id: tool_id}}},
+ %{id: id},
show_errors,
%{uri_origin: uri_origin}
) do
ready? = false
- url = uri_origin <> "/benchmark/#{tool_id}"
+ url = uri_origin <> "/benchmark/#{id}"
%{
id: :invite_form,
diff --git a/core/systems/benchmark/tool_model.ex b/core/systems/benchmark/tool_model.ex
index 04ac42fe9..ed65b60ee 100644
--- a/core/systems/benchmark/tool_model.ex
+++ b/core/systems/benchmark/tool_model.ex
@@ -6,6 +6,7 @@ defmodule Systems.Benchmark.ToolModel do
use Frameworks.Utility.Schema
import Ecto.Changeset
+ import CoreWeb.Gettext
alias Systems.{
Benchmark
@@ -18,7 +19,7 @@ defmodule Systems.Benchmark.ToolModel do
field(:data_set, :string)
field(:template_repo, :string)
field(:deadline, :string)
- field(:director, Ecto.Enum, values: [:project])
+ field(:director, Ecto.Enum, values: [:project, :assignment])
belongs_to(:auth_node, Core.Authorization.Node)
has_many(:spots, Benchmark.SpotModel, foreign_key: :tool_id)
has_many(:leaderboards, Benchmark.LeaderboardModel, foreign_key: :tool_id)
@@ -56,4 +57,37 @@ defmodule Systems.Benchmark.ToolModel do
defimpl Frameworks.GreenLight.AuthorizationNode do
def id(tool), do: tool.auth_node_id
end
+
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Concept.System.director(director)
+ end
+
+ def ready?(tool) do
+ changeset =
+ changeset(tool, %{})
+ |> validate()
+
+ changeset.valid?()
+ end
+
+ defimpl Frameworks.Concept.ToolModel do
+ alias Systems.Benchmark
+ def key(_), do: :benchmark
+ def auth_tree(%{auth_node: auth_node}), do: auth_node
+ def apply_label(_), do: dgettext("eyra-benchmark", "apply.cta.title")
+ def open_label(_), do: dgettext("eyra-benchmark", "open.cta.title")
+ def ready?(tool), do: Benchmark.ToolModel.ready?(tool)
+ def form(_), do: Benchmark.Form
+ def launcher(_), do: %{function: fn _ -> nil end, props: %{}}
+
+ def task_labels(_) do
+ %{
+ pending: dgettext("eyra-benchmark", "pending.label"),
+ participated: dgettext("eyra-benchmark", "participated.label")
+ }
+ end
+
+ def attention_list_enabled?(_t), do: false
+ def group_enabled?(_t), do: true
+ end
end
diff --git a/core/systems/benchmark/tool_page.ex b/core/systems/benchmark/tool_page.ex
index 35bffe097..c95e1630c 100644
--- a/core/systems/benchmark/tool_page.ex
+++ b/core/systems/benchmark/tool_page.ex
@@ -1,6 +1,7 @@
defmodule Systems.Benchmark.ToolPage do
use CoreWeb, :live_view
use CoreWeb.Layouts.Stripped.Component, :projects
+ use Systems.Observatory.Public
alias Frameworks.Pixel.{
Align,
@@ -20,7 +21,8 @@ defmodule Systems.Benchmark.ToolPage do
@impl true
def mount(%{"id" => id, "spot" => spot_id}, _session, socket) do
- model = %{id: String.to_integer(id), director: :benchmark}
+ model =
+ Benchmark.Public.get_tool!(String.to_integer(id), Benchmark.ToolModel.preload_graph(:down))
{
:ok,
diff --git a/core/systems/budget/_public.ex b/core/systems/budget/_public.ex
index cdfb32b07..bae32d955 100644
--- a/core/systems/budget/_public.ex
+++ b/core/systems/budget/_public.ex
@@ -271,7 +271,7 @@ defmodule Systems.Budget.Public do
def payout_reward(idempotence_key) when is_binary(idempotence_key) do
case get_reward(idempotence_key, Budget.RewardModel.preload_graph(:full)) do
- nil -> raise BudgetError, "No reward available to payout"
+ nil -> Logger.warn("No reward available to payout for #{idempotence_key}")
reward -> make_payment(reward)
end
end
diff --git a/core/systems/budget/_presenter.ex b/core/systems/budget/currency_selector.ex
similarity index 83%
rename from core/systems/budget/_presenter.ex
rename to core/systems/budget/currency_selector.ex
index 148698da6..91e1a0f5f 100644
--- a/core/systems/budget/_presenter.ex
+++ b/core/systems/budget/currency_selector.ex
@@ -1,9 +1,9 @@
-defmodule Systems.Budget.Presenter do
+defmodule Systems.Budget.CurrencySelector do
alias Systems.{
Budget
}
- def init_currency_selector(currencies, locale, parent) when is_list(currencies) do
+ def init(currencies, locale, parent) when is_list(currencies) do
options = currencies |> Enum.map(&to_option(&1, locale))
{selected_option_index, selected_currency} =
diff --git a/core/systems/budget/form.ex b/core/systems/budget/form.ex
index 2419e8b37..291624892 100644
--- a/core/systems/budget/form.ex
+++ b/core/systems/budget/form.ex
@@ -107,7 +107,7 @@ defmodule Systems.Budget.Form do
%{assigns: %{id: id, locale: locale, currencies: currencies}} = socket
) do
{currency_selector, selected_currency} =
- Budget.Presenter.init_currency_selector(currencies, locale, %{type: __MODULE__, id: id})
+ Budget.CurrencySelector.init(currencies, locale, %{type: __MODULE__, id: id})
socket |> assign(currency_selector: currency_selector, selected_currency: selected_currency)
end
diff --git a/core/systems/budget/funding_page.ex b/core/systems/budget/funding_page.ex
index 75923ec3a..5844408ea 100644
--- a/core/systems/budget/funding_page.ex
+++ b/core/systems/budget/funding_page.ex
@@ -64,7 +64,7 @@ defmodule Systems.Budget.FundingPage do
defp update_campaigns(%{assigns: %{selected_budget: selected_budget} = assigns} = socket) do
campaign_items =
selected_budget
- |> Campaign.Public.list_by_budget(Campaign.Model.preload_graph(:full))
+ |> Campaign.Public.list_by_budget(Campaign.Model.preload_graph(:down))
|> Enum.map(&to_content_list_item(&1, assigns))
socket |> assign(campaign_items: campaign_items)
diff --git a/core/systems/campaign/_assembly.ex b/core/systems/campaign/_assembly.ex
index 5dcbd781d..96b8024a0 100644
--- a/core/systems/campaign/_assembly.ex
+++ b/core/systems/campaign/_assembly.ex
@@ -6,36 +6,39 @@ defmodule Systems.Campaign.Assembly do
alias Core.Repo
alias Ecto.Multi
- alias Frameworks.Utility.EctoHelper
-
alias Systems.{
Campaign,
Assignment,
- Survey,
- Lab,
- Crew,
- Pool,
- Promotion
+ Promotion,
+ Pool
}
- def delete(%Campaign.Model{
- auth_node: auth_node,
- submissions: _submissions,
- promotion: promotion,
- promotable_assignment: %{
- crew: crew,
- assignable_experiment: experiment
- }
- }) do
+ def delete(%Campaign.Model{} = campaign) do
Multi.new()
- |> EctoHelper.delete(:promotion, promotion)
- |> Assignment.Public.delete_tool(experiment)
- |> EctoHelper.delete(:crew, crew)
- |> Multi.delete(:auth_node, auth_node)
+ |> delete(campaign)
|> Repo.transaction()
end
- def create(user, title, tool_type, pool, budget) do
+ def delete(
+ multi,
+ %Campaign.Model{
+ auth_node: auth_node,
+ promotion: promotion
+ } = campaign
+ ) do
+ multi
+ |> Multi.delete(:campaign, campaign)
+ |> Multi.delete(:campaign_auth_node, auth_node)
+ |> delete(promotion)
+ end
+
+ def delete(multi, %Promotion.Model{auth_node: auth_node} = promotion) do
+ multi
+ |> Multi.delete(:promotion, promotion)
+ |> Multi.delete(:promotion_auth_node, auth_node)
+ end
+
+ def create(template, user, title, pool, budget) do
profile = user |> Accounts.get_profile()
promotion_attrs = create_promotion_attrs(title, user, profile)
@@ -43,32 +46,13 @@ defmodule Systems.Campaign.Assembly do
campaign_auth_node = Authorization.create_node!()
promotion_auth_node = Authorization.create_node!(campaign_auth_node)
assignment_auth_node = Authorization.create_node!(campaign_auth_node)
- crew_auth_node = Authorization.create_node!(assignment_auth_node)
- experiment_auth_node = Authorization.create_node!(assignment_auth_node)
- tool_auth_node = Authorization.create_node!(experiment_auth_node)
-
- {:ok, tool} = create_tool(tool_type, tool_auth_node)
- {:ok, crew} = Crew.Public.create(crew_auth_node)
-
- {:ok, experiment} =
- Assignment.Public.create_experiment(
- experiment_attrs(tool_type),
- tool,
- experiment_auth_node
- )
-
- {:ok, assignment} =
- Assignment.Public.create(
- assignment_attrs(),
- crew,
- experiment,
- budget,
- assignment_auth_node
- )
{:ok, promotion} = Promotion.Public.create(promotion_attrs, promotion_auth_node)
{:ok, submission} = Pool.Public.create_submission(submission_attrs(), pool)
+ {:ok, assignment} =
+ Assignment.Assembly.create(template, :campaign, budget, assignment_auth_node)
+
{:ok, campaign} =
Campaign.Public.create(promotion, assignment, [submission], user, campaign_auth_node)
@@ -77,37 +61,8 @@ defmodule Systems.Campaign.Assembly do
campaign
end
- defp create_tool(tool_type, tool_auth_node) do
- tool_attrs = create_tool_attrs()
- context(tool_type).create_tool(tool_attrs, tool_auth_node)
- end
-
- defp context(:online), do: Survey.Public
- defp context(:lab), do: Lab.Public
-
- defp assignment_attrs(), do: %{director: :campaign}
defp submission_attrs(), do: %{director: :campaign, status: :idle}
- defp experiment_attrs(:online) do
- %{
- director: :campaign,
- devices: [:phone, :tablet, :desktop]
- }
- end
-
- defp experiment_attrs(:lab) do
- %{
- director: :campaign,
- devices: []
- }
- end
-
- defp create_tool_attrs() do
- %{
- director: :campaign
- }
- end
-
defp create_promotion_attrs(title, user, profile) do
image_id = image_catalog().random(:abstract)
@@ -138,23 +93,20 @@ defmodule Systems.Campaign.Assembly do
%{
budget: budget,
auth_node: assignment_auth_node,
- assignable_experiment:
- %{
- auth_node: experiment_auth_node
- } = experiment
+ info: info,
+ workflow: workflow
} = assignment
} = campaign
) do
campaign_auth_node = Authorization.copy(campaign_auth_node)
promotion_auth_node = Authorization.copy(promotion_auth_node, campaign_auth_node)
assignment_auth_node = Authorization.copy(assignment_auth_node, campaign_auth_node)
- experiment_auth_node = Authorization.copy(experiment_auth_node, assignment_auth_node)
promotion = Promotion.Public.copy(promotion, promotion_auth_node)
submissions = Pool.Public.copy(submissions)
- tool = Assignment.Public.copy_tool(experiment, experiment_auth_node)
- experiment = Assignment.Public.copy_experiment(experiment, tool, experiment_auth_node)
- assignment = Assignment.Public.copy(assignment, budget, experiment, assignment_auth_node)
+ info = Assignment.Public.copy_info(info)
+ workflow = Assignment.Public.copy_workflow(workflow)
+ assignment = Assignment.Public.copy(assignment, info, workflow, budget, assignment_auth_node)
campaign =
Campaign.Public.copy(campaign, promotion, assignment, submissions, campaign_auth_node)
@@ -167,8 +119,8 @@ defmodule Systems.Campaign.Assembly do
campaign: campaign,
promotion: promotion,
submissions: submissions,
- tool: tool,
- experiment: experiment,
+ info: info,
+ workflow: workflow,
assignment: assignment,
authors: authors
}
diff --git a/core/systems/campaign/_director.ex b/core/systems/campaign/_director.ex
new file mode 100644
index 000000000..fdc8d88a7
--- /dev/null
+++ b/core/systems/campaign/_director.ex
@@ -0,0 +1,13 @@
+defmodule Systems.Campaign.Director do
+ @behaviour Frameworks.Promotable.Director
+
+ alias Systems.{
+ Campaign
+ }
+
+ @impl true
+ defdelegate reward_value(promotable), to: Campaign.Public
+
+ @impl true
+ defdelegate validate_open(promotable, user), to: Campaign.Public
+end
diff --git a/core/systems/campaign/_presenter.ex b/core/systems/campaign/_presenter.ex
index 2c73c927a..9335053d6 100644
--- a/core/systems/campaign/_presenter.ex
+++ b/core/systems/campaign/_presenter.ex
@@ -1,5 +1,5 @@
defmodule Systems.Campaign.Presenter do
- use Systems.Presenter
+ @behaviour Frameworks.Concept.Presenter
alias Frameworks.Signal
@@ -10,45 +10,29 @@ defmodule Systems.Campaign.Presenter do
}
def update(%Campaign.Model{} = campaign, id, page) do
- Signal.Public.dispatch!(%{page: page}, %{id: id, model: campaign})
+ Signal.Public.dispatch!({:page, page}, %{id: id, model: campaign})
campaign
end
- # View Model By ID
-
- @impl true
- def view_model(id, Campaign.ContentPage = page, assigns) when is_number(id) do
- Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
- |> view_model(page, assigns)
- end
-
@impl true
- def view_model(id, Promotion.LandingPage = page, assigns) when is_number(id) do
- Campaign.Public.get_by_promotion(id, Campaign.Model.preload_graph(:full))
+ def view_model(%Assignment.Model{} = assignment, page, assigns) do
+ Campaign.Public.get_by_promotable(assignment, Campaign.Model.preload_graph(:down))
|> view_model(page, assigns)
end
@impl true
- def view_model(id, Assignment.CallbackPage = page, assigns) when is_number(id) do
- Campaign.Public.get_by_promotable(id, Campaign.Model.preload_graph(:full))
+ def view_model(%Promotion.Model{} = promotion, page, assigns) do
+ Campaign.Public.get_by_promotion(promotion, Campaign.Model.preload_graph(:down))
|> view_model(page, assigns)
end
- @impl true
- def view_model(id, Assignment.LandingPage = page, assigns) when is_number(id) do
- Campaign.Public.get_by_promotable(id, Campaign.Model.preload_graph(:full))
- |> view_model(page, assigns)
- end
-
- # View Model By Campaign
-
@impl true
def view_model(%Campaign.Model{} = campaign, page, assigns) do
builder(page).view_model(campaign, assigns)
end
defp builder(Campaign.ContentPage), do: Campaign.Builders.CampaignContentPage
- defp builder(Assignment.CallbackPage), do: Campaign.Builders.AssignmentCallbackPage
+ defp builder(Alliance.CallbackPage), do: Campaign.Builders.AssignmentCallbackPage
defp builder(Assignment.LandingPage), do: Campaign.Builders.AssignmentLandingPage
defp builder(Promotion.LandingPage), do: Campaign.Builders.PromotionLandingPage
end
diff --git a/core/systems/campaign/_public.ex b/core/systems/campaign/_public.ex
index cca9811ad..8d502a26d 100644
--- a/core/systems/campaign/_public.ex
+++ b/core/systems/campaign/_public.ex
@@ -11,13 +11,13 @@ defmodule Systems.Campaign.Public do
alias Frameworks.GreenLight.Principal
alias Frameworks.Signal
+ alias Frameworks.Concept.Directable
alias Systems.{
- Director,
Campaign,
Promotion,
Assignment,
- Survey,
+ Alliance,
Crew,
Budget,
Bookkeeping,
@@ -307,7 +307,7 @@ defmodule Systems.Campaign.Public do
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
|> Repo.insert() do
:ok = Authorization.assign_role(researcher, campaign, :owner)
- Signal.Public.dispatch!(:campaign_created, %{campaign: campaign})
+ Signal.Public.dispatch!({:campaign, :created}, %{campaign: campaign})
{:ok, campaign}
end
end
@@ -345,14 +345,14 @@ defmodule Systems.Campaign.Public do
]
}) do
# FIXME: budget change after pool update should be handled in student submission form
- budget = Director.get(pool).resolve_budget(pool_id, nil)
+ budget = Directable.director(pool).resolve_budget(pool_id, nil)
Assignment.Public.update(assignment, budget)
end
def submission_updated(_), do: nil
def delete(id) when is_number(id) do
- get!(id, Campaign.Model.preload_graph(:full))
+ get!(id, Campaign.Model.preload_graph(:down))
|> Campaign.Assembly.delete()
end
@@ -417,8 +417,8 @@ defmodule Systems.Campaign.Public do
|> Repo.all()
end
- def list_survey_tools(%Campaign.Model{} = campaign) do
- from(s in Survey.ToolModel, where: s.campaign_id == ^campaign.id)
+ def list_tools(%Campaign.Model{} = campaign) do
+ from(s in Alliance.ToolModel, where: s.campaign_id == ^campaign.id)
|> Repo.all()
end
@@ -427,29 +427,6 @@ defmodule Systems.Campaign.Public do
|> Repo.all()
end
- def search_subject(tool, %Core.Accounts.User{} = user) do
- Assignment.Public.search_subject(tool, user)
- end
-
- def search_subject(tool, public_id) do
- Assignment.Public.search_subject(tool, public_id)
- end
-
- def apply_member_and_activate_task(tool, user) do
- assignment = Assignment.Public.get_by_tool(tool, Assignment.Model.preload_graph(:full))
-
- %{submission: %{reward_value: reward_value}} =
- assignment
- |> get_by_promotable([:submissions])
- |> Campaign.Model.flatten()
-
- Assignment.Public.apply_member_and_activate_task(assignment, user, reward_value)
- end
-
- def assign_tester_role(tool, user) do
- Assignment.Public.assign_tester_role(tool, user)
- end
-
def completed?(%Campaign.Model{} = campaign) do
campaign
|> Campaign.Model.flatten()
@@ -464,7 +441,7 @@ defmodule Systems.Campaign.Public do
# temp solution for checking if campaign is ready to submit,
# TBD: replace with signal driven db field
- preload = Campaign.Model.preload_graph(:full)
+ preload = Campaign.Model.preload_graph(:down)
%{
promotion: promotion,
@@ -475,9 +452,33 @@ defmodule Systems.Campaign.Public do
Assignment.Public.ready?(assignment)
end
+ def assign_coordinators(campaign) do
+ from(u in User, where: u.coordinator == true)
+ |> Repo.all()
+ |> Enum.each(fn user ->
+ Authorization.assign_role(user, campaign, :coordinator)
+ end)
+ end
+
+ def update_coordinator_role(user, value) do
+ from(c in Campaign.Model)
+ |> Repo.all()
+ |> Enum.each(fn campaign ->
+ update_coordinator_role(campaign, user, value)
+ end)
+ end
+
+ defp update_coordinator_role(campaign, user, value) do
+ if value do
+ Authorization.assign_role(user, campaign, :coordinator)
+ else
+ Authorization.remove_role!(user, campaign, :coordinator)
+ end
+ end
+
def handle_exclusion(%Assignment.Model{} = assignment, items) when is_list(items) do
items |> Enum.each(&handle_exclusion(assignment, &1))
- Signal.Public.dispatch!(:assignment_updated, assignment)
+ Signal.Public.dispatch!({:assignment, :updated}, %{assignment: assignment})
end
def handle_exclusion(%Assignment.Model{} = assignment, %{id: id, active: active} = _item) do
@@ -500,9 +501,18 @@ defmodule Systems.Campaign.Public do
Assignment.Public.include(assignment, other)
end
+ def reward_value(%Assignment.Model{} = assignment) do
+ %{submission: %{reward_value: reward_value}} =
+ assignment
+ |> Campaign.Public.get_by_promotable([:submissions])
+ |> Campaign.Model.flatten()
+
+ reward_value
+ end
+
def validate_open(%Assignment.Model{} = assignment, user) do
assignment
- |> get_by_promotable(Campaign.Model.preload_graph(:full))
+ |> get_by_promotable(Campaign.Model.preload_graph(:down))
|> validate_open(user)
end
@@ -577,7 +587,7 @@ defmodule Systems.Campaign.Public do
|> Repo.all()
|> Enum.map(& &1.id)
- preload = Campaign.Model.preload_graph(:full)
+ preload = Campaign.Model.preload_graph(:down)
from(c in Campaign.Model,
inner_join: cs in Campaign.SubmissionModel,
@@ -622,18 +632,22 @@ defmodule Systems.Campaign.Public do
Synchronizes accepted student tasks with bookkeeping.
"""
def sync_student_credits() do
- from(a in Assignment.Model,
- inner_join: t in Crew.TaskModel,
- on: t.crew_id == a.crew_id,
- inner_join: m in Crew.MemberModel,
- on: m.id == t.member_id,
- inner_join: u in User,
- on: u.id == m.user_id,
- inner_join: b in Budget.Model,
- on: b.id == a.budget_id,
- where: t.status == :accepted and u.student == true,
+ from(assignment in Assignment.Model,
+ inner_join: task in Crew.TaskModel,
+ on: task.crew_id == assignment.crew_id,
+ inner_join: node in Authorization.Node,
+ on: node.id == task.auth_node_id,
+ inner_join: role in Authorization.RoleAssignment,
+ on: role.node_id == node.id,
+ inner_join: user in User,
+ on: user.id == role.principal_id,
+ inner_join: budget in Budget.Model,
+ on: budget.id == assignment.budget_id,
+ where: role.role == :owner,
+ where: task.status == :accepted,
+ where: user.student == true,
preload: [budget: [:fund, :reserve]],
- select: %{assignment: a, user: u}
+ select: %{assignment: assignment, user: user}
)
|> Repo.all()
|> Enum.each(&payout_participant(&1.assignment, &1.user))
diff --git a/core/systems/campaign/_switch.ex b/core/systems/campaign/_switch.ex
index 9ea13e0ae..dd22f2ce4 100644
--- a/core/systems/campaign/_switch.ex
+++ b/core/systems/campaign/_switch.ex
@@ -4,83 +4,64 @@ defmodule Systems.Campaign.Switch do
alias Systems.{
Campaign,
Promotion,
- Assignment,
- Pool
+ Assignment
}
@impl true
- def dispatch(signal, %{director: :campaign} = object) do
- handle(signal, object)
+ def intercept({:assignment, _} = signal, %{assignment: assignment} = message) do
+ if campaign =
+ Campaign.Public.get_by_promotable(assignment, Campaign.Model.preload_graph(:down)) do
+ handle(signal, message)
+ dispatch!({:campaign, signal}, Map.merge(message, %{campaign: campaign}))
+ end
end
- def handle(:survey_tool_updated, survey_tool) do
- experiment = Assignment.Public.get_experiment_by_tool(survey_tool)
- handle(:experiment_updated, experiment)
- end
-
- def handle(:lab_tool_updated, lab_tool) do
- experiment = Assignment.Public.get_experiment_by_tool(lab_tool)
- handle(:experiment_updated, experiment)
- end
-
- def handle(:experiment_updated, experiment) do
- assignment = Assignment.Public.get_by_assignable(experiment)
- handle(:assignment_updated, assignment)
- end
-
- def handle(:assignment_updated, assignment) do
- %{promotion: promotion} =
- campaign =
- Campaign.Public.get_by_promotable(assignment.id, Campaign.Model.preload_graph(:full))
-
- campaign
- |> Campaign.Presenter.update(promotion.id, Promotion.LandingPage)
- |> Campaign.Presenter.update(assignment.id, Assignment.LandingPage)
- |> Campaign.Presenter.update(campaign.id, Campaign.ContentPage)
- end
-
- def handle(:assignment_accepted, %{assignment: assignment, user: user}) do
- handle(:assignment_updated, assignment)
- Campaign.Public.payout_participant(assignment, user)
+ @impl true
+ def intercept({:promotion, _} = signal, %{promotion: promotion} = message) do
+ if campaign = Campaign.Public.get_by_promotion(promotion, Campaign.Model.preload_graph(:down)) do
+ dispatch!({:campaign, signal}, Map.merge(message, %{campaign: campaign}))
+ end
end
- def handle(:assignment_rejected, %{assignment: assignment, user: _user}) do
- handle(:assignment_updated, assignment)
+ @impl true
+ def intercept({:campaign, _} = signal, message) do
+ handle(signal, message)
end
- def handle(:assignment_completed, %{assignment: assignment, user: _user}) do
- handle(:assignment_updated, assignment)
+ @impl true
+ def intercept({:user_profile, _} = signal, message) do
+ handle(signal, message)
end
- def handle(:promotion_updated, promotion) do
- campaign = Campaign.Public.get_by_promotion(promotion, Campaign.Model.preload_graph(:full))
- promotable = Campaign.Model.promotable(campaign)
+ # HANDLE
- campaign
- |> Campaign.Presenter.update(promotion.id, Promotion.LandingPage)
- |> Campaign.Presenter.update(promotable.id, Assignment.LandingPage)
- |> Campaign.Presenter.update(campaign.id, Campaign.ContentPage)
+ defp handle({:user_profile, :updated}, %{user: user, user_changeset: user_changeset}) do
+ if Map.has_key?(user_changeset.changes, :coordinator) do
+ new_value = user_changeset.changes.coordinator
+ Campaign.Public.update_coordinator_role(user, new_value)
+ end
end
- def handle(:submission_updated, %Pool.SubmissionModel{} = submission) do
- campaign = Campaign.Public.get_by_submission(submission, Campaign.Model.preload_graph(:full))
- handle(:submission_updated, campaign)
+ defp handle(
+ {:campaign, event},
+ %{
+ campaign:
+ %Campaign.Model{
+ id: id,
+ promotion_id: promotion_id,
+ promotable_assignment_id: assignment_id
+ } = campaign
+ }
+ ) do
+ if event == :created do
+ Campaign.Public.assign_coordinators(campaign)
+ else
+ campaign
+ |> Campaign.Presenter.update(promotion_id, Promotion.LandingPage)
+ |> Campaign.Presenter.update(assignment_id, Assignment.LandingPage)
+ |> Campaign.Presenter.update(id, Campaign.ContentPage)
+ end
end
- def handle(
- :submission_updated,
- %Campaign.Model{
- promotion_id: promotion_id,
- promotable_assignment: %{
- id: promotable_assignment_id
- }
- } = campaign
- ) do
- Campaign.Public.submission_updated(campaign)
-
- campaign
- |> Campaign.Presenter.update(promotion_id, Promotion.LandingPage)
- |> Campaign.Presenter.update(promotable_assignment_id, Assignment.LandingPage)
- |> Campaign.Presenter.update(campaign.id, Campaign.ContentPage)
- end
+ defp handle({_, _}, _), do: nil
end
diff --git a/core/systems/campaign/builders/assignment_landing_page.ex b/core/systems/campaign/builders/assignment_landing_page.ex
index 07ab51ea7..be6047d68 100644
--- a/core/systems/campaign/builders/assignment_landing_page.ex
+++ b/core/systems/campaign/builders/assignment_landing_page.ex
@@ -11,8 +11,9 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do
alias Systems.{
Campaign,
Assignment,
+ Workflow,
Crew,
- Survey,
+ Alliance,
Lab,
Budget
}
@@ -26,80 +27,105 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do
|> view_model(assigns)
end
- def view_model(
- %{
- id: id,
- promotion: %{
- expectations: expectations,
- title: title
- },
- promotable:
- %{
- crew: crew
- } = assignment
- } = campaign,
- %{current_user: user} = _assigns
- ) do
+ def view_model(campaign, assigns) do
+ base = base(campaign, assigns)
+ extra = extra(campaign, assigns)
+
+ Map.merge(base, extra)
+ end
+
+ defp base(
+ %{
+ id: id,
+ promotion: %{
+ title: title
+ },
+ promotable: assignment
+ },
+ %{current_user: user}
+ ) do
reward =
Assignment.Public.idempotence_key(assignment, user)
|> Budget.Public.get_reward(budget: [currency: Budget.CurrencyModel.preload_graph(:full)])
- base = %{
+ %{
id: id,
title: title,
highlights: highlights(assignment, reward),
- hero_title: dgettext("link-survey", "task.hero.title")
+ hero_title: dgettext("eyra-alliance", "task.hero.title")
}
+ end
- extra =
- if Crew.Public.member?(crew, user) do
- member = Crew.Public.get_member!(crew, user)
- task = Crew.Public.get_task(crew, member)
- %{user: contact} = Campaign.Public.original_author(campaign)
-
- %{
- public_id: member.public_id,
- subtitle: assignment_subtitle(task),
- text: assignment_text(task, expectations),
- experiment: experiment(assignment, member, user, task, contact, title)
- }
- else
- # probably expired member
- %{
- public_id: nil,
- subtitle: dgettext("eyra-crew", "task.expired.subtitle"),
- text: dgettext("eyra-crew", "task.expired.text"),
- experiment: nil
- }
- end
+ defp extra(
+ %{promotable: %{crew: crew}} = campaign,
+ %{current_user: user} = assigns
+ ) do
+ tasks = Crew.Public.list_tasks_for_user(crew, user)
+ extra(tasks, campaign, assigns)
+ end
- Map.merge(base, extra)
+ defp extra(
+ [task],
+ %{
+ promotion: %{
+ title: title,
+ expectations: expectations
+ },
+ promotable:
+ %{
+ crew: crew,
+ workflow: workflow
+ } = assignment
+ } = campaign,
+ %{current_user: user}
+ ) do
+ member = Crew.Public.get_member(crew, user)
+ %{user: contact} = Campaign.Public.original_author(campaign)
+ [tool | _] = Workflow.Model.flatten(workflow)
+
+ %{
+ public_id: member.public_id,
+ subtitle: assignment_subtitle(task),
+ text: assignment_text(task, expectations),
+ task: task(assignment, tool, member, user, task, contact, title)
+ }
+ end
+
+ defp extra(_, _, _) do
+ # probably expired member
+ %{
+ public_id: nil,
+ subtitle: dgettext("eyra-crew", "task.expired.subtitle"),
+ text: dgettext("eyra-crew", "task.expired.text"),
+ task: nil
+ }
end
# Subtitle
defp assignment_subtitle(%{status: :pending}),
- do: dgettext("link-survey", "task.pending.subtitle")
+ do: dgettext("eyra-alliance", "task.pending.subtitle")
defp assignment_subtitle(%{status: :completed}),
- do: dgettext("link-survey", "task.completed.subtitle")
+ do: dgettext("eyra-alliance", "task.completed.subtitle")
defp assignment_subtitle(%{status: :accepted}),
- do: dgettext("link-survey", "task.accepted.subtitle")
+ do: dgettext("eyra-alliance", "task.accepted.subtitle")
defp assignment_subtitle(%{status: :rejected}),
- do: dgettext("link-survey", "task.rejected.subtitle")
+ do: dgettext("eyra-alliance", "task.rejected.subtitle")
defp assignment_subtitle(_),
do: dgettext("eyra-promotion", "expectations.public.label")
# Text
defp assignment_text(%{status: :completed}, _),
- do: dgettext("link-survey", "task.completed.text")
+ do: dgettext("eyra-alliance", "task.completed.text")
- defp assignment_text(%{status: :accepted}, _), do: dgettext("link-survey", "task.accepted.text")
+ defp assignment_text(%{status: :accepted}, _),
+ do: dgettext("eyra-alliance", "task.accepted.text")
defp assignment_text(%{status: :rejected, rejected_message: rejected_message}, _) do
- dgettext("link-survey", "task.rejected.text", reason: rejected_message)
+ dgettext("eyra-alliance", "task.rejected.text", reason: rejected_message)
end
defp assignment_text(_, expectations), do: expectations
@@ -117,82 +143,83 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do
highlights(assignment, nil) ++ [Campaign.Builders.Highlight.view_model(reward, :reward)]
end
- # Experiment
+ # Inquiry
- defp experiment(
- %{assignable_experiment: %{survey_tool: survey_tool}} = assignment,
+ defp task(
+ assignment,
+ %Alliance.ToolModel{} = tool,
member,
user,
task,
contact,
title
- )
- when not is_nil(survey_tool) do
- actions = survey_actions(assignment, member, user, task, contact, title)
+ ) do
+ actions = alliance_actions(assignment, tool, member, user, task, contact, title)
%{
- id: :experiment_task_view,
- view: Survey.ExperimentTaskView,
+ id: :task_view,
+ view: Alliance.TaskView,
model: %{
actions: actions
}
}
end
- defp experiment(
- %{assignable_experiment: %{lab_tool: lab_tool}} = _assignment,
+ defp task(
+ _assignment,
+ %Lab.ToolModel{} = tool,
member,
user,
task,
contact,
title
- )
- when not is_nil(lab_tool) do
- reservation = Lab.Public.reservation_for_user(lab_tool, user)
- actions = lab_actions(lab_tool, reservation, user, member, task, contact, title)
+ ) do
+ reservation = Lab.Public.reservation_for_user(tool, user)
+ actions = lab_actions(tool, reservation, user, member, task, contact, title)
%{
- id: :experiment_task_view,
- view: Lab.ExperimentTaskView,
+ id: :task_view,
+ view: Lab.TaskView,
model: %{
public_id: member.public_id,
status: task.status,
reservation: reservation,
- lab_tool: lab_tool,
+ lab_tool: tool,
actions: actions,
user: user
}
}
end
- defp experiment(_assignment, _member, _user, _task, _contact, _title), do: nil
+ defp task(_assignment, _tool, _member, _user, _task, _contact, _title), do: nil
- # Survey buttons
+ # Alliance buttons
- defp survey_actions(assignment, member, user, task, contact, title) do
+ defp alliance_actions(assignment, tool, member, user, task, contact, title) do
[]
- |> append(survey_cta(assignment, member, user))
+ |> append(alliance_cta(assignment, tool, member, user))
|> append_if(contact_enabled?(task), contact_action(contact, member, title))
end
- defp survey_cta(
- %{crew: crew, assignable_experiment: experiment} = _assignment,
+ defp alliance_cta(
+ %{crew: crew} = _assignment,
+ tool,
member,
user
) do
- case Crew.Public.get_task(crew, member) do
+ case Crew.Public.list_tasks_for_user(crew, user) do
nil ->
forward_action(user)
- task ->
+ [task] ->
case task.status do
- :pending -> open_action(user, experiment, crew, member.public_id)
+ :pending -> open_action(user, tool, crew, member.public_id)
_completed -> forward_action(user)
end
end
end
- # Survey forward button
+ # Alliance forward button
defp forward_action(user) do
%{
@@ -209,11 +236,11 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do
Phoenix.LiveView.push_redirect(socket, to: Accounts.start_page_path(user))
end
- # Survey open button
+ # Alliance open button
- defp open_action(user, experiment, crew, panl_id) do
- label = Assignment.ExperimentModel.open_label(experiment)
- path = Assignment.ExperimentModel.external_path(experiment, panl_id)
+ defp open_action(user, tool, crew, panl_id) do
+ label = Frameworks.Concept.ToolModel.open_label(tool)
+ path = Alliance.ToolModel.external_path(tool, panl_id)
%{
id: :open,
@@ -226,8 +253,7 @@ defmodule Systems.Campaign.Builders.AssignmentLandingPage do
end
def handle_open(%{user: user, crew: crew, path: path}, socket) do
- member = Crew.Public.get_member!(crew, user)
- task = Crew.Public.get_task(crew, member)
+ [task] = Crew.Public.list_tasks_for_user(crew, user)
Crew.Public.lock_task(task)
Phoenix.LiveView.redirect(socket, external: path)
end
diff --git a/core/systems/campaign/builders/highlight.ex b/core/systems/campaign/builders/highlight.ex
index 3f9ff7bcd..b11eec7e8 100644
--- a/core/systems/campaign/builders/highlight.ex
+++ b/core/systems/campaign/builders/highlight.ex
@@ -8,7 +8,7 @@ defmodule Systems.Campaign.Builders.Highlight do
}
defp vm(%Budget.CurrencyModel{} = currency, amount, :reward) do
- reward_title = dgettext("link-survey", "reward.highlight.title")
+ reward_title = dgettext("eyra-alliance", "reward.highlight.title")
locale = Gettext.get_locale(CoreWeb.Gettext)
reward_text = Budget.CurrencyModel.label(currency, locale, amount)
%{title: reward_title, text: reward_text}
@@ -25,34 +25,34 @@ defmodule Systems.Campaign.Builders.Highlight do
vm(currency, amount, :reward)
end
- def view_model(%Assignment.Model{assignable_experiment: experiment}, :duration) do
- duration = Assignment.ExperimentModel.duration(experiment)
+ def view_model(%Assignment.Model{info: info}, :duration) do
+ duration = Assignment.InfoModel.duration(info)
- duration_title = dgettext("link-survey", "duration.highlight.title")
- duration_text = dgettext("link-survey", "duration.highlight.text", duration: duration)
+ duration_title = dgettext("eyra-alliance", "duration.highlight.title")
+ duration_text = dgettext("eyra-alliance", "duration.highlight.text", duration: duration)
%{title: duration_title, text: duration_text}
end
def view_model(%Assignment.Model{} = assignment, :status) do
has_open_spots? = Assignment.Public.has_open_spots?(assignment)
- status_title = dgettext("link-survey", "status.highlight.title")
+ status_title = dgettext("eyra-alliance", "status.highlight.title")
status_text =
if has_open_spots? do
- dgettext("link-survey", "status.open.highlight.text")
+ dgettext("eyra-alliance", "status.open.highlight.text")
else
- dgettext("link-survey", "status.closed.highlight.text")
+ dgettext("eyra-alliance", "status.closed.highlight.text")
end
%{title: status_title, text: status_text}
end
- def view_model(%Assignment.Model{assignable_experiment: experiment}, :language) do
- language_title = dgettext("link-survey", "language.highlight.title")
+ def view_model(%Assignment.Model{info: info}, :language) do
+ language_title = dgettext("eyra-alliance", "language.highlight.title")
language_text =
- Assignment.ExperimentModel.languages(experiment)
+ Assignment.InfoModel.languages(info)
|> language_text()
%{title: language_title, text: language_text}
diff --git a/core/systems/campaign/builders/promotion_landing_page.ex b/core/systems/campaign/builders/promotion_landing_page.ex
index 597186245..b191a0b6a 100644
--- a/core/systems/campaign/builders/promotion_landing_page.ex
+++ b/core/systems/campaign/builders/promotion_landing_page.ex
@@ -7,10 +7,10 @@ defmodule Systems.Campaign.Builders.PromotionLandingPage do
alias Phoenix.LiveView
alias Systems.{
- Director,
Campaign,
Promotion,
- Assignment
+ Assignment,
+ Workflow
}
def view_model(
@@ -30,10 +30,10 @@ defmodule Systems.Campaign.Builders.PromotionLandingPage do
promotion: promotion,
promotable:
%{
- assignable_experiment: experiment
+ info: info
} = assignment
},
- _assigns
+ %{current_user: user}
) do
%{
id: id,
@@ -41,15 +41,15 @@ defmodule Systems.Campaign.Builders.PromotionLandingPage do
themes: themes(promotion),
organisation: organisation(promotion),
highlights: highlights(assignment, submission),
- call_to_action: apply_call_to_action(assignment),
- languages: Assignment.ExperimentModel.languages(experiment),
- devices: Assignment.ExperimentModel.devices(experiment)
+ call_to_action: apply_call_to_action(assignment, user),
+ languages: Assignment.InfoModel.languages(info),
+ devices: Assignment.InfoModel.devices(info)
}
|> merge(promotion |> Map.take([:image_id | Promotion.Model.plain_fields()]))
end
defp byline(authors) when is_list(authors) do
- "#{dgettext("link-survey", "by.author.label")}: " <>
+ "#{dgettext("eyra-alliance", "by.author.label")}: " <>
Enum.map_join(authors, ", ", & &1.fullname)
end
@@ -80,11 +80,15 @@ defmodule Systems.Campaign.Builders.PromotionLandingPage do
]
end
- defp apply_call_to_action(%{assignable_experiment: experiment} = assignment) do
+ defp apply_call_to_action(%{workflow: workflow} = assignment, user) do
+ [tool | _] = Workflow.Model.flatten(workflow)
+ task_identifier = Assignment.Private.task_identifier(tool, user)
+
%{
- label: Assignment.ExperimentModel.apply_label(experiment),
+ label: Frameworks.Concept.ToolModel.apply_label(tool),
target: %{type: :event, value: "apply"},
assignment: assignment,
+ task_identifier: task_identifier,
handle: &handle_apply/1
}
end
@@ -93,18 +97,23 @@ defmodule Systems.Campaign.Builders.PromotionLandingPage do
%{
assigns: %{
current_user: user,
- vm: %{call_to_action: %{assignment: %{id: id} = assignment}}
+ vm: %{
+ call_to_action: %{
+ assignment: %{id: id} = assignment,
+ task_identifier: task_identifier
+ }
+ }
}
} = socket
) do
- case Director.public(assignment).validate_open(assignment, user) do
+ case Campaign.Public.validate_open(assignment, user) do
{:error, error} ->
inform(error, socket)
:ok ->
reward_amount = Campaign.Public.reward_amount(assignment)
- case Assignment.Public.apply_member(assignment, user, reward_amount) do
+ case Assignment.Public.apply_member(assignment, user, task_identifier, reward_amount) do
{:ok, _} ->
LiveView.push_redirect(socket,
to: Routes.live_path(socket, Systems.Assignment.LandingPage, id)
diff --git a/core/systems/campaign/content_page.ex b/core/systems/campaign/content_page.ex
index 684b7b7c4..98753dbb7 100644
--- a/core/systems/campaign/content_page.ex
+++ b/core/systems/campaign/content_page.ex
@@ -6,6 +6,7 @@ defmodule Systems.Campaign.ContentPage do
use CoreWeb.Layouts.Workspace.Component, :campaign
use CoreWeb.UI.Responsive.Viewport
use CoreWeb.UI.PlainDialog
+ use Systems.Observatory.Public
import CoreWeb.Gettext
import Core.ImageCatalog, only: [image_catalog: 0]
@@ -13,16 +14,15 @@ defmodule Systems.Campaign.ContentPage do
alias Core.Accounts
alias CoreWeb.UI.ImageCatalogPicker
alias Systems.Promotion.FormView, as: PromotionForm
- import CoreWeb.Layouts.Workspace.Component
-
- alias CoreWeb.UI.Tabbar
- alias CoreWeb.UI.Navigation
alias Systems.{
Campaign,
- Pool
+ Pool,
+ Content
}
+ import Content.Page
+
@impl true
def get_authorization_context(%{"id" => id}, _session, _socket) do
Campaign.Public.get!(id)
@@ -30,7 +30,7 @@ defmodule Systems.Campaign.ContentPage do
@impl true
def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
- model = %{id: String.to_integer(id), director: :campaign}
+ model = Campaign.Public.get!(String.to_integer(id), Campaign.Model.preload_graph(:down))
tabbar_id = "campaign_content/#{id}"
{
@@ -233,7 +233,7 @@ defmodule Systems.Campaign.ContentPage do
end
@impl true
- def handle_info(%{id: :experiment_form, ready?: ready?}, socket),
+ def handle_info(%{id: :inquiry_form, ready?: ready?}, socket),
do: handle_info(%{id: :assignment_form, ready?: ready?}, socket)
@impl true
@@ -437,41 +437,23 @@ defmodule Systems.Campaign.ContentPage do
socket |> assign(tabbar_size: tabbar_size)
end
- defp tabbar_size({:unknown, _}), do: :unknown
- defp tabbar_size(bp), do: value(bp, :narrow, sm: %{30 => :wide})
-
- defp margin_x(:mobile), do: "mx-6"
- defp margin_x(_), do: "mx-10"
-
@impl true
def render(assigns) do
~H"""
- <.workspace title={dgettext("link-survey", "content.title")} menus={@menus}>
-
+ <%= if @icon do %>
+ <%= @title %>
+ <%= @description %>
+ <.wrap>
+
+
+
+
+
+
+ <% end %>
+
+
-
-
+ <.content_page
+ title={@vm.title}
+ menus={@menus}
+ tabs={@vm.tabs}
+ actions={@actions}
+ more_actions={@more_actions}
+ initial_tab={@initial_tab}
+ tabbar_id={@tabbar_id}
+ tabbar_size={@tabbar_size}
+ breakpoint={@breakpoint}
+ popup={@popup}
+ dialog={@dialog}
+ show_errors={@vm.show_errors}
+ />
"""
end
end
diff --git a/core/systems/campaign/builders/campaign_content_page.ex b/core/systems/campaign/content_page_builder.ex
similarity index 83%
rename from core/systems/campaign/builders/campaign_content_page.ex
rename to core/systems/campaign/content_page_builder.ex
index 65931406e..517813e1d 100644
--- a/core/systems/campaign/builders/campaign_content_page.ex
+++ b/core/systems/campaign/content_page_builder.ex
@@ -45,6 +45,7 @@ defmodule Systems.Campaign.Builders.CampaignContentPage do
preview_path = ~p"/promotion/#{promotion_id}?preview=true&back=#{uri_path}"
%{
+ title: dgettext("link-campaign", "content.title"),
id: campaign_id,
submission: submission,
promotion: promotion,
@@ -83,8 +84,8 @@ defmodule Systems.Campaign.Builders.CampaignContentPage do
id: :promotion_form,
ready: ready?,
show_errors: show_errors,
- title: dgettext("link-survey", "tabbar.item.promotion"),
- forward_title: dgettext("link-survey", "tabbar.item.promotion.forward"),
+ title: dgettext("link-campaign", "tabbar.item.promotion"),
+ forward_title: dgettext("link-campaign", "tabbar.item.promotion.forward"),
type: :fullpage,
live_component: Promotion.FormView,
props: %{
@@ -108,8 +109,8 @@ defmodule Systems.Campaign.Builders.CampaignContentPage do
id: :assignment_form,
ready: ready?,
show_errors: show_errors,
- title: dgettext("link-survey", "tabbar.item.assignment"),
- forward_title: dgettext("link-survey", "tabbar.item.assignment.forward"),
+ title: dgettext("link-campaign", "tabbar.item.assignment"),
+ forward_title: dgettext("link-campaign", "tabbar.item.assignment.forward"),
type: :fullpage,
live_component: Assignment.AssignmentForm,
props: %{
@@ -131,8 +132,8 @@ defmodule Systems.Campaign.Builders.CampaignContentPage do
) do
%{
id: :submission_form,
- title: dgettext("link-survey", "tabbar.item.submission"),
- forward_title: dgettext("link-survey", "tabbar.item.submission.forward"),
+ title: dgettext("link-campaign", "tabbar.item.submission"),
+ forward_title: dgettext("link-campaign", "tabbar.item.submission.forward"),
type: :fullpage,
live_component: Pool.CampaignSubmissionView,
props: %{
@@ -152,8 +153,8 @@ defmodule Systems.Campaign.Builders.CampaignContentPage do
) do
%{
id: :funding,
- title: dgettext("link-survey", "tabbar.item.funding"),
- forward_title: dgettext("link-survey", "tabbar.item.funding.forward"),
+ title: dgettext("link-campaign", "tabbar.item.funding"),
+ forward_title: dgettext("link-campaign", "tabbar.item.funding.forward"),
type: :fullpage,
live_component: Campaign.FundingView,
props: %{
@@ -178,8 +179,8 @@ defmodule Systems.Campaign.Builders.CampaignContentPage do
%{
id: :monitor,
- title: dgettext("link-survey", "tabbar.item.monitor"),
- forward_title: dgettext("link-survey", "tabbar.item.monitor.forward"),
+ title: dgettext("link-campaign", "tabbar.item.monitor"),
+ forward_title: dgettext("link-campaign", "tabbar.item.monitor.forward"),
type: :fullpage,
live_component: Campaign.MonitorView,
props: %{
diff --git a/core/systems/campaign/create_form.ex b/core/systems/campaign/create_form.ex
index c9ac26781..106b0a6c3 100644
--- a/core/systems/campaign/create_form.ex
+++ b/core/systems/campaign/create_form.ex
@@ -3,26 +3,26 @@ defmodule Systems.Campaign.CreateForm do
alias Frameworks.Pixel.Selector
alias Frameworks.Pixel.Text
+ alias Frameworks.Concept.Directable
alias Systems.{
Campaign,
Assignment,
- Pool,
- Director
+ Pool
}
# Handle Tool Type Selector Update
@impl true
def update(
- %{active_item_id: active_item_id, selector_id: :tool_type_selector},
- %{assigns: %{tool_type_labels: tool_type_labels}} = socket
+ %{active_item_id: active_item_id, selector_id: :template_selector},
+ %{assigns: %{template_labels: template_labels}} = socket
) do
- %{id: selected_tool_type} = Enum.find(tool_type_labels, &(&1.id == active_item_id))
+ %{id: selected_template} = Enum.find(template_labels, &(&1.id == active_item_id))
{
:ok,
socket
- |> assign(selected_tool_type: selected_tool_type)
+ |> assign(selected_template: selected_template)
}
end
@@ -50,16 +50,16 @@ defmodule Systems.Campaign.CreateForm do
:ok,
socket
|> assign(id: id, user: user, target: target, title: title)
- |> init_tool_types()
+ |> init_templates()
|> init_pools()
|> init_buttons()
}
end
- defp init_tool_types(socket) do
- selected_tool_type = :online
- tool_type_labels = Assignment.ToolTypes.labels(selected_tool_type)
- socket |> assign(tool_type_labels: tool_type_labels, selected_tool_type: selected_tool_type)
+ defp init_templates(socket) do
+ selected_template = :online
+ template_labels = Assignment.Templates.labels(selected_template)
+ socket |> assign(template_labels: template_labels, selected_template: selected_template)
end
defp init_pools(socket) do
@@ -102,10 +102,9 @@ defmodule Systems.Campaign.CreateForm do
def handle_event(
"proceed",
_,
- %{assigns: %{selected_tool_type: selected_tool_type, selected_pool: selected_pool}} =
- socket
+ %{assigns: %{selected_template: selected_template, selected_pool: selected_pool}} = socket
) do
- campaign = create_campaign(socket, selected_tool_type, selected_pool)
+ campaign = create_campaign(socket, selected_template, selected_pool)
{:noreply, socket |> redirect_to(campaign.id)}
end
@@ -126,26 +125,18 @@ defmodule Systems.Campaign.CreateForm do
defp create_campaign(
%{assigns: %{user: %{id: user_id}}} = socket,
- tool_type,
+ template,
%{id: currency_id} = pool
) do
- budget = Director.get(pool).resolve_budget(currency_id, user_id)
- socket |> create_campaign(tool_type, pool, budget)
+ budget = Directable.director(pool).resolve_budget(currency_id, user_id)
+ socket |> create_campaign(template, pool, budget)
end
- defp create_campaign(%{assigns: %{user: user}} = _socket, tool_type, pool, budget) do
+ defp create_campaign(%{assigns: %{user: user}} = _socket, template, pool, budget) do
title = dgettext("eyra-dashboard", "default.study.title")
- Campaign.Assembly.create(user, title, tool_type, pool, budget)
+ Campaign.Assembly.create(template, user, title, pool, budget)
end
- # data(title, :string)
- # data(buttons, :list)
- # data(pools, :list)
- # data(pool_labels, :list)
- # data(tool_type_labels, :list)
- # data(selected_tool_type, :map)
- # data(selected_pool, :map)
-
attr(:user, :any)
attr(:target, :any)
@@ -156,14 +147,14 @@ defmodule Systems.Campaign.CreateForm do
-
- <%= if @popup do %>
- <.popup>
-
-
-
-
-
-
-
- <.live_component module={@popup.module} {@popup.props} />
-
-
- <% end %>
-
- <%= if @dialog do %>
- <.popup>
- <.plain_dialog {@dialog} />
-
- <% end %>
-
-
@@ -186,7 +188,7 @@ defmodule Systems.Campaign.MonitorView do
@@ -268,18 +270,19 @@ defmodule Systems.Campaign.MonitorView do
%{
submission: submission,
promotable: %{
+ workflow: workflow,
crew: crew,
- assignable_experiment:
- %{
- subject_count: subject_count
- } = experiment
+ info: %{
+ subject_count: subject_count
+ }
}
},
attention_list_enabled?
) do
+ [tool] = Workflow.Model.flatten(workflow)
participated_count = Crew.Public.count_participated_tasks(crew)
pending_count = Crew.Public.count_pending_tasks(crew)
- vacant_count = experiment |> get_vacant_count(participated_count, pending_count)
+ vacant_count = get_vacant_count(subject_count, participated_count, pending_count)
status = Pool.SubmissionModel.status(submission)
active? = status === :accepted or Crew.Public.active?(crew)
@@ -287,25 +290,25 @@ defmodule Systems.Campaign.MonitorView do
{attention_columns, attention_tasks} =
if attention_list_enabled? do
Crew.Public.expired_pending_started_tasks(crew)
- |> to_view_model(:attention_tasks, target, experiment)
+ |> to_view_model(:attention_tasks, target, crew, tool)
else
{[], []}
end
{completed_columns, completed_tasks} =
Crew.Public.completed_tasks(crew)
- |> to_view_model(:completed_tasks, target, experiment)
+ |> to_view_model(:completed_tasks, target, crew, tool)
{rejected_columns, rejected_tasks} =
Crew.Public.rejected_tasks(crew)
- |> to_view_model(:rejected_tasks, target, experiment)
+ |> to_view_model(:rejected_tasks, target, crew, tool)
{accepted_columns, accepted_tasks} =
Crew.Public.accepted_tasks(crew)
- |> to_view_model(:accepted_tasks, target, experiment)
+ |> to_view_model(:accepted_tasks, target, crew, tool)
%{
- experiment: experiment,
+ tool: tool,
active?: active?,
subject_count: subject_count,
pending_count: pending_count,
@@ -335,7 +338,7 @@ defmodule Systems.Campaign.MonitorView do
}
end
- defp get_vacant_count(%{subject_count: subject_count} = _experiment, finished, pending) do
+ defp get_vacant_count(subject_count, finished, pending) do
case subject_count do
count when is_nil(count) -> 0
count when count > 0 -> max(0, count - (finished + pending))
@@ -343,15 +346,16 @@ defmodule Systems.Campaign.MonitorView do
end
end
- defp is_lab_experiment(%{lab_tool_id: lab_tool_id} = _experiment), do: lab_tool_id != nil
+ defp is_lab_tool(%Lab.ToolModel{}), do: true
+ defp is_lab_tool(_), do: false
- defp reservation(%{lab_tool_id: lab_tool_id} = _experiment, user_id) when lab_tool_id != nil do
- tool = Lab.Public.get(lab_tool_id)
+ defp reservation(%{lab_tool_id: lab_tool_id} = _tool, user_id) when lab_tool_id != nil do
+ tool = Lab.Public.get_tool!(lab_tool_id)
user = Core.Accounts.get_user!(user_id)
Lab.Public.reservation_for_user(tool, user)
end
- defp reservation(_experiment, _user_id), do: nil
+ defp reservation(_tool, _user_id), do: nil
defp time_slot(nil), do: nil
@@ -362,21 +366,21 @@ defmodule Systems.Campaign.MonitorView do
defp time_slot_message(nil), do: "Participated without reservation"
defp time_slot_message(time_slot), do: "🗓 " <> Lab.TimeSlotModel.message(time_slot)
- defp to_view_model([], _, _, _), do: {[], []}
+ defp to_view_model([], _, _, _, _), do: {[], []}
- defp to_view_model(tasks, :attention_tasks, target, experiment) do
+ defp to_view_model(tasks, :attention_tasks, target, crew, tool) do
columns = [
dgettext("link-monitor", "column.participant"),
dgettext("link-monitor", "column.message")
]
- tasks = Enum.map(tasks, &to_view_model(:attention, target, experiment, &1))
+ tasks = Enum.map(tasks, &to_view_model(:attention, target, crew, tool, &1))
{columns, tasks}
end
- defp to_view_model(tasks, :completed_tasks, target, experiment) do
+ defp to_view_model(tasks, :completed_tasks, target, crew, tool) do
columns =
- if is_lab_experiment(experiment) do
+ if is_lab_tool(tool) do
[
dgettext("link-monitor", "column.participant"),
dgettext("link-monitor", "column.reservation"),
@@ -386,39 +390,49 @@ defmodule Systems.Campaign.MonitorView do
["Subject", "Finished"]
end
- tasks = Enum.map(tasks, &to_view_model(:waitinglist, target, experiment, &1))
+ tasks = Enum.map(tasks, &to_view_model(:waitinglist, target, crew, tool, &1))
{columns, tasks}
end
- defp to_view_model(tasks, :rejected_tasks, target, experiment) do
+ defp to_view_model(tasks, :rejected_tasks, target, crew, tool) do
columns = [
dgettext("link-monitor", "column.participant"),
dgettext("link-monitor", "column.reason"),
dgettext("link-monitor", "column.rejected")
]
- tasks = Enum.map(tasks, &to_view_model(:rejected, target, experiment, &1))
+ tasks = Enum.map(tasks, &to_view_model(:rejected, target, crew, tool, &1))
{columns, tasks}
end
- defp to_view_model(tasks, :accepted_tasks, target, experiment) do
+ defp to_view_model(tasks, :accepted_tasks, target, crew, tool) do
columns = [
dgettext("link-monitor", "column.participant"),
dgettext("link-monitor", "column.accepted")
]
- tasks = Enum.map(tasks, &to_view_model(:accepted, target, experiment, &1))
+ tasks = Enum.map(tasks, &to_view_model(:accepted, target, crew, tool, &1))
{columns, tasks}
end
- defp to_view_model(:attention, target, _experiment, %Crew.TaskModel{
- id: id,
- started_at: started_at,
- member_id: member_id
- }) do
- %{public_id: public_id} = Crew.Public.get_member!(member_id)
+ defp to_view_model(type, target, crew, tool, %Crew.TaskModel{} = task) do
+ [user] = Authorization.users_with_role(task, :owner)
+ member = Crew.Public.get_member(crew, user)
+ to_view_model(type, target, tool, task, member)
+ end
+
+ defp to_view_model(
+ :attention,
+ target,
+ _tool,
+ %Crew.TaskModel{
+ id: id,
+ started_at: started_at
+ },
+ %{public_id: public_id}
+ ) do
date_string =
started_at
|> Timestamp.apply_timezone()
@@ -434,16 +448,19 @@ defmodule Systems.Campaign.MonitorView do
}
end
- defp to_view_model(:waitinglist, target, experiment, %Crew.TaskModel{
- id: id,
- completed_at: completed_at,
- member_id: member_id
- }) do
- %{user_id: user_id, public_id: public_id} = Crew.Public.get_member!(member_id)
-
+ defp to_view_model(
+ :waitinglist,
+ target,
+ tool,
+ %Crew.TaskModel{
+ id: id,
+ completed_at: completed_at
+ },
+ %{user_id: user_id, public_id: public_id}
+ ) do
description =
- if is_lab_experiment(experiment) do
- if reservation = reservation(experiment, user_id) do
+ if is_lab_tool(tool) do
+ if reservation = reservation(tool, user_id) do
time_slot(reservation)
|> time_slot_message()
else
@@ -468,15 +485,18 @@ defmodule Systems.Campaign.MonitorView do
}
end
- defp to_view_model(:rejected, target, _experiment, %Crew.TaskModel{
- id: id,
- rejected_category: rejected_category,
- rejected_message: rejected_message,
- rejected_at: rejected_at,
- member_id: member_id
- }) do
- %{public_id: public_id} = Crew.Public.get_member!(member_id)
-
+ defp to_view_model(
+ :rejected,
+ target,
+ _tool,
+ %Crew.TaskModel{
+ id: id,
+ rejected_category: rejected_category,
+ rejected_message: rejected_message,
+ rejected_at: rejected_at
+ },
+ %{public_id: public_id}
+ ) do
date_string =
rejected_at
|> Timestamp.apply_timezone()
@@ -495,13 +515,16 @@ defmodule Systems.Campaign.MonitorView do
}
end
- defp to_view_model(:accepted, _target, _experiment, %Crew.TaskModel{
- id: id,
- accepted_at: accepted_at,
- member_id: member_id
- }) do
- %{public_id: public_id} = Crew.Public.get_member!(member_id)
-
+ defp to_view_model(
+ :accepted,
+ _target,
+ _tool,
+ %Crew.TaskModel{
+ id: id,
+ accepted_at: accepted_at
+ },
+ %{public_id: public_id}
+ ) do
date_string =
accepted_at
|> Timestamp.apply_timezone()
diff --git a/core/systems/campaign/overview_page.ex b/core/systems/campaign/overview_page.ex
index a67cb6601..9af8f21b5 100644
--- a/core/systems/campaign/overview_page.ex
+++ b/core/systems/campaign/overview_page.ex
@@ -35,7 +35,7 @@ defmodule Systems.Campaign.OverviewPage do
end
defp update_campaigns(%{assigns: %{current_user: user} = assigns} = socket) do
- preload = Campaign.Model.preload_graph(:full)
+ preload = Campaign.Model.preload_graph(:down)
campaigns =
user
@@ -129,7 +129,7 @@ defmodule Systems.Campaign.OverviewPage do
@impl true
def handle_event("duplicate", %{"item" => campaign_id}, socket) do
- preload = Campaign.Model.preload_graph(:full)
+ preload = Campaign.Model.preload_graph(:down)
campaign = Campaign.Public.get!(String.to_integer(campaign_id), preload)
Campaign.Assembly.copy(campaign)
@@ -213,7 +213,7 @@ defmodule Systems.Campaign.OverviewPage do
@impl true
def render(assigns) do
~H"""
- <.workspace title={dgettext("link-survey", "title")} menus={@menus}>
+ <.workspace title={dgettext("eyra-alliance", "title")} menus={@menus}>
<%= if @popup do %>
<.popup>
@@ -244,17 +244,17 @@ defmodule Systems.Campaign.OverviewPage do
<%= if Enum.count(@campaigns) > 0 do %>
- <%= dgettext("link-survey", "vacant.label") %>: <%= @vm.vacant_count %>
+ <%= dgettext("eyra-alliance", "vacant.label") %>: <%= @vm.vacant_count %>
- <%= dgettext("link-survey", "campaign.overview.title") %>
+ <%= dgettext("eyra-alliance", "campaign.overview.title") %>
-
+
-
+
+
+
+ """
+ end
+
+ def create_actions(%{assigns: %{breakpoint: {:unknown, _}}} = _socket), do: []
+
+ def create_actions(%{assigns: %{vm: %{actions: actions}}} = socket) do
+ actions
+ |> Keyword.keys()
+ |> Enum.map(&create_action(Keyword.get(actions, &1), socket))
+ |> Enum.filter(&(not is_nil(&1)))
+ end
+
+ def create_action(action, %{assigns: %{breakpoint: breakpoint}}) do
+ Breakpoint.value(breakpoint, nil,
+ xs: %{0 => action.icon},
+ md: %{40 => action.label, 100 => action.icon},
+ lg: %{50 => action.label}
+ )
+ end
+
+ def tabbar_size({:unknown, _}), do: :unknown
+ def tabbar_size(bp), do: Breakpoint.value(bp, :narrow, sm: %{30 => :wide})
+
+ defmacro __using__(_opts) do
+ quote do
+ use CoreWeb, :live_view
+ use CoreWeb.Layouts.Workspace.Component, :projects
+ use CoreWeb.UI.Responsive.Viewport
+ use Systems.Observatory.Public
+
+ alias Frameworks.Pixel.Flash
+
+ import CoreWeb.Gettext
+ import Systems.Content.Page
+
+ defp initialize(socket, id, model, tabbar_id, initial_tab, locale) do
+ socket
+ |> assign(
+ id: id,
+ model: model,
+ tabbar_id: tabbar_id,
+ initial_tab: initial_tab,
+ locale: locale,
+ changesets: %{},
+ publish_clicked: false,
+ dialog: nil,
+ popup: nil,
+ side_panel: nil,
+ action_map: %{},
+ more_actions: []
+ )
+ |> assign_viewport()
+ |> assign_breakpoint()
+ |> update_tabbar_size()
+ |> update_menus()
+ end
+
+ defoverridable handle_uri: 1
+
+ @impl true
+ def handle_uri(socket) do
+ socket =
+ socket
+ |> observe_view_model()
+ |> update_actions()
+ |> update_menus()
+
+ super(socket)
+ end
+
+ defoverridable handle_view_model_updated: 1
+
+ def handle_view_model_updated(socket) do
+ socket
+ |> update_actions()
+ |> update_menus()
+ end
+
+ @impl true
+ def handle_resize(socket) do
+ socket
+ |> update_tabbar_size()
+ |> update_actions()
+ |> update_menus()
+ end
+
+ @impl true
+ def handle_event(
+ "action_click",
+ %{"item" => action_id},
+ %{assigns: %{vm: %{actions: actions}}} = socket
+ ) do
+ action_id = String.to_existing_atom(action_id)
+ action = Keyword.get(actions, action_id)
+
+ {
+ :noreply,
+ socket
+ |> action.handle_click.()
+ |> update_view_model()
+ |> update_actions()
+ }
+ end
+
+ @impl true
+ def handle_event("inform_ok", _params, socket) do
+ {:noreply, socket |> assign(dialog: nil)}
+ end
+
+ @impl true
+ def handle_info({:handle_auto_save_done, _}, socket) do
+ socket |> update_menus()
+ {:noreply, socket}
+ end
+
+ @impl true
+ def handle_info({:flash, type, message}, socket) do
+ socket |> Flash.put(type, message, true)
+ {:noreply, socket}
+ end
+
+ @impl true
+ def handle_info({:show_popup, popup}, socket) do
+ {:noreply, socket |> assign(popup: popup)}
+ end
+
+ @impl true
+ def handle_info({:hide_popup}, socket) do
+ {:noreply, socket |> assign(popup: nil)}
+ end
+
+ @impl true
+ def handle_info(%{id: form_id, ready?: ready?}, socket) do
+ ready_key = String.to_atom("#{form_id}_ready?")
+
+ socket =
+ if socket.assigns[ready_key] != ready? do
+ socket
+ |> assign(ready_key, ready?)
+ else
+ socket
+ end
+
+ {:noreply, socket}
+ end
+
+ @impl true
+ def handle_info({:signal_test, _}, socket) do
+ {:noreply, socket}
+ end
+
+ def update_actions(socket) do
+ socket
+ |> assign(actions: create_actions(socket))
+ end
+
+ def update_tabbar_size(%{assigns: %{breakpoint: breakpoint}} = socket) do
+ tabbar_size = tabbar_size(breakpoint)
+ socket |> assign(tabbar_size: tabbar_size)
+ end
+ end
+ end
+end
diff --git a/core/systems/crew/_public.ex b/core/systems/crew/_public.ex
index 798f962ca..2d1266281 100644
--- a/core/systems/crew/_public.ex
+++ b/core/systems/crew/_public.ex
@@ -27,11 +27,10 @@ defmodule Systems.Crew.Public do
|> Repo.one()
end
- def create(auth_node, attrs \\ %{}) do
+ def prepare(auth_node, attrs \\ %{}) do
%Crew.Model{}
|> Crew.Model.changeset(attrs)
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert()
end
def active?(crew) do
@@ -42,12 +41,11 @@ defmodule Systems.Crew.Public do
# Tasks
def get_task(_crew, nil), do: nil
- def get_task(crew, member) do
+ def get_task(crew, [_ | _] = identifier) do
from(task in Crew.TaskModel,
- where:
- task.crew_id == ^crew.id and
- task.member_id == ^member.id and
- task.expired == false
+ where: task.crew_id == ^crew.id,
+ where: task.identifier == ^identifier,
+ where: task.expired == false
)
|> Repo.one()
end
@@ -56,44 +54,95 @@ defmodule Systems.Crew.Public do
Repo.get!(Crew.TaskModel, id) |> Repo.preload(preload)
end
- def create_task(crew, member, expire_at) do
- attrs = %{status: :pending, expire_at: expire_at}
+ def create_task(crew, members, identifier, expire_at \\ nil)
+
+ def create_task(crew, [_ | _] = members, [_ | _] = identifier, expire_at) do
+ attrs = %{identifier: identifier, status: :pending, expire_at: expire_at}
+ user_ids = Enum.map(members, & &1.user_id)
%Crew.TaskModel{}
|> Crew.TaskModel.changeset(attrs)
|> Ecto.Changeset.put_assoc(:crew, crew)
- |> Ecto.Changeset.put_assoc(:member, member)
+ |> Ecto.Changeset.put_assoc(:auth_node, Authorization.Node.create(user_ids, :owner))
|> Repo.insert()
end
- def create_task!(crew, member, expire_at) do
- case create_task(crew, member, expire_at) do
+ def create_task(crew, member, [_ | _] = identifier, expire_at),
+ do: create_task(crew, [member], identifier, expire_at)
+
+ def create_task!(crew, members, identifier, expire_at \\ nil)
+
+ def create_task!(crew, [_ | _] = members, [_ | _] = identifier, expire_at) do
+ case create_task(crew, members, identifier, expire_at) do
+ {:ok, task} -> task
+ _ -> nil
+ end
+ end
+
+ def create_task!(crew, member, [_ | _] = identifier, expire_at) do
+ case create_task(crew, member, identifier, expire_at) do
{:ok, task} -> task
_ -> nil
end
end
- def list_tasks(crew) do
+ def list_tasks(crew, order_by \\ {:desc, :id}) do
from(task in Crew.TaskModel,
- where: task.crew_id == ^crew.id and task.expired == false
+ where: task.crew_id == ^crew.id,
+ where: task.expired == false,
+ order_by: ^order_by
+ )
+ |> Repo.all()
+ end
+
+ def list_tasks_by_template(crew, task_template, order_by \\ {:desc, :id}) do
+ from(t in task_query_by_template(crew, task_template),
+ order_by: ^order_by
)
|> Repo.all()
end
- def task_query(crew, status_list) do
+ def list_tasks_for_user(crew, user_ref, order_by \\ {:desc, :id}) do
+ from(t in task_query(crew, user_ref, false), order_by: ^order_by)
+ |> Repo.all()
+ end
+
+ def task_query(crew, status_list, expired) when is_list(status_list) do
from(t in Crew.TaskModel,
where:
t.crew_id == ^crew.id and
- t.status in ^status_list
+ t.status in ^status_list and
+ t.expired == ^expired
)
end
- def task_query(crew, status_list, expired) do
+ def task_query(crew, user_ref, expired) do
+ user_id = user_id(user_ref)
+
+ from(task in Crew.TaskModel,
+ inner_join: node in Authorization.Node,
+ on: node.id == task.auth_node_id,
+ inner_join: role in Authorization.RoleAssignment,
+ on: role.node_id == node.id,
+ where: role.principal_id == ^user_id,
+ where: role.role == :owner,
+ where: task.crew_id == ^crew.id,
+ where: task.expired == ^expired
+ )
+ end
+
+ def task_query(crew, status_list) when is_list(status_list) do
from(t in Crew.TaskModel,
where:
t.crew_id == ^crew.id and
- t.status in ^status_list and
- t.expired == ^expired
+ t.status in ^status_list
+ )
+ end
+
+ def task_query_by_template(crew, task_template) when is_list(task_template) do
+ from(task in Crew.TaskModel,
+ where: task.crew_id == ^crew.id,
+ where: fragment("?::text[] @> ?", task.identifier, ^task_template)
)
end
@@ -148,22 +197,12 @@ defmodule Systems.Crew.Public do
count_tasks(crew, [:completed, :rejected, :accepted])
end
- def setup_tasks_for_members!(members, crew) do
- members
- |> Enum.map(
- &(Crew.TaskModel.changeset(%Crew.TaskModel{}, %{status: :pending})
- |> Ecto.Changeset.put_change(:member_id, &1.id)
- |> Ecto.Changeset.put_assoc(:crew, crew))
- )
- |> Enum.map(&Repo.insert!(&1))
- end
-
def cancel_task(%Crew.TaskModel{} = task) do
- update_task(task, %{started_at: nil})
+ update_task(task, %{started_at: nil}, :canceled)
end
def lock_task(%Crew.TaskModel{} = task) do
- update_task(task, %{started_at: Timestamp.naive_now()})
+ update_task(task, %{started_at: Timestamp.naive_now()}, :locked)
end
def activate_task(%Crew.TaskModel{status: status, started_at: started_at} = task) do
@@ -173,35 +212,45 @@ defmodule Systems.Crew.Public do
:pending ->
case started_at do
nil ->
- update_task(task, %{
- status: :completed,
- started_at: timestamp,
- completed_at: timestamp
- })
+ update_task(
+ task,
+ %{
+ status: :completed,
+ started_at: timestamp,
+ completed_at: timestamp
+ },
+ :completed
+ )
_ ->
- update_task(task, %{status: :completed, completed_at: timestamp})
+ update_task(task, %{status: :completed, completed_at: timestamp}, :completed)
end
_ ->
- {:ok, %{task: task}}
+ {:ok, %{crew_task: task}}
end
end
def activate_task!(%Crew.TaskModel{} = task) do
case Crew.Public.activate_task(task) do
- {:ok, %{task: task}} -> task
+ {:ok, %{crew_task: task}} -> task
_ -> nil
end
end
def reject_task(multi, %Crew.TaskModel{} = task, %{category: category, message: message}) do
- multi_update(multi, :task, task, %{
- status: :rejected,
- rejected_at: Timestamp.naive_now(),
- rejected_category: category,
- rejected_message: message
- })
+ multi_update(
+ multi,
+ :task,
+ task,
+ %{
+ status: :rejected,
+ rejected_at: Timestamp.naive_now(),
+ rejected_category: category,
+ rejected_message: message
+ },
+ :rejected
+ )
end
def reject_task(multi, id, rejection) do
@@ -222,10 +271,14 @@ defmodule Systems.Crew.Public do
end
def accept_task(%Crew.TaskModel{} = task) do
- update_task(task, %{
- status: :accepted,
- accepted_at: Timestamp.naive_now()
- })
+ update_task(
+ task,
+ %{
+ status: :accepted,
+ accepted_at: Timestamp.naive_now()
+ },
+ :accepted
+ )
end
def accept_task(id) do
@@ -233,48 +286,43 @@ defmodule Systems.Crew.Public do
|> accept_task()
end
- def update_task(%Crew.TaskModel{} = task, attrs) do
+ def update_task(%Crew.TaskModel{} = task, attrs, event) do
Multi.new()
- |> multi_update(:task, task, attrs)
+ |> multi_update(:task, task, attrs, event)
|> Repo.transaction()
end
- def multi_update(multi, :task, task, attrs) do
+ def multi_update(multi, :task, task, attrs, event) do
changeset = Crew.TaskModel.changeset(task, attrs)
- multi_update(multi, :task, changeset)
+ multi_update(multi, :task, changeset, event)
end
- def multi_update(multi, :task, changeset) do
+ def multi_update(multi, :task, changeset, event \\ :updated) do
multi
- |> Multi.update(:task, changeset)
- |> Signal.Public.multi_dispatch(:crew_task_updated, changeset)
+ |> Multi.update(:crew_task, changeset)
+ |> Signal.Public.multi_dispatch({:crew_task, event}, %{changeset: changeset})
end
def delete_task(%Crew.TaskModel{} = task) do
- update_task(task, %{expired: true})
+ update_task(task, %{expired: true}, :deleted)
end
def delete_task(_), do: nil
# Members
- def cancel(crew, user) do
+ def cancel(crew, user_ref) do
Multi.new()
- |> cancel(crew, user)
+ |> cancel(crew, user_ref)
|> Repo.transaction()
end
- def cancel(%Multi{} = multi, crew, user) do
- if member?(crew, user) do
- member = get_member!(crew, user)
- task = get_task(crew, member)
+ def cancel(%Multi{} = multi, crew, user_ref) do
+ member = get_member(crew, user_ref)
+ task_query = task_query(crew, user_ref, false)
- # temporary cancel is implemented by expiring the task
- multi
- |> Multi.update(:member, Crew.MemberModel.changeset(member, %{expired: true}))
- |> multi_update(:task, task, %{expired: true})
- else
- Logger.warn("Unable to cancel, user #{user.id} is not a member on crew #{crew.id}")
- end
+ multi
+ |> Multi.update(:member, Crew.MemberModel.changeset(member, %{expired: true}))
+ |> Multi.update_all(:tasks, task_query, set: [expired: true])
end
def count_members(crew) do
@@ -285,16 +333,18 @@ defmodule Systems.Crew.Public do
|> Repo.one()
end
- def public_id(crew, user) do
+ def public_id(crew, user_ref) do
crew
- |> member_query(user)
+ |> member_query(user_ref)
|> select([m], m.public_id)
|> Repo.one()
end
- def get_member!(crew, user) do
+ def get_member(crew, user_ref) do
+ user_id = user_id(user_ref)
+
from(m in Crew.MemberModel,
- where: m.crew_id == ^crew.id and m.user_id == ^user.id and m.expired == false
+ where: m.crew_id == ^crew.id and m.user_id == ^user_id and m.expired == false
)
|> Repo.one()
end
@@ -303,33 +353,41 @@ defmodule Systems.Crew.Public do
Repo.get!(Crew.MemberModel, id)
end
- def list_members_without_task(crew) do
- member_ids_with_task =
- from(t in Crew.TaskModel,
- where: t.crew_id == ^crew.id and t.expired == false,
- select: t.member_id
- )
-
- from(m in Crew.MemberModel,
- where: m.crew_id == ^crew.id and m.id not in subquery(member_ids_with_task)
- )
- |> Repo.all()
+ def apply_member(%Crew.Model{} = crew, %User{} = user, [_ | _] = identifier, expire_at \\ nil) do
+ if member = get_expired_member(crew, user, [:crew]) do
+ member = reset_member(member, expire_at)
+ {:ok, %{member: member}}
+ else
+ Multi.new()
+ |> insert(:member, crew, user, %{expire_at: expire_at})
+ |> insert(:crew_task, crew, user, %{
+ identifier: identifier,
+ status: :pending,
+ expire_at: expire_at
+ })
+ |> insert(:role_assignment, crew, user, :participant)
+ |> Repo.transaction()
+ end
end
- def apply_member(%Crew.Model{} = crew, %User{} = user, expire_at \\ nil) do
- if member = get_expired_member(crew, user) do
+ def apply_member_with_role(
+ %Crew.Model{} = crew,
+ %User{} = user,
+ role \\ :participant,
+ expire_at \\ nil
+ ) do
+ if member = get_expired_member(crew, user, [:crew]) do
member = reset_member(member, expire_at)
{:ok, %{member: member}}
else
Multi.new()
|> insert(:member, crew, user, %{expire_at: expire_at})
- |> insert(:task, crew, %{status: :pending, expire_at: expire_at})
- |> insert(:role_assignment, crew, user, :participant)
+ |> insert(:role_assignment, crew, user, role)
|> Repo.transaction()
end
end
- defp insert(multi, :member = name, crew, user, attrs) do
+ defp insert(multi, :member = name, crew, %User{} = user, attrs) do
Multi.insert(
multi,
name,
@@ -340,24 +398,29 @@ defmodule Systems.Crew.Public do
)
end
- defp insert(multi, :role_assignment = name, crew, user, role) do
+ defp insert(multi, :role_assignment = name, crew, %User{} = user, role) do
Multi.insert(multi, name, Authorization.build_role_assignment(user, crew, role))
end
- defp insert(multi, :task = name, crew, attrs) do
- Multi.insert(multi, name, fn %{member: member} ->
+ defp insert(multi, :crew_task = name, crew, %User{} = user, attrs) do
+ Multi.insert(
+ multi,
+ name,
%Crew.TaskModel{}
|> Crew.TaskModel.changeset(attrs)
|> Ecto.Changeset.put_assoc(:crew, crew)
- |> Ecto.Changeset.put_assoc(:member, member)
- end)
+ |> Ecto.Changeset.put_assoc(:auth_node, Authorization.Node.create(user.id, :owner))
+ )
end
- def get_expired_member(%Crew.Model{} = crew, %User{} = user) do
+ def get_expired_member(%Crew.Model{} = crew, user_ref, preload \\ []) do
+ user_id = user_id(user_ref)
+
from(m in Crew.MemberModel,
+ preload: ^preload,
where:
m.crew_id == ^crew.id and
- m.user_id == ^user.id and
+ m.user_id == ^user_id and
m.expired == true
)
|> Repo.one()
@@ -371,9 +434,9 @@ defmodule Systems.Crew.Public do
|> Repo.all()
end
- def reset_member(%Crew.MemberModel{} = member, expire_at) do
+ def reset_member(%Crew.MemberModel{crew: crew} = member, expire_at) do
member_query = from(m in Crew.MemberModel, where: m.id == ^member.id)
- task_query = from(t in Crew.TaskModel, where: t.member_id == ^member.id)
+ task_query = task_query(crew, member, true)
member_attrs = Crew.MemberModel.reset_attrs(expire_at)
task_attrs = Crew.TaskModel.reset_attrs(expire_at)
@@ -387,15 +450,15 @@ defmodule Systems.Crew.Public do
|> Repo.one()
end
- def member?(crew, user) do
+ def member?(crew, user_ref) do
crew
- |> member_query(user)
+ |> member_query(user_ref)
|> Repo.exists?()
end
- def expired_member?(crew, user) do
+ def expired_member?(crew, user_ref) do
crew
- |> member_query(user, true)
+ |> member_query(user_ref, true)
|> Repo.exists?()
end
@@ -408,15 +471,31 @@ defmodule Systems.Crew.Public do
|> Repo.one()
end
- defp member_query(crew, user, expired \\ false) do
+ def member_query(crew, user_ref, expired \\ false) do
+ user_id = user_id(user_ref)
+
from(m in Crew.MemberModel,
where:
m.crew_id == ^crew.id and
- m.user_id == ^user.id and
+ m.user_id == ^user_id and
m.expired == ^expired
)
end
+ def member_query(task_ids) do
+ from(member in Crew.MemberModel,
+ inner_join: user in User,
+ on: member.user_id == user.id,
+ inner_join: role in Authorization.RoleAssignment,
+ on: role.principal_id == user.id,
+ inner_join: node in Authorization.Node,
+ on: node.id == role.node_id,
+ inner_join: task in Crew.TaskModel,
+ on: task.auth_node_id == role.node_id,
+ where: task.id in subquery(task_ids)
+ )
+ end
+
@doc """
Marks members & tasks as expired when:
- expire_at is in the past
@@ -433,10 +512,8 @@ defmodule Systems.Crew.Public do
# 3. end of survey pointing to wrong url (redirecting, but to the wrong campaign)
task_query = from(t in Crew.TaskModel, where: t.expire_at <= ^now and is_nil(t.started_at))
- member_ids = from(t in task_query, select: t.member_id)
-
- member_query =
- from(m in Crew.MemberModel, where: m.expire_at <= ^now and m.id in subquery(member_ids))
+ task_ids = from(t in task_query, select: t.id)
+ member_query = member_query(task_ids)
Multi.new()
|> Multi.update_all(:members, member_query, set: [expired: true])
@@ -461,14 +538,18 @@ defmodule Systems.Crew.Public do
is_nil(t.completed_at)
)
- member_ids = from(t in task_query, select: t.member_id)
+ user_ids = from(t in task_query, select: t.user_id)
member_query =
- from(m in Crew.MemberModel, where: m.expire_at <= ^now and m.id in subquery(member_ids))
+ from(m in Crew.MemberModel, where: m.expire_at <= ^now and m.user_id in subquery(user_ids))
Multi.new()
|> Multi.update_all(:members, member_query, set: [expired: true])
|> Multi.update_all(:tasks, task_query, set: [expired: true])
|> Repo.transaction()
end
+
+ def user_id(%User{id: id}), do: id
+ def user_id(%Crew.MemberModel{user_id: id}), do: id
+ def user_id(id) when is_integer(id), do: id
end
diff --git a/core/systems/crew/model.ex b/core/systems/crew/model.ex
index 93645d588..70c1332c3 100644
--- a/core/systems/crew/model.ex
+++ b/core/systems/crew/model.ex
@@ -5,6 +5,8 @@ defmodule Systems.Crew.Model do
use Ecto.Schema
import Ecto.Changeset
+ alias Systems.Crew
+
schema "crews" do
has_many(:tasks, Systems.Crew.TaskModel, foreign_key: :crew_id)
has_many(:members, Systems.Crew.MemberModel, foreign_key: :crew_id)
@@ -15,6 +17,8 @@ defmodule Systems.Crew.Model do
@fields ~w()a
+ def auth_tree(%Crew.Model{auth_node: auth_node}), do: auth_node
+
defimpl Frameworks.GreenLight.AuthorizationNode do
def id(crew), do: crew.auth_node_id
end
diff --git a/core/systems/crew/task_model.ex b/core/systems/crew/task_model.ex
index 4f0e20c5e..fb4aac2a8 100644
--- a/core/systems/crew/task_model.ex
+++ b/core/systems/crew/task_model.ex
@@ -10,6 +10,7 @@ defmodule Systems.Crew.TaskModel do
require Crew.RejectCategories
schema "crew_tasks" do
+ field(:identifier, {:array, :string})
field(:status, Ecto.Enum, values: Crew.TaskStatus.values())
field(:started_at, :naive_datetime)
field(:completed_at, :naive_datetime)
@@ -23,18 +24,23 @@ defmodule Systems.Crew.TaskModel do
field(:rejected_message, :string)
belongs_to(:crew, Crew.Model)
- belongs_to(:member, Crew.MemberModel)
+ belongs_to(:auth_node, Core.Authorization.Node)
timestamps()
end
- @fields ~w(status started_at completed_at expire_at expired accepted_at rejected_at rejected_category rejected_message)a
+ @fields ~w(identifier status started_at completed_at expire_at expired accepted_at rejected_at rejected_category rejected_message)a
+
+ defimpl Frameworks.GreenLight.AuthorizationNode do
+ def id(task), do: task.auth_node_id
+ end
@doc false
def changeset(task, attrs) do
task
|> cast(attrs, @fields)
- |> validate_required([:status])
+ |> validate_required([:identifier, :status])
+ |> unique_constraint(:identifier)
end
def reset_attrs(expire_at) do
diff --git a/core/systems/data_donation/_public.ex b/core/systems/data_donation/_public.ex
index 87cbcd4c1..e695e3fe8 100644
--- a/core/systems/data_donation/_public.ex
+++ b/core/systems/data_donation/_public.ex
@@ -1,163 +1,2 @@
defmodule Systems.DataDonation.Public do
- @moduledoc """
-
- A data donation allows a researcher to ask participants to submit data. This
- data is submitted in the form of a file that is stored on the participants
- device.
-
- Tools are provided that allow for execution of filtering code on the device
- of the participant. This ensures that only the data that is needed for the
- study is shared with the researcher.
- """
-
- import Ecto.Query, warn: false
- alias Core.Repo
-
- alias Ecto.Multi
- alias Core.Authorization
-
- alias Systems.{
- DataDonation
- }
-
- def list do
- Repo.all(DataDonation.ToolModel)
- end
-
- def list_tasks(tool_id, preload \\ []) do
- from(task in DataDonation.TaskModel,
- where: task.tool_id == ^tool_id,
- order_by: {:asc, :position},
- preload: ^preload
- )
- |> Repo.all()
- end
-
- def get_tool!(id, preload \\ []) do
- from(a in DataDonation.ToolModel, preload: ^preload)
- |> Repo.get!(id)
- end
-
- def get_task!(id, preload \\ []) do
- from(task in DataDonation.TaskModel, preload: ^preload)
- |> Repo.get!(id)
- end
-
- def get_task(tool_id, position, preload \\ []) do
- from(task in DataDonation.TaskModel,
- where: task.tool_id == ^tool_id,
- where: task.position == ^position,
- preload: ^preload
- )
- |> Repo.one()
- end
-
- def get_document_task!(id, preload \\ []) do
- from(task in DataDonation.DocumentTaskModel, preload: ^preload)
- |> Repo.get!(id)
- end
-
- def create(
- %{subject_count: _, director: _} = attrs,
- %Authorization.Node{} = auth_node
- ) do
- attrs = Map.put(attrs, :status, :concept)
-
- %DataDonation.ToolModel{}
- |> DataDonation.ToolModel.changeset(attrs)
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- end
-
- def add_task(tool, task_type) when is_binary(task_type) do
- add_task(tool, String.to_existing_atom(task_type))
- end
-
- def add_task(%DataDonation.ToolModel{} = tool, task_type) do
- Multi.new()
- |> Multi.run(:position, fn _, _ ->
- {:ok, task_count(tool)}
- end)
- |> Multi.insert(:task_special, create_task_special(task_type))
- |> Multi.insert(:task, fn %{position: position, task_special: task_special} ->
- create_task(tool, position, task_type, task_special)
- end)
- |> Repo.transaction()
- end
-
- def task_count(%DataDonation.ToolModel{id: tool_id}) do
- from(task in DataDonation.TaskModel,
- where: task.tool_id == ^tool_id,
- select: count(task.id)
- )
- |> Repo.one()
- end
-
- def create_task(%DataDonation.ToolModel{} = tool, position, task_type, special) do
- %DataDonation.TaskModel{}
- |> DataDonation.TaskModel.changeset(%{position: position})
- |> Ecto.Changeset.put_assoc(:tool, tool)
- |> Ecto.Changeset.put_assoc(task_type, special)
- end
-
- def create_task_special(:survey_task) do
- %DataDonation.SurveyTaskModel{}
- |> DataDonation.SurveyTaskModel.changeset(%{})
- end
-
- def create_task_special(:request_task) do
- %DataDonation.DocumentTaskModel{}
- |> DataDonation.DocumentTaskModel.changeset(%{})
- end
-
- def create_task_special(:download_task) do
- %DataDonation.DocumentTaskModel{}
- |> DataDonation.DocumentTaskModel.changeset(%{})
- end
-
- def create_task_special(:donate_task) do
- %DataDonation.DonateTaskModel{}
- |> DataDonation.DonateTaskModel.changeset(%{})
- end
-
- def update(changeset) do
- Multi.new()
- |> Repo.multi_update(:data_donation_tool, changeset)
- |> Repo.transaction()
- end
-
- def delete(%DataDonation.ToolModel{} = tool) do
- Multi.new()
- |> Multi.delete(:data_donation_tool, tool)
- |> Repo.transaction()
- end
-
- def delete(%DataDonation.TaskModel{} = task) do
- Repo.delete(task)
- end
-
- def switch_position(
- %DataDonation.TaskModel{position: position1} = task1,
- %DataDonation.TaskModel{position: position2} = task2
- ) do
- Multi.new()
- |> Multi.update(:task1, DataDonation.TaskModel.changeset(task1, %{position: position2}))
- |> Multi.update(:task2, DataDonation.TaskModel.changeset(task2, %{position: position1}))
- |> Repo.transaction()
- end
-
- def copy(%DataDonation.ToolModel{} = tool, auth_node) do
- %DataDonation.ToolModel{}
- |> DataDonation.ToolModel.changeset(Map.from_struct(tool))
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert!()
- end
-end
-
-defimpl Core.Persister, for: Systems.DataDonation.ToolModel do
- def save(_tool, changeset) do
- case Systems.DataDonation.Public.update(changeset) do
- {:ok, %{data_donation_tool: tool}} -> {:ok, tool}
- _ -> {:error, changeset}
- end
- end
end
diff --git a/core/systems/data_donation/document_task_model.ex b/core/systems/data_donation/document_task_model.ex
deleted file mode 100644
index 87c60dd8b..000000000
--- a/core/systems/data_donation/document_task_model.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-defmodule Systems.DataDonation.DocumentTaskModel do
- use Ecto.Schema
- import Ecto.Changeset
-
- schema "data_donation_document_tasks" do
- field(:document_name, :string)
- field(:document_ref, :string)
- timestamps()
- end
-
- @fields ~w(document_name document_ref)a
-
- def changeset(model, params) do
- model
- |> cast(params, @fields)
- end
-end
diff --git a/core/systems/data_donation/donate_task_model.ex b/core/systems/data_donation/donate_task_model.ex
deleted file mode 100644
index d763899e8..000000000
--- a/core/systems/data_donation/donate_task_model.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-defmodule Systems.DataDonation.DonateTaskModel do
- use Ecto.Schema
- import Ecto.Changeset
-
- schema "data_donation_donate_tasks" do
- timestamps()
- end
-
- @fields ~w()a
-
- def changeset(model, params) do
- model
- |> cast(params, @fields)
- end
-end
diff --git a/core/systems/data_donation/spot_model.ex b/core/systems/data_donation/spot_model.ex
deleted file mode 100644
index c088ed7b7..000000000
--- a/core/systems/data_donation/spot_model.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-defmodule Systems.DataDonation.SpotModel do
- use Ecto.Schema
- import Ecto.Changeset
-
- schema "data_donation_spots" do
- belongs_to(:tool, DataDonation.ToolModel)
- belongs_to(:auth_node, Core.Authorization.Node)
- has_many(:statuses, DataDonation.TaskSpotStatusModel, foreign_key: :spot_id)
-
- timestamps()
- end
-
- @fields ~w(position title subtitle)a
-
- def changeset(model, params) do
- model
- |> cast(params, @fields)
- |> validate_required(@fields)
- end
-end
diff --git a/core/systems/data_donation/survey_task_model.ex b/core/systems/data_donation/survey_task_model.ex
deleted file mode 100644
index 274cf0c7b..000000000
--- a/core/systems/data_donation/survey_task_model.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-defmodule Systems.DataDonation.SurveyTaskModel do
- use Ecto.Schema
- import Ecto.Changeset
-
- schema "data_donation_survey_tasks" do
- timestamps()
- end
-
- @fields ~w()a
-
- def changeset(model, params) do
- model
- |> cast(params, @fields)
- end
-end
diff --git a/core/systems/data_donation/task_builder_view.ex b/core/systems/data_donation/task_builder_view.ex
deleted file mode 100644
index 40ec4b8e8..000000000
--- a/core/systems/data_donation/task_builder_view.ex
+++ /dev/null
@@ -1,99 +0,0 @@
-defmodule Systems.DataDonation.TaskBuilderView do
- use CoreWeb, :live_component
-
- import Frameworks.Pixel.SidePanel
-
- alias Systems.{
- DataDonation
- }
-
- import DataDonation.TaskViews
-
- @impl true
- def update(%{action: "delete", task: task}, socket) do
- DataDonation.Public.delete(task)
- {:ok, socket |> update_tasks()}
- end
-
- @impl true
- def update(%{action: "up", task: %{tool_id: tool_id, position: position} = task}, socket) do
- if task_above = DataDonation.Public.get_task(tool_id, position - 1) do
- DataDonation.Public.switch_position(task, task_above)
- {:ok, socket |> update_tasks()}
- else
- {:ok, socket}
- end
- end
-
- @impl true
- def update(%{action: "down", task: %{tool_id: tool_id, position: position} = task}, socket) do
- if task_below = DataDonation.Public.get_task(tool_id, position + 1) do
- DataDonation.Public.switch_position(task, task_below)
- {:ok, socket |> update_tasks()}
- else
- {:ok, socket}
- end
- end
-
- @impl true
- def update(%{id: id, tool_id: tool_id, flow: flow, library: library}, socket) do
- {
- :ok,
- socket
- |> assign(
- id: id,
- tool_id: tool_id,
- flow: flow,
- library: library
- )
- |> update_tool()
- |> update_tasks()
- }
- end
-
- defp update_tool(%{assigns: %{tool_id: tool_id}} = socket) do
- tool = DataDonation.Public.get_tool!(tool_id)
- socket |> assign(tool: tool)
- end
-
- defp update_tasks(%{assigns: %{tool_id: tool_id}} = socket) do
- tasks = DataDonation.Public.list_tasks(tool_id, DataDonation.TaskModel.preload_graph(:down))
- socket |> assign(tasks: tasks)
- end
-
- @impl true
- def handle_event("add", %{"item" => item}, %{assigns: %{tool: tool}} = socket) do
- {:ok, _} = DataDonation.Public.add_task(tool, "#{item}_task")
-
- {
- :noreply,
- socket
- |> update_tasks()
- }
- end
-
- @impl true
- def render(assigns) do
- ~H"""
-
+
+ <%= if @popup do %>
+ <.popup>
+
+
+
+
+ <.live_component module={@popup.module} {@popup.props} />
+
+
+ <% end %>
+
+ <%= if @dialog do %>
+ <.popup>
+ <.plain_dialog {@dialog} />
+
+ <% end %>
+
+
-
- """
- end
-end
diff --git a/core/systems/data_donation/task_cell.ex b/core/systems/data_donation/task_cell.ex
deleted file mode 100644
index d6c180722..000000000
--- a/core/systems/data_donation/task_cell.ex
+++ /dev/null
@@ -1,175 +0,0 @@
-defmodule Systems.DataDonation.TaskCell do
- use CoreWeb, :live_component
-
- alias Systems.{
- DataDonation
- }
-
- @impl true
- def update(
- %{id: id, entity_id: entity_id, parent: parent, relative_position: relative_position},
- socket
- ) do
- {
- :ok,
- socket
- |> assign(
- id: id,
- entity_id: entity_id,
- parent: parent,
- expanded?: false,
- relative_position: relative_position
- )
- |> update_task()
- |> update_task_form()
- |> update_special_form()
- |> update_title()
- |> update_buttons()
- }
- end
-
- @impl true
- def handle_event("collapse", _params, socket) do
- {:noreply, socket |> assign(expanded?: false)}
- end
-
- @impl true
- def handle_event("expand", _params, socket) do
- {:noreply, socket |> assign(expanded?: true)}
- end
-
- @impl true
- def handle_event(action, _params, %{assigns: %{parent: parent, task: task}} = socket) do
- update_target(parent, %{module: __MODULE__, action: action, task: task})
- {:noreply, socket}
- end
-
- defp update_task(%{assigns: %{entity_id: entity_id}} = socket) do
- task = DataDonation.Public.get_task!(entity_id, DataDonation.TaskModel.preload_graph(:down))
- socket |> assign(task: task)
- end
-
- defp update_task_form(%{assigns: %{id: id, task: task}} = socket) do
- task_form = %{
- id: "#{id}_task_form",
- module: DataDonation.TaskForm,
- entity_id: task.id
- }
-
- socket |> assign(task_form: task_form)
- end
-
- defp update_special_form(%{assigns: %{task: task}} = socket) do
- special_form = special_form(task)
- socket |> assign(special_form: special_form)
- end
-
- defp special_form(%{request_task: %{id: id}}),
- do: %{
- id: "#{id}_request_form",
- module: DataDonation.DocumentTaskForm,
- entity_id: id,
- parent: %{type: __MODULE__, id: id}
- }
-
- defp special_form(%{download_task: %{id: id}}),
- do: %{
- id: "#{id}_request_form",
- module: DataDonation.DocumentTaskForm,
- entity_id: id,
- parent: %{type: __MODULE__, id: id}
- }
-
- defp special_form(_), do: nil
-
- defp update_title(%{assigns: %{task: task}} = socket) do
- title = get_title(task)
- assign(socket, title: title)
- end
-
- defp update_buttons(socket) do
- up_button = %{
- action: %{type: :send, event: "up"},
- face: %{type: :icon, icon: :arrow_up}
- }
-
- down_button = %{
- action: %{type: :send, event: "down"},
- face: %{type: :icon, icon: :arrow_down}
- }
-
- delete_button = %{
- action: %{type: :send, event: "delete"},
- face: %{type: :icon, icon: :delete_red}
- }
-
- collapse_button = %{
- action: %{type: :send, event: "collapse"},
- face: %{
- type: :label,
- label: dgettext("eyra-ui", "collapse.button"),
- text_color: "text-primary",
- icon: :chevron_up
- }
- }
-
- expand_button = %{
- action: %{type: :send, event: "expand"},
- face: %{
- type: :label,
- label: dgettext("eyra-ui", "expand.button"),
- text_color: "text-primary",
- icon: :chevron_down
- }
- }
-
- assign(socket,
- up_button: up_button,
- down_button: down_button,
- delete_button: delete_button,
- collapse_button: collapse_button,
- expand_button: expand_button
- )
- end
-
- defp get_title(%{survey_task_id: id}) when not is_nil(id),
- do: dgettext("eyra-data-donation", "task.survey.title")
-
- defp get_title(%{request_task_id: id}) when not is_nil(id),
- do: dgettext("eyra-data-donation", "task.request.title")
-
- defp get_title(%{download_task_id: id}) when not is_nil(id),
- do: dgettext("eyra-data-donation", "task.download.title")
-
- defp get_title(%{donate_task_id: id}) when not is_nil(id),
- do: dgettext("eyra-data-donation", "task.donate.title")
-
- @impl true
- def render(assigns) do
- ~H"""
-
-
-
-
-
- <%= @flow.title %>
- <%= @flow.description %>
- <.spacing value="M" />
- <.list tasks={@tasks} parent={%{type: __MODULE__, id: @id}} />
-
-
-
- <.side_panel id={:library} parent={:task_builder}>
-
- <.library {@library} />
-
-
-
-
- <% else %>
-
- <% end %>
-
- """
- end
-end
diff --git a/core/systems/data_donation/task_form.ex b/core/systems/data_donation/task_form.ex
deleted file mode 100644
index a3ce30a74..000000000
--- a/core/systems/data_donation/task_form.ex
+++ /dev/null
@@ -1,87 +0,0 @@
-defmodule Systems.DataDonation.TaskForm do
- use CoreWeb.LiveForm
-
- import Frameworks.Pixel.Form
-
- alias Systems.{
- DataDonation
- }
-
- @impl true
- def update(
- %{id: id, entity_id: entity_id},
- socket
- ) do
- entity = DataDonation.Public.get_task!(entity_id)
- changeset = DataDonation.TaskModel.changeset(entity, %{})
-
- {
- :ok,
- socket
- |> assign(
- id: id,
- entity: entity,
- changeset: changeset
- )
- |> update_platform_options()
- |> update_selected_platform()
- }
- end
-
- defp update_selected_platform(%{assigns: %{entity: %{platform: platform}}} = socket) do
- assign(socket, selected_platform: platform)
- end
-
- defp update_platform_options(socket) do
- platform_options = DataDonation.Platforms.labels()
- assign(socket, platform_options: platform_options)
- end
-
- # Handle Events
- @impl true
- def handle_event("select-option", %{"id" => platform}, %{assigns: %{entity: entity}} = socket) do
- {
- :noreply,
- socket
- |> assign(selected_platform: platform)
- |> save(entity, %{platform: platform})
- }
- end
-
- @impl true
- def handle_event("save", %{"task_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
- {
- :noreply,
- socket
- |> save(entity, attrs)
- }
- end
-
- # Saving
-
- def save(socket, %DataDonation.TaskModel{} = entity, attrs) do
- changeset = DataDonation.TaskModel.changeset(entity, attrs)
-
- socket
- |> save(changeset)
- end
-
- @impl true
- def render(assigns) do
- ~H"""
-
- <%= @title %>
-
- <%= if @relative_position != :bottom do %>
-
- <% end %>
- <%= if @relative_position != :top do %>
-
- <% end %>
-
-
- <%= if @expanded? do %>
- <.live_component {@task_form} />
- <%= if @special_form do %>
- <.live_component {@special_form} />
- <.spacing value="XS" />
- <% end %>
-
- <.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself} >
- <.dropdown
- form={form}
- field={:platform}
- options={@platform_options}
- label_text={dgettext("eyra-data-donation", "task.platform.label")}
- target={@myself}
- />
- <.text_input form={form} field={:title} label_text={dgettext("eyra-data-donation", "task.title.label")} />
- <.text_input form={form} field={:description} label_text={dgettext("eyra-data-donation", "task.description.label")} />
-
-
- """
- end
-end
diff --git a/core/systems/data_donation/task_model.ex b/core/systems/data_donation/task_model.ex
deleted file mode 100644
index 84eb6d9de..000000000
--- a/core/systems/data_donation/task_model.ex
+++ /dev/null
@@ -1,48 +0,0 @@
-defmodule Systems.DataDonation.TaskModel do
- use Ecto.Schema
- use Frameworks.Utility.Schema
-
- import Ecto.Changeset
-
- alias Systems.{
- DataDonation
- }
-
- schema "data_donation_tasks" do
- field(:platform, :string)
- field(:position, :integer)
- field(:title, :string)
- field(:description, :string)
-
- belongs_to(:tool, DataDonation.ToolModel)
- has_many(:statuses, DataDonation.TaskSpotStatusModel, foreign_key: :task_id)
-
- belongs_to(:survey_task, DataDonation.SurveyTaskModel)
- belongs_to(:request_task, DataDonation.DocumentTaskModel)
- belongs_to(:download_task, DataDonation.DocumentTaskModel)
- belongs_to(:donate_task, DataDonation.DonateTaskModel)
-
- timestamps()
- end
-
- @fields ~w(platform position title description)a
-
- def changeset(model, params) do
- model
- |> cast(params, @fields)
- end
-
- def preload_graph(:down),
- do:
- preload_graph([
- :survey_task,
- :request_task,
- :download_task,
- :donate_task
- ])
-
- def preload_graph(:survey_task), do: [survey_task: []]
- def preload_graph(:request_task), do: [request_task: []]
- def preload_graph(:download_task), do: [download_task: []]
- def preload_graph(:donate_task), do: [donate_task: []]
-end
diff --git a/core/systems/data_donation/task_spot_status_model.ex b/core/systems/data_donation/task_spot_status_model.ex
deleted file mode 100644
index d59a43dc8..000000000
--- a/core/systems/data_donation/task_spot_status_model.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-defmodule Systems.DataDonation.TaskSpotStatusModel do
- use Ecto.Schema
- import Ecto.Changeset
-
- schema "data_donation_task_spot_status" do
- field(:status, :string)
- belongs_to(:spot, DataDonation.SpotModel)
- belongs_to(:task, DataDonation.TaskModel)
- end
-
- @fields ~w(position title subtitle)a
-
- def changeset(model, params) do
- model
- |> cast(params, @fields)
- |> validate_required(@fields)
- end
-end
diff --git a/core/systems/data_donation/task_views.ex b/core/systems/data_donation/task_views.ex
deleted file mode 100644
index 57a26b94f..000000000
--- a/core/systems/data_donation/task_views.ex
+++ /dev/null
@@ -1,75 +0,0 @@
-defmodule Systems.DataDonation.TaskViews do
- use CoreWeb, :html
-
- alias Systems.DataDonation
- alias Frameworks.Pixel.Panel
- alias Frameworks.Pixel.Align
-
- attr(:title, :string, required: true)
- attr(:description, :string, required: true)
- attr(:items, :list, required: true)
-
- def library(assigns) do
- ~H"""
-
- <%= @title %>
- <%= @description %>
- <.spacing value="M" />
-
- """
- end
-
- attr(:id, :string, required: true)
- attr(:title, :string, required: true)
- attr(:description, :string, required: true)
-
- def library_item(assigns) do
- ~H"""
-
- <%= for item <- @items do %>
- <.library_item {item} />
- <% end %>
-
-
-
- <%= @title %>
- <.spacing value="XS" />
- <%= @description %>
- <.spacing value="M" />
- <.wrap>
-
-
-
-
- """
- end
-
- defp relative_position(0, _count), do: :top
- defp relative_position(position, count) when position == count - 1, do: :bottom
- defp relative_position(_position, _count), do: :middle
-
- attr(:tasks, :list, required: true)
- attr(:parent, :map, required: true)
-
- def list(assigns) do
- ~H"""
-
-
- <%= dgettext("eyra-data-donation", "task.list.hint") %>
-
- <%= for task <- @tasks do %>
- <.live_component
- id={"task-cell-#{task.id}"}
- module={DataDonation.TaskCell}
- entity_id={task.id}
- parent={@parent}
- relative_position={relative_position(task.position, Enum.count(@tasks))}
- />
- <% end %>
-
- """
- end
-end
diff --git a/core/systems/data_donation/tool_form.ex b/core/systems/data_donation/tool_form.ex
deleted file mode 100644
index cedb47375..000000000
--- a/core/systems/data_donation/tool_form.ex
+++ /dev/null
@@ -1,101 +0,0 @@
-defmodule Systems.DataDonation.ToolForm do
- use CoreWeb.LiveForm
-
- alias Core.Accounts
-
- import Frameworks.Pixel.Form
- alias Frameworks.Pixel.Selector
-
- alias Systems.{
- DataDonation
- }
-
- require Systems.DataDonation.Platforms
-
- @impl true
- def update(
- %{active_item_ids: active_item_ids, selector_id: selector_id},
- %{assigns: %{entity: entity}} = socket
- ) do
- {:ok, socket |> save(entity, %{selector_id => active_item_ids})}
- end
-
- @impl true
- def update(
- %{id: id, entity_id: entity_id},
- socket
- ) do
- entity = DataDonation.Public.get_tool!(entity_id)
- changeset = DataDonation.ToolModel.changeset(entity, %{})
-
- {
- :ok,
- socket
- |> assign(
- id: id,
- entity_id: entity_id,
- entity: entity,
- changeset: changeset
- )
- |> update_platform_labels()
- }
- end
-
- defp update_platform_labels(%{assigns: %{entity: %{platforms: platforms}}} = socket) do
- platform_labels = DataDonation.Platforms.labels(platforms)
- socket |> assign(platform_labels: platform_labels)
- end
-
- # Handle Events
-
- @impl true
- def handle_event("save", %{"tool_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
- {
- :noreply,
- socket
- |> save(entity, attrs)
- }
- end
-
- @impl true
- def handle_event(
- "delete",
- _params,
- %{assigns: %{entity_id: entity_id, current_user: user}} = socket
- ) do
- DataDonation.Public.get_tool!(entity_id)
- |> DataDonation.Public.delete()
-
- {:noreply, push_redirect(socket, to: Accounts.start_page_path(user))}
- end
-
- # Saving
- def save(socket, %DataDonation.ToolModel{} = entity, attrs) do
- changeset = DataDonation.ToolModel.changeset(entity, attrs)
-
- socket
- |> save(changeset)
- end
-
- attr(:entity_id, :any, required: true)
- @impl true
- def render(assigns) do
- ~H"""
-
- <.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself} >
- <.number_input form={form} field={:subject_count} label_text={dgettext("eyra-data-donation", "config.nrofsubjects.label")} />
-
- <.spacing value="M" />
-
- <%= dgettext("eyra-data-donation", "platforms.title") %>
- <.live_component
- module={Selector}
- id={:platforms}
- items={@platform_labels}
- type={:label}
- parent={%{type: __MODULE__, id: @id}}
- />
-
- """
- end
-end
diff --git a/core/systems/data_donation/tool_model.ex b/core/systems/data_donation/tool_model.ex
deleted file mode 100644
index 75141f94e..000000000
--- a/core/systems/data_donation/tool_model.ex
+++ /dev/null
@@ -1,71 +0,0 @@
-defmodule Systems.DataDonation.ToolModel do
- @moduledoc """
- The data donation tool schema.
- """
- use Ecto.Schema
- use Frameworks.Utility.Model
- use Frameworks.Utility.Schema
-
- require Core.Enums.Themes
-
- import Ecto.Changeset
-
- alias Systems.{
- DataDonation
- }
-
- require DataDonation.Platforms
-
- schema "data_donation_tools" do
- field(:status, Ecto.Enum, values: DataDonation.ToolStatus.values(), default: :concept)
- field(:platforms, {:array, Ecto.Enum}, values: DataDonation.Platforms.schema_values())
- field(:subject_count, :integer, default: 0)
- field(:director, Ecto.Enum, values: [:project])
-
- belongs_to(:auth_node, Core.Authorization.Node)
- has_many(:tasks, DataDonation.TaskModel, foreign_key: :tool_id)
-
- timestamps()
- end
-
- @fields ~w(platforms subject_count director)a
- @required_fields ~w()a
-
- @impl true
- def operational_fields, do: @fields
-
- @impl true
- def operational_validation(changeset), do: changeset
-
- def preload_graph(:full),
- do:
- preload_graph([
- :auth_node
- ])
-
- def preload_graph(:auth_node), do: [auth_node: []]
-
- defimpl Frameworks.GreenLight.AuthorizationNode do
- def id(tool), do: tool.auth_node_id
- end
-
- def changeset(tool, params) do
- tool
- |> cast(params, @fields)
- |> validate_required(@required_fields)
- end
-
- # def validate_optional_number(changeset, field, opts) do
- # if blank?(changeset, field) do
- # changeset
- # else
- # changeset |> validate_number(field, opts)
- # end
- # end
-
- # defp blank?(changeset, field) do
- # %{changes: changes} = changeset
- # value = Map.get(changes, field)
- # blank?(value)
- # end
-end
diff --git a/core/systems/director.ex b/core/systems/director.ex
deleted file mode 100644
index 28edef457..000000000
--- a/core/systems/director.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-defmodule Systems.Director do
- def get(director), do: module(director, "Director")
- def public(director), do: module(director, "Public")
- def presenter(director), do: module(director, "Presenter")
-
- defp module(%{director: director}, name) when is_atom(director),
- do: module(Atom.to_string(director), name)
-
- defp module(director, name) when is_atom(director), do: module(Atom.to_string(director), name)
-
- defp module(director, name) do
- director = Macro.camelize(director)
-
- "Elixir.Systems.#{director}.#{name}"
- |> String.to_existing_atom()
- end
-end
diff --git a/core/systems/document/_presenter.ex b/core/systems/document/_presenter.ex
new file mode 100644
index 000000000..f9404f06e
--- /dev/null
+++ b/core/systems/document/_presenter.ex
@@ -0,0 +1,12 @@
+defmodule Systems.Document.Presenter do
+ @behaviour Frameworks.Concept.Presenter
+
+ alias Systems.{
+ Document
+ }
+
+ @impl true
+ def view_model(%Document.ToolModel{} = _tool, _page, _assigns) do
+ %{}
+ end
+end
diff --git a/core/systems/document/_public.ex b/core/systems/document/_public.ex
new file mode 100644
index 000000000..636bed344
--- /dev/null
+++ b/core/systems/document/_public.ex
@@ -0,0 +1,28 @@
+defmodule Systems.Document.Public do
+ import Ecto.Query, warn: false
+ alias Core.Repo
+
+ alias Systems.{
+ Document
+ }
+
+ def get_tool!(id, preload \\ []) do
+ from(tool in Document.ToolModel, preload: ^preload)
+ |> Repo.get!(id)
+ end
+
+ def prepare_tool(attrs, auth_node \\ Core.Authorization.prepare_node()) do
+ %Document.ToolModel{}
+ |> Document.ToolModel.changeset(attrs)
+ |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
+ end
+end
+
+defimpl Core.Persister, for: Systems.Document.ToolModel do
+ def save(_task, changeset) do
+ case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :document_tool) do
+ {:ok, %{document_tool: tool}} -> {:ok, tool}
+ _ -> {:error, changeset}
+ end
+ end
+end
diff --git a/core/systems/document/_routes.ex b/core/systems/document/_routes.ex
new file mode 100644
index 000000000..d7341b21c
--- /dev/null
+++ b/core/systems/document/_routes.ex
@@ -0,0 +1,4 @@
+defmodule Systems.Document.Routes do
+ defmacro routes() do
+ end
+end
diff --git a/core/systems/document/content_page.ex b/core/systems/document/content_page.ex
new file mode 100644
index 000000000..6e93263f3
--- /dev/null
+++ b/core/systems/document/content_page.ex
@@ -0,0 +1,45 @@
+defmodule Systems.Document.ContentPage do
+ use Systems.Content.Page
+
+ alias Systems.{
+ Document
+ }
+
+ @impl true
+ def get_authorization_context(%{"id" => id}, _session, _socket) do
+ Document.Public.get_tool!(id)
+ end
+
+ @impl true
+ def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
+ model =
+ Document.Public.get_tool!(String.to_integer(id), Document.ToolModel.preload_graph(:down))
+
+ tabbar_id = "lab_content/#{id}"
+
+ {
+ :ok,
+ socket |> initialize(id, model, tabbar_id, initial_tab, locale)
+ }
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.content_page
+ title={@vm.title}
+ show_errors={@vm.show_errors}
+ tabs={@vm.tabs}
+ menus={@menus}
+ actions={@actions}
+ more_actions={@more_actions}
+ initial_tab={@initial_tab}
+ tabbar_id={@tabbar_id}
+ tabbar_size={@tabbar_size}
+ breakpoint={@breakpoint}
+ popup={@popup}
+ dialog={@dialog}
+ />
+ """
+ end
+end
diff --git a/core/systems/document/content_page_builder.ex b/core/systems/document/content_page_builder.ex
new file mode 100644
index 000000000..f3f162f00
--- /dev/null
+++ b/core/systems/document/content_page_builder.ex
@@ -0,0 +1,20 @@
+defmodule Systems.Document.ContentPageBuilder do
+ import CoreWeb.Gettext
+
+ alias Systems.{
+ Document
+ }
+
+ def view_model(
+ %Document.ToolModel{id: id},
+ _assigns
+ ) do
+ %{
+ id: id,
+ title: dgettext("eyra-document", "content.title"),
+ tabs: [],
+ actions: [],
+ show_errors: false
+ }
+ end
+end
diff --git a/core/systems/document/pdf_view.ex b/core/systems/document/pdf_view.ex
new file mode 100644
index 000000000..3371aa514
--- /dev/null
+++ b/core/systems/document/pdf_view.ex
@@ -0,0 +1,22 @@
+defmodule Systems.Document.PDFView do
+ use CoreWeb, :html
+
+ import Frameworks.Pixel.Line
+
+ attr(:title, :string, required: true)
+ attr(:url, :string, required: true)
+
+ def pdf_view(assigns) do
+ send(self(), {:complete_task, %{}})
+
+ ~H"""
+
+ <%= @title %>
+ <.line />
+
+ """
+ end
+end
diff --git a/core/systems/data_donation/document_task_form.ex b/core/systems/document/tool_form.ex
similarity index 59%
rename from core/systems/data_donation/document_task_form.ex
rename to core/systems/document/tool_form.ex
index 4d1e5b7e7..2c5546ac3 100644
--- a/core/systems/data_donation/document_task_form.ex
+++ b/core/systems/document/tool_form.ex
@@ -1,62 +1,60 @@
-defmodule Systems.DataDonation.DocumentTaskForm do
+defmodule Systems.Document.ToolForm do
use CoreWeb.LiveForm
use CoreWeb.FileUploader, ~w(.pdf)
+ alias CoreWeb.Endpoint
+
alias Systems.{
- DataDonation
+ Document
}
@impl true
def process_file(
%{assigns: %{entity: entity}} = socket,
- {_local_relative_path, local_full_path, remote_file}
+ {local_relative_path, _local_full_path, remote_file}
) do
+ ref = "#{Endpoint.url()}#{local_relative_path}"
+
socket
- |> save(entity, %{document_ref: local_full_path, document_name: remote_file})
+ |> save(entity, %{ref: ref, name: remote_file})
end
@impl true
def update(
%{
id: id,
- parent: parent,
- entity_id: entity_id
+ entity: entity
},
socket
) do
- placeholder = dgettext("eyra-data-donation", "pdf-select-placeholder")
- select_button = dgettext("eyra-data-donation", "pdf-select-file-button")
- replace_button = dgettext("eyra-data-donation", "pdf-replace-file-button")
+ label = dgettext("eyra-document", "pdf-select-label")
+ placeholder = dgettext("eyra-document", "pdf-select-placeholder")
+ select_button = dgettext("eyra-document", "pdf-select-file-button")
+ replace_button = dgettext("eyra-document", "pdf-replace-file-button")
{
:ok,
socket
|> assign(
id: id,
- parent: parent,
+ label: label,
placeholder: placeholder,
select_button: select_button,
replace_button: replace_button,
- entity_id: entity_id
+ entity: entity
)
|> init_file_uploader(:pdf)
- |> update_entity()
}
end
- defp update_entity(%{assigns: %{entity_id: entity_id}} = socket) do
- entity = DataDonation.Public.get_document_task!(entity_id)
- assign(socket, entity: entity)
- end
-
@impl true
def handle_event("change", _params, socket) do
{:noreply, socket}
end
# Saving
- def save(socket, %DataDonation.DocumentTaskModel{} = entity, attrs) do
- changeset = DataDonation.DocumentTaskModel.changeset(entity, attrs)
+ def save(socket, %Document.ToolModel{} = entity, attrs) do
+ changeset = Document.ToolModel.changeset(entity, attrs)
socket
|> save(changeset)
@@ -67,18 +65,18 @@ defmodule Systems.DataDonation.DocumentTaskForm do
~H"""
+
+
+
<.form id="select_file_form" for={%{}} phx-change="change" phx-target="" >
- <%= @placeholder %>
+ <%= @label %>
<.spacing value="XXS" />
<% else %>
diff --git a/core/systems/document/tool_model.ex b/core/systems/document/tool_model.ex
new file mode 100644
index 000000000..27f411107
--- /dev/null
+++ b/core/systems/document/tool_model.ex
@@ -0,0 +1,69 @@
+defmodule Systems.Document.ToolModel do
+ use Ecto.Schema
+ use Frameworks.Utility.Schema
+
+ import Ecto.Changeset
+ import CoreWeb.Gettext
+
+ schema "document_tools" do
+ field(:name, :string)
+ field(:ref, :string)
+ field(:director, Ecto.Enum, values: [:assignment])
+ belongs_to(:auth_node, Core.Authorization.Node)
+
+ timestamps()
+ end
+
+ @fields ~w(name ref)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: preload_graph([])
+
+ defimpl Frameworks.Concept.ToolModel do
+ alias Systems.Document
+ def key(_), do: :document
+ def auth_tree(%{auth_node: auth_node}), do: auth_node
+ def apply_label(_), do: dgettext("eyra-document", "apply.cta.title")
+ def open_label(_), do: dgettext("eyra-document", "open.cta.title")
+ def ready?(tool), do: Document.ToolModel.ready?(tool)
+ def form(_), do: Document.ToolForm
+
+ def launcher(%{ref: ref}),
+ do: %{
+ function: &Document.PDFView.pdf_view/1,
+ props: %{url: ref, title: dgettext("eyra-document", "component.title")}
+ }
+
+ def task_labels(_) do
+ %{
+ pending: dgettext("eyra-document", "pending.label"),
+ participated: dgettext("eyra-document", "participated.label")
+ }
+ end
+
+ def attention_list_enabled?(_t), do: false
+ def group_enabled?(_t), do: true
+ end
+
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Utility.Module.get(director, "Director")
+ end
+end
diff --git a/core/systems/email/signal_handlers.ex b/core/systems/email/signal_handlers.ex
index a99c82570..c6e5c9f67 100644
--- a/core/systems/email/signal_handlers.ex
+++ b/core/systems/email/signal_handlers.ex
@@ -6,7 +6,7 @@ defmodule Core.Mailer.SignalHandlers do
alias Systems.Notification.Box
@impl true
- def dispatch(:new_notification, %{box: box, data: %{title: title}}) do
+ def intercept(:new_notification, %{box: box, data: %{title: title}}) do
if feature_enabled?(:notification_mails) do
for mail <- base_emails(box) do
mail
diff --git a/core/systems/feldspar/_presenter.ex b/core/systems/feldspar/_presenter.ex
new file mode 100644
index 000000000..7ba42cb44
--- /dev/null
+++ b/core/systems/feldspar/_presenter.ex
@@ -0,0 +1,12 @@
+defmodule Systems.Feldspar.Presenter do
+ @behaviour Frameworks.Concept.Presenter
+
+ alias Systems.{
+ Feldspar
+ }
+
+ @impl true
+ def view_model(%Feldspar.ToolModel{} = _tool, _page, _assigns) do
+ %{}
+ end
+end
diff --git a/core/systems/feldspar/_private.ex b/core/systems/feldspar/_private.ex
new file mode 100644
index 000000000..105ec0036
--- /dev/null
+++ b/core/systems/feldspar/_private.ex
@@ -0,0 +1,7 @@
+defmodule Systems.Feldspar.Private do
+ def get_backend do
+ :core
+ |> Application.fetch_env!(:feldspar)
+ |> Access.fetch!(:backend)
+ end
+end
diff --git a/core/systems/feldspar/_public.ex b/core/systems/feldspar/_public.ex
new file mode 100644
index 000000000..a2b305430
--- /dev/null
+++ b/core/systems/feldspar/_public.ex
@@ -0,0 +1,42 @@
+defmodule Systems.Feldspar.Public do
+ import Ecto.Query, warn: false
+ alias Core.Repo
+
+ alias Systems.{
+ Feldspar
+ }
+
+ import Feldspar.Private, only: [get_backend: 0]
+
+ def get_tool!(id, preload \\ []) do
+ from(tool in Feldspar.ToolModel, preload: ^preload)
+ |> Repo.get!(id)
+ end
+
+ def prepare_tool(attrs, auth_node \\ Core.Authorization.prepare_node()) do
+ %Feldspar.ToolModel{}
+ |> Feldspar.ToolModel.changeset(attrs)
+ |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
+ end
+
+ def store(zip_file) do
+ get_backend().store(zip_file)
+ end
+
+ def get_public_url(id) do
+ get_backend().get_public_url(id)
+ end
+
+ def remove(id) do
+ get_backend().remove(id)
+ end
+end
+
+defimpl Core.Persister, for: Systems.Feldspar.ToolModel do
+ def save(_tool, changeset) do
+ case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :feldspar_tool) do
+ {:ok, %{feldspar_tool: feldspar_tool}} -> {:ok, feldspar_tool}
+ _ -> {:error, changeset}
+ end
+ end
+end
diff --git a/core/systems/feldspar/_routes.ex b/core/systems/feldspar/_routes.ex
new file mode 100644
index 000000000..883880a55
--- /dev/null
+++ b/core/systems/feldspar/_routes.ex
@@ -0,0 +1,10 @@
+defmodule Systems.Feldspar.Routes do
+ defmacro routes() do
+ quote do
+ scope "/feldspar", Systems.Feldspar do
+ pipe_through([:browser])
+ live("/apps/:id", AppPage)
+ end
+ end
+ end
+end
diff --git a/core/systems/feldspar/app_page.ex b/core/systems/feldspar/app_page.ex
new file mode 100644
index 000000000..6106aeab8
--- /dev/null
+++ b/core/systems/feldspar/app_page.ex
@@ -0,0 +1,52 @@
+defmodule Systems.Feldspar.AppPage do
+ alias Systems.Feldspar
+ use CoreWeb, :live_view
+ use CoreWeb.Layouts.Stripped.Component, :projects
+
+ import Feldspar.AppView
+
+ @impl true
+ def mount(%{"id" => app_id}, _session, socket) do
+ app_url = Feldspar.Public.get_public_url(app_id) <> "/index.html"
+
+ {
+ :ok,
+ socket
+ |> update_menus()
+ |> assign(app_url: app_url, error: nil)
+ }
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.stripped menus={@menus} footer?={false}>
+ <.app_view url={@app_url} />
+
+ """
+ end
+
+ @impl true
+ def handle_event("app_event", %{"__type__" => type, "json_string" => event}, socket) do
+ {
+ :noreply,
+ socket |> handle(type, event)
+ }
+ end
+
+ @impl true
+ def handle_event("app_event", event, socket) do
+ {
+ :noreply,
+ socket |> handle(nil, inspect(event))
+ }
+ end
+
+ defp handle(socket, "CommandSystemDonate", event) do
+ Frameworks.Pixel.Flash.put_error(socket, "Unsupported donation " <> event)
+ end
+
+ defp handle(socket, _, event) do
+ Frameworks.Pixel.Flash.put_error(socket, "Unsupported " <> event)
+ end
+end
diff --git a/core/systems/feldspar/app_view.ex b/core/systems/feldspar/app_view.ex
new file mode 100644
index 000000000..4ec810bab
--- /dev/null
+++ b/core/systems/feldspar/app_view.ex
@@ -0,0 +1,19 @@
+defmodule Systems.Feldspar.AppView do
+ use CoreWeb, :html
+
+ attr(:url, :string, required: true)
+
+ def app_view(assigns) do
+ ~H"""
+
-
+
<%= dgettext("eyra-privacy", "form.title") %>
diff --git a/core/systems/project/_assembly.ex b/core/systems/project/_assembly.ex
index ba2d77388..1979da06f 100644
--- a/core/systems/project/_assembly.ex
+++ b/core/systems/project/_assembly.ex
@@ -1,17 +1,19 @@
defmodule Systems.Project.Assembly do
alias Core.Repo
alias Ecto.Multi
+ alias Ecto.Changeset
import Ecto.Query, warn: false
+ alias Frameworks.Utility.EctoHelper
alias Core.Authorization
alias Systems.{
Project,
- DataDonation,
+ Assignment,
Benchmark
}
def delete(%Project.Model{auth_node: %{id: node_id}}) do
- from(ra in Core.Authorization.RoleAssignment,
+ from(ra in Authorization.RoleAssignment,
where: ra.node_id == ^node_id
)
|> Repo.delete_all()
@@ -25,112 +27,158 @@ defmodule Systems.Project.Assembly do
end
def create(name, user, :empty) do
+ project = prepare_project(name, [], user)
+
Multi.new()
- |> prepare_project(name, user)
+ |> Multi.insert(:project, project)
|> Repo.transaction()
end
- def create(name, user, :data_donation) do
+ def create(name, user, template) do
+ items = prepare_items(template)
+ project = prepare_project(name, items, user)
+
Multi.new()
- |> prepare_project(name, user)
- |> prepare_tool_ref(0, :data_donation)
- |> prepare_item(0, "Data Donation")
+ |> Multi.insert(:project, project)
+ |> EctoHelper.run(:auth, &update_auth/2)
+ |> EctoHelper.run(:path, &update_path/2)
|> Repo.transaction()
end
- def create(name, user, :benchmark) do
+ def create_item(template, name, %Project.NodeModel{} = node)
+ when is_binary(name) do
Multi.new()
- |> prepare_project(name, user)
- |> prepare_tool_ref(0, :benchmark)
- |> prepare_item(0, "Challenge Round 1")
- |> prepare_tool_ref(1, :benchmark)
- |> prepare_item(1, "Challenge Round 2")
+ |> Multi.insert(
+ :item,
+ prepare_item(template, name)
+ |> Changeset.put_assoc(:node, node)
+ )
+ |> EctoHelper.run(:node, &load_node!/1)
+ |> EctoHelper.run(:auth, &update_auth/2)
+ |> EctoHelper.run(:path, &update_path/2)
|> Repo.transaction()
end
- def create_item(%Project.NodeModel{id: node_id} = node, tool_special) do
- project =
- from(p in Project.Model, where: p.root_id == ^node_id, preload: [:auth_node])
- |> Repo.one!()
+ # LOAD
- Multi.new()
- |> Multi.insert(:auth_node, fn _ ->
- Authorization.make_node(project.auth_node)
- end)
- |> prepare_tool(tool_special)
- |> Multi.insert(:tool_ref, fn %{tool: tool} ->
- Project.Public.create_tool_ref(tool_special, tool)
- end)
- |> Multi.insert(:item, fn %{tool_ref: tool_ref} ->
- Project.Public.create_item(
- %{name: "Item", project_path: [project.id, node_id]},
- node,
- tool_ref
- )
- end)
- |> Repo.transaction()
+ defp load_node!(%{item: %{node_id: node_id}}) do
+ {:ok, Project.Public.get_node!(node_id, Project.NodeModel.preload_graph(:down))}
end
- defp prepare_project(multi, name, user) do
- multi
- |> Project.Public.create(%{name: name})
- |> Multi.run(:assign_role, fn _, %{project: project} ->
- {:ok, Authorization.assign_role(user, project, :owner)}
- end)
+ # PREPARE
+
+ defp prepare_project(name, items, user) when is_list(items) do
+ Project.Public.prepare(%{name: name}, items, user)
end
- defp prepare_item(multi, index, name) do
- multi
- |> Multi.insert({:item, index}, fn %{
- {:tool_ref, ^index} => tool_ref,
- project: project,
- root: root
- } ->
- Project.Public.create_item(
- %{name: name, project_path: [project.id, root.id]},
- root,
- tool_ref
- )
- end)
+ defp prepare_items(:data_donation) do
+ [prepare_item(:data_donation, "Data Donation Assignment")]
end
- defp prepare_tool_ref(multi, index, :data_donation) when is_integer(index) do
- multi
- |> Multi.insert({:tool_auth_node, index}, fn %{root: %{auth_node: auth_node}} ->
- Authorization.make_node(auth_node)
- end)
- |> Multi.insert({:tool, index}, fn %{{:tool_auth_node, ^index} => tool_auth_node} ->
- DataDonation.Public.create(%{subject_count: 0, director: :project}, tool_auth_node)
- end)
- |> Multi.insert({:tool_ref, index}, fn %{{:tool, ^index} => tool} ->
- Project.Public.create_tool_ref(:data_donation_tool, tool)
- end)
+ defp prepare_items(:benchmark) do
+ [
+ prepare_item(:benchmark, "Challenge Round 1"),
+ prepare_item(:benchmark, "Challenge Round 2")
+ ]
end
- defp prepare_tool_ref(multi, index, :benchmark) when is_integer(index) do
- multi
- |> Multi.insert({:tool_auth_node, index}, fn %{root: %{auth_node: auth_node}} ->
- Authorization.make_node(auth_node)
- end)
- |> Multi.insert({:tool, index}, fn %{{:tool_auth_node, ^index} => tool_auth_node} ->
- Benchmark.Public.create(%{title: "", director: :project}, tool_auth_node)
- end)
- |> Multi.insert({:tool_ref, index}, fn %{{:tool, ^index} => tool} ->
- Project.Public.create_tool_ref(:benchmark_tool, tool)
- end)
+ defp prepare_item(:benchmark, name) do
+ {:ok, tool} =
+ Benchmark.Public.prepare_tool(%{title: "", director: :project})
+ |> Changeset.apply_action(:prepare)
+
+ {:ok, tool_ref} =
+ Project.Public.prepare_tool_ref(:benchmark, :benchmark_tool, tool)
+ |> Changeset.apply_action(:prepare)
+
+ Project.Public.prepare_item(%{name: name, project_path: []}, tool_ref)
end
- defp prepare_tool(multi, :data_donation_tool) do
- multi
- |> Multi.insert(:tool, fn %{auth_node: auth_node} ->
- DataDonation.Public.create(%{subject_count: 0, director: :project}, auth_node)
- end)
+ defp prepare_item(:data_donation, name) do
+ {:ok, assignment} =
+ Assignment.Assembly.prepare(:data_donation, :project, nil)
+ |> Changeset.apply_action(:prepare)
+
+ Project.Public.prepare_item(%{name: name, project_path: []}, assignment)
end
- defp prepare_tool(multi, :benchmark_tool) do
+ # PROJECT PATH
+ def update_path(multi, %{project: project}), do: update_path(multi, project)
+
+ def update_path(multi, %{node: %{project_path: project_path} = node}),
+ do: update_path(multi, node, project_path)
+
+ def update_path(multi, %Project.Model{id: id, root: root}) do
+ update_path(multi, root, [id])
+ end
+
+ def update_path(
+ multi,
+ %Project.NodeModel{children: %Ecto.Association.NotLoaded{}} = node,
+ project_path
+ ) do
+ update_path(multi, Repo.preload(node, :children), project_path)
+ end
+
+ def update_path(
+ multi,
+ %Project.NodeModel{items: %Ecto.Association.NotLoaded{}} = node,
+ project_path
+ ) do
+ update_path(multi, Repo.preload(node, :items), project_path)
+ end
+
+ def update_path(
+ multi,
+ %Project.NodeModel{id: id, items: items, children: children} = node,
+ project_path
+ ) do
+ changeset = Project.NodeModel.changeset(node, %{project_path: project_path})
+ new_project_path = append_path(project_path, node)
+
multi
- |> Multi.insert(:tool, fn %{auth_node: auth_node} ->
- Benchmark.Public.create(%{title: "", director: :project}, auth_node)
+ |> Multi.update("node_#{id}", changeset)
+ |> update_paths(items, new_project_path)
+ |> update_paths(children, new_project_path)
+ end
+
+ def update_path(multi, %Project.ItemModel{id: id} = item, project_path) do
+ changeset = Project.ItemModel.changeset(item, %{project_path: project_path})
+
+ Multi.update(multi, "item_#{id}", changeset)
+ end
+
+ def update_paths(multi, [_ | _] = elements, project_path) do
+ Enum.reduce(elements, multi, fn element, multi ->
+ update_path(multi, element, project_path)
end)
end
+
+ def update_paths(multi, _, _parent_path), do: multi
+
+ def append_path(path, %{id: id}) when is_list(path), do: append_path(path, id)
+
+ def append_path(path, sub_path) when is_list(path) and is_integer(sub_path),
+ do: path ++ [sub_path]
+
+ # AUTHORIZATION
+
+ def update_auth(multi, %{project: project}), do: update_auth(multi, project)
+ def update_auth(multi, %{node: node}), do: update_auth(multi, node)
+ def update_auth(multi, %{item: item}), do: update_auth(multi, item)
+
+ def update_auth(multi, %Project.Model{} = project) do
+ auth_tree = Project.Model.auth_tree(project)
+ Authorization.link(multi, auth_tree)
+ end
+
+ def update_auth(multi, %Project.NodeModel{} = project) do
+ auth_tree = Project.NodeModel.auth_tree(project)
+ Authorization.link(multi, auth_tree)
+ end
+
+ def update_auth(multi, %Project.ItemModel{} = project) do
+ auth_tree = Project.ItemModel.auth_tree(project)
+ Authorization.link(multi, auth_tree)
+ end
end
diff --git a/core/systems/project/_presenter.ex b/core/systems/project/_presenter.ex
index 40449b7f3..fb8d2ecc4 100644
--- a/core/systems/project/_presenter.ex
+++ b/core/systems/project/_presenter.ex
@@ -1,22 +1,10 @@
defmodule Systems.Project.Presenter do
- use Systems.Presenter
+ @behaviour Frameworks.Concept.Presenter
alias Systems.{
Project
}
- @impl true
- def view_model(id, Project.NodePage = page, assigns) when is_number(id) do
- Project.Public.get_node!(id, Project.NodeModel.preload_graph(:down))
- |> view_model(page, assigns)
- end
-
- @impl true
- def view_model(id, Project.ItemContentPage = page, assigns) when is_number(id) do
- Project.Public.get_item!(id, Project.ItemModel.preload_graph(:down))
- |> view_model(page, assigns)
- end
-
@impl true
def view_model(%Project.NodeModel{} = node, page, assigns) do
builder(page).view_model(node, assigns)
@@ -28,5 +16,5 @@ defmodule Systems.Project.Presenter do
end
defp builder(Project.NodePage), do: Project.NodePageBuilder
- defp builder(Project.ItemContentPage), do: Project.ContentPageBuilder.Item
+ defp builder(Project.ItemContentPage), do: Project.ItemContentPageBuilder
end
diff --git a/core/systems/project/_public.ex b/core/systems/project/_public.ex
index 4e5b67c61..420e87ca4 100644
--- a/core/systems/project/_public.ex
+++ b/core/systems/project/_public.ex
@@ -1,13 +1,13 @@
defmodule Systems.Project.Public do
import Ecto.Query, warn: false
- alias Ecto.Multi
alias Core.Repo
alias Core.Accounts.User
alias Core.Authorization
alias Systems.{
- Project
+ Project,
+ Assignment
}
def get!(id, preload \\ []) do
@@ -34,6 +34,49 @@ defmodule Systems.Project.Public do
|> Repo.one!()
end
+ def get_item_by_tool_ref(tool_ref, preload \\ [])
+
+ def get_item_by_tool_ref(%Project.ToolRefModel{id: tool_ref_id}, preload) do
+ get_item_by_tool_ref(tool_ref_id, preload)
+ end
+
+ def get_item_by_tool_ref(tool_ref_id, preload) when is_integer(tool_ref_id) do
+ from(item in Project.ItemModel,
+ where: item.tool_ref_id == ^tool_ref_id,
+ preload: ^preload
+ )
+ |> Repo.one()
+ end
+
+ def get_item_by_assignment(assignment, preload \\ [])
+
+ def get_item_by_assignment(%Assignment.Model{id: assignment_id}, preload) do
+ get_item_by_assignment(assignment_id, preload)
+ end
+
+ def get_item_by_assignment(assignment_id, preload) do
+ from(item in Project.ItemModel,
+ where: item.assignment_id in ^assignment_id,
+ preload: ^preload
+ )
+ |> Repo.one()
+ end
+
+ def get_tool_ref_by_tool(%{id: id} = tool, preload \\ []) do
+ field = Project.ToolRefModel.tool_id_field(tool)
+
+ query_tool_refs_by_tool(id, field, preload)
+ |> Repo.one()
+ end
+
+ def query_tool_refs_by_tool(tool_id, field, preload \\ [])
+ when is_integer(tool_id) and is_atom(field) do
+ from(tool_ref in Project.ToolRefModel,
+ where: field(tool_ref, ^field) == ^tool_id,
+ preload: ^preload
+ )
+ end
+
@doc """
Returns the list of projects that are owned by the user.
"""
@@ -55,7 +98,7 @@ defmodule Systems.Project.Public do
end
def delete(id) when is_number(id) do
- get!(id, Project.Model.preload_graph(:full))
+ get!(id, Project.Model.preload_graph(:down))
|> Project.Assembly.delete()
end
@@ -64,27 +107,20 @@ defmodule Systems.Project.Public do
|> Project.Assembly.delete()
end
- def create(
- %Multi{} = multi,
- %{name: _name} = attrs
- ) do
- multi
- |> Multi.insert(:project_auth_node, Authorization.make_node())
- |> Multi.insert(:root_auth_node, fn %{project_auth_node: project_auth_node} ->
- Authorization.make_node(project_auth_node)
- end)
- |> Multi.insert(:root, fn %{root_auth_node: root_auth_node} ->
- create_node(%{name: "Project", project_path: [0]}, root_auth_node)
- end)
- |> Multi.insert(:project, fn %{root: root, project_auth_node: project_auth_node} ->
- create(attrs, root, project_auth_node)
- end)
- |> Multi.update(:project_path, fn %{project: project, root: root} ->
- update_project_path(root, [project.id])
- end)
- end
-
- def create(
+ def prepare(
+ %{name: _name} = attrs,
+ items,
+ user
+ )
+ when is_list(items) do
+ {:ok, root} =
+ prepare_node(%{name: "Project", project_path: []}, items)
+ |> Ecto.Changeset.apply_action(:prepare)
+
+ prepare(attrs, root, Authorization.prepare_node(user, :owner))
+ end
+
+ def prepare(
%{name: _name} = attrs,
%Project.NodeModel{} = root,
%Authorization.Node{} = auth_node
@@ -95,55 +131,42 @@ defmodule Systems.Project.Public do
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
end
- def create_node(
+ def prepare_node(
%{name: _, project_path: _} = attrs,
- %Authorization.Node{} = auth_node
- ) do
- %Project.NodeModel{}
- |> Project.NodeModel.changeset(attrs)
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- end
-
- def create_node(
- %{name: _, project_path: _} = attrs,
- children,
items,
- %Authorization.Node{} = auth_node
- ) do
+ auth_node \\ Authorization.prepare_node()
+ )
+ when is_list(items) do
%Project.NodeModel{}
|> Project.NodeModel.changeset(attrs)
- |> Ecto.Changeset.put_assoc(:children, children)
|> Ecto.Changeset.put_assoc(:items, items)
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
end
- def create_item(
+ def prepare_item(attrs, %Project.ToolRefModel{} = tool_ref) do
+ prepare_item(attrs, :tool_ref, tool_ref)
+ end
+
+ def prepare_item(attrs, %Assignment.Model{} = assignment) do
+ prepare_item(attrs, :assignment, assignment)
+ end
+
+ def prepare_item(
%{name: _name, project_path: _} = attrs,
- %Project.NodeModel{} = node,
- %Project.ToolRefModel{} = tool_ref
+ field_name,
+ concrete
) do
%Project.ItemModel{}
|> Project.ItemModel.changeset(attrs)
- |> Ecto.Changeset.put_assoc(:node, node)
- |> Ecto.Changeset.put_assoc(:tool_ref, tool_ref)
+ |> Ecto.Changeset.put_assoc(field_name, concrete)
end
- def create_tool_ref(tool_key, tool) do
+ def prepare_tool_ref(special, tool_key, tool) do
%Project.ToolRefModel{}
- |> Project.ToolRefModel.changeset(%{})
+ |> Project.ToolRefModel.changeset(%{special: special})
|> Ecto.Changeset.put_assoc(tool_key, tool)
end
- def update_project_path(%Project.NodeModel{} = node, project_path) when is_list(project_path) do
- node
- |> Project.NodeModel.changeset(%{project_path: project_path})
- end
-
- def update_project_path(%Project.ItemModel{} = item, project_path) when is_list(project_path) do
- item
- |> Project.ItemModel.changeset(%{project_path: project_path})
- end
-
def add_item(%Project.ItemModel{} = item, %Project.NodeModel{} = node) do
item
|> Project.ItemModel.changeset(%{})
@@ -174,7 +197,5 @@ defmodule Systems.Project.Public do
|> Enum.map(fn %{id: id} -> id end)
from(u in User, where: u.id in ^owner_ids, preload: ^preload, order_by: u.id) |> Repo.all()
- # AUTH: needs to be marked save. Current user is normally not allowed to
- # access other users.
end
end
diff --git a/core/systems/project/_switch.ex b/core/systems/project/_switch.ex
index fcdbd0db7..a5a5fc15a 100644
--- a/core/systems/project/_switch.ex
+++ b/core/systems/project/_switch.ex
@@ -1,2 +1,58 @@
defmodule Systems.Project.Switch do
+ use Frameworks.Signal.Handler
+
+ alias Frameworks.Signal
+
+ alias Systems.{
+ Project
+ }
+
+ @impl true
+ def intercept({:alliance_tool, _} = signal, %{alliance_tool: tool} = message),
+ do: handle({:tool, signal}, Map.merge(message, %{tool: tool}))
+
+ @impl true
+ def intercept({:lab_tool, _} = signal, %{lab_tool: tool} = message),
+ do: handle({:tool, signal}, Map.merge(message, %{tool: tool}))
+
+ @impl true
+ def intercept({:feldspar_tool, _} = signal, %{feldspar_tool: tool} = message),
+ do: handle({:tool, signal}, Map.merge(message, %{tool: tool}))
+
+ @impl true
+ def intercept({:document_tool, _} = signal, %{document_tool: tool} = message),
+ do: handle({:tool, signal}, Map.merge(message, %{tool: tool}))
+
+ @impl true
+ def intercept({:benchmark_tool, _} = signal, %{benchmark_tool: tool} = message),
+ do: handle({:tool, signal}, Map.merge(message, %{tool: tool}))
+
+ @impl true
+ def intercept({:tool_ref, _} = signal, %{tool_ref: tool_ref} = message) do
+ if project_item = Project.Public.get_item_by_tool_ref(tool_ref) do
+ dispatch!(
+ {:project_item, signal},
+ Map.merge(message, %{project_item: project_item})
+ )
+ end
+ end
+
+ @impl true
+ def intercept({:project_item, _}, %{project_item: project_item}) do
+ update_pages(project_item)
+ end
+
+ defp handle({:tool, signal}, %{tool: tool} = message) do
+ Project.Public.get_tool_ref_by_tool(tool)
+ |> then(&dispatch!({:tool_ref, signal}, Map.merge(message, %{tool_ref: &1})))
+ end
+
+ defp update_pages(%Project.ItemModel{} = item) do
+ [Project.NodePage]
+ |> Enum.each(&update_page(&1, item))
+ end
+
+ defp update_page(page, %{id: id} = model) when is_atom(page) do
+ dispatch!({:page, page}, %{id: id, model: model})
+ end
end
diff --git a/core/systems/project/content_page_builder/item.ex b/core/systems/project/content_page_builder/item.ex
deleted file mode 100644
index 759245a36..000000000
--- a/core/systems/project/content_page_builder/item.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-defmodule Systems.Project.ContentPageBuilder.Item do
- def view_model(item, assigns) do
- get_builder(item).view_model(item, assigns)
- end
-
- defp get_builder(%{tool_ref: %{data_donation_tool: tool}}) when not is_nil(tool) do
- Systems.Project.ContentPageBuilder.ItemDataDonation
- end
-
- defp get_builder(%{tool_ref: %{benchmark_tool: tool}}) when not is_nil(tool) do
- Systems.Project.ContentPageBuilder.ItemBenchmark
- end
-
- defp get_builder(item) do
- raise "Unsupported item: #{item}"
- end
-end
diff --git a/core/systems/project/content_page_builder/item_data_donation.ex b/core/systems/project/content_page_builder/item_data_donation.ex
deleted file mode 100644
index 57687bc66..000000000
--- a/core/systems/project/content_page_builder/item_data_donation.ex
+++ /dev/null
@@ -1,239 +0,0 @@
-defmodule Systems.Project.ContentPageBuilder.ItemDataDonation do
- import CoreWeb.Gettext
-
- alias Systems.{
- Project,
- DataDonation,
- Privacy
- }
-
- def view_model(
- %{
- id: id,
- tool_ref: %{
- data_donation_tool: tool
- }
- } = item,
- assigns
- ) do
- show_errors = show_errors(tool, assigns)
- tabs = create_tabs(item, show_errors, assigns)
- action_map = action_map(tool)
- actions = actions(tool, action_map)
-
- %{
- id: id,
- title: dgettext("eyra-data-donation", "content.title"),
- tabs: tabs,
- actions: actions,
- show_errors: show_errors
- }
- end
-
- defp show_errors(%{status: _status}, %{publish_clicked: _publish_clicked}) do
- # concept? = status == :concept
- # publish_clicked or not concept?
- false
- end
-
- defp action_map(%{id: tool_id}) do
- %{
- preview: %{
- action: %{type: :http_get, to: "/data-donation/#{tool_id}", target: "_blank"}
- },
- publish: %{
- action: %{type: :send, event: "action_click", item: :publish},
- handle_click: &handle_publish/1
- },
- retract: %{
- action: %{type: :send, event: "action_click", item: :retract},
- handle_click: &handle_retract/1
- },
- close: %{
- action: %{type: :send, event: "action_click", item: :close},
- handle_click: &handle_close/1
- },
- open: %{
- action: %{type: :send, event: "action_click", item: :open},
- handle_click: &handle_open/1
- }
- }
- end
-
- defp actions(%{status: :online}, %{retract: retract}), do: [retract: retract]
-
- defp actions(%{status: :offline}, %{publish: publish, close: close}),
- do: [publish: publish, close: close]
-
- defp actions(%{status: :idle}, %{open: open}), do: [open: open]
-
- defp actions(%{status: _concept}, %{publish: publish, preview: preview}),
- do: [publish: publish, preview: preview]
-
- defp handle_publish(socket) do
- socket
- end
-
- defp handle_retract(socket) do
- socket
- end
-
- defp handle_close(socket) do
- socket
- end
-
- defp handle_open(socket) do
- socket
- end
-
- defp create_tabs(item, show_errors, assigns) do
- get_tab_keys()
- |> Enum.map(&create_tab(&1, item, show_errors, assigns))
- end
-
- defp get_tab_keys() do
- [:config, :tasks, :privacy, :invite, :monitor]
- end
-
- defp create_tab(
- :config,
- %{tool_ref: %{data_donation_tool_id: tool_id}} = item,
- show_errors,
- _assigns
- ) do
- ready? = false
-
- %{
- id: :config_form,
- ready: ready?,
- show_errors: show_errors,
- title: dgettext("eyra-project", "tabbar.item.config"),
- forward_title: dgettext("eyra-project", "tabbar.item.config.forward"),
- type: :fullpage,
- live_component: Project.ItemConfigForm,
- props: %{
- entity: item,
- sub_form: %{
- id: :tool_form,
- module: DataDonation.ToolForm,
- entity_id: tool_id
- }
- }
- }
- end
-
- defp create_tab(
- :tasks,
- %{tool_ref: %{data_donation_tool_id: tool_id}},
- show_errors,
- _assigns
- ) do
- ready? = false
-
- %{
- id: :tasks_form,
- ready: ready?,
- show_errors: show_errors,
- title: dgettext("eyra-project", "tabbar.item.tasks"),
- forward_title: dgettext("eyra-project", "tabbar.item.tasks.forward"),
- type: :fullpage,
- live_component: DataDonation.TaskBuilderView,
- props: %{
- tool_id: tool_id,
- flow: %{
- title: dgettext("eyra-data-donation", "task.list.title"),
- description: dgettext("eyra-data-donation", "task.list.description")
- },
- library: %{
- title: dgettext("eyra-data-donation", "task.library.title"),
- description: dgettext("eyra-data-donation", "task.library.description"),
- items: [
- %{
- id: :survey,
- title: dgettext("eyra-data-donation", "task.survey.title"),
- description: dgettext("eyra-data-donation", "task.survey.description")
- },
- %{
- id: :request,
- title: dgettext("eyra-data-donation", "task.request.title"),
- description: dgettext("eyra-data-donation", "task.request.description")
- },
- %{
- id: :download,
- title: dgettext("eyra-data-donation", "task.download.title"),
- description: dgettext("eyra-data-donation", "task.download.description")
- },
- %{
- id: :donate,
- title: dgettext("eyra-data-donation", "task.donate.title"),
- description: dgettext("eyra-data-donation", "task.donate.description")
- }
- ]
- }
- }
- }
- end
-
- defp create_tab(
- :privacy,
- _item,
- show_errors,
- _assigns
- ) do
- ready? = false
-
- %{
- id: :privacy_form,
- ready: ready?,
- show_errors: show_errors,
- title: dgettext("eyra-project", "tabbar.item.privacy"),
- forward_title: dgettext("eyra-project", "tabbar.item.privacy.forward"),
- type: :fullpage,
- live_component: Privacy.Form,
- props: %{
- entity: %{}
- }
- }
- end
-
- defp create_tab(
- :invite,
- %{tool_ref: %{data_donation_tool: %{id: tool_id}}},
- show_errors,
- %{uri_origin: uri_origin}
- ) do
- ready? = false
- url = uri_origin <> "/data-donation/#{tool_id}"
-
- %{
- id: :invite_form,
- ready: ready?,
- show_errors: show_errors,
- title: dgettext("eyra-project", "tabbar.item.invite"),
- forward_title: dgettext("eyra-project", "tabbar.item.invite.forward"),
- type: :fullpage,
- live_component: Project.InviteForm,
- props: %{
- url: url
- }
- }
- end
-
- defp create_tab(
- :monitor,
- item,
- _show_errors,
- _assigns
- ) do
- %{
- id: :monitor,
- title: dgettext("eyra-project", "tabbar.item.monitor"),
- forward_title: dgettext("eyra-project", "tabbar.item.monitor.forward"),
- type: :fullpage,
- live_component: Project.ItemMonitorView,
- props: %{
- entity: item
- }
- }
- end
-end
diff --git a/core/systems/project/create_item_popup.ex b/core/systems/project/create_item_popup.ex
new file mode 100644
index 000000000..f336d5dc7
--- /dev/null
+++ b/core/systems/project/create_item_popup.ex
@@ -0,0 +1,114 @@
+defmodule Systems.Project.CreateItemPopup do
+ use CoreWeb, :live_component
+
+ alias Frameworks.Pixel.Selector
+
+ alias Systems.{
+ Project
+ }
+
+ # Handle Tool Type Selector Update
+ @impl true
+ def update(
+ %{active_item_id: active_item_id, selector_id: :template_selector},
+ %{assigns: %{template_labels: template_labels}} = socket
+ ) do
+ %{id: selected_template} = Enum.find(template_labels, &(&1.id == active_item_id))
+
+ {
+ :ok,
+ socket
+ |> assign(selected_template: selected_template)
+ }
+ end
+
+ # Initial Update
+ @impl true
+ def update(%{id: id, node: node, target: target}, socket) do
+ title = dgettext("eyra-project", "create.item.title")
+
+ {
+ :ok,
+ socket
+ |> assign(id: id, node: node, target: target, title: title)
+ |> init_templates()
+ |> init_buttons()
+ }
+ end
+
+ defp init_templates(socket) do
+ selected_template = :empty
+ template_labels = Project.Templates.labels(selected_template)
+ socket |> assign(template_labels: template_labels, selected_template: selected_template)
+ end
+
+ defp init_buttons(%{assigns: %{myself: myself}} = socket) do
+ socket
+ |> assign(
+ buttons: [
+ %{
+ action: %{type: :send, event: "proceed", target: myself},
+ face: %{
+ type: :primary,
+ label: dgettext("eyra-project", "create.proceed.button")
+ }
+ },
+ %{
+ action: %{type: :send, event: "cancel", target: myself},
+ face: %{type: :label, label: dgettext("eyra-ui", "cancel.button")}
+ }
+ ]
+ )
+ end
+
+ @impl true
+ def handle_event(
+ "proceed",
+ _,
+ %{assigns: %{selected_template: selected_template}} = socket
+ ) do
+ create_item(socket, selected_template)
+
+ {:noreply, socket |> close()}
+ end
+
+ @impl true
+ def handle_event("cancel", _, socket) do
+ {:noreply, socket |> close()}
+ end
+
+ defp close(%{assigns: %{target: target}} = socket) do
+ update_target(target, %{module: __MODULE__, action: :close})
+ socket
+ end
+
+ defp create_item(%{assigns: %{node: node}}, template) do
+ name = Project.Templates.translate(template)
+ Project.Assembly.create_item(template, name, node)
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
<%= @title %>
- <.spacing value="XS" />
+ <.spacing value="S" />
<.live_component
module={Selector}
id={:template_selector}
diff --git a/core/systems/project/item_content_page.ex b/core/systems/project/item_content_page.ex
deleted file mode 100644
index 49913c748..000000000
--- a/core/systems/project/item_content_page.ex
+++ /dev/null
@@ -1,317 +0,0 @@
-defmodule Systems.Project.ItemContentPage do
- use CoreWeb, :live_view
- use CoreWeb.Layouts.Workspace.Component, :projects
- use CoreWeb.UI.Responsive.Viewport
- use CoreWeb.UI.PlainDialog
-
- import CoreWeb.Gettext
-
- import CoreWeb.Layouts.Workspace.Component
-
- alias CoreWeb.UI.Tabbar
- alias CoreWeb.UI.Navigation
-
- alias Systems.{
- Project
- }
-
- @impl true
- def get_authorization_context(%{"id" => id}, _session, _socket) do
- Project.Public.get_item!(id)
- end
-
- @impl true
- def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
- model = %{id: String.to_integer(id), director: :project}
- tabbar_id = "project_item_content/#{id}"
-
- {
- :ok,
- socket
- |> assign(
- id: id,
- model: model,
- tabbar_id: tabbar_id,
- initial_tab: initial_tab,
- locale: locale,
- changesets: %{},
- publish_clicked: false,
- dialog: nil,
- popup: nil,
- side_panel: nil,
- action_map: %{},
- more_actions: []
- )
- |> assign_viewport()
- |> assign_breakpoint()
- |> update_tabbar_size()
- |> update_menus()
- }
- end
-
- @impl true
- def mount(params, session, socket) do
- mount(Map.put(params, "tab", nil), session, socket)
- end
-
- defoverridable handle_uri: 1
-
- @impl true
- def handle_uri(socket) do
- socket =
- socket
- |> observe_view_model()
- |> update_actions()
- |> update_menus()
-
- super(socket)
- end
-
- defoverridable handle_view_model_updated: 1
-
- def handle_view_model_updated(socket) do
- socket
- |> update_actions()
- |> update_menus()
- end
-
- @impl true
- def handle_resize(socket) do
- socket
- |> update_tabbar_size()
- |> update_actions()
- |> update_menus()
- end
-
- @impl true
- def handle_event(
- "action_click",
- %{"item" => action_id},
- %{assigns: %{vm: %{actions: actions}}} = socket
- ) do
- action_id = String.to_existing_atom(action_id)
- action = Keyword.get(actions, action_id)
-
- {
- :noreply,
- socket
- |> action.handle_click.()
- |> update_view_model()
- |> update_actions()
- }
- end
-
- @impl true
- def handle_event("inform_ok", _params, socket) do
- {:noreply, socket |> assign(dialog: nil)}
- end
-
- @impl true
- def handle_info({:handle_auto_save_done, _}, socket) do
- socket |> update_menus()
- {:noreply, socket}
- end
-
- @impl true
- def handle_info({:flash, type, message}, socket) do
- socket |> Flash.put(type, message, true)
- {:noreply, socket}
- end
-
- @impl true
- def handle_info({:show_popup, popup}, socket) do
- {:noreply, socket |> assign(popup: popup)}
- end
-
- @impl true
- def handle_info({:hide_popup}, socket) do
- {:noreply, socket |> assign(popup: nil)}
- end
-
- @impl true
- def handle_info(%{id: form_id, ready?: ready?}, socket) do
- ready_key = String.to_atom("#{form_id}_ready?")
-
- socket =
- if socket.assigns[ready_key] != ready? do
- socket
- |> assign(ready_key, ready?)
- else
- socket
- end
-
- {:noreply, socket}
- end
-
- @impl true
- def handle_info({:signal_test, _}, socket) do
- {:noreply, socket}
- end
-
- defp action_map(%{assigns: %{vm: %{actions: actions}}}) do
- actions
- |> Enum.map(&{elem(&1, 0), action(&1)})
- |> Enum.into(%{})
- end
-
- defp action({:publish, %{action: action}}) do
- %{
- label: %{
- action: action,
- face: %{
- type: :primary,
- label: dgettext("eyra-project", "publish.button"),
- bg_color: "bg-success"
- }
- },
- icon: %{
- action: action,
- face: %{type: :icon, icon: :publish, alt: dgettext("eyra-project", "publish.button")}
- }
- }
- end
-
- defp action({:preview, %{action: action}}) do
- %{
- label: %{
- action: action,
- face: %{
- type: :primary,
- label: dgettext("eyra-project", "preview.button"),
- bg_color: "bg-primary"
- }
- },
- icon: %{
- action: action,
- face: %{type: :icon, icon: :preview, alt: dgettext("eyra-project", "preview.button")}
- }
- }
- end
-
- defp action({:retract, %{action: action}}) do
- %{
- label: %{
- action: action,
- face: %{
- type: :secondary,
- label: dgettext("eyra-project", "retract.button"),
- text_color: "text-error",
- border_color: "border-error"
- }
- },
- icon: %{
- action: action,
- face: %{type: :icon, icon: :retract, alt: dgettext("eyra-project", "retract.button")}
- }
- }
- end
-
- defp action({:close, %{action: action}}) do
- %{
- label: %{
- action: action,
- face: %{
- type: :primary,
- label: dgettext("eyra-project", "close.button")
- }
- },
- icon: %{
- action: action,
- face: %{type: :icon, icon: :close, alt: dgettext("eyra-project", "close.button")}
- }
- }
- end
-
- defp action({:open, %{action: action}}) do
- %{
- label: %{
- action: action,
- face: %{
- type: :primary,
- label: dgettext("eyra-project", "open.button")
- }
- },
- icon: %{
- action: action,
- face: %{type: :icon, icon: :open, alt: dgettext("eyra-project", "open.button")}
- }
- }
- end
-
- defp update_actions(socket) do
- action_map = action_map(socket)
- actions = create_actions(action_map, socket)
-
- socket
- |> assign(
- action_map: action_map,
- actions: actions
- )
- end
-
- defp create_actions(_, %{assigns: %{breakpoint: {:unknown, _}}} = _socket), do: []
-
- defp create_actions(
- action_map,
- %{assigns: %{vm: %{actions: actions}}} = socket
- ) do
- actions
- |> Keyword.keys()
- |> Enum.map(&create_action(&1, Map.get(action_map, &1), socket))
- |> Enum.filter(&(not is_nil(&1)))
- end
-
- defp create_action(_, action, %{assigns: %{breakpoint: breakpoint}}) do
- value(breakpoint, nil,
- xs: %{0 => action.icon},
- md: %{40 => action.label, 100 => action.icon},
- lg: %{50 => action.label}
- )
- end
-
- defp update_tabbar_size(%{assigns: %{breakpoint: breakpoint}} = socket) do
- tabbar_size = tabbar_size(breakpoint)
- socket |> assign(tabbar_size: tabbar_size)
- end
-
- defp tabbar_size({:unknown, _}), do: :unknown
- defp tabbar_size(bp), do: value(bp, :narrow, sm: %{30 => :wide})
-
- defp margin_x(:mobile), do: "mx-6"
- defp margin_x(_), do: "mx-10"
-
- @impl true
- def render(assigns) do
- ~H"""
- <.workspace title={@vm.title} menus={@menus} >
- <:top_bar>
-
-
-
-
-
-
<%= if Enum.count(@vm.node_cards) > 0 do %>
diff --git a/core/systems/project/node_page_builder.ex b/core/systems/project/node_page_builder.ex
index e174bf9bb..5963052c5 100644
--- a/core/systems/project/node_page_builder.ex
+++ b/core/systems/project/node_page_builder.ex
@@ -33,9 +33,8 @@ defmodule Systems.Project.NodePageBuilder do
end
defp to_item_cards(%{items: items}, assigns) do
- Enum.map(
- items,
- &ViewModelBuilder.view_model(&1, {Project.NodePage, :item_card}, assigns)
- )
+ items
+ |> Enum.sort_by(& &1.inserted_at, {:asc, NaiveDateTime})
+ |> Enum.map(&ViewModelBuilder.view_model(&1, {Project.NodePage, :item_card}, assigns))
end
end
diff --git a/core/systems/project/overview_page.ex b/core/systems/project/overview_page.ex
index 7aaea3ed9..47aa9ef44 100644
--- a/core/systems/project/overview_page.ex
+++ b/core/systems/project/overview_page.ex
@@ -33,7 +33,7 @@ defmodule Systems.Project.OverviewPage do
end
defp update_projects(%{assigns: %{current_user: user}} = socket) do
- preload = Project.Model.preload_graph(:full)
+ preload = Project.Model.preload_graph(:down)
projects = Project.Public.list_owned_projects(user, preload: preload)
socket
@@ -152,6 +152,14 @@ defmodule Systems.Project.OverviewPage do
}
end
+ @impl true
+ def handle_info(%{auto_save: _status}, socket) do
+ {
+ :noreply,
+ socket
+ }
+ end
+
@impl true
def handle_info({:handle_auto_save_done, :project_overview_popup}, socket) do
{
diff --git a/core/systems/project/tool_ref_model.ex b/core/systems/project/tool_ref_model.ex
index f6e17c97d..705abef23 100644
--- a/core/systems/project/tool_ref_model.ex
+++ b/core/systems/project/tool_ref_model.ex
@@ -4,24 +4,32 @@ defmodule Systems.Project.ToolRefModel do
import Ecto.Changeset
+ alias Frameworks.Concept
+
alias Systems.{
Project,
- Survey,
+ Document,
+ Alliance,
Lab,
- DataDonation,
+ Feldspar,
Benchmark
}
schema "tool_refs" do
- has_one(:item, Project.ItemModel, foreign_key: :tool_ref_id)
- belongs_to(:survey_tool, Survey.ToolModel)
+ field(:special, Ecto.Atom)
+
+ belongs_to(:alliance_tool, Alliance.ToolModel)
belongs_to(:lab_tool, Lab.ToolModel)
- belongs_to(:data_donation_tool, DataDonation.ToolModel)
+ belongs_to(:feldspar_tool, Feldspar.ToolModel)
belongs_to(:benchmark_tool, Benchmark.ToolModel)
+ belongs_to(:document_tool, Document.ToolModel)
+
+ has_one(:item, Project.ItemModel, foreign_key: :tool_ref_id)
+
timestamps()
end
- @required_fields ~w()a
+ @required_fields ~w(special)a
@fields @required_fields
@doc false
@@ -34,18 +42,45 @@ defmodule Systems.Project.ToolRefModel do
def preload_graph(:down),
do:
preload_graph([
- :survey_tool,
+ :alliance_tool,
+ :document_tool,
:lab_tool,
- :data_donation_tool,
+ :feldspar_tool,
:benchmark_tool
])
- def preload_graph(:survey_tool), do: [survey_tool: Survey.ToolModel.preload_graph(:full)]
- def preload_graph(:lab_tool), do: [lab_tool: Lab.ToolModel.preload_graph(:full)]
+ def preload_graph(:alliance_tool),
+ do: [alliance_tool: Alliance.ToolModel.preload_graph(:down)]
+
+ def preload_graph(:document_tool),
+ do: [document_tool: Document.ToolModel.preload_graph(:down)]
+
+ def preload_graph(:lab_tool), do: [lab_tool: Lab.ToolModel.preload_graph(:down)]
- def preload_graph(:data_donation_tool),
- do: [data_donation_tool: DataDonation.ToolModel.preload_graph(:full)]
+ def preload_graph(:feldspar_tool),
+ do: [feldspar_tool: Feldspar.ToolModel.preload_graph(:down)]
def preload_graph(:benchmark_tool),
do: [benchmark_tool: Benchmark.ToolModel.preload_graph(:down)]
+
+ def auth_tree(%Project.ToolRefModel{} = tool_ref) do
+ Concept.ToolModel.auth_tree(tool(tool_ref))
+ end
+
+ def flatten(item), do: tool(item)
+
+ def external_path(%{alliance_tool: alliance_tool}, panl_id) do
+ Alliance.ToolModel.external_path(alliance_tool, panl_id)
+ end
+
+ def external_path(_, _), do: nil
+
+ def tool_field(tool), do: String.to_existing_atom("#{Concept.ToolModel.key(tool)}_tool")
+ def tool_id_field(tool), do: String.to_existing_atom("#{Concept.ToolModel.key(tool)}_tool_id")
+
+ def tool(%{alliance_tool: %{id: _id} = tool}), do: tool
+ def tool(%{feldspar_tool: %{id: _id} = tool}), do: tool
+ def tool(%{document_tool: %{id: _id} = tool}), do: tool
+ def tool(%{lab_tool: %{id: _id} = tool}), do: tool
+ def tool(%{benchmark_tool: %{id: _id} = tool}), do: tool
end
diff --git a/core/systems/project/tool_ref_view.ex b/core/systems/project/tool_ref_view.ex
new file mode 100644
index 000000000..724461531
--- /dev/null
+++ b/core/systems/project/tool_ref_view.ex
@@ -0,0 +1,29 @@
+defmodule Systems.Project.ToolRefView do
+ use CoreWeb, :html
+
+ alias Frameworks.Concept
+
+ alias Systems.{
+ Project
+ }
+
+ defp get_tool(tool_ref), do: Project.ToolRefModel.tool(tool_ref)
+ defp get_work(tool), do: Concept.ToolModel.launcher(tool)
+
+ attr(:tool_ref, :map, required: true)
+ attr(:task, :map, required: true)
+
+ def tool_ref_view(%{tool_ref: tool_ref} = assigns) do
+ assigns =
+ tool_ref
+ |> get_tool()
+ |> get_work()
+ |> then(&assign(assigns, :work, &1))
+
+ ~H"""
+ ",
- "http://example.org/survey?d="
+ "http://example.org/alliance?a=",
+ "http://example.org/alliance?c=",
+ "http://example.org/alliance?d="
] do
test "disallow URL: #{url}" do
changeset =
- Survey.ToolModel.changeset(%Survey.ToolModel{}, :auto_save, %{survey_url: unquote(url)})
+ Alliance.ToolModel.changeset(%Alliance.ToolModel{}, :auto_save, %{
+ url: unquote(url)
+ })
refute changeset.valid?
end
end
end
- describe "prepare_survey_url" do
+ describe "prepare_alliance_url" do
test "URL without params stays the same" do
- assert Survey.ToolModel.prepare_url("http://example.org/test?a=b", %{}) ==
+ assert Alliance.ToolModel.prepare_url("http://example.org/test?a=b", %{}) ==
"http://example.org/test?a=b"
end
test "URL with param has replacement" do
- assert Survey.ToolModel.prepare_url(
+ assert Alliance.ToolModel.prepare_url(
"http://example.org/test?participant=",
%{
"participantId" => 123
diff --git a/core/test/systems/assignment/_public_test.exs b/core/test/systems/assignment/_public_test.exs
index b9332d02e..df5e1424e 100644
--- a/core/test/systems/assignment/_public_test.exs
+++ b/core/test/systems/assignment/_public_test.exs
@@ -3,8 +3,6 @@ defmodule Systems.Assignment.PublicTest do
import Systems.NextAction.TestHelper
describe "assignments" do
- alias Core.Accounts
-
alias Systems.{
Assignment,
Crew,
@@ -12,54 +10,75 @@ defmodule Systems.Assignment.PublicTest do
}
alias Core.Factories
- alias CoreWeb.UI.Timestamp
test "has_open_spots?/1 true, with 1 expired pending task" do
- %{crew: crew} = assignment = create_assignment(31, 1)
- _task = create_task(crew, :pending, true)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(31, 1)
+
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+
+ _task = Crew.Factories.create_task(crew, member, ["task1"], expired: true)
assert Assignment.Public.has_open_spots?(assignment)
end
test "has_open_spots?/1 true, with 1 expired pending task and 1 completed task" do
- %{crew: crew} = assignment = create_assignment(31, 2)
- _task = create_task(crew, :completed, false)
- _task = create_task(crew, :pending, true)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(31, 2)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+
+ _task = Crew.Factories.create_task(crew, member, ["task1"], status: :completed)
+ _task = Crew.Factories.create_task(crew, member, ["task2"], expired: true)
assert Assignment.Public.has_open_spots?(assignment)
end
test "has_open_spots?/1 true, with 0 tasks" do
- assignment = create_assignment(31, 1)
+ assignment = Assignment.Factories.create_assignment(31, 1)
assert Assignment.Public.has_open_spots?(assignment)
end
test "has_open_spots?/1 false, with 1 pending task left" do
- %{crew: crew} = assignment = create_assignment(31, 1)
- _task = create_task(crew, :pending, false)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(31, 1)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+ _task = Crew.Factories.create_task(crew, member, ["task1"])
assert not Assignment.Public.has_open_spots?(assignment)
end
test "has_open_spots?/1 false, with completed tasks" do
- %{crew: crew} = assignment = create_assignment(31, 1)
- _task = create_task(crew, :completed, false)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(31, 1)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+ _task = Crew.Factories.create_task(crew, member, ["task1"], status: :completed)
assert not Assignment.Public.has_open_spots?(assignment)
end
test "mark_expired?/1 force=false, marked 1 expired task" do
- %{crew: crew} = assignment = create_assignment(31, 1)
- task1 = create_task(crew, :pending, false, 31)
- task2 = create_task(crew, :pending, false, 20)
- task3 = create_task(crew, :completed, false, 60)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(31, 1)
+
+ user1 = Factories.insert!(:member)
+ user2 = Factories.insert!(:member)
+ user3 = Factories.insert!(:member)
+
+ member1 = Crew.Factories.create_member(crew, user1)
+ member2 = Crew.Factories.create_member(crew, user2)
+ member3 = Crew.Factories.create_member(crew, user3)
+
+ task1 = Crew.Factories.create_task(crew, member1, ["task1"])
+ task2 = Crew.Factories.create_task(crew, member2, ["task2"], minutes_ago: 20)
+
+ task3 =
+ Crew.Factories.create_task(crew, member3, ["task3"], status: :completed, minutes_ago: 60)
Assignment.Public.mark_expired_debug(assignment, false)
- assert %{expired: true} = Crew.Public.get_member!(task1.member_id)
- assert %{expired: false} = Crew.Public.get_member!(task2.member_id)
- assert %{expired: false} = Crew.Public.get_member!(task3.member_id)
+ assert is_nil(Crew.Public.get_member(crew, user1))
+ assert %{expired: false} = Crew.Public.get_member(crew, user2)
+ assert %{expired: false} = Crew.Public.get_member(crew, user3)
assert %{expired: true} = Crew.Public.get_task!(task1.id)
assert %{expired: false} = Crew.Public.get_task!(task2.id)
@@ -67,37 +86,48 @@ defmodule Systems.Assignment.PublicTest do
end
test "apply_expired?/1 force=true, marked all pending tasks" do
- %{crew: crew} = assignment = create_assignment(31, 1)
- task1 = create_task(crew, :pending, false, 31)
- task2 = create_task(crew, :pending, false, 20)
- task3 = create_task(crew, :completed, false, 60)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(31, 1)
+
+ user1 = Factories.insert!(:member)
+ user2 = Factories.insert!(:member)
+ user3 = Factories.insert!(:member)
+
+ member1 = Crew.Factories.create_member(crew, user1)
+ member2 = Crew.Factories.create_member(crew, user2)
+ member3 = Crew.Factories.create_member(crew, user3)
+
+ task1 = Crew.Factories.create_task(crew, member1, ["task1"])
+ task2 = Crew.Factories.create_task(crew, member2, ["task2"], minutes_ago: 20)
+
+ task3 =
+ Crew.Factories.create_task(crew, member3, ["task3"], status: :completed, minutes_ago: 60)
Assignment.Public.mark_expired_debug(assignment, true)
- assert %{expired: true} = Crew.Public.get_member!(task1.member_id)
- assert %{expired: true} = Crew.Public.get_member!(task2.member_id)
- assert %{expired: false} = Crew.Public.get_member!(task3.member_id)
+ assert is_nil(Crew.Public.get_member(crew, user1))
+ assert is_nil(Crew.Public.get_member(crew, user2))
+ assert %{expired: false} = Crew.Public.get_member(crew, user3)
assert %{expired: true} = Crew.Public.get_task!(task1.id)
assert %{expired: true} = Crew.Public.get_task!(task2.id)
assert %{expired: false} = Crew.Public.get_task!(task3.id)
end
- test "apply_member/2 creates reward with deposit" do
+ test "apply_member/4 creates reward with deposit" do
%{id: assignment_id, crew: crew, budget: %{id: budget_id}} =
- assignment = create_assignment(31, 1)
+ assignment = Assignment.Factories.create_assignment(31, 1)
- task = create_task(crew, :pending, false, 31)
+ %{id: user_id} = user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+
+ task = Crew.Factories.create_task(crew, member, ["task1"])
Assignment.Public.mark_expired_debug(assignment, false)
- assert %{expired: true} = Crew.Public.get_member!(task.member_id)
+ assert is_nil(Crew.Public.get_member(crew, user))
assert %{expired: true} = Crew.Public.get_task!(task.id)
- member = Crew.Public.get_member!(task.member_id)
- %{id: user_id} = user = Accounts.get_user!(member.user_id)
-
- Assignment.Public.apply_member(assignment, user, 1000)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
idempotence_key = "assignment=#{assignment_id},user=#{user_id}"
@@ -117,28 +147,27 @@ defmodule Systems.Assignment.PublicTest do
] = Budget.Public.list_rewards(user, [:budget, :user, :deposit, :payment])
end
- test "apply_member/2 re-uses expired member" do
- %{crew: crew} = assignment = create_assignment(31, 1)
- task = create_task(crew, :pending, false, 31)
+ test "apply_member/4 re-uses expired member" do
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(31, 1)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+ task = Crew.Factories.create_task(crew, member, ["task1"])
Assignment.Public.mark_expired_debug(assignment, false)
- assert %{expired: true} = Crew.Public.get_member!(task.member_id)
+ assert is_nil(Crew.Public.get_member(crew, user))
assert %{expired: true} = Crew.Public.get_task!(task.id)
- member = Crew.Public.get_member!(task.member_id)
- user = Accounts.get_user!(member.user_id)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
- Assignment.Public.apply_member(assignment, user, 1000)
-
- assert %{expired: false} = Crew.Public.get_member!(task.member_id)
+ assert %{expired: false} = Crew.Public.get_member(crew, user)
assert %{expired: false} = Crew.Public.get_task!(task.id)
end
test "rollback_expired_deposits/0 resets reward" do
user = Factories.insert!(:member)
- assignment = create_assignment(31, 1)
- Assignment.Public.apply_member(assignment, user, 1000)
+ assignment = Assignment.Factories.create_assignment(31, 1)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
Assignment.Public.mark_expired_debug(assignment, true)
Assignment.Public.rollback_expired_deposits()
@@ -159,11 +188,11 @@ defmodule Systems.Assignment.PublicTest do
test "apply_member/3 re-apply member creates reward with next attempt deposit" do
user = Factories.insert!(:member)
- assignment = create_assignment(31, 1)
- Assignment.Public.apply_member(assignment, user, 1000)
+ assignment = Assignment.Factories.create_assignment(31, 1)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
Assignment.Public.mark_expired_debug(assignment, true)
Assignment.Public.rollback_expired_deposits()
- Assignment.Public.apply_member(assignment, user, 2000)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 2000)
deposit_idempotence_key =
"assignment=#{assignment.id},user=#{user.id},type=deposit,attempt=1"
@@ -185,11 +214,11 @@ defmodule Systems.Assignment.PublicTest do
test "payout_participant/2 creates transaction from budget reserve to user wallet" do
user = Factories.insert!(:member)
- assignment = create_assignment(31, 1)
- Assignment.Public.apply_member(assignment, user, 1000)
+ assignment = Assignment.Factories.create_assignment(31, 1)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
Assignment.Public.mark_expired_debug(assignment, true)
Assignment.Public.rollback_expired_deposits()
- Assignment.Public.apply_member(assignment, user, 2000)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 2000)
Assignment.Public.payout_participant(assignment, user)
deposit_idempotence_key =
@@ -214,11 +243,11 @@ defmodule Systems.Assignment.PublicTest do
test "payout_participant/2 twice fails" do
user = Factories.insert!(:member)
- assignment = create_assignment(31, 1)
- Assignment.Public.apply_member(assignment, user, 1000)
+ assignment = Assignment.Factories.create_assignment(31, 1)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
Assignment.Public.mark_expired_debug(assignment, true)
Assignment.Public.rollback_expired_deposits()
- Assignment.Public.apply_member(assignment, user, 2000)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 2000)
assert {:ok, _} = Assignment.Public.payout_participant(assignment, user)
assert {:error, _, :payment_already_available, %{}} =
@@ -227,11 +256,11 @@ defmodule Systems.Assignment.PublicTest do
test "rewarded_amount/2 after payout" do
user = Factories.insert!(:member)
- assignment = create_assignment(31, 1)
- Assignment.Public.apply_member(assignment, user, 1000)
+ assignment = Assignment.Factories.create_assignment(31, 1)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
Assignment.Public.mark_expired_debug(assignment, true)
Assignment.Public.rollback_expired_deposits()
- Assignment.Public.apply_member(assignment, user, 2000)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 2000)
Assignment.Public.payout_participant(assignment, user)
assert Assignment.Public.rewarded_amount(assignment, user) == 2000
@@ -239,60 +268,69 @@ defmodule Systems.Assignment.PublicTest do
test "rewarded_amount/2 before payout" do
user = Factories.insert!(:member)
- assignment = create_assignment(31, 1)
- Assignment.Public.apply_member(assignment, user, 1000)
+ assignment = Assignment.Factories.create_assignment(31, 1)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 1000)
Assignment.Public.mark_expired_debug(assignment, true)
Assignment.Public.rollback_expired_deposits()
- Assignment.Public.apply_member(assignment, user, 2000)
+ Assignment.Public.apply_member(assignment, user, ["task1"], 2000)
assert Assignment.Public.rewarded_amount(assignment, user) == 0
end
test "open_spot_count/3 with 1 expired spot" do
- %{crew: crew} = assignment = create_assignment(10, 3)
- _task1 = create_task(crew, :pending, false, 10)
- _task2 = create_task(crew, :pending, true, 31)
- _task3 = create_task(crew, :completed, false)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(10, 3)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+
+ _task1 = Crew.Factories.create_task(crew, member, ["task1"], minutes_ago: 10)
+ _task2 = Crew.Factories.create_task(crew, member, ["task2"], expired: true)
+ _task3 = Crew.Factories.create_task(crew, member, ["task3"], status: :completed)
assert Assignment.Public.open_spot_count(assignment) == 1
end
test "open_spot_count/3 with 1 expired and one open spot" do
- %{crew: crew} = assignment = create_assignment(10, 4)
- _task1 = create_task(crew, :pending, false, 10)
- _task2 = create_task(crew, :pending, true, 31)
- _task3 = create_task(crew, :completed, false)
+ %{crew: crew} = assignment = Assignment.Factories.create_assignment(10, 4)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+ _task1 = Crew.Factories.create_task(crew, member, ["task1"], minutes_ago: 10)
+ _task2 = Crew.Factories.create_task(crew, member, ["task2"], expired: true)
+ _task3 = Crew.Factories.create_task(crew, member, ["task3"], status: :completed)
assert Assignment.Public.open_spot_count(assignment) == 2
end
test "open_spot_count/3 with all open spots" do
- assignment = create_assignment(31, 3)
+ assignment = Assignment.Factories.create_assignment(31, 3)
assert Assignment.Public.open_spot_count(assignment) == 3
end
test "next_action (Assignment.CheckRejection) after rejection of task" do
- %{id: id, crew: crew} = create_assignment(31, 3)
- %{id: task_id, member: %{user: user}} = create_task(crew, :pending, false, 10)
+ %{id: id, crew: crew} = Assignment.Factories.create_assignment(31, 3)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+ %{id: task_id} = Crew.Factories.create_task(crew, member, ["task1"], minutes_ago: 10)
Crew.Public.reject_task(task_id, %{category: :other, message: "rejected"})
- assert_next_action(user, "/assignment/#{id}")
+ assert_next_action(user, "/assignment/#{id}/landing")
end
test "next_action cleared after acceptence of task" do
- %{id: id, crew: crew} = create_assignment(31, 3)
- %{id: task_id, member: %{user: user}} = create_task(crew, :pending, false, 10)
+ %{id: id, crew: crew} = Assignment.Factories.create_assignment(31, 3)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew, user)
+ %{id: task_id} = Crew.Factories.create_task(crew, member, ["task1"], minutes_ago: 10)
Crew.Public.reject_task(task_id, %{category: :other, message: "rejected"})
Crew.Public.accept_task(task_id)
- refute_next_action(user, "/assignment/#{id}")
+ refute_next_action(user, "/assignment/#{id}/landing")
end
test "exclude/2" do
- %{id: id1} = assignment1 = create_assignment(31, 2)
- %{id: id2} = assignment2 = create_assignment(31, 2)
+ %{id: id1} = assignment1 = Assignment.Factories.create_assignment(31, 2)
+ %{id: id2} = assignment2 = Assignment.Factories.create_assignment(31, 2)
Assignment.Public.exclude(assignment1, assignment2)
@@ -306,8 +344,8 @@ defmodule Systems.Assignment.PublicTest do
end
test "include/2" do
- assignment1 = create_assignment(31, 2)
- assignment2 = create_assignment(31, 2)
+ assignment1 = Assignment.Factories.create_assignment(31, 2)
+ assignment2 = Assignment.Factories.create_assignment(31, 2)
{:ok, _} = Assignment.Public.exclude(assignment1, assignment2)
@@ -326,8 +364,8 @@ defmodule Systems.Assignment.PublicTest do
end
test "excluded?/2 false: user has no task on excluded assignment" do
- assignment1 = create_assignment(31, 2)
- assignment2 = create_assignment(31, 2)
+ assignment1 = Assignment.Factories.create_assignment(31, 2)
+ assignment2 = Assignment.Factories.create_assignment(31, 2)
user = Factories.insert!(:member)
@@ -336,30 +374,39 @@ defmodule Systems.Assignment.PublicTest do
end
test "excluded?/2 false: user has task on non-excluded assignment" do
- %{crew: crew1} = create_assignment(31, 2)
- assignment2 = create_assignment(31, 2)
+ %{crew: crew1} = Assignment.Factories.create_assignment(31, 2)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew1, user)
+
+ assignment2 = Assignment.Factories.create_assignment(31, 2)
- %{member: %{user: user}} = create_task(crew1, :pending, false, 10)
+ Crew.Factories.create_task(crew1, member, ["task1"], minutes_ago: 10)
assert Assignment.Public.excluded?(assignment2, user) == false
end
test "excluded?/2 true: user has task on excluded assignment" do
- %{crew: crew1} = assignment1 = create_assignment(31, 2)
- assignment2 = create_assignment(31, 2)
+ %{crew: crew1} = assignment1 = Assignment.Factories.create_assignment(31, 2)
+ assignment2 = Assignment.Factories.create_assignment(31, 2)
- %{member: %{user: user}} = create_task(crew1, :pending, false, 10)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew1, user)
+
+ Crew.Factories.create_task(crew1, member, ["task1"], minutes_ago: 10)
Assignment.Public.exclude(assignment1, assignment2)
assert Assignment.Public.excluded?(assignment2, user) == true
end
test "excluded?/2 user has task on multiple excluded assignment" do
- %{crew: crew1} = assignment1 = create_assignment(31, 2)
- assignment2 = create_assignment(31, 2)
- assignment3 = create_assignment(31, 2)
- assignment4 = create_assignment(31, 2)
+ %{crew: crew1} = assignment1 = Assignment.Factories.create_assignment(31, 2)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew1, user)
- %{member: %{user: user}} = create_task(crew1, :pending, false, 10)
+ assignment2 = Assignment.Factories.create_assignment(31, 2)
+ assignment3 = Assignment.Factories.create_assignment(31, 2)
+ assignment4 = Assignment.Factories.create_assignment(31, 2)
+
+ Crew.Factories.create_task(crew1, member, ["task1"], minutes_ago: 10)
Assignment.Public.exclude(assignment1, assignment2)
Assignment.Public.exclude(assignment1, assignment3)
@@ -370,12 +417,15 @@ defmodule Systems.Assignment.PublicTest do
end
test "excluded?/2 user has no task on multiple excluded assignment" do
- %{crew: crew1} = assignment1 = create_assignment(31, 2)
- assignment2 = create_assignment(31, 2)
- assignment3 = create_assignment(31, 2)
- assignment4 = create_assignment(31, 2)
+ %{crew: crew1} = assignment1 = Assignment.Factories.create_assignment(31, 2)
+ assignment2 = Assignment.Factories.create_assignment(31, 2)
+ assignment3 = Assignment.Factories.create_assignment(31, 2)
+ assignment4 = Assignment.Factories.create_assignment(31, 2)
- %{member: %{user: user}} = create_task(crew1, :pending, false, 10)
+ user = Factories.insert!(:member)
+ member = Crew.Factories.create_member(crew1, user)
+
+ Crew.Factories.create_task(crew1, member, ["task1"], minutes_ago: 10)
Assignment.Public.exclude(assignment4, assignment2)
Assignment.Public.exclude(assignment4, assignment3)
@@ -385,39 +435,5 @@ defmodule Systems.Assignment.PublicTest do
assert Assignment.Public.excluded?(assignment3, user) == false
assert Assignment.Public.excluded?(assignment4, user) == false
end
-
- defp create_assignment(duration, subject_count) do
- crew = Factories.insert!(:crew)
-
- survey_tool =
- Factories.insert!(:survey_tool, %{
- survey_url: "http://eyra.co/survey/123"
- })
-
- experiment =
- Factories.insert!(:experiment, %{
- survey_tool: survey_tool,
- duration: Integer.to_string(duration),
- subject_count: subject_count
- })
-
- Factories.insert!(:assignment, %{experiment: experiment, crew: crew})
- end
-
- defp create_task(crew, status, expired, minutes_ago \\ 31) when is_boolean(expired) do
- updated_at = Timestamp.naive_from_now(minutes_ago * -1)
-
- user = Factories.insert!(:member)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
-
- _task =
- Factories.insert!(:crew_task, %{
- crew: crew,
- member: member,
- status: status,
- expired: expired,
- updated_at: updated_at
- })
- end
end
end
diff --git a/core/test/systems/assignment/factories.ex b/core/test/systems/assignment/factories.ex
new file mode 100644
index 000000000..310fb409c
--- /dev/null
+++ b/core/test/systems/assignment/factories.ex
@@ -0,0 +1,79 @@
+defmodule Systems.Assignment.Factories do
+ alias Core.Factories
+
+ alias Systems.Alliance
+
+ def create_info(duration, subject_count) do
+ Factories.insert!(
+ :assignment_info,
+ %{
+ subject_count: subject_count,
+ duration: duration,
+ language: "en",
+ devices: [:desktop]
+ }
+ )
+ end
+
+ def create_tool(auth_node) do
+ Factories.insert!(:alliance_tool, %{
+ url: "https://eyra.co/alliance/123",
+ auth_node: auth_node,
+ director: :assignment
+ })
+ end
+
+ def create_tool_ref(%Alliance.ToolModel{} = tool) do
+ Factories.insert!(:tool_ref, %{
+ alliance_tool: tool
+ })
+ end
+
+ def create_workflow(type \\ :single_task) do
+ Factories.insert!(:workflow, %{type: type})
+ end
+
+ def create_workflow_item(workflow, tool_ref) do
+ Factories.insert!(:workflow_item, %{
+ workflow: workflow,
+ tool_ref: tool_ref
+ })
+ end
+
+ def create_assignment(info, workflow, auth_node, budget, director) do
+ crew = Factories.insert!(:crew)
+
+ Factories.insert!(:assignment, %{
+ info: info,
+ workflow: workflow,
+ crew: crew,
+ auth_node: auth_node,
+ budget: budget,
+ director: director
+ })
+ end
+
+ def create_assignment(info, workflow, auth_node) do
+ crew = Factories.insert!(:crew)
+
+ Factories.insert!(:assignment, %{
+ info: info,
+ workflow: workflow,
+ crew: crew,
+ auth_node: auth_node
+ })
+ end
+
+ def create_assignment(duration, subject_count) when is_integer(duration) do
+ assignment_auth_node = Factories.build(:auth_node)
+ tool_auth_node = Factories.build(:auth_node, %{parent: assignment_auth_node})
+
+ info = create_info(Integer.to_string(duration), subject_count)
+ tool = create_tool(tool_auth_node)
+ tool_ref = create_tool_ref(tool)
+ workflow = create_workflow()
+ _workflow_item = create_workflow_item(workflow, tool_ref)
+
+ create_assignment(info, workflow, assignment_auth_node)
+ end
+end
diff --git a/core/test/systems/assignment/landing_page_test.exs b/core/test/systems/assignment/landing_page_test.exs
index f9f677ca0..5ad88cbf5 100644
--- a/core/test/systems/assignment/landing_page_test.exs
+++ b/core/test/systems/assignment/landing_page_test.exs
@@ -9,14 +9,14 @@ defmodule Systems.Assignment.LandingPageTest do
Budget
}
- describe "show landing page for: campaign -> assignment -> survey_tool" do
+ describe "show landing page for: campaign -> assignment -> alliance_tool" do
setup [:login_as_member]
setup do
campaign_auth_node = Factories.insert!(:auth_node)
promotion_auth_node = Factories.insert!(:auth_node, %{parent: campaign_auth_node})
assignment_auth_node = Factories.insert!(:auth_node, %{parent: campaign_auth_node})
- experiment_auth_node = Factories.insert!(:auth_node, %{parent: assignment_auth_node})
+ tool_auth_node = Factories.insert!(:auth_node, %{parent: assignment_auth_node})
currency = Budget.Factories.create_currency("test_1234", :legal, "ƒ", 2)
budget = Budget.Factories.create_budget("test_1234", currency)
@@ -24,38 +24,19 @@ defmodule Systems.Assignment.LandingPageTest do
pool =
Factories.insert!(:pool, %{name: "test_1234", director: :citizen, currency: currency})
- survey_tool =
- Factories.insert!(
- :survey_tool,
- %{
- survey_url: "https://eyra.co/fake_survey",
- director: :campaign
- }
- )
-
- experiment =
- Factories.insert!(
- :experiment,
- %{
- auth_node: experiment_auth_node,
- survey_tool: survey_tool,
- subject_count: 10,
- duration: "10",
- language: "en",
- devices: [:desktop],
- director: :campaign
- }
- )
+ tool = Assignment.Factories.create_tool(tool_auth_node)
+ tool_ref = Assignment.Factories.create_tool_ref(tool)
+ workflow = Assignment.Factories.create_workflow()
+ _workflow_item = Assignment.Factories.create_workflow_item(workflow, tool_ref)
+ info = Assignment.Factories.create_info("10", 10)
assignment =
- Factories.insert!(
- :assignment,
- %{
- auth_node: assignment_auth_node,
- budget: budget,
- experiment: experiment,
- director: :campaign
- }
+ Assignment.Factories.create_assignment(
+ info,
+ workflow,
+ assignment_auth_node,
+ budget,
+ :campaign
)
promotion =
@@ -98,7 +79,7 @@ defmodule Systems.Assignment.LandingPageTest do
} do
Core.Authorization.assign_role(user, campaign, :owner)
- _member = Assignment.Public.apply_member(assignment, user, 500)
+ _member = Assignment.Public.apply_member(assignment, user, ["task1"], 500)
{:ok, _view, html} =
live(conn, Routes.live_path(conn, Assignment.LandingPage, assignment.id))
@@ -119,8 +100,8 @@ defmodule Systems.Assignment.LandingPageTest do
} do
Core.Authorization.assign_role(user, campaign, :owner)
- {:ok, %{member: member}} = Assignment.Public.apply_member(assignment, user, 500)
- task = Crew.Public.get_task(assignment.crew, member)
+ {:ok, %{member: _member}} = Assignment.Public.apply_member(assignment, user, ["task1"], 500)
+ task = Crew.Public.get_task(assignment.crew, ["task1"])
{:ok, view, _html} =
live(conn, Routes.live_path(conn, Assignment.LandingPage, assignment.id))
@@ -130,7 +111,7 @@ defmodule Systems.Assignment.LandingPageTest do
|> element("[phx-click=\"open\"]")
|> render_click()
- assert {:error, {:redirect, %{to: "https://eyra.co/fake_survey?panl_id=1"}}} = html
+ assert {:error, {:redirect, %{to: "https://eyra.co/alliance/123?panl_id=1"}}} = html
task = Crew.Public.get_task!(task.id)
assert %Systems.Crew.TaskModel{started_at: started_at, updated_at: updated_at} = task
@@ -144,8 +125,8 @@ defmodule Systems.Assignment.LandingPageTest do
} do
Core.Authorization.assign_role(user, campaign, :owner)
- {:ok, %{member: member}} = Assignment.Public.apply_member(assignment, user, 500)
- task = Crew.Public.get_task(assignment.crew, member)
+ {:ok, %{member: _member}} = Assignment.Public.apply_member(assignment, user, ["task1"], 500)
+ task = Crew.Public.get_task(assignment.crew, ["task1"])
Crew.Public.lock_task(task)
{:ok, _view, html} =
@@ -167,8 +148,8 @@ defmodule Systems.Assignment.LandingPageTest do
} do
Core.Authorization.assign_role(user, campaign, :owner)
- {:ok, %{member: member}} = Assignment.Public.apply_member(assignment, user, 500)
- task = Crew.Public.get_task(assignment.crew, member)
+ {:ok, %{member: _member}} = Assignment.Public.apply_member(assignment, user, ["task1"], 500)
+ task = Crew.Public.get_task(assignment.crew, ["task1"])
Crew.Public.lock_task(task)
Crew.Public.activate_task(task)
diff --git a/core/test/systems/campaign/_assembly_test.exs b/core/test/systems/campaign/_assembly_test.exs
index cb2569410..fcebf54a1 100644
--- a/core/test/systems/campaign/_assembly_test.exs
+++ b/core/test/systems/campaign/_assembly_test.exs
@@ -8,7 +8,8 @@ defmodule Systems.Campaign.AssemblyTest do
alias Systems.{
Campaign,
Assignment,
- Survey,
+ Workflow,
+ Alliance,
Lab,
Pool,
Budget
@@ -49,7 +50,7 @@ defmodule Systems.Campaign.AssemblyTest do
}}
end)
- campaign = Campaign.Assembly.create(researcher, "New Campaign", :online, pool, budget)
+ campaign = Campaign.Assembly.create(:online, researcher, "New Campaign", pool, budget)
assert %Systems.Campaign.Model{
auth_node: %Core.Authorization.Node{
@@ -58,27 +59,16 @@ defmodule Systems.Campaign.AssemblyTest do
},
promotable_assignment:
%Systems.Assignment.Model{
- director: :campaign,
- assignable_experiment: %{
- devices: [:phone, :tablet, :desktop],
+ info: %Systems.Assignment.InfoModel{
+ subject_count: nil,
duration: nil,
- ethical_approval: nil,
- ethical_code: nil,
language: nil,
- subject_count: nil,
- director: :campaign,
- lab_tool_id: nil,
- survey_tool: %Survey.ToolModel{
- survey_url: nil,
- director: :campaign,
- auth_node: %Core.Authorization.Node{
- parent_id: survey_tool_auth_node_parent_id
- }
- },
- auth_node: %Core.Authorization.Node{
- id: experiment_auth_node_id,
- parent_id: experiment_auth_node_parent_id
- }
+ devices: [:phone, :tablet, :desktop],
+ ethical_approval: nil,
+ ethical_code: nil
+ },
+ workflow: %Systems.Workflow.Model{
+ id: workflow_id
},
auth_node: %Core.Authorization.Node{
id: assignment_auth_node_id,
@@ -112,13 +102,37 @@ defmodule Systems.Campaign.AssemblyTest do
assert promotion_auth_node_parent_id == campaign_auth_node_id
assert assignment_auth_node_parent_id == campaign_auth_node_id
- assert experiment_auth_node_parent_id == assignment_auth_node_id
assert crew_auth_node_parent_id == assignment_auth_node_id
- assert survey_tool_auth_node_parent_id == experiment_auth_node_id
assert banner_photo_url == researcher.profile.photo_url
assert banner_title == researcher.displayname
+ # WORKFLOW
+
+ assert %Systems.Workflow.Model{
+ type: :single_task,
+ items: [
+ %Systems.Workflow.ItemModel{
+ group: nil,
+ position: nil,
+ title: nil,
+ description: nil,
+ tool_ref: %{
+ alliance_tool: %Systems.Alliance.ToolModel{
+ auth_node: %Core.Authorization.Node{
+ parent_id: ^assignment_auth_node_id
+ },
+ url: nil,
+ director: :assignment
+ },
+ feldspar_tool_id: nil,
+ document_tool_id: nil,
+ lab_tool_id: nil
+ }
+ }
+ ]
+ } = Assignment.Public.get_workflow!(workflow_id, Workflow.Model.preload_graph(:down))
+
# CAMPAIGN AUTHORS
assert %{
@@ -178,7 +192,7 @@ defmodule Systems.Campaign.AssemblyTest do
}}
end)
- campaign = Campaign.Assembly.create(researcher, "New Campaign", :lab, pool, budget)
+ campaign = Campaign.Assembly.create(:lab, researcher, "New Campaign", pool, budget)
assert %Systems.Campaign.Model{
auth_node: %Core.Authorization.Node{
@@ -186,27 +200,16 @@ defmodule Systems.Campaign.AssemblyTest do
parent_id: nil
},
promotable_assignment: %Systems.Assignment.Model{
- director: :campaign,
- assignable_experiment: %{
- devices: [],
+ info: %Systems.Assignment.InfoModel{
+ subject_count: nil,
duration: nil,
- ethical_approval: nil,
- ethical_code: nil,
language: nil,
- subject_count: nil,
- director: :campaign,
- survey_tool_id: nil,
- lab_tool: %Lab.ToolModel{
- id: lab_tool_id,
- director: :campaign,
- auth_node: %Core.Authorization.Node{
- parent_id: lab_tool_auth_node_parent_id
- }
- },
- auth_node: %Core.Authorization.Node{
- id: experiment_auth_node_id,
- parent_id: experiment_auth_node_parent_id
- }
+ devices: [],
+ ethical_approval: nil,
+ ethical_code: nil
+ },
+ workflow: %Systems.Workflow.Model{
+ id: workflow_id
},
auth_node: %Core.Authorization.Node{
id: assignment_auth_node_id,
@@ -220,12 +223,36 @@ defmodule Systems.Campaign.AssemblyTest do
}
} = campaign
- assert Lab.Public.get_time_slots(lab_tool_id) == []
-
assert assignment_auth_node_parent_id == campaign_auth_node_id
- assert experiment_auth_node_parent_id == assignment_auth_node_id
assert crew_auth_node_parent_id == assignment_auth_node_id
- assert lab_tool_auth_node_parent_id == experiment_auth_node_id
+
+ # WORKFLOW
+
+ assert %Systems.Workflow.Model{
+ type: :single_task,
+ items: [
+ %Systems.Workflow.ItemModel{
+ group: nil,
+ position: nil,
+ title: nil,
+ description: nil,
+ tool_ref: %{
+ lab_tool: %Systems.Lab.ToolModel{
+ id: lab_tool_id,
+ auth_node: %Core.Authorization.Node{
+ parent_id: ^assignment_auth_node_id
+ },
+ director: :assignment
+ },
+ feldspar_tool_id: nil,
+ document_tool_id: nil,
+ alliance_tool_id: nil
+ }
+ }
+ ]
+ } = Assignment.Public.get_workflow!(workflow_id, Workflow.Model.preload_graph(:down))
+
+ assert Lab.Public.get_time_slots(lab_tool_id) == []
end
test "delete", %{user: researcher, mock: mock, budget: budget, pool: pool} do
@@ -239,32 +266,17 @@ defmodule Systems.Campaign.AssemblyTest do
}}
end)
- %{id: id} = Campaign.Assembly.create(researcher, "New Campaign", :online, pool, budget)
+ %{id: id} = Campaign.Assembly.create(:online, researcher, "New Campaign", pool, budget)
- campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
+ campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:down))
Campaign.Assembly.delete(campaign)
assert Repo.get(Campaign.Model, campaign.id) == nil
+ assert Repo.get(Core.Authorization.Node, campaign.auth_node_id) == nil
assert Repo.get(Systems.Promotion.Model, campaign.promotion_id) == nil
assert Repo.get(Core.Authorization.Node, campaign.promotion.auth_node_id) == nil
- assert Repo.get(Systems.Assignment.Model, campaign.promotable_assignment_id) == nil
- assert Repo.get(Core.Authorization.Node, campaign.promotable_assignment.auth_node_id) == nil
- assert Repo.get(Systems.Crew.Model, campaign.promotable_assignment.crew_id) == nil
-
- assert Repo.get(Core.Authorization.Node, campaign.promotable_assignment.crew.auth_node_id) ==
- nil
-
- assert Repo.get(
- Survey.ToolModel,
- campaign.promotable_assignment.assignable_experiment.survey_tool_id
- ) ==
- nil
-
- assert Repo.get(
- Core.Authorization.Node,
- campaign.promotable_assignment.assignable_experiment.survey_tool.auth_node_id
- ) == nil
+ assert Repo.get(Systems.Assignment.Model, campaign.promotable_assignment_id) != nil
end
test "copy", %{user: researcher, mock: mock, budget: budget, pool: pool} do
@@ -278,7 +290,7 @@ defmodule Systems.Campaign.AssemblyTest do
}}
end)
- %{id: id} = Campaign.Assembly.create(researcher, "New Campaign", :online, pool, budget)
+ %{id: id} = Campaign.Assembly.create(:online, researcher, "New Campaign", pool, budget)
%{
id: id,
@@ -288,13 +300,19 @@ defmodule Systems.Campaign.AssemblyTest do
} = submission
],
promotable_assignment: %{
+ info: info,
crew: crew1,
- assignable_experiment:
- %{
- survey_tool: tool
- } = experiment
+ workflow: %{
+ items: [
+ %{
+ tool_ref: %Systems.Project.ToolRefModel{
+ alliance_tool: tool
+ }
+ }
+ ]
+ }
}
- } = Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
+ } = Campaign.Public.get!(id, Campaign.Model.preload_graph(:down))
# Update Submission
reward_value = 1
@@ -324,17 +342,16 @@ defmodule Systems.Campaign.AssemblyTest do
|> Repo.update!()
# Update Tool
- survey_url = "https://eyra.co/surveys/1"
- director = :campaign
+ alliance_url = "https://eyra.co/alliances/1"
tool
- |> Survey.ToolModel.changeset(:update, %{
- survey_url: survey_url,
- director: director
+ |> Alliance.ToolModel.changeset(:update, %{
+ url: alliance_url,
+ director: :assignment
})
|> Repo.update!()
- # Update Experiment
+ # Update Inquiry
subject_count = 2
duration = "10"
language = "en"
@@ -342,22 +359,21 @@ defmodule Systems.Campaign.AssemblyTest do
ethical_code = "RERB"
devices = [:desktop, :tablet]
- experiment
- |> Assignment.ExperimentModel.changeset(:update, %{
+ info
+ |> Assignment.InfoModel.changeset(:update, %{
subject_count: subject_count,
duration: duration,
language: language,
ethical_approval: ethical_approval,
ethical_code: ethical_code,
- devices: devices,
- director: director
+ devices: devices
})
|> Repo.update!()
- campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
+ campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:down))
{:ok, %{campaign: %{id: id2}}} = Campaign.Assembly.copy(campaign)
- campaign2 = Campaign.Public.get!(id2, Campaign.Model.preload_graph(:full))
+ campaign2 = Campaign.Public.get!(id2, Campaign.Model.preload_graph(:down))
assert %{
authors: [%{}],
@@ -382,19 +398,16 @@ defmodule Systems.Campaign.AssemblyTest do
title: "New Campaign (copy)"
},
promotable_assignment: %{
- crew: crew2,
- assignable_experiment: %{
+ info: %{
subject_count: ^subject_count,
duration: ^duration,
language: ^language,
ethical_approval: ^ethical_approval,
ethical_code: ^ethical_code,
- devices: ^devices,
- director: ^director,
- survey_tool: %{
- survey_url: ^survey_url
- }
- }
+ devices: ^devices
+ },
+ crew: crew2,
+ workflow: %{items: []}
}
} = campaign2
diff --git a/core/test/systems/campaign/_public_test.exs b/core/test/systems/campaign/_public_test.exs
index ca54668e4..4f3826c65 100644
--- a/core/test/systems/campaign/_public_test.exs
+++ b/core/test/systems/campaign/_public_test.exs
@@ -9,8 +9,8 @@ defmodule Systems.Campaign.PublicTest do
Budget
}
- alias Core.Factories
alias CoreWeb.UI.Timestamp
+ alias Core.Factories
setup do
currency = Budget.Factories.create_currency("fake_currency", :legal, "ƒ", 2)
@@ -19,9 +19,14 @@ defmodule Systems.Campaign.PublicTest do
{:ok, currency: currency, budget: budget, user: user}
end
- test "mark_expired_debug?/0 should mark 1 expired task in online campaign", %{budget: budget} do
- %{promotable_assignment: %{crew: crew}} = create_campaign(:accepted, budget)
- task = create_task(crew, :pending, false, -31)
+ test "mark_expired_debug?/0 should mark 1 expired task in online campaign", %{
+ budget: budget,
+ user: user
+ } do
+ %{promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
+
+ task = Campaign.Factories.create_task(["task1"], crew, :pending, false, 31)
Campaign.Public.mark_expired_debug()
@@ -29,23 +34,29 @@ defmodule Systems.Campaign.PublicTest do
end
test "mark_expired_debug?/0 should mark 0 expired tasks in submitted campaign", %{
- budget: budget
+ budget: budget,
+ user: user
} do
- %{promotable_assignment: %{crew: crew}} = create_campaign(:submitted, budget)
- task = create_task(crew, :pending, false, -31)
+ %{promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :submitted, 1, budget)
+
+ task = Campaign.Factories.create_task(["task1"], crew, :pending, false, 31)
Campaign.Public.mark_expired_debug()
assert %{expired: false} = Crew.Public.get_task!(task.id)
end
- test "mark_expired_debug?/0 should mark 1 expired tasks in closed campaign", %{budget: budget} do
+ test "mark_expired_debug?/0 should mark 1 expired tasks in closed campaign", %{
+ budget: budget,
+ user: user
+ } do
schedule_end = yesterday() |> Timestamp.format_user_input_date()
%{promotable_assignment: %{crew: crew}} =
- create_campaign(:accepted, budget, nil, schedule_end)
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget, nil, schedule_end)
- task = create_task(crew, :pending, false, -31)
+ task = Campaign.Factories.create_task(["task1"], crew, :pending, false, 31)
Campaign.Public.mark_expired_debug()
@@ -53,15 +64,23 @@ defmodule Systems.Campaign.PublicTest do
end
test "mark_expired_debug?/1 should mark 1 expired tasks in scheduled campaign", %{
- budget: budget
+ budget: budget,
+ user: user
} do
schedule_start = tomorrow() |> Timestamp.format_user_input_date()
schedule_end = next_week() |> Timestamp.format_user_input_date()
%{promotable_assignment: %{crew: crew}} =
- create_campaign(:accepted, budget, schedule_start, schedule_end)
+ Campaign.Factories.create_campaign(
+ user,
+ :accepted,
+ 1,
+ budget,
+ schedule_start,
+ schedule_end
+ )
- task = create_task(crew, :pending, false, -31)
+ task = Campaign.Factories.create_task(["task1"], crew, :pending, false, 31)
Campaign.Public.mark_expired_debug(true)
@@ -69,23 +88,29 @@ defmodule Systems.Campaign.PublicTest do
end
test "mark_expired_debug?/1 should mark 1 expired tasks in submitted campaign", %{
- budget: budget
+ budget: budget,
+ user: user
} do
- %{promotable_assignment: %{crew: crew}} = create_campaign(:submitted, budget)
- task = create_task(crew, :pending, false, -31)
+ %{promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :submitted, 1, budget)
+
+ task = Campaign.Factories.create_task(["task1"], crew, :pending, false, 31)
Campaign.Public.mark_expired_debug(true)
assert %{expired: false} = Crew.Public.get_task!(task.id)
end
- test "mark_expired_debug?/1 should mark 1 expired tasks in closed campaign", %{budget: budget} do
+ test "mark_expired_debug?/1 should mark 1 expired tasks in closed campaign", %{
+ budget: budget,
+ user: user
+ } do
schedule_end = yesterday() |> Timestamp.format_user_input_date()
%{promotable_assignment: %{crew: crew}} =
- create_campaign(:accepted, budget, nil, schedule_end)
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget, nil, schedule_end)
- task = create_task(crew, :pending, false, -31)
+ task = Campaign.Factories.create_task(["task1"], crew, :pending, false, 31)
Campaign.Public.mark_expired_debug(true)
@@ -93,27 +118,36 @@ defmodule Systems.Campaign.PublicTest do
end
test "mark_expired_debug?/0 should mark 1 expired tasks in scheduled campaign", %{
- budget: budget
+ budget: budget,
+ user: user
} do
schedule_start = tomorrow() |> Timestamp.format_user_input_date()
schedule_end = next_week() |> Timestamp.format_user_input_date()
%{promotable_assignment: %{crew: crew}} =
- create_campaign(:accepted, budget, schedule_start, schedule_end)
+ Campaign.Factories.create_campaign(
+ user,
+ :accepted,
+ 1,
+ budget,
+ schedule_start,
+ schedule_end
+ )
- task = create_task(crew, :pending, false, -31)
+ task = Campaign.Factories.create_task(["task1"], crew, :pending, false, 31)
Campaign.Public.mark_expired_debug()
assert %{expired: true} = Crew.Public.get_task!(task.id)
end
- test "payout_participant/2 One transaction of one student", %{budget: budget} do
+ test "payout_participant/2 One transaction of one student", %{budget: budget, user: user} do
student = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew} = assignment} = create_campaign(:accepted, budget)
+ %{promotable_assignment: %{crew: crew} = assignment} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
- create_task(student, crew, :accepted, false, -31)
+ Campaign.Factories.create_task(["task1"], student, crew, :accepted, false, 31)
Budget.Factories.create_reward(assignment, student, budget)
Campaign.Public.payout_participant(assignment, student)
@@ -132,14 +166,17 @@ defmodule Systems.Campaign.PublicTest do
Bookkeeping.Public.balance({:wallet, "fake_currency", student.id})
end
- test "payout_participant/2 Two transactions of one student", %{budget: budget} do
+ test "payout_participant/2 Two transactions of one student", %{budget: budget, user: user} do
student = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew1} = assignment1} = create_campaign(:accepted, budget)
- %{promotable_assignment: %{crew: crew2} = assignment2} = create_campaign(:accepted, budget)
+ %{promotable_assignment: %{crew: crew1} = assignment1} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
+
+ %{promotable_assignment: %{crew: crew2} = assignment2} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
- create_task(student, crew1, :accepted, false, -31)
- create_task(student, crew2, :accepted, false, -31)
+ Campaign.Factories.create_task(["task1"], student, crew1, :accepted, false, 31)
+ Campaign.Factories.create_task(["task2"], student, crew2, :accepted, false, 31)
Budget.Factories.create_reward(assignment1, student, budget)
Budget.Factories.create_reward(assignment2, student, budget)
@@ -161,17 +198,20 @@ defmodule Systems.Campaign.PublicTest do
Bookkeeping.Public.balance({:wallet, "fake_currency", student.id})
end
- test "payout_participant/2 Two transactions of two students", %{budget: budget} do
+ test "payout_participant/2 Two transactions of two students", %{budget: budget, user: user} do
student1 = Factories.insert!(:member, %{student: true})
student2 = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew1} = assignment1} = create_campaign(:accepted, budget)
- %{promotable_assignment: %{crew: crew2} = assignment2} = create_campaign(:accepted, budget)
+ %{promotable_assignment: %{crew: crew1} = assignment1} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
- create_task(student1, crew1, :accepted, false, -31)
- create_task(student1, crew2, :accepted, false, -31)
- create_task(student2, crew1, :accepted, false, -31)
- create_task(student2, crew2, :accepted, false, -31)
+ %{promotable_assignment: %{crew: crew2} = assignment2} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
+
+ Campaign.Factories.create_task(["task1"], student1, crew1, :accepted, false, 31)
+ Campaign.Factories.create_task(["task2"], student1, crew2, :accepted, false, 31)
+ Campaign.Factories.create_task(["task3"], student2, crew1, :accepted, false, 31)
+ Campaign.Factories.create_task(["task4"], student2, crew2, :accepted, false, 31)
Budget.Factories.create_reward(assignment1, student1, budget)
Budget.Factories.create_reward(assignment2, student1, budget)
@@ -203,11 +243,16 @@ defmodule Systems.Campaign.PublicTest do
Bookkeeping.Public.balance({:wallet, "fake_currency", student2.id})
end
- test "payout_participant/2 One transaction of one student (via signals)", %{budget: budget} do
+ test "payout_participant/2 One transaction of one student (via signals)", %{
+ budget: budget,
+ user: user
+ } do
student = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew} = assignment} = create_campaign(:accepted, budget)
- task = create_task(student, crew, :pending, false, -31)
+ %{promotable_assignment: %{crew: crew} = assignment} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
+
+ task = Campaign.Factories.create_task(["task1"], student, crew, :pending, false, 31)
Budget.Factories.create_reward(assignment, student, budget)
# accept task should send signal to campaign to reward student
@@ -228,34 +273,42 @@ defmodule Systems.Campaign.PublicTest do
end
test "payout_participant/2 One transaction of one student failed: task already accepted (via signals)",
- %{budget: budget} do
+ %{budget: budget, user: user} do
student = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew} = assignment} = create_campaign(:accepted, budget)
- task = create_task(student, crew, :accepted, false, -31)
+ %{promotable_assignment: %{crew: crew} = assignment} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
+
+ task = Campaign.Factories.create_task(["task1"], student, crew, :accepted, false, 31)
Budget.Factories.create_reward(assignment, student, budget)
# accept task should send signal to campaign to reward student
Crew.Public.accept_task(task)
+ Bookkeeping.Public.list_accounts(["wallet"])
+
assert Enum.empty?(Bookkeeping.Public.list_accounts(["wallet"]))
assert Enum.empty?(Bookkeeping.Public.list_entries({:wallet, "fake_currency", student.id}))
end
test "payout_participant/2 Multiple transactions of two students (via signals)", %{
- budget: budget
+ budget: budget,
+ user: user
} do
student1 = Factories.insert!(:member, %{student: true})
student2 = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew1} = assignment1} = create_campaign(:accepted, budget)
- %{promotable_assignment: %{crew: crew2} = assignment2} = create_campaign(:accepted, budget)
+ %{promotable_assignment: %{crew: crew1} = assignment1} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
- task1 = create_task(student1, crew1, :pending, false, -31)
- task2 = create_task(student1, crew2, :pending, false, -31)
- task3 = create_task(student2, crew1, :pending, false, -31)
- _task4 = create_task(student2, crew2, :pending, false, -31)
+ %{promotable_assignment: %{crew: crew2} = assignment2} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
+
+ task1 = Campaign.Factories.create_task(["task1"], student1, crew1, :pending, false, 31)
+ task2 = Campaign.Factories.create_task(["task2"], student1, crew2, :pending, false, 31)
+ task3 = Campaign.Factories.create_task(["task3"], student2, crew1, :pending, false, 31)
+ _task4 = Campaign.Factories.create_task(["task4"], student2, crew2, :pending, false, 31)
Budget.Factories.create_reward(assignment1, student1, budget)
Budget.Factories.create_reward(assignment2, student1, budget)
@@ -287,12 +340,13 @@ defmodule Systems.Campaign.PublicTest do
Bookkeeping.Public.balance({:wallet, "fake_currency", student2.id})
end
- test "sync_student_credits/0 One transaction of one student", %{budget: budget} do
+ test "sync_student_credits/0 One transaction of one student", %{budget: budget, user: user} do
student = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew} = assignment} = create_campaign(:accepted, budget)
+ %{promotable_assignment: %{crew: crew} = assignment} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
- create_task(student, crew, :accepted, false, -31)
+ Campaign.Factories.create_task(["task1"], student, crew, :accepted, false, 31)
Budget.Factories.create_reward(assignment, student, budget)
Campaign.Public.sync_student_credits()
@@ -312,13 +366,15 @@ defmodule Systems.Campaign.PublicTest do
end
test "sync_student_credits/0 One transaction of one student (sync twice -> no error)", %{
- budget: budget
+ budget: budget,
+ user: user
} do
student = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew} = assignment} = create_campaign(:accepted, budget)
+ %{promotable_assignment: %{crew: crew} = assignment} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
- create_task(student, crew, :accepted, false, -31)
+ Campaign.Factories.create_task(["task1"], student, crew, :accepted, false, 31)
Budget.Factories.create_reward(assignment, student, budget)
Campaign.Public.sync_student_credits()
@@ -338,17 +394,20 @@ defmodule Systems.Campaign.PublicTest do
Bookkeeping.Public.balance({:wallet, "fake_currency", student.id})
end
- test "sync_student_credits/0 Two transactions of two students", %{budget: budget} do
+ test "sync_student_credits/0 Two transactions of two students", %{budget: budget, user: user} do
student1 = Factories.insert!(:member, %{student: true})
student2 = Factories.insert!(:member, %{student: true})
- %{promotable_assignment: %{crew: crew1} = assignment1} = create_campaign(:accepted, budget)
- %{promotable_assignment: %{crew: crew2} = assignment2} = create_campaign(:accepted, budget)
+ %{promotable_assignment: %{crew: crew1} = assignment1} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
+
+ %{promotable_assignment: %{crew: crew2} = assignment2} =
+ Campaign.Factories.create_campaign(user, :accepted, 1, budget)
- create_task(student1, crew1, :accepted, false, -31)
- create_task(student1, crew2, :accepted, false, -31)
- create_task(student2, crew1, :accepted, false, -31)
- create_task(student2, crew2, :accepted, false, -31)
+ Campaign.Factories.create_task(["task1"], student1, crew1, :accepted, false, 31)
+ Campaign.Factories.create_task(["task2"], student1, crew2, :accepted, false, 31)
+ Campaign.Factories.create_task(["task3"], student2, crew1, :accepted, false, 31)
+ Campaign.Factories.create_task(["task4"], student2, crew2, :accepted, false, 31)
Budget.Factories.create_reward(assignment1, student1, budget)
Budget.Factories.create_reward(assignment2, student1, budget)
@@ -377,88 +436,16 @@ defmodule Systems.Campaign.PublicTest do
Bookkeeping.Public.balance({:wallet, "fake_currency", student2.id})
end
- defp create_campaign(status, budget, schedule_start \\ nil, schedule_end \\ nil) do
- promotion = Factories.insert!(:promotion)
-
- pool = Factories.insert!(:pool, %{name: "test_pool", director: :citizen})
-
- submission =
- Factories.insert!(:submission, %{
- pool: pool,
- reward_value: 2,
- status: status,
- schedule_start: schedule_start,
- schedule_end: schedule_end,
- director: :campaign
- })
-
- crew = Factories.insert!(:crew)
- survey_tool = Factories.insert!(:survey_tool)
-
- experiment =
- Factories.insert!(:experiment, %{
- survey_tool: survey_tool,
- duration: "10",
- subject_count: 1
- })
-
- assignment =
- Factories.insert!(:assignment, %{
- budget: budget,
- experiment: experiment,
- crew: crew,
- director: :campaign
- })
-
- Factories.insert!(:campaign, %{
- assignment: assignment,
- promotion: promotion,
- submissions: [submission]
- })
- end
-
- defp create_task(crew, status, expired, minutes_ago) when is_boolean(expired) do
- Factories.insert!(:member, %{student: true})
- |> create_task(crew, status, expired, minutes_ago)
- end
-
- defp create_task(user, crew, status, expired, minutes_ago) when is_boolean(expired) do
- updated_at = naive_timestamp(minutes_ago)
- expire_at = naive_timestamp(-1)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
-
- _task =
- Factories.insert!(:crew_task, %{
- crew: crew,
- member: member,
- status: status,
- expired: expired,
- expire_at: expire_at,
- updated_at: updated_at
- })
- end
-
defp yesterday() do
- timestamp(-24 * 60)
+ Campaign.Factories.timestamp(-24 * 60)
end
defp tomorrow() do
- timestamp(24 * 60)
+ Campaign.Factories.timestamp(24 * 60)
end
defp next_week() do
- timestamp(7 * 24 * 60)
- end
-
- defp timestamp(shift_minutes) do
- Timestamp.now()
- |> Timestamp.shift_minutes(shift_minutes)
- end
-
- defp naive_timestamp(shift_minutes) do
- timestamp(shift_minutes)
- |> DateTime.to_naive()
- |> NaiveDateTime.truncate(:second)
+ Campaign.Factories.timestamp(7 * 24 * 60)
end
end
end
diff --git a/core/test/systems/campaign/factories.ex b/core/test/systems/campaign/factories.ex
new file mode 100644
index 000000000..de1a4c583
--- /dev/null
+++ b/core/test/systems/campaign/factories.ex
@@ -0,0 +1,94 @@
+defmodule Systems.Campaign.Factories do
+ alias CoreWeb.UI.Timestamp
+
+ alias Systems.{
+ Assignment,
+ Crew
+ }
+
+ alias Core.Factories
+ alias Core.Authorization
+
+ def create_campaign(
+ researcher,
+ status,
+ subject_count \\ 1,
+ budget \\ nil,
+ schedule_start \\ nil,
+ schedule_end \\ nil
+ ) do
+ promotion = Factories.insert!(:promotion)
+
+ pool = Factories.insert!(:pool, %{name: "test_pool", director: :citizen})
+
+ submission =
+ Factories.insert!(:submission, %{
+ pool: pool,
+ reward_value: 2,
+ status: status,
+ schedule_start: schedule_start,
+ schedule_end: schedule_end
+ })
+
+ campaign_auth_node = Factories.insert!(:auth_node)
+ assignment_auth_node = Factories.insert!(:auth_node, %{parent: campaign_auth_node})
+ tool_auth_node = Factories.insert!(:auth_node, %{parent: assignment_auth_node})
+
+ tool = Assignment.Factories.create_tool(tool_auth_node)
+ tool_ref = Assignment.Factories.create_tool_ref(tool)
+ workflow = Assignment.Factories.create_workflow()
+ _workflow_item = Assignment.Factories.create_workflow_item(workflow, tool_ref)
+ info = Assignment.Factories.create_info("10", subject_count)
+
+ assignment =
+ Assignment.Factories.create_assignment(
+ info,
+ workflow,
+ assignment_auth_node,
+ budget,
+ :campaign
+ )
+
+ campaign =
+ Factories.insert!(:campaign, %{
+ assignment: assignment,
+ promotion: promotion,
+ submissions: [submission],
+ auth_node: campaign_auth_node
+ })
+
+ :ok = Authorization.assign_role(researcher, campaign, :owner)
+
+ campaign
+ end
+
+ def create_task(identifier, crew, status, expired, minutes_ago) when is_boolean(expired) do
+ user = Core.Factories.insert!(:member, %{student: true})
+ create_task(identifier, user, crew, status, expired, minutes_ago)
+ end
+
+ def create_task(identifier, user, crew, status, expired, minutes_ago)
+ when is_boolean(expired) do
+ expire_at = naive_timestamp(-1)
+
+ member = Crew.Factories.create_member(crew, user)
+
+ Crew.Factories.create_task(crew, member, identifier,
+ status: status,
+ expired: expired,
+ expire_at: expire_at,
+ minutes_ago: minutes_ago
+ )
+ end
+
+ def timestamp(shift_minutes) do
+ Timestamp.now()
+ |> Timestamp.shift_minutes(shift_minutes)
+ end
+
+ def naive_timestamp(shift_minutes) do
+ timestamp(shift_minutes)
+ |> DateTime.to_naive()
+ |> NaiveDateTime.truncate(:second)
+ end
+end
diff --git a/core/test/systems/campaign/model_test.exs b/core/test/systems/campaign/model_test.exs
index 0ac5c1c57..cdf462504 100644
--- a/core/test/systems/campaign/model_test.exs
+++ b/core/test/systems/campaign/model_test.exs
@@ -51,7 +51,7 @@ defmodule Systems.Campaign.ModelTest do
assert {:ok, %Campaign.Model{}} =
Campaign.Public.create(promotion, assignment, [submission], researcher, auth_node)
- assert_signal_dispatched(:campaign_created)
+ assert_signal_dispatched({:campaign, :created})
end
test "delete/1 deletes the campaign" do
diff --git a/core/test/systems/campaign/monitor_view_test.exs b/core/test/systems/campaign/monitor_view_test.exs
index f8ee89fa2..67643e952 100644
--- a/core/test/systems/campaign/monitor_view_test.exs
+++ b/core/test/systems/campaign/monitor_view_test.exs
@@ -1,25 +1,22 @@
defmodule Systems.Campaign.MonitorViewTest do
- use CoreWeb.ConnCase, async: false
+ use CoreWeb.ConnCase, async: true
import Phoenix.ConnTest
import Phoenix.LiveViewTest
- import Frameworks.Signal.TestHelper
import ExUnit.Assertions
- alias Core.Authorization
alias CoreWeb.UI.Timestamp
alias Systems.{
Campaign,
- Crew,
- Budget
+ Crew
}
describe "show content page for campaign" do
setup [:login_as_researcher]
test "No member applied", %{conn: %{assigns: %{current_user: user}} = conn} do
- %{id: id} = create_campaign(user, :accepted, 1)
+ %{id: id} = Campaign.Factories.create_campaign(user, :accepted, 1)
{:ok, _view, html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
@@ -31,14 +28,19 @@ defmodule Systems.Campaign.MonitorViewTest do
test "Member applied but expired and not completed", %{
conn: %{assigns: %{current_user: user}} = conn
} do
- %{id: id, promotable_assignment: %{crew: crew}} = create_campaign(user, :accepted, 1)
+ %{id: id, promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 1)
- {:ok, %{task: task}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{crew_task: task}} = Crew.Public.apply_member(crew, user, ["task1"])
- Crew.Public.update_task(task, %{
- started_at: Timestamp.naive_from_now(-60),
- expire_at: Timestamp.naive_from_now(-31)
- })
+ Crew.Public.update_task(
+ task,
+ %{
+ started_at: Timestamp.naive_from_now(-60),
+ expire_at: Timestamp.naive_from_now(-31)
+ },
+ :locked
+ )
{:ok, _view, html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
@@ -55,14 +57,19 @@ defmodule Systems.Campaign.MonitorViewTest do
test "Member applied but expired and not completed: reject -> dialog", %{
conn: %{assigns: %{current_user: user}} = conn
} do
- %{id: id, promotable_assignment: %{crew: crew}} = create_campaign(user, :accepted, 1)
+ %{id: id, promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 1)
- {:ok, %{task: task}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{crew_task: task}} = Crew.Public.apply_member(crew, user, ["task1"])
- Crew.Public.update_task(task, %{
- started_at: Timestamp.naive_from_now(-60),
- expire_at: Timestamp.naive_from_now(-31)
- })
+ Crew.Public.update_task(
+ task,
+ %{
+ started_at: Timestamp.naive_from_now(-60),
+ expire_at: Timestamp.naive_from_now(-31)
+ },
+ :locked
+ )
{:ok, view, _html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
@@ -78,89 +85,119 @@ defmodule Systems.Campaign.MonitorViewTest do
test "Member applied but expired and not completed: accept ", %{
conn: %{assigns: %{current_user: user}} = conn
} do
- %{id: id, promotable_assignment: %{crew: crew}} = create_campaign(user, :accepted, 1)
+ %{id: id, promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 1)
- {:ok, %{task: task}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{crew_task: task}} = Crew.Public.apply_member(crew, user, ["task1"])
- Crew.Public.update_task(task, %{
- started_at: Timestamp.naive_from_now(-60),
- expire_at: Timestamp.naive_from_now(-31)
- })
+ Crew.Public.update_task(
+ task,
+ %{
+ started_at: Timestamp.naive_from_now(-60),
+ expire_at: Timestamp.naive_from_now(-31)
+ },
+ :locked
+ )
- {:ok, view, _html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
+ {:ok, view, html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
+
+ html =~ "Goedgekeurd0"
- _html =
+ html =
view
|> element("[phx-click=\"accept\"]")
|> render_click()
- assert_signals_dispatched(:crew_task_updated, 1)
+ html =~ "Goedgekeurd1"
end
test "Member applied but expired and not completed: accept_all", %{
conn: %{assigns: %{current_user: user}} = conn
} do
- %{id: id, promotable_assignment: %{crew: crew}} = create_campaign(user, :accepted, 2)
+ %{id: id, promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 2)
- {:ok, %{task: task}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{crew_task: task}} = Crew.Public.apply_member(crew, user, ["task1"])
- Crew.Public.update_task(task, %{
- started_at: Timestamp.naive_from_now(-60),
- expire_at: Timestamp.naive_from_now(-31)
- })
+ Crew.Public.update_task(
+ task,
+ %{
+ started_at: Timestamp.naive_from_now(-60),
+ expire_at: Timestamp.naive_from_now(-31)
+ },
+ :locked
+ )
user2 = Factories.insert!(:member)
- {:ok, %{task: task2}} = Crew.Public.apply_member(crew, user2)
+ {:ok, %{crew_task: task2}} = Crew.Public.apply_member(crew, user2, ["task2"])
- Crew.Public.update_task(task2, %{
- started_at: Timestamp.naive_from_now(-60),
- expire_at: Timestamp.naive_from_now(-31)
- })
+ Crew.Public.update_task(
+ task2,
+ %{
+ started_at: Timestamp.naive_from_now(-60),
+ expire_at: Timestamp.naive_from_now(-31)
+ },
+ :locked
+ )
- {:ok, view, _html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
+ {:ok, view, html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
+
+ html =~ "Goedgekeurd0"
- _html =
+ html =
view
|> element("[phx-click=\"accept_all_pending_started\"]")
|> render_click()
- assert_signals_dispatched(:crew_task_updated, 2)
+ html =~ "Goedgekeurd2"
end
test "Member completed: accept_all", %{
conn: %{assigns: %{current_user: user}} = conn
} do
- %{id: id, promotable_assignment: %{crew: crew}} = create_campaign(user, :accepted, 2)
+ %{id: id, promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 2)
- {:ok, %{task: task}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{crew_task: task}} = Crew.Public.apply_member(crew, user, ["task1"])
- Crew.Public.update_task(task, %{
- status: :completed,
- completed_at: Timestamp.naive_now()
- })
+ Crew.Public.update_task(
+ task,
+ %{
+ status: :completed,
+ completed_at: Timestamp.naive_now()
+ },
+ :locked
+ )
user2 = Factories.insert!(:member)
- {:ok, %{task: task2}} = Crew.Public.apply_member(crew, user2)
+ {:ok, %{crew_task: task2}} = Crew.Public.apply_member(crew, user2, ["task2"])
- Crew.Public.update_task(task2, %{
- status: :completed,
- completed_at: Timestamp.naive_now()
- })
+ Crew.Public.update_task(
+ task2,
+ %{
+ status: :completed,
+ completed_at: Timestamp.naive_now()
+ },
+ :locked
+ )
- {:ok, view, _html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
+ {:ok, view, html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
+
+ html =~ "Goedgekeurd0"
- _html =
+ html =
view
|> element("[phx-click=\"accept_all_completed\"]")
|> render_click()
- assert_signals_dispatched(:crew_task_updated, 2)
+ html =~ "Goedgekeurd2"
end
test "Member applied and completed", %{conn: %{assigns: %{current_user: user}} = conn} do
- %{id: id, promotable_assignment: %{crew: crew}} = create_campaign(user, :accepted, 1)
+ %{id: id, promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 1)
- {:ok, %{task: task}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{crew_task: task}} = Crew.Public.apply_member(crew, user, ["task1"])
Crew.Public.activate_task(task)
{:ok, _view, html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
@@ -171,9 +208,10 @@ defmodule Systems.Campaign.MonitorViewTest do
end
test "Member applied", %{conn: %{assigns: %{current_user: user}} = conn} do
- %{id: id, promotable_assignment: %{crew: crew}} = create_campaign(user, :accepted, 1)
+ %{id: id, promotable_assignment: %{crew: crew}} =
+ Campaign.Factories.create_campaign(user, :accepted, 1)
- {:ok, _} = Crew.Public.apply_member(crew, user)
+ {:ok, _} = Crew.Public.apply_member(crew, user, ["task1"])
{:ok, _view, html} = live(conn, Routes.live_path(conn, Campaign.ContentPage, id))
@@ -182,50 +220,4 @@ defmodule Systems.Campaign.MonitorViewTest do
assert html =~ "Vrij: 0"
end
end
-
- defp create_campaign(
- researcher,
- status,
- subject_count,
- schedule_start \\ nil,
- schedule_end \\ nil
- ) do
- currency = Budget.Factories.create_currency("test_1234", :legal, "ƒ", 2)
- budget = Budget.Factories.create_budget("test_1234", currency)
- pool = Factories.insert!(:pool, %{name: "test_1234", director: :citizen, currency: currency})
-
- promotion = Factories.insert!(:promotion)
-
- submission =
- Factories.insert!(:submission, %{
- pool: pool,
- status: status,
- schedule_start: schedule_start,
- schedule_end: schedule_end
- })
-
- crew = Factories.insert!(:crew)
- survey_tool = Factories.insert!(:survey_tool)
-
- experiment =
- Factories.insert!(:experiment, %{
- survey_tool: survey_tool,
- duration: "10",
- subject_count: subject_count
- })
-
- assignment =
- Factories.insert!(:assignment, %{budget: budget, experiment: experiment, crew: crew})
-
- campaign =
- Factories.insert!(:campaign, %{
- assignment: assignment,
- promotion: promotion,
- submissions: [submission]
- })
-
- :ok = Authorization.assign_role(researcher, campaign, :owner)
-
- campaign
- end
end
diff --git a/core/test/systems/pool/switch_test.exs b/core/test/systems/campaign/switch_test.exs
similarity index 86%
rename from core/test/systems/pool/switch_test.exs
rename to core/test/systems/campaign/switch_test.exs
index 1dc4bb0d6..a2ef2119c 100644
--- a/core/test/systems/pool/switch_test.exs
+++ b/core/test/systems/campaign/switch_test.exs
@@ -1,11 +1,11 @@
-defmodule Systems.Pool.SwitchTest do
+defmodule Systems.Campaign.SwitchTest do
use Core.DataCase, async: true
alias Core.Factories
alias Core.Authorization
alias Ecto.Changeset
alias Core.Accounts.User
- alias Systems.Pool.Switch
+ alias Systems.Campaign.Switch
setup do
{:ok,
@@ -20,7 +20,7 @@ defmodule Systems.Pool.SwitchTest do
campaign: campaign,
coordinator: coordinator
} do
- Switch.dispatch(:campaign_created, %{
+ Switch.intercept({:campaign, :created}, %{
campaign: campaign
})
@@ -35,7 +35,7 @@ defmodule Systems.Pool.SwitchTest do
campaign: campaign,
coordinator: coordinator
} do
- Switch.dispatch(:user_profile_updated, %{
+ Switch.intercept({:user_profile, :updated}, %{
user: coordinator,
user_changeset: Changeset.cast(%User{}, %{coordinator: true}, [:coordinator])
})
@@ -50,7 +50,7 @@ defmodule Systems.Pool.SwitchTest do
} do
student = Factories.insert!(:member, %{student: true})
- Switch.dispatch(:user_profile_updated, %{
+ Switch.intercept({:user_profile, :updated}, %{
user: student,
user_changeset: Changeset.cast(%User{}, %{coordinator: false}, [:coordinator])
})
@@ -64,7 +64,7 @@ defmodule Systems.Pool.SwitchTest do
user = Factories.insert!(:member)
Authorization.assign_role(user, campaign, :coordinator)
- Switch.dispatch(:user_profile_updated, %{
+ Switch.intercept({:user_profile, :updated}, %{
user: user,
user_changeset: Changeset.cast(%User{}, %{coordinator: false}, [:coordinator])
})
diff --git a/core/test/systems/campaign/view_model_builder.exs b/core/test/systems/campaign/view_model_builder.exs
index 1e5cdaf01..2570de5b2 100644
--- a/core/test/systems/campaign/view_model_builder.exs
+++ b/core/test/systems/campaign/view_model_builder.exs
@@ -15,8 +15,13 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
setup do
user = Factories.insert!(:member)
- survey_tool = Factories.insert!(:survey_tool, %{survey_url: "https://eyra.co/fake_survey"})
- assignment = Factories.insert!(:assignment, %{survey_tool: survey_tool})
+
+ alliance_tool =
+ Factories.insert!(:alliance_tool, %{
+ url: "https://eyra.co/fake_alliance"
+ })
+
+ assignment = Factories.insert!(:assignment, %{alliance_tool: alliance_tool})
promotion =
Factories.insert!(:promotion, %{
@@ -34,7 +39,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
})
campaign =
- Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
+ Campaign.Public.get!(id, Campaign.Model.preload_graph(:down))
|> Campaign.Public.flatten()
{:ok, campaign: campaign, user: user}
@@ -42,14 +47,14 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
test "With applied member", %{campaign: campaign, user: user} do
{:ok, %{member: member}} =
- Crew.Public.apply_member(campaign.promotable_assignment.crew, user)
+ Crew.Public.apply_member(campaign.promotable_assignment.crew, user, ["task1"])
view_model = Campaign.Builders.AssignmentLandingPage.view_model(campaign, user)
assert %{
call_to_action: %{
label: "Naar vragenlijst",
- path: "https://eyra.co/fake_survey?panl_id=1",
+ path: "https://eyra.co/fake_alliance?panl_id=1",
target: %{type: :event, value: "open"}
},
hero_title: "Online Studie",
@@ -65,8 +70,8 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
end
test "With started member", %{campaign: campaign, user: user} do
- member = Crew.Public.apply_member(campaign.promotable_assignment.crew, user)
- task = Crew.Public.get_task(campaign.promotable_assignment.crew, member)
+ member = Crew.Public.apply_member(campaign.promotable_assignment.crew, user, ["task1"])
+ [task] = Crew.Public.list_tasks_for_user(campaign.promotable_assignment.crew, user)
Crew.Public.lock_task(task)
view_model = Campaign.Builders.AssignmentLandingPage.view_model(campaign, user)
@@ -74,7 +79,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
assert %{
call_to_action: %{
label: "Naar vragenlijst",
- path: "https://eyra.co/fake_survey?panl_id=1",
+ path: "https://eyra.co/fake_alliance?panl_id=1",
target: %{type: :event, value: "open"}
},
hero_title: "Online Studie",
@@ -93,9 +98,9 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
test "With finished member", %{campaign: campaign, user: user} do
{:ok, %{member: member}} =
- Crew.Public.apply_member(campaign.promotable_assignment.crew, user)
+ Crew.Public.apply_member(campaign.promotable_assignment.crew, user, ["task1"])
- task = Crew.Public.get_task(campaign.promotable_assignment.crew, member)
+ [task] = Crew.Public.list_tasks_for_user(campaign.promotable_assignment.crew, user)
Crew.Public.lock_task(task)
Crew.Public.activate_task(task)
@@ -104,7 +109,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
assert %{
call_to_action: %{
label: "Naar vragenlijst",
- path: "https://eyra.co/fake_survey?panl_id=1",
+ path: "https://eyra.co/fake_alliance?panl_id=1",
target: %{type: :event, value: "open"}
},
hero_title: "Online Studie",
@@ -129,7 +134,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
%{id: id, promotion: promotion} = Factories.insert!(:campaign, %{submissions: [submission]})
- campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
+ campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:down))
view_model = Campaign.Builders.AssignmentLandingPage.view_model(campaign, user)
assert %{
@@ -155,11 +160,11 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
setup do
user = Factories.insert!(:member)
- survey_tool =
+ alliance_tool =
Factories.insert!(
- :survey_tool,
+ :alliance_tool,
%{
- survey_url: "https://eyra.co/fake_survey",
+ url: "https://eyra.co/fake_alliance",
subject_count: 10,
duration: "10",
language: "en",
@@ -167,7 +172,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
}
)
- assignment = Factories.insert!(:assignment, %{survey_tool: survey_tool})
+ assignment = Factories.insert!(:assignment, %{alliance_tool: alliance_tool})
promotion =
Factories.insert!(
@@ -195,7 +200,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
submission: [submission]
})
- campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:full))
+ campaign = Campaign.Public.get!(id, Campaign.Model.preload_graph(:down))
{:ok, campaign: campaign, user: user, author: author}
end
@@ -238,7 +243,7 @@ defmodule Systems.Campaign.ViewModelBuilderTest do
user2 = Factories.insert!(:member)
{:ok, %{member: member}} =
- Crew.Public.apply_member(campaign.promotable_assignment.crew, user2)
+ Crew.Public.apply_member(campaign.promotable_assignment.crew, user2, ["task2"])
view_model = Campaign.Builders.PromotionLandingPage.view_model(campaign, user)
diff --git a/core/test/systems/campaign/x.html b/core/test/systems/campaign/x.html
new file mode 100644
index 000000000..7f6ee7b31
--- /dev/null
+++ b/core/test/systems/campaign/x.html
@@ -0,0 +1,201 @@
+
+ >
+
+
+
\ No newline at end of file
diff --git a/core/test/systems/crew/_public_test.exs b/core/test/systems/crew/_public_test.exs
index 3c7569919..272baf5d3 100644
--- a/core/test/systems/crew/_public_test.exs
+++ b/core/test/systems/crew/_public_test.exs
@@ -2,13 +2,14 @@ defmodule Systems.Crew.PublicTest do
use Core.DataCase
alias Core.Authorization
alias CoreWeb.UI.Timestamp
+ alias Frameworks.GreenLight
describe "crews" do
alias Systems.Crew
test "list/0 returns all created crews with preloaded references" do
- {:ok, crew1} = Crew.Public.create(Core.Authorization.make_node())
- {:ok, crew2} = Crew.Public.create(Core.Authorization.make_node())
+ {:ok, crew1} = Crew.Public.prepare(Core.Authorization.prepare_node()) |> Core.Repo.insert()
+ {:ok, crew2} = Crew.Public.prepare(Core.Authorization.prepare_node()) |> Core.Repo.insert()
list = Crew.Public.list()
assert list |> Enum.find(&(&1.id == crew1.id))
assert list |> Enum.find(&(&1.id == crew2.id))
@@ -18,7 +19,7 @@ defmodule Systems.Crew.PublicTest do
end
test "get/1 returns crew with preloaded references" do
- {:ok, crew} = Crew.Public.create(Core.Authorization.make_node())
+ {:ok, crew} = Crew.Public.prepare(Core.Authorization.prepare_node()) |> Core.Repo.insert()
crew = Crew.Public.get!(crew.id)
assert crew.tasks == []
@@ -62,17 +63,25 @@ defmodule Systems.Crew.PublicTest do
end
test "apply_member/2 creates member + pending task" do
- user = Factories.insert!(:member)
+ %{id: user_id} = user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: %{id: member_id}, task: task}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{member: %{user_id: ^user_id}, crew_task: task}} =
+ Crew.Public.apply_member(crew, user, ["task1"])
assert %{
status: :pending,
expired: nil,
started_at: nil,
completed_at: nil,
- member_id: ^member_id
+ auth_node: %Core.Authorization.Node{
+ role_assignments: [
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id,
+ role: :owner
+ }
+ ]
+ }
} = task
end
@@ -80,7 +89,7 @@ defmodule Systems.Crew.PublicTest do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: member}} = Crew.Public.apply_member(crew, user)
+ {:ok, %{member: member}} = Crew.Public.apply_member(crew, user, ["task1"])
assert member.crew_id == crew.id
assert member.user_id == user.id
@@ -95,11 +104,11 @@ defmodule Systems.Crew.PublicTest do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: member1}} = Crew.Public.apply_member(crew, user, expire_at(-1))
+ {:ok, %{member: member1}} = Crew.Public.apply_member(crew, user, ["task1"], expire_at(-1))
Crew.Public.mark_expired()
- {:ok, %{member: member2}} = Crew.Public.apply_member(crew, user, expire_at(1))
+ {:ok, %{member: member2}} = Crew.Public.apply_member(crew, user, ["task1"], expire_at(1))
assert member1.id == member2.id
assert %{expired: false} = member2
@@ -107,10 +116,12 @@ defmodule Systems.Crew.PublicTest do
test "list_members/1 lists only members from that crew" do
user = Factories.insert!(:member)
+
crew1 = Factories.insert!(:crew)
crew2 = Factories.insert!(:crew)
- {:ok, %{member: member1}} = Crew.Public.apply_member(crew1, user)
- {:ok, %{member: member2}} = Crew.Public.apply_member(crew2, user)
+
+ {:ok, %{member: member1}} = Crew.Public.apply_member(crew1, user, ["task1"])
+ {:ok, %{member: member2}} = Crew.Public.apply_member(crew2, user, ["task2"])
list1 = Crew.Public.list_members(crew1)
list2 = Crew.Public.list_members(crew2)
@@ -128,7 +139,8 @@ defmodule Systems.Crew.PublicTest do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: member, task: task}} = Crew.Public.apply_member(crew, user, nil)
+ {:ok, %{member: member, crew_task: task}} =
+ Crew.Public.apply_member(crew, user, ["task1"], nil)
assert Crew.Public.mark_expired()
@@ -140,7 +152,8 @@ defmodule Systems.Crew.PublicTest do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: member, task: task}} = Crew.Public.apply_member(crew, user, expire_at(1))
+ {:ok, %{member: member, crew_task: task}} =
+ Crew.Public.apply_member(crew, user, ["task1"], expire_at(1))
assert Crew.Public.mark_expired()
@@ -152,7 +165,9 @@ defmodule Systems.Crew.PublicTest do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: member, task: task}} = Crew.Public.apply_member(crew, user, expire_at(-1))
+ {:ok, %{member: member, crew_task: task}} =
+ Crew.Public.apply_member(crew, user, ["task1"], expire_at(-1))
+
Crew.Public.lock_task(task)
assert Crew.Public.mark_expired()
@@ -167,7 +182,8 @@ defmodule Systems.Crew.PublicTest do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: member, task: task}} = Crew.Public.apply_member(crew, user, expire_at(-1))
+ {:ok, %{member: member, crew_task: task}} =
+ Crew.Public.apply_member(crew, user, ["task1"], expire_at(-1))
assert Crew.Public.mark_expired()
@@ -182,10 +198,11 @@ defmodule Systems.Crew.PublicTest do
user2 = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: member1, task: task1}} =
- Crew.Public.apply_member(crew, user1, expire_at(-1))
+ {:ok, %{member: member1, crew_task: task1}} =
+ Crew.Public.apply_member(crew, user1, ["task1"], expire_at(-1))
- {:ok, %{member: member2, task: task2}} = Crew.Public.apply_member(crew, user2, expire_at(1))
+ {:ok, %{member: member2, crew_task: task2}} =
+ Crew.Public.apply_member(crew, user2, ["task2"], expire_at(1))
assert Crew.Public.mark_expired()
@@ -202,8 +219,8 @@ defmodule Systems.Crew.PublicTest do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
- {:ok, %{member: %{id: member_id} = member, task: task}} =
- Crew.Public.apply_member(crew, user, expire_at(-1))
+ {:ok, %{member: %{id: member_id} = member, crew_task: task}} =
+ Crew.Public.apply_member(crew, user, ["task1"], expire_at(-1))
assert Crew.Public.member?(crew, user)
assert [%{id: ^member_id}] = Crew.Public.list_members(crew)
@@ -223,82 +240,313 @@ defmodule Systems.Crew.PublicTest do
alias Core.Factories
test "create_task/2 returns valid task" do
- user = Factories.insert!(:member)
- crew = Factories.insert!(:crew)
+ %{id: user_id} = user = Factories.insert!(:member)
+ %{id: crew_id} = crew = Factories.insert!(:crew)
member = Factories.insert!(:crew_member, %{crew: crew, user: user})
- {:ok, task} = Crew.Public.create_task(crew, member, nil)
+ {:ok, task} = Crew.Public.create_task(crew, member, ["task1"], nil)
+
+ assert %{
+ crew_id: ^crew_id,
+ auth_node: %Core.Authorization.Node{
+ role_assignments: [
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id,
+ role: :owner
+ }
+ ]
+ }
+ } = task
+ end
+
+ test "list_tasks/1 returns one task for the crew" do
+ %{id: crew_id} = crew = Factories.insert!(:crew)
+
+ user1 = Factories.insert!(:member)
+ user2 = Factories.insert!(:member)
- assert task.crew_id == crew.id
- assert task.member_id == member.id
+ member1 = Factories.insert!(:crew_member, %{crew: crew, user: user1})
+ member2 = Factories.insert!(:crew_member, %{crew: crew, user: user2})
- list = Crew.Public.list_members_without_task(crew)
- assert is_nil(list |> Enum.find(&(&1.id == member.id)))
+ {:ok, %{auth_node_id: auth_node_id}} =
+ Crew.Public.create_task(crew, [member1, member2], ["task1"], nil)
+
+ list = Crew.Public.list_tasks(crew)
+
+ assert [
+ %Systems.Crew.TaskModel{
+ identifier: ["task1"],
+ status: :pending,
+ started_at: nil,
+ completed_at: nil,
+ accepted_at: nil,
+ rejected_at: nil,
+ expire_at: nil,
+ expired: false,
+ rejected_category: nil,
+ rejected_message: nil,
+ crew_id: ^crew_id,
+ auth_node_id: ^auth_node_id
+ }
+ ] = list
end
- test "list_tasks/2 returns all available tasks for the crew" do
- user = Factories.insert!(:member)
+ test "list_tasks/1 returns 3 tasks for the crew, latest first" do
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
- {:ok, _task} = Crew.Public.create_task(crew, member, nil)
+ user1 = Factories.insert!(:member)
+ user2 = Factories.insert!(:member)
+
+ member1 = Factories.insert!(:crew_member, %{crew: crew, user: user1})
+ member2 = Factories.insert!(:crew_member, %{crew: crew, user: user2})
+
+ {:ok, _} = Crew.Public.create_task(crew, [member1], ["task1"], nil)
+ {:ok, _} = Crew.Public.create_task(crew, [member1, member2], ["task2"], nil)
+ {:ok, _} = Crew.Public.create_task(crew, [member2], ["task3"], nil)
list = Crew.Public.list_tasks(crew)
- assert list |> Enum.find(&(&1.member_id == member.id))
+
+ assert [
+ %Systems.Crew.TaskModel{identifier: ["task3"]},
+ %Systems.Crew.TaskModel{identifier: ["task2"]},
+ %Systems.Crew.TaskModel{identifier: ["task1"]}
+ ] = list
+ end
+
+ test "list_tasks_for_user/2 returns tasks for one member, latest first" do
+ crew = Factories.insert!(:crew)
+
+ user1 = Factories.insert!(:member)
+ user2 = Factories.insert!(:member)
+
+ member1 = Factories.insert!(:crew_member, %{crew: crew, user: user1})
+ member2 = Factories.insert!(:crew_member, %{crew: crew, user: user2})
+
+ {:ok, _} = Crew.Public.create_task(crew, [member1], ["task1"], nil)
+ {:ok, _} = Crew.Public.create_task(crew, [member1, member2], ["task2"], nil)
+ {:ok, _} = Crew.Public.create_task(crew, [member2], ["task3"], nil)
+
+ assert [
+ %Systems.Crew.TaskModel{identifier: ["task2"]},
+ %Systems.Crew.TaskModel{identifier: ["task1"]}
+ ] = Crew.Public.list_tasks_for_user(crew, member1)
+
+ assert [
+ %Systems.Crew.TaskModel{identifier: ["task3"]},
+ %Systems.Crew.TaskModel{identifier: ["task2"]}
+ ] = Crew.Public.list_tasks_for_user(crew, member2)
end
test "count_tasks/2 returns correct nr of tasks in the crew" do
user = Factories.insert!(:member)
+ auth_node = auth_node_with_owner(user)
+
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+ _member = Factories.insert!(:crew_member, %{crew: crew, user: user})
assert Crew.Public.count_tasks(crew, [:pending, :completed]) == 0
- _task = Factories.insert!(:crew_task, %{crew: crew, member: member, status: :pending})
+ _task =
+ Factories.insert!(:crew_task, %{
+ identifier: ["task1"],
+ crew: crew,
+ auth_node: auth_node,
+ status: :pending
+ })
assert Crew.Public.count_tasks(crew, [:pending]) == 1
assert Crew.Public.count_tasks(crew, [:completed]) == 0
- _task = Factories.insert!(:crew_task, %{crew: crew, member: member, status: :completed})
+ _task =
+ Factories.insert!(:crew_task, %{
+ identifier: ["task2"],
+ crew: crew,
+ auth_node: auth_node,
+ status: :completed
+ })
+
assert Crew.Public.count_tasks(crew, [:pending]) == 1
assert Crew.Public.count_tasks(crew, [:completed]) == 1
end
test "create_task/2 succeeds for member" do
+ %{id: user_id} = user = Factories.insert!(:member)
+ crew = Factories.insert!(:crew)
+ member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+
+ assert %{
+ status: :pending,
+ auth_node: %{
+ role_assignments: [%{principal_id: ^user_id}]
+ }
+ } = Crew.Public.create_task!(crew, member, ["task1"], nil)
+ end
+
+ test "create_task/2 multiple tasks succeeds for member" do
+ %{id: user_id} = user = Factories.insert!(:member)
+ crew = Factories.insert!(:crew)
+ member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+
+ assert %{
+ status: :pending,
+ auth_node: %{
+ role_assignments: [%{principal_id: ^user_id}]
+ }
+ } = Crew.Public.create_task!(crew, member, ["task1"], nil)
+
+ assert %{
+ status: :pending,
+ auth_node: %{
+ role_assignments: [%{principal_id: ^user_id}]
+ }
+ } = Crew.Public.create_task!(crew, member, ["task2"], nil)
+
+ assert %{
+ status: :pending,
+ auth_node: %{
+ role_assignments: [%{principal_id: ^user_id}]
+ }
+ } = Crew.Public.create_task!(crew, member, ["task3"], nil)
+ end
+
+ test "create_task/2 multiple tasks succeeds for multiple members" do
+ crew = Factories.insert!(:crew)
+
+ user1 = Factories.insert!(:member)
+ user2 = Factories.insert!(:member)
+
+ member1 = Factories.insert!(:crew_member, %{crew: crew, user: user1})
+ member2 = Factories.insert!(:crew_member, %{crew: crew, user: user2})
+
+ assert {:ok, _} = Crew.Public.create_task(crew, member1, ["task1", "member1"], nil)
+ assert {:ok, _} = Crew.Public.create_task(crew, member1, ["task2", "member1"], nil)
+ assert {:ok, _} = Crew.Public.create_task(crew, member2, ["task1", "member2"], nil)
+ assert {:ok, _} = Crew.Public.create_task(crew, member2, ["task2", "member2"], nil)
+ end
+
+ test "create_task/2 single task succeeds for team" do
+ %{id: crew_id} = crew = Factories.insert!(:crew)
+
+ %{id: user_id1} = user1 = Factories.insert!(:member)
+ %{id: user_id2} = user2 = Factories.insert!(:member)
+
+ member1 = Factories.insert!(:crew_member, %{crew: crew, user: user1})
+ member2 = Factories.insert!(:crew_member, %{crew: crew, user: user2})
+
+ assert %{
+ identifier: ["task1"],
+ status: :pending,
+ crew_id: ^crew_id,
+ auth_node: %Core.Authorization.Node{
+ parent_id: nil,
+ role_assignments: [
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id1,
+ role: :owner
+ },
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id2,
+ role: :owner
+ }
+ ]
+ }
+ } = Crew.Public.create_task!(crew, [member1, member2], ["task1"], nil)
+ end
+
+ test "create_task/2 multiple tasks succeeds for multiple teams" do
+ %{id: crew_id} = crew = Factories.insert!(:crew)
+
+ %{id: user_id1} = user1 = Factories.insert!(:member)
+ %{id: user_id2} = user2 = Factories.insert!(:member)
+ %{id: user_id3} = user3 = Factories.insert!(:member)
+
+ member1 = Factories.insert!(:crew_member, %{crew: crew, user: user1})
+ member2 = Factories.insert!(:crew_member, %{crew: crew, user: user2})
+ member3 = Factories.insert!(:crew_member, %{crew: crew, user: user3})
+
+ assert %{
+ identifier: ["task1"],
+ status: :pending,
+ crew_id: ^crew_id,
+ auth_node: %Core.Authorization.Node{
+ parent_id: nil,
+ role_assignments: [
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id1,
+ role: :owner
+ },
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id2,
+ role: :owner
+ }
+ ]
+ }
+ } = Crew.Public.create_task!(crew, [member1, member2], ["task1"], nil)
+
+ assert %{
+ identifier: ["task2"],
+ status: :pending,
+ crew_id: ^crew_id,
+ auth_node: %Core.Authorization.Node{
+ parent_id: nil,
+ role_assignments: [
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id2,
+ role: :owner
+ },
+ %Core.Authorization.RoleAssignment{
+ principal_id: ^user_id3,
+ role: :owner
+ }
+ ]
+ }
+ } = Crew.Public.create_task!(crew, [member2, member3], ["task2"], nil)
+ end
+
+ test "create_task/2 multiple tasks fails for one member: identifier must be unique" do
user = Factories.insert!(:member)
crew = Factories.insert!(:crew)
member = Factories.insert!(:crew_member, %{crew: crew, user: user})
- assert %{status: :pending, member_id: member_id} =
- Crew.Public.create_task!(crew, member, nil)
+ assert {:ok, _} = Crew.Public.create_task(crew, member, ["task1"], nil)
- assert member_id == member.id
+ assert {:error,
+ %{
+ errors: [identifier: {"has already been taken", _}]
+ }} = Crew.Public.create_task(crew, member, ["task1"], nil)
end
- test "setup_tasks_for_members/2 " do
+ test "create_task/2 multiple tasks fails for multiple members: identifier must be unique" do
crew = Factories.insert!(:crew)
+
user1 = Factories.insert!(:member)
user2 = Factories.insert!(:member)
+
member1 = Factories.insert!(:crew_member, %{crew: crew, user: user1})
member2 = Factories.insert!(:crew_member, %{crew: crew, user: user2})
- list = Crew.Public.setup_tasks_for_members!([member1, member2], crew)
- assert list |> Enum.find(&(&1.member_id == member1.id))
- assert list |> Enum.find(&(&1.member_id == member2.id))
- assert Crew.Public.count_tasks(crew, [:pending]) == 2
+ assert {:ok, _} = Crew.Public.create_task(crew, member1, ["task1"], nil)
+
+ assert {:error,
+ %{
+ errors: [identifier: {"has already been taken", _}]
+ }} = Crew.Public.create_task(crew, member2, ["task1"], nil)
end
test "activate_task/1 marks pending task completed" do
user = Factories.insert!(:member)
+ auth_node = auth_node_with_owner(user)
+
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+ _member = Factories.insert!(:crew_member, %{crew: crew, user: user})
assert Crew.Public.count_tasks(crew, [:completed]) == 0
task =
Factories.insert!(:crew_task, %{
+ identifier: ["task1"],
crew: crew,
- member: member,
+ auth_node: auth_node,
status: :pending
})
@@ -309,41 +557,47 @@ defmodule Systems.Crew.PublicTest do
test "activate_task/1 does not mark accepted task completed" do
user = Factories.insert!(:member)
+ auth_node = auth_node_with_owner(user)
+
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+ _member = Factories.insert!(:crew_member, %{crew: crew, user: user})
assert Crew.Public.count_tasks(crew, [:completed]) == 0
task =
Factories.insert!(:crew_task, %{
+ identifier: ["task1"],
crew: crew,
- member: member,
+ auth_node: auth_node,
status: :pending
})
assert Crew.Public.count_tasks(crew, [:completed]) == 0
- {:ok, %{task: task}} = Crew.Public.accept_task(task)
+ {:ok, %{crew_task: task}} = Crew.Public.accept_task(task)
assert %{status: :accepted} = Crew.Public.activate_task!(task)
assert Crew.Public.count_tasks(crew, [:completed]) == 0
end
test "activate_task/1 does not mark rejected task completed" do
user = Factories.insert!(:member)
+ auth_node = auth_node_with_owner(user)
+
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+ _member = Factories.insert!(:crew_member, %{crew: crew, user: user})
assert Crew.Public.count_tasks(crew, [:completed]) == 0
task =
Factories.insert!(:crew_task, %{
+ identifier: ["task1"],
crew: crew,
- member: member,
+ auth_node: auth_node,
status: :pending
})
assert Crew.Public.count_tasks(crew, [:completed]) == 0
- {:ok, %{task: task}} =
+ {:ok, %{crew_task: task}} =
Crew.Public.reject_task(task, %{
category: :attention_checks_failed,
message: "rejection message"
@@ -355,15 +609,18 @@ defmodule Systems.Crew.PublicTest do
test "accept_task/1 marks task accepted" do
user = Factories.insert!(:member)
+ auth_node = auth_node_with_owner(user)
+
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+ _member = Factories.insert!(:crew_member, %{crew: crew, user: user})
assert Crew.Public.count_tasks(crew, [:accepted]) == 0
task =
Factories.insert!(:crew_task, %{
+ identifier: ["task1"],
crew: crew,
- member: member,
+ auth_node: auth_node,
status: :pending
})
@@ -371,7 +628,7 @@ defmodule Systems.Crew.PublicTest do
assert {:ok,
%{
- task: %{
+ crew_task: %{
status: :accepted,
accepted_at: accepted_at
}
@@ -383,15 +640,18 @@ defmodule Systems.Crew.PublicTest do
test "reject_task/1 marks task rejected" do
user = Factories.insert!(:member)
+ auth_node = auth_node_with_owner(user)
+
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+ _member = Factories.insert!(:crew_member, %{crew: crew, user: user})
assert Crew.Public.count_tasks(crew, [:rejected]) == 0
task =
Factories.insert!(:crew_task, %{
+ identifier: ["task1"],
crew: crew,
- member: member,
+ auth_node: auth_node,
status: :pending
})
@@ -401,7 +661,7 @@ defmodule Systems.Crew.PublicTest do
assert {:ok,
%{
- task: %{
+ crew_task: %{
status: :rejected,
rejected_at: rejected_at,
rejected_category: :attention_checks_failed,
@@ -415,32 +675,40 @@ defmodule Systems.Crew.PublicTest do
test "delete_task/1 " do
user = Factories.insert!(:member)
+ auth_node = auth_node_with_owner(user)
+
crew = Factories.insert!(:crew)
- member = Factories.insert!(:crew_member, %{crew: crew, user: user})
+ _member = Factories.insert!(:crew_member, %{crew: crew, user: user})
assert Crew.Public.count_tasks(crew, [:pending]) == 0
task =
Factories.insert!(:crew_task, %{
+ identifier: ["task1"],
crew: crew,
- member: member,
+ auth_node: auth_node,
status: :pending
})
assert Crew.Public.count_tasks(crew, [:pending]) == 1
- list = Crew.Public.list_members_without_task(crew)
- assert is_nil(list |> Enum.find(&(&1.id == member.id)))
-
Crew.Public.delete_task(task)
assert Crew.Public.count_tasks(crew, [:pending]) == 0
-
- list = Crew.Public.list_members_without_task(crew)
- assert list |> Enum.find(&(&1.id == member.id))
end
end
defp expire_at(minutes) do
Timestamp.naive_from_now(minutes)
end
+
+ defp auth_node_with_owner(user) do
+ Factories.insert!(:auth_node, %{
+ role_assignments: [
+ %{
+ role: :owner,
+ principal_id: GreenLight.Principal.id(user)
+ }
+ ]
+ })
+ end
end
diff --git a/core/test/systems/crew/factories.ex b/core/test/systems/crew/factories.ex
new file mode 100644
index 000000000..017eb99bd
--- /dev/null
+++ b/core/test/systems/crew/factories.ex
@@ -0,0 +1,29 @@
+defmodule Systems.Crew.Factories do
+ alias CoreWeb.UI.Timestamp
+
+ def create_member(crew, user) do
+ Core.Factories.insert!(:crew_member, %{crew: crew, user: user})
+ end
+
+ def create_task(crew, member, [_ | _] = identifier, opts \\ []) do
+ {status, opts} = Keyword.pop(opts, :status, :pending)
+ {expired, opts} = Keyword.pop(opts, :expired, false)
+ {expire_at, opts} = Keyword.pop(opts, :expire_at, nil)
+ {minutes_ago, opts} = Keyword.pop(opts, :minutes_ago, 31)
+
+ {updated_at, _opts} =
+ Keyword.pop(opts, :expired_at, Timestamp.naive_from_now(minutes_ago * -1))
+
+ auth_node = Core.Authorization.Node.create([member.user.id], :owner)
+
+ Core.Factories.insert!(:crew_task, %{
+ identifier: identifier,
+ crew: crew,
+ auth_node: auth_node,
+ status: status,
+ expired: expired,
+ expire_at: expire_at,
+ updated_at: updated_at
+ })
+ end
+end
diff --git a/core/test/systems/feldspar/app_page_test.exs b/core/test/systems/feldspar/app_page_test.exs
new file mode 100644
index 000000000..550e13f4c
--- /dev/null
+++ b/core/test/systems/feldspar/app_page_test.exs
@@ -0,0 +1,21 @@
+defmodule Systems.Feldspar.AppPageTest do
+ use CoreWeb.ConnCase
+ import Phoenix.ConnTest
+ import Phoenix.LiveViewTest
+
+ describe "render an app page" do
+ test "renders page with iframe", %{conn: conn} do
+ {:ok, _view, html} = live(conn, ~p"/feldspar/apps/test")
+ assert html =~ "
- <%= if @entity.document_name do %>
- <%= @entity.document_name %>
+ <%= if @entity.name do %>
+ <%= @entity.name %>
<% else %>
<%= @placeholder %>
<% end %>
- <%= if @entity.document_name do %>
+ <%= if @entity.name do %>
+ <%!-- Ensure that updates don't alter the hierarchy in front of the iframe.
+ Changing the preceding siblings of the iframe would result in a reload of the iframe
+ due to Morphdom (https://github.com/patrick-steele-idem/morphdom/issues/200).
+ --%>
+
+ """
+ end
+end
diff --git a/core/systems/feldspar/content_page.ex b/core/systems/feldspar/content_page.ex
new file mode 100644
index 000000000..4a08b71b2
--- /dev/null
+++ b/core/systems/feldspar/content_page.ex
@@ -0,0 +1,45 @@
+defmodule Systems.Feldspar.ContentPage do
+ use Systems.Content.Page
+
+ alias Systems.{
+ Feldspar
+ }
+
+ @impl true
+ def get_authorization_context(%{"id" => id}, _session, _socket) do
+ Feldspar.Public.get_tool!(id)
+ end
+
+ @impl true
+ def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
+ model =
+ Feldspar.Public.get_tool!(String.to_integer(id), Feldspar.ToolModel.preload_graph(:down))
+
+ tabbar_id = "feldspar_content/#{id}"
+
+ {
+ :ok,
+ socket |> initialize(id, model, tabbar_id, initial_tab, locale)
+ }
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.content_page
+ title={@vm.title}
+ show_errors={@vm.show_errors}
+ tabs={@vm.tabs}
+ menus={@menus}
+ actions={@actions}
+ more_actions={@more_actions}
+ initial_tab={@initial_tab}
+ tabbar_id={@tabbar_id}
+ tabbar_size={@tabbar_size}
+ breakpoint={@breakpoint}
+ popup={@popup}
+ dialog={@dialog}
+ />
+ """
+ end
+end
diff --git a/core/systems/feldspar/content_page_builder.ex b/core/systems/feldspar/content_page_builder.ex
new file mode 100644
index 000000000..7f11a827a
--- /dev/null
+++ b/core/systems/feldspar/content_page_builder.ex
@@ -0,0 +1,20 @@
+defmodule Systems.Feldspar.ContentPageBuilder do
+ import CoreWeb.Gettext
+
+ alias Systems.{
+ Feldspar
+ }
+
+ def view_model(
+ %Feldspar.ToolModel{id: id},
+ _assigns
+ ) do
+ %{
+ id: id,
+ title: dgettext("eyra-feldspar", "content.title"),
+ tabs: [],
+ actions: [],
+ show_errors: false
+ }
+ end
+end
diff --git a/core/systems/feldspar/local_fs.ex b/core/systems/feldspar/local_fs.ex
new file mode 100644
index 000000000..96c168601
--- /dev/null
+++ b/core/systems/feldspar/local_fs.ex
@@ -0,0 +1,37 @@
+defmodule Systems.Feldspar.LocalFS do
+ alias CoreWeb.Endpoint
+
+ def store(zip_file) do
+ id = Ecto.UUID.generate()
+ path = get_path(id)
+ File.mkdir!(path)
+ :zip.unzip(to_charlist(zip_file), cwd: to_charlist(path))
+ id
+ end
+
+ def storage_path(id) do
+ get_path(id)
+ end
+
+ def get_public_url(id) do
+ "#{Endpoint.url()}/#{public_path()}/#{id}"
+ end
+
+ def remove(id) do
+ with {:ok, _} <- File.rm_rf(get_path(id)) do
+ :ok
+ end
+ end
+
+ defp get_path(id) do
+ Path.join(get_root_path(), id)
+ end
+
+ def get_root_path do
+ :core
+ |> Application.get_env(:feldspar, [])
+ |> Access.fetch!(:local_fs_root_path)
+ end
+
+ def public_path, do: "/feldspar/apps"
+end
diff --git a/core/systems/feldspar/plug.ex b/core/systems/feldspar/plug.ex
new file mode 100644
index 000000000..8fd01567d
--- /dev/null
+++ b/core/systems/feldspar/plug.ex
@@ -0,0 +1,33 @@
+defmodule Systems.Feldspar.Plug do
+ @behaviour Plug
+
+ defmacro setup() do
+ quote do
+ plug(Systems.Feldspar.Plug, at: Systems.Feldspar.LocalFS.public_path())
+ end
+ end
+
+ @impl true
+ def init(opts) do
+ opts
+ # Ensure that init works, from will be set dynamically later on
+ |> Keyword.put(:from, {nil, nil})
+ |> Plug.Static.init()
+ end
+
+ @impl true
+ def call(
+ conn,
+ options
+ ) do
+ call(Systems.Feldspar.Private.get_backend(), conn, options)
+ end
+
+ def call(Systems.Feldspar.LocalFS, conn, options) do
+ root_path = Systems.Feldspar.LocalFS.get_root_path()
+ options = Map.put(options, :from, root_path)
+ Plug.Static.call(conn, options)
+ end
+
+ def call(_, conn, _options), do: conn
+end
diff --git a/core/systems/feldspar/s3.ex b/core/systems/feldspar/s3.ex
new file mode 100644
index 000000000..257393cc2
--- /dev/null
+++ b/core/systems/feldspar/s3.ex
@@ -0,0 +1,69 @@
+defmodule Systems.Feldspar.S3 do
+ alias ExAws.S3
+
+ def store(zip_file) do
+ id = Ecto.UUID.generate()
+ :ok = upload_zip_content(zip_file, id)
+ id
+ end
+
+ def get_public_url(id) do
+ settings = s3_settings()
+ public_url = Access.get(settings, :public_url)
+ "#{public_url}/#{object_key(id)}/index.html"
+ end
+
+ def remove(id) do
+ bucket = Access.fetch!(s3_settings(), :bucket)
+
+ objects =
+ S3.list_objects_v2(bucket, prefix: object_key(id))
+ |> backend().request!()
+ |> get_in([:body, :contents])
+ |> Enum.map(&Access.fetch!(&1, :key))
+
+ S3.delete_all_objects(bucket, objects)
+ |> backend().request!()
+
+ :ok
+ end
+
+ defp upload_zip_content(zip_file, target) do
+ settings = s3_settings()
+
+ {:ok, zip_handle} = :zip.zip_open(to_charlist(zip_file), [:memory])
+
+ upload_file = fn file ->
+ {:ok, {_, data}} = :zip.zip_get(file, zip_handle)
+
+ S3.put_object(
+ Access.fetch!(settings, :bucket),
+ "#{object_key(target)}/#{file}",
+ data
+ )
+ |> backend().request!()
+ end
+
+ # Read the zip file names and skip the comment (first item)
+ {:ok, [_ | contents]} = :zip.zip_list_dir(zip_handle)
+
+ contents
+ |> Enum.map(fn {:zip_file, file, _, _, _, _} -> file end)
+ |> Task.async_stream(upload_file, max_concurrency: 10)
+ |> Stream.run()
+ end
+
+ defp object_key(id) do
+ prefix = Access.get(s3_settings(), :prefix, "")
+ "#{prefix}#{id}"
+ end
+
+ defp s3_settings do
+ Application.fetch_env!(:core, :feldspar)
+ end
+
+ defp backend do
+ # Allow mocking
+ Access.get(s3_settings(), :s3_backend, ExAws)
+ end
+end
diff --git a/core/systems/feldspar/tool_form.ex b/core/systems/feldspar/tool_form.ex
new file mode 100644
index 000000000..5342aba47
--- /dev/null
+++ b/core/systems/feldspar/tool_form.ex
@@ -0,0 +1,112 @@
+defmodule Systems.Feldspar.ToolForm do
+ use CoreWeb.LiveForm
+ use CoreWeb.FileUploader, ~w(.zip)
+
+ alias Systems.{
+ Feldspar
+ }
+
+ @impl true
+ def process_file(
+ %{assigns: %{entity: entity}} = socket,
+ {_local_relative_path, local_full_path, remote_file}
+ ) do
+ archive_ref =
+ Feldspar.Public.store(local_full_path)
+ |> Feldspar.Public.get_public_url()
+
+ socket
+ |> save(entity, %{archive_ref: archive_ref, archive_name: remote_file})
+ end
+
+ @impl true
+ def update(
+ %{
+ id: id,
+ entity: entity
+ },
+ socket
+ ) do
+ label = dgettext("eyra-feldspar", "zip-select-label")
+ placeholder = dgettext("eyra-feldspar", "zip-select-placeholder")
+ select_button = dgettext("eyra-feldspar", "zip-select-file-button")
+ replace_button = dgettext("eyra-feldspar", "zip-replace-file-button")
+
+ {
+ :ok,
+ socket
+ |> assign(
+ id: id,
+ label: label,
+ placeholder: placeholder,
+ select_button: select_button,
+ replace_button: replace_button,
+ entity: entity
+ )
+ |> init_file_uploader(:file)
+ }
+ end
+
+ @impl true
+ def handle_event("change", _params, socket) do
+ {:noreply, socket |> handle_upload_error()}
+ end
+
+ defp handle_upload_error(socket) do
+ if has_upload_error(socket) do
+ if has_upload_error(socket, :too_large) do
+ Frameworks.Pixel.Flash.push_error(dgettext("eyra-feldspar", "zip-too-large"))
+ else
+ Frameworks.Pixel.Flash.push_error()
+ end
+ end
+
+ socket
+ end
+
+ defp has_upload_error(%{assigns: %{uploads: %{file: %{errors: [_ | _]}}}}), do: true
+ defp has_upload_error(_), do: false
+
+ defp has_upload_error(%{assigns: %{uploads: %{file: %{errors: errors}}}}, error_type) do
+ Enum.find(errors, fn {_, type} -> type === error_type end) != nil
+ end
+
+ # Saving
+ def save(socket, %Feldspar.ToolModel{} = entity, attrs) do
+ changeset = Feldspar.ToolModel.changeset(entity, attrs)
+
+ socket
+ |> save(changeset)
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+
+
+
+ <.form id="select_file_form" for={%{}} phx-change="change" phx-target="" >
+ <%= @label %>
+ <.spacing value="XXS" />
+
+ """
+ end
+end
diff --git a/core/systems/feldspar/tool_model.ex b/core/systems/feldspar/tool_model.ex
new file mode 100644
index 000000000..b9d68fd46
--- /dev/null
+++ b/core/systems/feldspar/tool_model.ex
@@ -0,0 +1,73 @@
+defmodule Systems.Feldspar.ToolModel do
+ use Ecto.Schema
+ use Frameworks.Utility.Schema
+
+ import CoreWeb.Gettext
+ import Ecto.Changeset
+
+ schema "feldspar_tools" do
+ field(:archive_name, :string)
+ field(:archive_ref, :string)
+ field(:director, Ecto.Enum, values: [:project, :assignment])
+
+ belongs_to(:auth_node, Core.Authorization.Node)
+
+ timestamps()
+ end
+
+ @fields ~w(archive_name archive_ref director)a
+ @required_fields ~w(archive_name archive_ref)a
+
+ 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: preload_graph([])
+
+ defimpl Frameworks.Concept.ToolModel do
+ alias Systems.Feldspar
+ def key(_), do: :feldspar
+ def auth_tree(%{auth_node: auth_node}), do: auth_node
+ def apply_label(_), do: dgettext("eyra-feldspar", "apply.cta.title")
+ def open_label(_), do: dgettext("eyra-feldspar", "open.cta.title")
+ def ready?(tool), do: Feldspar.ToolModel.ready?(tool)
+ def form(_), do: Feldspar.ToolForm
+
+ def launcher(%{archive_ref: archive_ref}) when is_binary(archive_ref) do
+ %{
+ function: &Feldspar.AppView.app_view/1,
+ props: %{url: archive_ref <> "/index.html"}
+ }
+ end
+
+ def launcher(_), do: nil
+
+ def task_labels(_) do
+ %{
+ pending: dgettext("eyra-feldspar", "pending.label"),
+ participated: dgettext("eyra-feldspar", "participated.label")
+ }
+ end
+
+ def attention_list_enabled?(_t), do: false
+ def group_enabled?(_t), do: true
+ end
+
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Utility.Module.get(director, "Director")
+ end
+end
diff --git a/core/systems/lab/_presenter.ex b/core/systems/lab/_presenter.ex
new file mode 100644
index 000000000..fadef4ac8
--- /dev/null
+++ b/core/systems/lab/_presenter.ex
@@ -0,0 +1,12 @@
+defmodule Systems.Lab.Presenter do
+ @behaviour Frameworks.Concept.Presenter
+
+ alias Systems.{
+ Lab
+ }
+
+ @impl true
+ def view_model(%Lab.ToolModel{} = _tool, _page, _assigns) do
+ %{}
+ end
+end
diff --git a/core/systems/lab/_public.ex b/core/systems/lab/_public.ex
index 3db386630..c27277079 100644
--- a/core/systems/lab/_public.ex
+++ b/core/systems/lab/_public.ex
@@ -4,7 +4,6 @@ defmodule Systems.Lab.Public do
alias Systems.Lab.VUDaySchedule, as: DaySchedule
alias Core.Repo
- alias Ecto.Multi
alias Frameworks.{
Signal
@@ -16,7 +15,7 @@ defmodule Systems.Lab.Public do
alias Core.Accounts.User
- def get(id, preload \\ []) do
+ def get_tool!(id, preload \\ []) do
from(lab_tool in Lab.ToolModel,
preload: ^preload
)
@@ -24,11 +23,10 @@ defmodule Systems.Lab.Public do
|> filter_double_time_slots()
end
- def create_tool(attrs, auth_node) do
+ def prepare_tool(attrs, auth_node \\ Core.Authorization.prepare_node()) do
%Lab.ToolModel{}
|> Lab.ToolModel.changeset(:mount, attrs)
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert()
end
def copy(%Lab.ToolModel{} = tool, auth_node) do
@@ -38,19 +36,6 @@ defmodule Systems.Lab.Public do
|> Repo.insert!()
end
- def update_tool(changeset) do
- result =
- Multi.new()
- |> Repo.multi_update(:tool, changeset)
- |> Repo.transaction()
-
- with {:ok, %{tool: tool}} <- result do
- Signal.Public.dispatch!(:lab_tool_updated, tool)
- end
-
- result
- end
-
def get_time_slot(id, preload \\ []) do
from(ts in Lab.TimeSlotModel,
where: ts.id == ^id,
@@ -192,7 +177,7 @@ defmodule Systems.Lab.Public do
entries
|> Enum.each(&submit_day_entry(&1, tool, og_date, og_location, date, location))
- Signal.Public.dispatch!(:lab_tool_updated, tool)
+ Signal.Public.dispatch!(:lab_tool, tool)
end
defp submit_day_entry(
@@ -244,7 +229,7 @@ defmodule Systems.Lab.Public do
|> time_slot_query(from, to, location)
|> Repo.update_all(set: [enabled?: false]) do
if count > 0 do
- Signal.Public.dispatch!(:lab_tool_updated, tool)
+ Signal.Public.dispatch!(:lab_tool, tool)
end
{count, nil}
@@ -336,9 +321,9 @@ defmodule Systems.Lab.Public do
conflict_target: [:user_id, :time_slot_id],
on_conflict: {:replace, [:status]}
) do
- tool = get(tool_id)
+ tool = get_tool!(tool_id)
- Signal.Public.dispatch!(:lab_reservation_created, %{
+ Signal.Public.dispatch!({:lab_tool, :reservation_created}, %{
tool: tool,
user: user,
time_slot: time_slot
@@ -362,7 +347,10 @@ defmodule Systems.Lab.Public do
with {update_count, _} <- Repo.update_all(query, set: [status: :cancelled]) do
if update_count > 0 do
- Signal.Public.dispatch!(:lab_reservations_cancelled, %{tool: get(tool_id), user: user})
+ Signal.Public.dispatch!({:lab_tool, :reservations_cancelled}, %{
+ tool: get_tool!(tool_id),
+ user: user
+ })
end
unless update_count < 2 do
@@ -394,8 +382,8 @@ end
defimpl Core.Persister, for: Systems.Lab.ToolModel do
def save(_tool, changeset) do
- case Systems.Lab.Public.update_tool(changeset) do
- {:ok, %{tool: tool}} -> {:ok, tool}
+ case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :lab_tool) do
+ {:ok, %{lab_tool: lab_tool}} -> {:ok, lab_tool}
_ -> {:error, changeset}
end
end
diff --git a/core/systems/lab/check_in_view.ex b/core/systems/lab/check_in_view.ex
index ba9c46566..e7c92d232 100644
--- a/core/systems/lab/check_in_view.ex
+++ b/core/systems/lab/check_in_view.ex
@@ -4,10 +4,10 @@ defmodule Systems.Lab.CheckInView do
require Logger
alias Core.Accounts
- alias Systems.Director
import Frameworks.Pixel.Form
alias Frameworks.Pixel.Panel
alias Frameworks.Pixel.Text
+ alias Frameworks.Concept.Directable
alias Systems.{
Lab
@@ -52,7 +52,7 @@ defmodule Systems.Lab.CheckInView do
|> String.to_integer()
|> Accounts.get_user!()
- Director.public(tool).apply_member_and_activate_task(tool, user)
+ Directable.director(tool).apply_member_and_activate_task(tool, user)
{:noreply, socket |> assign(query: nil, message: nil)}
end
@@ -87,7 +87,7 @@ defmodule Systems.Lab.CheckInView do
defp search(public_id, tool) when is_integer(public_id) do
item =
- Director.public(tool).search_subject(tool, public_id)
+ Directable.director(tool).search_subject(tool, public_id)
|> to_view_model(tool)
case item do
@@ -173,11 +173,11 @@ defmodule Systems.Lab.CheckInView do
reservation = reservation(user, tool)
time_slot = time_slot(reservation)
- search_result = Director.public(tool).search_subject(tool, user)
+ search_result = Directable.director(tool).search_subject(tool, user)
status =
case search_result do
- {_, task} -> item_status(task, reservation)
+ {_, [task]} -> item_status(task, reservation)
_ -> :reservation_not_found
end
@@ -209,7 +209,7 @@ defmodule Systems.Lab.CheckInView do
user_id: user_id,
public_id: public_id
} = _member,
- %{completed_at: completed_at} = task
+ [%{completed_at: completed_at} = task]
},
tool
) do
diff --git a/core/systems/lab/content_page.ex b/core/systems/lab/content_page.ex
new file mode 100644
index 000000000..5df5829e2
--- /dev/null
+++ b/core/systems/lab/content_page.ex
@@ -0,0 +1,43 @@
+defmodule Systems.Lab.ContentPage do
+ use Systems.Content.Page
+
+ alias Systems.{
+ Lab
+ }
+
+ @impl true
+ def get_authorization_context(%{"id" => id}, _session, _socket) do
+ Lab.Public.get_tool!(id)
+ end
+
+ @impl true
+ def mount(%{"id" => id, "tab" => initial_tab}, %{"locale" => locale}, socket) do
+ model = Lab.Public.get_tool!(String.to_integer(id), Lab.ToolModel.preload_graph(:down))
+ tabbar_id = "lab_content/#{id}"
+
+ {
+ :ok,
+ socket |> initialize(id, model, tabbar_id, initial_tab, locale)
+ }
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ <.content_page
+ title={@vm.title}
+ show_errors={@vm.show_errors}
+ tabs={@vm.tabs}
+ menus={@menus}
+ actions={@actions}
+ more_actions={@more_actions}
+ initial_tab={@initial_tab}
+ tabbar_id={@tabbar_id}
+ tabbar_size={@tabbar_size}
+ breakpoint={@breakpoint}
+ popup={@popup}
+ dialog={@dialog}
+ />
+ """
+ end
+end
diff --git a/core/systems/lab/content_page_builder.ex b/core/systems/lab/content_page_builder.ex
new file mode 100644
index 000000000..2effc65c9
--- /dev/null
+++ b/core/systems/lab/content_page_builder.ex
@@ -0,0 +1,20 @@
+defmodule Systems.Lab.ContentPageBuilder do
+ import CoreWeb.Gettext
+
+ alias Systems.{
+ Lab
+ }
+
+ def view_model(
+ %Lab.ToolModel{id: id},
+ _assigns
+ ) do
+ %{
+ id: id,
+ title: dgettext("link-lab", "content.title"),
+ tabs: [],
+ actions: [],
+ show_errors: false
+ }
+ end
+end
diff --git a/core/systems/lab/item_content_page_builder.ex b/core/systems/lab/item_content_page_builder.ex
new file mode 100644
index 000000000..1215f0a68
--- /dev/null
+++ b/core/systems/lab/item_content_page_builder.ex
@@ -0,0 +1,9 @@
+defmodule Systems.Lab.ItemContentPageBuilder do
+ alias Systems.{
+ Lab
+ }
+
+ def view_model(%Lab.ToolModel{} = _tool, _assigns) do
+ %{}
+ end
+end
diff --git a/core/systems/lab/public_page.ex b/core/systems/lab/public_page.ex
index 0fb1ebb03..716627a5f 100644
--- a/core/systems/lab/public_page.ex
+++ b/core/systems/lab/public_page.ex
@@ -9,7 +9,7 @@ defmodule Systems.Lab.PublicPage do
}
def mount(%{"id" => id}, _session, %{assigns: %{current_user: user}} = socket) do
- tool = Lab.Public.get(id, [:time_slots])
+ tool = Lab.Public.get_tool!(id, [:time_slots])
{:ok,
socket
diff --git a/core/systems/lab/experiment_task_view.ex b/core/systems/lab/task_view.ex
similarity index 98%
rename from core/systems/lab/experiment_task_view.ex
rename to core/systems/lab/task_view.ex
index 8d1a625de..90f914d7b 100644
--- a/core/systems/lab/experiment_task_view.ex
+++ b/core/systems/lab/task_view.ex
@@ -1,4 +1,4 @@
-defmodule Systems.Lab.ExperimentTaskView do
+defmodule Systems.Lab.TaskView do
use CoreWeb, :live_component
import CoreWeb.UI.Navigation, only: [button_bar: 1]
@@ -186,7 +186,7 @@ defmodule Systems.Lab.ExperimentTaskView do
end
defp id_text(public_id) do
- label = dgettext("link-lab", "experiment.checkin.label")
+ label = dgettext("link-lab", "inquiry.checkin.label")
"🆔 #{label} #{public_id}"
end
diff --git a/core/systems/lab/tool_form.ex b/core/systems/lab/tool_form.ex
index 379d4113f..f8f803c65 100644
--- a/core/systems/lab/tool_form.ex
+++ b/core/systems/lab/tool_form.ex
@@ -57,7 +57,7 @@ defmodule Systems.Lab.ToolForm do
end
defp update_entity(%{assigns: %{entity_id: entity_id}} = socket) do
- entity = Lab.Public.get(entity_id, [:time_slots])
+ entity = Lab.Public.get_tool!(entity_id, [:time_slots])
changeset = Lab.ToolModel.changeset(entity, :create, %{})
socket
diff --git a/core/systems/lab/tool_model.ex b/core/systems/lab/tool_model.ex
index 5e5f8428f..c7be6c7d0 100644
--- a/core/systems/lab/tool_model.ex
+++ b/core/systems/lab/tool_model.ex
@@ -6,6 +6,7 @@ defmodule Systems.Lab.ToolModel do
require Core.Enums.Themes
import Ecto.Changeset
+ import CoreWeb.Gettext
schema "lab_tools" do
belongs_to(:auth_node, Core.Authorization.Node)
@@ -16,7 +17,7 @@ defmodule Systems.Lab.ToolModel do
on_delete: :delete_all
)
- field(:director, Ecto.Enum, values: [:campaign])
+ field(:director, Ecto.Enum, values: [:assignment])
timestamps()
end
@@ -30,7 +31,7 @@ defmodule Systems.Lab.ToolModel do
@impl true
def operational_validation(changeset), do: changeset
- def preload_graph(:full),
+ def preload_graph(:down),
do:
preload_graph([
:auth_node,
@@ -46,13 +47,56 @@ defmodule Systems.Lab.ToolModel do
def changeset(tool, :auto_save, params) do
tool
- |> cast(params, @fields)
- |> validate_required(@required_fields)
+ |> changeset(params)
+ |> validate()
end
def changeset(tool, _, params) do
tool
+ |> changeset(params)
|> cast(params, [:director])
+ end
+
+ def changeset(tool, params) do
+ tool
|> 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
+
+ defimpl Frameworks.Concept.ToolModel do
+ alias Systems.Lab
+ def key(_), do: :lab
+ def auth_tree(%{auth_node: auth_node}), do: auth_node
+ def apply_label(_), do: dgettext("link-lab", "apply.cta.title")
+ def open_label(_), do: dgettext("link-lab", "open.cta.title")
+ def ready?(tool), do: Lab.ToolModel.ready?(tool)
+ def form(_), do: Lab.Form
+ def launcher(_), do: %{function: fn _ -> nil end, props: %{}}
+
+ def task_labels(_) do
+ %{
+ pending: dgettext("link-lab", "pending.label"),
+ participated: dgettext("link-lab", "participated.label")
+ }
+ end
+
+ def attention_list_enabled?(_t), do: false
+ def group_enabled?(_t), do: true
+ end
+
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Utility.Module.get(director, "Director")
+ end
end
diff --git a/core/systems/next_action/_presenter.ex b/core/systems/next_action/_presenter.ex
index 8b2fcf250..301debcef 100644
--- a/core/systems/next_action/_presenter.ex
+++ b/core/systems/next_action/_presenter.ex
@@ -1,5 +1,5 @@
defmodule Systems.NextAction.Presenter do
- use Systems.Presenter
+ use Frameworks.Concept.Presenter
alias Frameworks.Signal
@@ -8,28 +8,12 @@ defmodule Systems.NextAction.Presenter do
}
@impl true
- def view_model(
- %{presenter: Systems.NextAction.Presenter},
- page,
- %{current_user: user} = assigns
- ) do
- view_model(user.id, page, assigns)
+ def view_model(%Core.Accounts.User{} = user, NextAction.OverviewPage, _) do
+ %{next_actions: NextAction.Public.list_next_actions(user)}
end
- @impl true
- def view_model(user_id, NextAction.OverviewPage, %{current_user: user})
- when is_number(user_id) do
- %{
- next_actions: NextAction.Public.list_next_actions(user)
- }
- end
-
- def update(model, id, page) do
- Signal.Public.dispatch!(%{page: page}, %{
- id: id,
- model: model |> Map.put(:presenter, __MODULE__)
- })
-
+ def update(model, %Core.Accounts.User{id: id}, page) do
+ Signal.Public.dispatch!({:page, page}, %{id: id, model: model})
model
end
end
diff --git a/core/systems/next_action/_public.ex b/core/systems/next_action/_public.ex
index ad03c405a..cf2611bac 100644
--- a/core/systems/next_action/_public.ex
+++ b/core/systems/next_action/_public.ex
@@ -41,9 +41,15 @@ defmodule Systems.NextAction.Public do
end
@doc """
- Creates a next action.
+ Creates a next action for the audience.
"""
- def create_next_action(%User{} = user, action, opts \\ []) when is_atom(action) do
+ def create_next_action(audience, action, opts \\ [])
+
+ def create_next_action([_ | _] = users, action, opts) when is_atom(action) do
+ Enum.map(users, &create_next_action(&1, action, opts))
+ end
+
+ def create_next_action(%User{} = user, action, opts) when is_atom(action) do
key = Keyword.get(opts, :key)
conflict_target_fragment =
@@ -65,7 +71,7 @@ defmodule Systems.NextAction.Public do
conflict_target: {:unsafe_fragment, conflict_target_fragment}
)
|> tap(
- &Signal.Public.dispatch!(:next_action_created, %{
+ &Signal.Public.dispatch!({:next_action, :created}, %{
action_type: action,
user: user,
action: &1,
@@ -74,7 +80,13 @@ defmodule Systems.NextAction.Public do
)
end
- def clear_next_action(user, action, opts \\ []) do
+ def clear_next_action(audience, action, opts \\ [])
+
+ def clear_next_action([_ | _] = users, action, opts) do
+ Enum.map(users, &clear_next_action(&1, action, opts))
+ end
+
+ def clear_next_action(user, action, opts) do
key = Keyword.get(opts, :key)
action_string = to_string(action)
@@ -82,7 +94,11 @@ defmodule Systems.NextAction.Public do
|> where_key_is(key)
|> Repo.delete_all()
|> tap(fn _ ->
- Signal.Public.dispatch!(:next_action_cleared, %{user: user, action_type: action, key: key})
+ Signal.Public.dispatch!({:next_action, :cleared}, %{
+ user: user,
+ action_type: action,
+ key: key
+ })
end)
end
diff --git a/core/systems/next_action/_switch.ex b/core/systems/next_action/_switch.ex
index 4a1e111ba..9aa82a4ab 100644
--- a/core/systems/next_action/_switch.ex
+++ b/core/systems/next_action/_switch.ex
@@ -5,13 +5,11 @@ defmodule Systems.NextAction.Switch do
NextAction
}
- def dispatch(:next_action_created, %{user: user, action: action}) do
- %{created: action}
- |> NextAction.Presenter.update(user.id, NextAction.OverviewPage)
+ def intercept({:next_action, :created}, %{user: user, action: _action}) do
+ NextAction.Presenter.update(user, user, NextAction.OverviewPage)
end
- def dispatch(:next_action_cleared, %{user: user, action_type: action_type}) do
- %{cleared: action_type}
- |> NextAction.Presenter.update(user.id, NextAction.OverviewPage)
+ def intercept({:next_action, :cleared}, %{user: user, action_type: _action_type}) do
+ NextAction.Presenter.update(user, user, NextAction.OverviewPage)
end
end
diff --git a/core/systems/next_action/overview_page.ex b/core/systems/next_action/overview_page.ex
index 6d695546c..9403f2111 100644
--- a/core/systems/next_action/overview_page.ex
+++ b/core/systems/next_action/overview_page.ex
@@ -4,6 +4,7 @@ defmodule Systems.NextAction.OverviewPage do
"""
use CoreWeb, :live_view
use CoreWeb.Layouts.Workspace.Component, :todo
+ use Systems.Observatory.Public
import CoreWeb.Layouts.Workspace.Component
@@ -12,15 +13,10 @@ defmodule Systems.NextAction.OverviewPage do
}
def mount(_params, _session, %{assigns: %{current_user: user}} = socket) do
- model = %{
- id: user.id,
- presenter: Systems.NextAction.Presenter
- }
-
{
:ok,
socket
- |> assign(model: model)
+ |> assign(model: user)
|> observe_view_model()
|> update_menus()
|> refresh_next_actions()
diff --git a/core/systems/notification/_public.ex b/core/systems/notification/_public.ex
index a7831508a..fc45e8437 100644
--- a/core/systems/notification/_public.ex
+++ b/core/systems/notification/_public.ex
@@ -102,7 +102,7 @@ defmodule Systems.Notification.Public do
{:ok, box} =
%Box{}
|> Box.changeset(%{})
- |> Ecto.Changeset.put_assoc(:auth_node, Authorization.make_node())
+ |> Ecto.Changeset.put_assoc(:auth_node, Authorization.prepare_node())
|> Repo.insert()
|> Authorization.assign_role(user, :owner)
diff --git a/core/systems/observatory/_public.ex b/core/systems/observatory/_public.ex
index 821ec6d66..2bf6a1bf4 100644
--- a/core/systems/observatory/_public.ex
+++ b/core/systems/observatory/_public.ex
@@ -1,5 +1,4 @@
defmodule Systems.Observatory.Public do
- alias Systems.Director
alias CoreWeb.Endpoint
def subscribe(signal, key \\ []) do
@@ -38,18 +37,10 @@ defmodule Systems.Observatory.Public do
socket
end
- def update_view_model(%{assigns: %{model: %{presenter: presenter}}} = socket, model_or_id, page) do
- update_view_model(socket, presenter, model_or_id, page)
- end
-
- def update_view_model(%{assigns: %{model: model}} = socket, model_or_id, page) do
- update_view_model(socket, Director.presenter(model), model_or_id, page)
- end
-
- defp update_view_model(socket, presenter, model_or_id, page) do
+ def update_view_model(socket, model, page, presenter) do
vm =
presenter
- |> get_view_model(socket, model_or_id, page)
+ |> get_view_model(socket, model, page)
socket
|> Phoenix.Component.assign(vm: vm)
@@ -58,21 +49,19 @@ defmodule Systems.Observatory.Public do
defp get_view_model(
presenter,
%{assigns: assigns} = _socket,
- model_or_id,
+ model,
page
) do
presenter
- |> apply(:view_model, [model_or_id, page, assigns])
+ |> apply(:view_model, [model, page, assigns])
end
defmacro __using__(_opts \\ []) do
quote do
- import unquote(__MODULE__), only: [observe: 2]
-
import CoreWeb.Gettext
alias Systems.Observatory.Public
- # data(vm, :map)
+ @presenter Frameworks.Concept.System.presenter(__MODULE__)
def handle_info(%{auto_save: status}, socket) do
{
@@ -85,21 +74,21 @@ defmodule Systems.Observatory.Public do
{
:noreply,
socket
- |> Public.update_view_model(model, __MODULE__)
+ |> Public.update_view_model(model, __MODULE__, @presenter)
|> handle_view_model_updated()
|> put_updated_info_flash()
}
end
- def observe_view_model(%{assigns: %{model: %{id: id}}} = socket) do
+ def observe_view_model(%{assigns: %{model: %{id: id} = model}} = socket) do
socket
|> Public.observe([{__MODULE__, [id]}])
- |> Public.update_view_model(id, __MODULE__)
+ |> Public.update_view_model(model, __MODULE__, @presenter)
end
- def update_view_model(%{assigns: %{model: %{id: id}}} = socket) do
+ def update_view_model(%{assigns: %{model: %{id: id} = model}} = socket) do
socket
- |> Public.update_view_model(id, __MODULE__)
+ |> Public.update_view_model(model, __MODULE__, @presenter)
end
def handle_view_model_updated(socket) do
diff --git a/core/systems/observatory/_switch.ex b/core/systems/observatory/_switch.ex
index c2471aa44..8281a3e86 100644
--- a/core/systems/observatory/_switch.ex
+++ b/core/systems/observatory/_switch.ex
@@ -5,7 +5,7 @@ defmodule Systems.Observatory.Switch do
Observatory
}
- def dispatch(%{page: page}, %{id: id} = message) do
+ def intercept({:page, page}, %{id: id} = message) do
Observatory.Public.local_dispatch(page, [id], message)
end
end
diff --git a/core/systems/pool/_presenter.ex b/core/systems/pool/_presenter.ex
new file mode 100644
index 000000000..b127412a0
--- /dev/null
+++ b/core/systems/pool/_presenter.ex
@@ -0,0 +1,10 @@
+defmodule Systems.Pool.Presenter do
+ @behaviour Frameworks.Concept.Presenter
+
+ alias Systems.Pool
+
+ @impl true
+ def view_model(%Pool.Model{director: director} = pool, page, assigns) do
+ Frameworks.Concept.System.presenter(director).view_model(pool, page, assigns)
+ end
+end
diff --git a/core/systems/pool/_public.ex b/core/systems/pool/_public.ex
index 3211d781d..e282056d9 100644
--- a/core/systems/pool/_public.ex
+++ b/core/systems/pool/_public.ex
@@ -5,6 +5,7 @@ defmodule Systems.Pool.Public do
alias Ecto.Multi
alias Ecto.Changeset
alias Frameworks.Signal
+ alias Frameworks.Concept.Directable
alias Core.{
Repo,
@@ -13,7 +14,6 @@ defmodule Systems.Pool.Public do
}
alias Systems.{
- Director,
Pool,
Bookkeeping,
NextAction,
@@ -120,7 +120,7 @@ defmodule Systems.Pool.Public do
end
def submit(%Pool.SubmissionModel{id: id, pool: pool}) do
- Director.get(pool).submit(id)
+ Directable.director(pool).submit(id)
end
def list_directors() do
@@ -129,7 +129,7 @@ defmodule Systems.Pool.Public do
select: p.director
)
|> Repo.all()
- |> Enum.map(&Systems.Director.get/1)
+ |> Enum.map(&Directable.director/1)
end
def get!(id, preload \\ []), do: Repo.get!(Pool.Model, id) |> Repo.preload(preload)
@@ -229,7 +229,7 @@ defmodule Systems.Pool.Public do
end
def get_or_create_budget(%Pool.Model{} = pool) do
- Director.get(pool).create_budget(pool)
+ Directable.director(pool).create_budget(pool)
end
def create!(name, target, currency, org, director) do
@@ -290,7 +290,7 @@ defmodule Systems.Pool.Public do
Multi.new()
|> Multi.update(:submission, changeset)
|> Multi.run(:dispatch, fn _, %{submission: submission} ->
- Signal.Public.dispatch!(:submission_updated, submission)
+ Signal.Public.dispatch!({:submission, :updated}, submission)
{:ok, true}
end)
|> Multi.run(:notify, fn _, %{submission: submission} ->
@@ -309,7 +309,7 @@ defmodule Systems.Pool.Public do
Multi.new()
|> Multi.update(:criteria, changeset)
|> Multi.run(:dispatch, fn _, %{criteria: criteria} ->
- Signal.Public.dispatch!(:criteria_updated, criteria)
+ Signal.Public.dispatch!({:criteria, :updated}, criteria)
end)
|> Repo.transaction()
end
diff --git a/core/systems/pool/_switch.ex b/core/systems/pool/_switch.ex
index 43943c765..0c7b7ee62 100644
--- a/core/systems/pool/_switch.ex
+++ b/core/systems/pool/_switch.ex
@@ -1,65 +1,27 @@
defmodule Systems.Pool.Switch do
use Frameworks.Signal.Handler
- alias Frameworks.Signal
- alias Core.Authorization
- alias Core.Accounts.User
- alias Systems.Campaign
- alias Core.Repo
- import Ecto.Query
-
alias Systems.{
Pool
}
- def dispatch(:criteria_updated, %{submission_id: submission_id} = _criteria) do
- submission = Pool.Public.get_submission!(submission_id)
- Signal.Public.dispatch!(:submission_updated, submission)
- end
-
- def dispatch(:submission_updated, %{pool_id: pool_id} = submission) do
- Pool.Public.get!(pool_id)
- |> push_update(pool_id, Pool.DetailPage)
-
- submission
- |> push_update(submission.id, Pool.SubmissionPage)
- end
-
@impl true
- def dispatch(:campaign_created, %{campaign: campaign}) do
- from(u in User, where: u.coordinator == true)
- |> Repo.all()
- |> Enum.each(fn user ->
- Authorization.assign_role(user, campaign, :coordinator)
- end)
+ def intercept({:criteria, :updated} = signal, %{submission_id: submission_id} = _criteria) do
+ dispatch!({:submission, signal}, Pool.Public.get_submission!(submission_id))
end
@impl true
- def dispatch(:user_profile_updated, %{user: user, user_changeset: user_changeset}) do
- if Map.has_key?(user_changeset.changes, :coordinator) do
- from(s in Campaign.Model)
- |> Repo.all()
- |> Enum.each(fn campaign ->
- update_coordinator_role(campaign, user, user_changeset.changes.coordinator)
- end)
- end
- end
-
- defp update_coordinator_role(campaign, user, assign?) do
- if assign? do
- Authorization.assign_role(user, campaign, :coordinator)
- else
- Authorization.remove_role!(user, campaign, :coordinator)
- end
+ def intercept({:submission, _} = signal, submission) do
+ update_page(Pool.SubmissionPage, submission)
+ dispatch!({:pool, signal}, Pool.Public.get_by_submission!(submission))
end
- defp push_update(%Pool.Model{} = pool, id, page) do
- Signal.Public.dispatch!(%{page: page}, %{id: id, model: pool})
- pool
+ @impl true
+ def intercept({:pool, _}, pool) do
+ update_page(Pool.DetailPage, pool)
end
- defp push_update(%Pool.SubmissionModel{} = submission, id, page) do
- Signal.Public.dispatch!(%{page: page}, %{id: id, model: submission})
- submission
+ defp update_page(page, %{id: id} = model) do
+ dispatch!({:page, page}, %{id: id, model: model})
end
end
diff --git a/core/systems/pool/builders/campaign_item.ex b/core/systems/pool/builders/campaign_item.ex
index 41c8d730c..93d653242 100644
--- a/core/systems/pool/builders/campaign_item.ex
+++ b/core/systems/pool/builders/campaign_item.ex
@@ -21,7 +21,7 @@ defmodule Systems.Pool.Builders.CampaignItem do
},
promotable:
%{
- assignable_experiment: %{
+ assignable_inquiry: %{
subject_count: target_subject_count
}
} = assignment
diff --git a/core/systems/pool/model.ex b/core/systems/pool/model.ex
index ca51efa6f..c268c18ed 100644
--- a/core/systems/pool/model.ex
+++ b/core/systems/pool/model.ex
@@ -44,6 +44,10 @@ defmodule Systems.Pool.Model do
def id(pool), do: pool.auth_node_id
end
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Concept.System.director(director)
+ end
+
@fields ~w(name virtual_icon target director archived)a
@required_fields @fields
diff --git a/core/systems/pool/pages/detail_page.ex b/core/systems/pool/pages/detail_page.ex
index 559d54ae0..e88e157c1 100644
--- a/core/systems/pool/pages/detail_page.ex
+++ b/core/systems/pool/pages/detail_page.ex
@@ -5,6 +5,7 @@ defmodule Systems.Pool.DetailPage do
use CoreWeb, :live_view
use CoreWeb.Layouts.Workspace.Component, :pool_detail
use CoreWeb.UI.Responsive.Viewport
+ use Systems.Observatory.Public
import CoreWeb.Layouts.Workspace.Component
alias CoreWeb.UI.Tabbar
@@ -18,7 +19,7 @@ defmodule Systems.Pool.DetailPage do
@impl true
def mount(%{"id" => pool_id, "tab" => initial_tab}, _session, socket) do
pool_id = String.to_integer(pool_id)
- model = Pool.Public.get!(pool_id)
+ model = Pool.Public.get!(pool_id, Pool.Model.preload_graph([:org, :currency, :participants]))
tabbar_id = "pool_detail/#{pool_id}"
{
diff --git a/core/systems/pool/pages/participant_page.ex b/core/systems/pool/pages/participant_page.ex
index 3563f9ef8..7d5461eb6 100644
--- a/core/systems/pool/pages/participant_page.ex
+++ b/core/systems/pool/pages/participant_page.ex
@@ -24,7 +24,7 @@ defmodule Systems.Pool.ParticipantPage do
Budget.Public.list_wallets(user)
|> Enum.map(&Budget.WalletViewBuilder.view_model(&1, assigns))
- campaign_preload = Campaign.Model.preload_graph(:full)
+ campaign_preload = Campaign.Model.preload_graph(:down)
contributions =
user
diff --git a/core/systems/pool/pages/submission_page.ex b/core/systems/pool/pages/submission_page.ex
index 950a38f28..d15cedd81 100644
--- a/core/systems/pool/pages/submission_page.ex
+++ b/core/systems/pool/pages/submission_page.ex
@@ -5,6 +5,7 @@ defmodule Systems.Pool.SubmissionPage do
use CoreWeb, :live_view
use CoreWeb.Layouts.Workspace.Component, :pool_submission
use CoreWeb.UI.PlainDialog
+ use Systems.Observatory.Public
import CoreWeb.Gettext
@@ -23,8 +24,9 @@ defmodule Systems.Pool.SubmissionPage do
@impl true
def mount(%{"id" => id}, _session, socket) do
submission_id = String.to_integer(id)
- %{pool: %{director: director}} = Pool.Public.get_submission!(submission_id, [:pool])
- model = %{id: submission_id, director: director}
+
+ model =
+ Pool.Public.get_submission!(submission_id, pool: Pool.Model.preload_graph([:org, :currency]))
{
:ok,
diff --git a/core/systems/pool/submission_model.ex b/core/systems/pool/submission_model.ex
index c142c4203..3e9cbcc6f 100644
--- a/core/systems/pool/submission_model.ex
+++ b/core/systems/pool/submission_model.ex
@@ -26,8 +26,6 @@ defmodule Systems.Pool.SubmissionModel do
has_one(:criteria, Pool.CriteriaModel, foreign_key: :submission_id)
belongs_to(:pool, Pool.Model, on_replace: :update)
- field(:director, Ecto.Enum, values: [:campaign])
-
timestamps()
end
@@ -77,7 +75,6 @@ defmodule Systems.Pool.SubmissionModel do
def changeset(submission, attrs) do
submission
- |> cast(attrs, [:director])
|> cast(attrs, @fields)
end
diff --git a/core/systems/pool/views/campaign_submission_view.ex b/core/systems/pool/views/campaign_submission_view.ex
index 9d6149898..5739a19e4 100644
--- a/core/systems/pool/views/campaign_submission_view.ex
+++ b/core/systems/pool/views/campaign_submission_view.ex
@@ -4,6 +4,7 @@ defmodule Systems.Pool.CampaignSubmissionView do
alias Core.Enums.{Genders, DominantHands, NativeLanguages}
alias Frameworks.Pixel.Selector
alias Frameworks.Pixel.Text
+ alias Frameworks.Concept.Directable
alias Systems.{
Campaign,
@@ -153,7 +154,7 @@ defmodule Systems.Pool.CampaignSubmissionView do
criteria
) do
inclusion_labels =
- Systems.Director.get(pool).inclusion_criteria()
+ Directable.director(pool).inclusion_criteria()
|> Enum.map(&get_inclusion_labels(&1, criteria))
|> Map.new()
diff --git a/core/systems/privacy/form.ex b/core/systems/privacy/form.ex
index 45b49ded5..6ef57c2f4 100644
--- a/core/systems/privacy/form.ex
+++ b/core/systems/privacy/form.ex
@@ -6,7 +6,7 @@ defmodule Systems.Privacy.Form do
~H"""
+
+
+
+
+ <% else %>
+
+ <% end %>
+
+
+ <%= if @entity.archive_name do %>
+ <%= @entity.archive_name %>
+ <% else %>
+ <%= @placeholder %>
+ <% end %>
+
+ <%= if @entity.archive_name do %>
+
+ <.live_file_input upload={@uploads.file} />
+
+
+ <%= @title %>
+ <.spacing value="S" />
+ <.live_component
+ module={Selector}
+ id={:template_selector}
+ items={@template_labels}
+ type={:radio}
+ optional?={false}
+ parent={%{type: __MODULE__, id: @id}}
+ />
+
+ <.spacing value="M" />
+
+ """
+ end
+end
diff --git a/core/systems/project/create_project_popup.ex b/core/systems/project/create_project_popup.ex
index a3afb54da..494d5e841 100644
--- a/core/systems/project/create_project_popup.ex
+++ b/core/systems/project/create_project_popup.ex
@@ -96,7 +96,7 @@ defmodule Systems.Project.CreatePopup do
~H"""
+ <%= for button <- @buttons do %>
+
+ <% end %>
+
+
-
-
- """
- end
-end
diff --git a/core/systems/project/item_form.ex b/core/systems/project/item_form.ex
new file mode 100644
index 000000000..d15e249c7
--- /dev/null
+++ b/core/systems/project/item_form.ex
@@ -0,0 +1,77 @@
+defmodule Systems.Project.ItemForm do
+ use CoreWeb.LiveForm
+
+ alias Systems.{
+ Project
+ }
+
+ # Handle initial update
+ @impl true
+ def update(
+ %{id: id, entity: item, target: target},
+ socket
+ ) do
+ changeset = Project.ItemModel.changeset(item, %{})
+
+ close_button = %{
+ action: %{type: :send, event: "close"},
+ face: %{type: :icon, icon: :close}
+ }
+
+ {
+ :ok,
+ socket
+ |> assign(
+ id: id,
+ entity: item,
+ target: target,
+ close_button: close_button,
+ changeset: changeset
+ )
+ }
+ end
+
+ # Handle Events
+ @impl true
+ def handle_event("close", _params, socket) do
+ send(self(), %{module: __MODULE__, action: :close})
+ {:noreply, socket}
+ end
+
+ @impl true
+ def handle_event("save", %{"item_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
+ {
+ :noreply,
+ socket
+ |> save(entity, attrs)
+ }
+ end
+
+ # Saving
+
+ def save(socket, entity, attrs) do
+ changeset = Project.ItemModel.changeset(entity, attrs)
+
+ socket
+ |> save(changeset)
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
-
- <%= if @popup do %>
- <.popup>
-
-
-
-
- <.live_component module={@popup.module} {@popup.props} />
-
-
- <% end %>
-
- <%= if @dialog do %>
- <.popup>
- <.plain_dialog {@dialog} />
-
- <% end %>
-
-
+
+ """
+ end
+end
diff --git a/core/systems/project/item_model.ex b/core/systems/project/item_model.ex
index 23310d9c6..fd208a82c 100644
--- a/core/systems/project/item_model.ex
+++ b/core/systems/project/item_model.ex
@@ -7,7 +7,7 @@ defmodule Systems.Project.ItemModel do
alias Systems.{
Project,
- DataDonation,
+ Assignment,
Benchmark
}
@@ -16,6 +16,7 @@ defmodule Systems.Project.ItemModel do
field(:project_path, {:array, :integer})
belongs_to(:node, Project.NodeModel)
belongs_to(:tool_ref, Project.ToolRefModel)
+ belongs_to(:assignment, Assignment.Model)
timestamps()
end
@@ -29,39 +30,75 @@ defmodule Systems.Project.ItemModel do
|> validate_required(@required_fields)
end
+ def preload_graph(:up),
+ do:
+ preload_graph([
+ :node
+ ])
+
def preload_graph(:down),
do:
preload_graph([
- :node,
- :tool_ref
+ :tool_ref,
+ :assignment
])
def preload_graph(:node), do: [node: [:parent, :children, :items, :auth_node]]
def preload_graph(:tool_ref), do: [tool_ref: Project.ToolRefModel.preload_graph(:down)]
+ def preload_graph(:assignment), do: [assignment: Assignment.Model.preload_graph(:down)]
+
+ def special(%{tool_ref: %{id: _id} = special}), do: special
+ def special(%{assignment: %{id: _id} = special}), do: special
+
+ def auth_tree(%Project.ItemModel{tool_ref: %Ecto.Association.NotLoaded{}} = item) do
+ auth_tree(Repo.preload(item, :tool_ref))
+ end
+
+ def auth_tree(%Project.ItemModel{tool_ref: tool_ref}) when not is_nil(tool_ref) do
+ Project.ToolRefModel.auth_tree(tool_ref)
+ end
+
+ def auth_tree(%Project.ItemModel{assignment: %Ecto.Association.NotLoaded{}} = item) do
+ auth_tree(Repo.preload(item, :assignment))
+ end
+
+ def auth_tree(%Project.ItemModel{assignment: assignment}) when not is_nil(assignment) do
+ Assignment.Model.auth_tree(assignment)
+ end
+
+ def auth_tree(items) when is_list(items) do
+ Enum.map(items, &auth_tree/1)
+ end
defimpl Frameworks.Utility.ViewModelBuilder do
use CoreWeb, :verified_routes
- def view_model(%Project.ItemModel{} = project, page, %{current_user: user}) do
- vm(project, page, user)
+ def view_model(%Project.ItemModel{} = item, page, %{current_user: user}) do
+ vm(item, page, user)
end
defp vm(
%{
id: id,
name: name,
- tool_ref: %{
- data_donation_tool:
- %{
+ assignment:
+ %{
+ id: assignment_id,
+ info: %{
subject_count: subject_count
- } = tool
- }
+ }
+ } = assignment
},
{Project.NodePage, :item_card},
_user
) do
- tags = get_card_tags(tool)
- path = ~p"/project/item/#{id}/content"
+ tags = get_card_tags(assignment)
+ path = ~p"/assignment/#{assignment_id}/content"
+
+ edit = %{
+ action: %{type: :send, event: "edit", item: id},
+ face: %{type: :label, label: "Edit", wrap: true}
+ }
delete = %{
action: %{type: :send, event: "delete", item: id},
@@ -76,7 +113,7 @@ defmodule Systems.Project.ItemModel do
title: name,
tags: tags,
info: ["#{subject_count} participants | 0 donations"],
- left_actions: [],
+ left_actions: [edit],
right_actions: [delete]
}
end
@@ -88,6 +125,7 @@ defmodule Systems.Project.ItemModel do
tool_ref: %{
benchmark_tool:
%{
+ id: benchmark_id,
status: status,
director: _director,
spots: spots,
@@ -99,9 +137,14 @@ defmodule Systems.Project.ItemModel do
_user
) do
tags = get_card_tags(tool)
- path = ~p"/project/item/#{id}/content"
+ path = ~p"/benchmark/#{benchmark_id}/content"
label = get_label(status)
+ edit = %{
+ action: %{type: :send, event: "edit", item: id},
+ face: %{type: :label, label: "Edit", wrap: true}
+ }
+
delete = %{
action: %{type: :send, event: "delete", item: id},
face: %{type: :icon, icon: :delete}
@@ -127,7 +170,7 @@ defmodule Systems.Project.ItemModel do
title: name,
tags: tags,
info: [info_line_1],
- left_actions: [],
+ left_actions: [edit],
right_actions: [delete]
}
end
@@ -150,8 +193,9 @@ defmodule Systems.Project.ItemModel do
defp get_label(:idle), do: %{type: :idle, text: dgettext("eyra-project", "label.idle")}
- defp get_card_tags(%DataDonation.ToolModel{platforms: platforms}) when is_list(platforms),
- do: Enum.map(platforms, &DataDonation.Platforms.translate(&1))
+ defp get_card_tags(%Assignment.Model{}) do
+ ["Assignment"]
+ end
defp get_card_tags(%Benchmark.ToolModel{}), do: ["Challenge"]
defp get_card_tags(_), do: []
diff --git a/core/systems/project/model.ex b/core/systems/project/model.ex
index 6ecda4b9f..51b207655 100644
--- a/core/systems/project/model.ex
+++ b/core/systems/project/model.ex
@@ -25,7 +25,7 @@ defmodule Systems.Project.Model do
|> validate_required(@required_fields)
end
- def preload_graph(:full),
+ def preload_graph(:down),
do:
preload_graph([
:root,
@@ -35,6 +35,10 @@ defmodule Systems.Project.Model do
def preload_graph(:root), do: [root: Project.NodeModel.preload_graph(:down)]
def preload_graph(:auth_node), do: [auth_node: []]
+ def auth_tree(%Project.Model{auth_node: auth_node, root: root}) do
+ {auth_node, Project.NodeModel.auth_tree(root)}
+ end
+
defimpl Frameworks.GreenLight.AuthorizationNode do
def id(project), do: project.auth_node_id
end
diff --git a/core/systems/project/node_model.ex b/core/systems/project/node_model.ex
index eba6da06f..efaffee9d 100644
--- a/core/systems/project/node_model.ex
+++ b/core/systems/project/node_model.ex
@@ -41,6 +41,22 @@ defmodule Systems.Project.NodeModel do
def preload_graph(:items), do: [items: Project.ItemModel.preload_graph(:down)]
def preload_graph(:auth_node), do: [auth_node: []]
+ def auth_tree(%Project.NodeModel{children: %Ecto.Association.NotLoaded{}} = node) do
+ auth_tree(Repo.preload(node, :children))
+ end
+
+ def auth_tree(%Project.NodeModel{items: %Ecto.Association.NotLoaded{}} = node) do
+ auth_tree(Repo.preload(node, :items))
+ end
+
+ def auth_tree(%Project.NodeModel{auth_node: auth_node, children: children, items: items}) do
+ {auth_node, auth_tree(children) ++ Project.ItemModel.auth_tree(items)}
+ end
+
+ def auth_tree(nodes) when is_list(nodes) do
+ Enum.map(nodes, &auth_tree/1)
+ end
+
defimpl Frameworks.GreenLight.AuthorizationNode do
def id(project_node), do: project_node.auth_node_id
end
diff --git a/core/systems/project/node_page.ex b/core/systems/project/node_page.ex
index 12f2db212..4c8e1350d 100644
--- a/core/systems/project/node_page.ex
+++ b/core/systems/project/node_page.ex
@@ -2,6 +2,7 @@ defmodule Systems.Project.NodePage do
use CoreWeb, :live_view
use CoreWeb.Layouts.Workspace.Component, :projects
use CoreWeb.UI.PlainDialog
+ use Systems.Observatory.Public
import CoreWeb.Layouts.Workspace.Component
@@ -12,12 +13,13 @@ defmodule Systems.Project.NodePage do
}
def mount(%{"id" => id}, _session, socket) do
- model = %{id: String.to_integer(id), director: :project}
+ model =
+ Project.Public.get_node!(String.to_integer(id), Project.NodeModel.preload_graph(:down))
{
:ok,
socket
- |> assign(model: model)
+ |> assign(model: model, popup: nil)
|> observe_view_model()
|> update_menus()
}
@@ -27,6 +29,19 @@ defmodule Systems.Project.NodePage do
socket |> update_menus()
end
+ @impl true
+ def handle_event("edit", %{"item" => item_id}, socket) do
+ item = Project.Public.get_item!(String.to_integer(item_id))
+
+ popup = %{
+ module: Project.ItemForm,
+ entity: item,
+ target: self()
+ }
+
+ {:noreply, assign(socket, popup: popup)}
+ end
+
@impl true
def handle_event("delete", %{"item" => item_id}, socket) do
Project.Public.delete_item(String.to_integer(item_id))
@@ -39,6 +54,20 @@ defmodule Systems.Project.NodePage do
}
end
+ @impl true
+ def handle_event("create_item", _params, %{assigns: %{vm: %{node: node}}} = socket) do
+ popup = %{
+ module: Project.CreateItemPopup,
+ target: self(),
+ node: node
+ }
+
+ {
+ :noreply,
+ socket |> assign(popup: popup)
+ }
+ end
+
@impl true
def handle_event(
"card_clicked",
@@ -50,6 +79,25 @@ defmodule Systems.Project.NodePage do
{:noreply, push_redirect(socket, to: path)}
end
+ @impl true
+ def handle_info(%{module: _, action: :close}, socket) do
+ {
+ :noreply,
+ socket
+ |> assign(popup: nil)
+ |> update_view_model()
+ }
+ end
+
+ @impl true
+ def handle_info({:handle_auto_save_done, :node_page_popup}, socket) do
+ {
+ :noreply,
+ socket
+ |> update_view_model()
+ }
+ end
+
@doc """
## Attributes
- vm: Observed view model: title, node_cards, item_cards
@@ -59,6 +107,15 @@ defmodule Systems.Project.NodePage do
def render(assigns) do
~H"""
<.workspace title={@vm.title} menus={@menus}>
+
+ <%= if @popup do %>
+ <.popup>
+
+
+
+
+ <.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself} >
+ <.text_input form={form} field={:name} label_text={dgettext("eyra-project", "item.form.name.label")} />
+
+
+ <%= dgettext("eyra-project", "item.form.title") %>
+
+
+
+ <.live_component id={:node_page_popup} module={@popup.module} {@popup} />
+
+
+ <% end %>
+
+ <.function_component function={@work.function} props={@work.props} />
+
+ """
+ end
+end
diff --git a/core/systems/project/tools.ex b/core/systems/project/tools.ex
deleted file mode 100644
index 2401794f8..000000000
--- a/core/systems/project/tools.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule Systems.Project.Tools do
- use Core.Enums.Base, {:project_tools, [:data_donation, :benchmark]}
-end
diff --git a/core/systems/promotion/_presenter.ex b/core/systems/promotion/_presenter.ex
new file mode 100644
index 000000000..cef338448
--- /dev/null
+++ b/core/systems/promotion/_presenter.ex
@@ -0,0 +1,10 @@
+defmodule Systems.Promotion.Presenter do
+ @behaviour Frameworks.Concept.Presenter
+
+ alias Systems.Promotion
+
+ @impl true
+ def view_model(%Promotion.Model{director: director} = promotion, page, assigns) do
+ Frameworks.Concept.System.presenter(director).view_model(promotion, page, assigns)
+ end
+end
diff --git a/core/systems/promotion/_public.ex b/core/systems/promotion/_public.ex
index 65c70c2bb..5b2403e8d 100644
--- a/core/systems/promotion/_public.ex
+++ b/core/systems/promotion/_public.ex
@@ -43,7 +43,7 @@ defmodule Systems.Promotion.Public do
|> Repo.transaction()
with {:ok, %{promotion: promotion}} <- result do
- Signal.Public.dispatch!(:promotion_updated, promotion)
+ Signal.Public.dispatch!({:promotion, :updated}, promotion)
end
result
diff --git a/core/systems/promotion/landing_page.ex b/core/systems/promotion/landing_page.ex
index 84b1aa3cb..4405ea6f1 100644
--- a/core/systems/promotion/landing_page.ex
+++ b/core/systems/promotion/landing_page.ex
@@ -5,6 +5,7 @@ defmodule Systems.Promotion.LandingPage do
use CoreWeb, :live_view
use CoreWeb.UI.PlainDialog
use CoreWeb.Layouts.Website.Component, :promotion
+ use Systems.Observatory.Public
import CoreWeb.UI.Responsive.Viewport
diff --git a/core/systems/promotion/model.ex b/core/systems/promotion/model.ex
index 22a639c15..bb521a134 100644
--- a/core/systems/promotion/model.ex
+++ b/core/systems/promotion/model.ex
@@ -49,6 +49,10 @@ defmodule Systems.Promotion.Model do
def id(promotion), do: promotion.auth_node_id
end
+ defimpl Frameworks.Concept.Directable do
+ def director(%{director: director}), do: Frameworks.Utility.Module.get(director, "Director")
+ end
+
def preload_graph(:full) do
[:submission]
end
diff --git a/core/systems/routes.ex b/core/systems/routes.ex
index a7b2cb944..5cd0fd3f7 100644
--- a/core/systems/routes.ex
+++ b/core/systems/routes.ex
@@ -13,8 +13,10 @@ defmodule Systems.Routes do
:promotion,
:pool,
:lab,
- :data_donation,
:benchmark,
+ :feldspar,
+ :document,
+ :alliance,
:budget
]
end
diff --git a/core/systems/student/_director.ex b/core/systems/student/_director.ex
index 7b6f94527..0f76fbad8 100644
--- a/core/systems/student/_director.ex
+++ b/core/systems/student/_director.ex
@@ -4,7 +4,7 @@ defmodule Systems.Student.Director do
defexception [:message]
end
- @behaviour Systems.Pool.External
+ @behaviour Frameworks.Concept.PoolDirector
alias CoreWeb.UI.Timestamp
diff --git a/core/systems/student/_presenter.ex b/core/systems/student/_presenter.ex
index 1bc8b8cc8..95681c1be 100644
--- a/core/systems/student/_presenter.ex
+++ b/core/systems/student/_presenter.ex
@@ -1,25 +1,18 @@
defmodule Systems.Student.Presenter do
- use Systems.Presenter
+ @behaviour Frameworks.Concept.Presenter
alias Systems.{
Student,
Pool
}
- @impl true
- def view_model(id, Pool.SubmissionPage = page, assigns) when is_integer(id) do
- Pool.Public.get_submission!(id, pool: Pool.Model.preload_graph([:org, :currency]))
- |> view_model(page, assigns)
- end
-
@impl true
def view_model(%Pool.SubmissionModel{} = submission, Pool.SubmissionPage, assigns) do
Student.Pool.SubmissionPageBuilder.view_model(submission, assigns)
end
@impl true
- def view_model(id, Pool.DetailPage, assigns) do
- pool = Pool.Public.get!(id, Pool.Model.preload_graph([:org, :currency, :participants]))
+ def view_model(%Pool.Model{} = pool, Pool.DetailPage, assigns) do
Student.Pool.DetailPageBuilder.view_model(pool, assigns)
end
end
diff --git a/core/systems/student/_switch.ex b/core/systems/student/_switch.ex
index 18270963a..e4973d2b5 100644
--- a/core/systems/student/_switch.ex
+++ b/core/systems/student/_switch.ex
@@ -8,7 +8,7 @@ defmodule Systems.Student.Switch do
alias Core.Accounts
@impl true
- def dispatch(:features_updated, %{features: features, features_changeset: features_changeset}) do
+ def intercept(:features_updated, %{features: features, features_changeset: features_changeset}) do
with %{user_id: user_id, study_program_codes: old_codes} <- features,
%{changes: %{study_program_codes: new_codes}} <- features_changeset do
user = Accounts.get_user!(user_id)
diff --git a/core/systems/student/pool/detail_page_builder.ex b/core/systems/student/pool/detail_page_builder.ex
index c400697ce..35a6b13df 100644
--- a/core/systems/student/pool/detail_page_builder.ex
+++ b/core/systems/student/pool/detail_page_builder.ex
@@ -54,7 +54,7 @@ defmodule Systems.Student.Pool.DetailPageBuilder do
end
defp load_campaigns(pool) do
- preload = Campaign.Model.preload_graph(:full)
+ preload = Campaign.Model.preload_graph(:down)
Campaign.Public.list_submitted(pool, preload: preload)
|> Enum.map(&Campaign.Model.flatten(&1))
diff --git a/core/systems/student/pool/submission_page_builder.ex b/core/systems/student/pool/submission_page_builder.ex
index 3306a9319..608a84f81 100644
--- a/core/systems/student/pool/submission_page_builder.ex
+++ b/core/systems/student/pool/submission_page_builder.ex
@@ -31,7 +31,7 @@ defmodule Systems.Student.Pool.SubmissionPageBuilder do
preview_path = ~p"/promotion/#{promotion.id}?preview=true"
excluded_campaigns =
- Campaign.Public.list_excluded_campaigns([campaign], Campaign.Model.preload_graph(:full))
+ Campaign.Public.list_excluded_campaigns([campaign], Campaign.Model.preload_graph(:down))
|> Enum.map(&Campaign.Model.flatten(&1))
|> Enum.map(&Pool.Builders.CampaignItem.view_model(&1))
diff --git a/core/systems/support/form.ex b/core/systems/support/form.ex
new file mode 100644
index 000000000..da14be4c8
--- /dev/null
+++ b/core/systems/support/form.ex
@@ -0,0 +1,15 @@
+defmodule Systems.Support.Form do
+ use CoreWeb.LiveForm
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+
+
+ <%= dgettext("eyra-support", "content.form.title") %>
+
+
+ """
+ end
+end
diff --git a/core/systems/survey/_public.ex b/core/systems/survey/_public.ex
deleted file mode 100644
index a87793407..000000000
--- a/core/systems/survey/_public.ex
+++ /dev/null
@@ -1,130 +0,0 @@
-defmodule Systems.Survey.Public do
- @moduledoc """
-
- Survey tools allow a researcher to setup a link to an external survey
- tool. The participant goes through the flow described below:
-
- - Receive invitation to start a survey (mail, push etc.).
- - Open survey tool, this opens it on the platform and requires authentication.
- - The participant is then redirected to the survey at a 3rd party web-application.
- - After completion the user is redirect back to the platform.
- - The platform registers the completion of this survey for the participant.
-
-
- A researcher is required to configure the 3rd party application with a redirect
- link. The redirect link to be used is show on the survey tool configuration
- screen (with copy button).
-
- IDEA: The tool requires a sucessful round-trip with a verify flow to ensure
- that everything is configured correctly.
-
- Participants need to be invited to a particular survey explicitly. This avoids
- the situation where a new user joins a study and then can immediately complete
- previous surveys.
-
- Once a participant has completed a survey they are no longer allowed to enter it
- a second time. The status is clearly shown when the attempt to do so.
-
- IDEA: A list of surveys can be access by the notification icon which is shown
- on all screens.
- """
-
- import Ecto.Query, warn: false
- alias Ecto.Multi
- alias Core.Repo
-
- alias Frameworks.{
- Signal
- }
-
- alias Systems.{
- Survey
- }
-
- @doc """
- Returns the list of survey_tools.
- """
- def list_survey_tools do
- Repo.all(Survey.ToolModel)
- end
-
- @doc """
- Gets a single survey_tool.
-
- Raises `Ecto.NoResultsError` if the Survey tool does not exist.
- """
- def get_survey_tool!(id), do: Repo.get!(Survey.ToolModel, id)
- def get_survey_tool(id), do: Repo.get(Survey.ToolModel, id)
-
- @doc """
- Creates a survey_tool.
- """
- def create_tool(attrs, auth_node) do
- %Survey.ToolModel{}
- |> Survey.ToolModel.changeset(:mount, attrs)
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert()
- end
-
- @doc """
- Updates a survey_tool.
- """
- def update_survey_tool(%Survey.ToolModel{} = survey_tool, type, attrs) do
- survey_tool
- |> Survey.ToolModel.changeset(type, attrs)
- |> update_survey_tool()
- end
-
- def update_survey_tool(_, _, _), do: {:error, nil}
-
- def update_survey_tool(changeset) do
- result =
- Multi.new()
- |> Repo.multi_update(:tool, changeset)
- |> Repo.transaction()
-
- with {:ok, %{tool: tool}} <- result do
- Signal.Public.dispatch!(:survey_tool_updated, tool)
- end
-
- result
- end
-
- @doc """
- Deletes a survey_tool.
- """
- def delete_survey_tool(%Survey.ToolModel{} = survey_tool) do
- Repo.delete(survey_tool)
- end
-
- @doc """
- Returns an `%Ecto.Changeset{}` for tracking survey_tool changes.
- """
- def change_survey_tool(%Survey.ToolModel{} = survey_tool, type, attrs \\ %{}) do
- Survey.ToolModel.changeset(survey_tool, type, attrs)
- end
-
- def copy(%Survey.ToolModel{} = tool, auth_node) do
- %Survey.ToolModel{}
- |> Survey.ToolModel.changeset(:copy, Map.from_struct(tool))
- |> Ecto.Changeset.put_assoc(:auth_node, auth_node)
- |> Repo.insert!()
- end
-
- def ready?(%Survey.ToolModel{} = survey_tool) do
- changeset =
- %Survey.ToolModel{}
- |> Survey.ToolModel.operational_changeset(Map.from_struct(survey_tool))
-
- changeset.valid?
- end
-end
-
-defimpl Core.Persister, for: Systems.Survey.ToolModel do
- def save(_tool, changeset) do
- case Systems.Survey.Public.update_survey_tool(changeset) do
- {:ok, %{tool: tool}} -> {:ok, tool}
- _ -> {:error, changeset}
- end
- end
-end
diff --git a/core/systems/test/presenter.ex b/core/systems/test/_presenter.ex
similarity index 93%
rename from core/systems/test/presenter.ex
rename to core/systems/test/_presenter.ex
index e97c4e9ff..06503ed16 100644
--- a/core/systems/test/presenter.ex
+++ b/core/systems/test/_presenter.ex
@@ -1,5 +1,5 @@
defmodule Systems.Test.Presenter do
- use Systems.Presenter
+ use Frameworks.Concept.Presenter
alias Systems.Observatory
diff --git a/core/systems/test/model.ex b/core/systems/test/model.ex
index d54cd1e21..c3459241f 100644
--- a/core/systems/test/model.ex
+++ b/core/systems/test/model.ex
@@ -1,17 +1,17 @@
defmodule Systems.Test.Model do
defstruct [:director, :id, :name, :department, :age]
-end
-defimpl Frameworks.Utility.ViewModelBuilder, for: Systems.Test.Model do
- def view_model(%Systems.Test.Model{} = model, page, _assigns) do
- vm(model, page)
- end
+ defimpl Frameworks.Utility.ViewModelBuilder do
+ def view_model(%Systems.Test.Model{} = model, page, _assigns) do
+ vm(model, page)
+ end
- defp vm(%{id: id, name: name, department: department, age: age}, Systems.Test.Page) do
- %{
- id: id,
- title: name,
- subtitle: "Age: #{age} - Works at: #{department}"
- }
+ defp vm(%{id: id, name: name, department: department, age: age}, Systems.Test.Page) do
+ %{
+ id: id,
+ title: name,
+ subtitle: "Age: #{age} - Works at: #{department}"
+ }
+ end
end
end
diff --git a/core/systems/test/page.ex b/core/systems/test/page.ex
index af96587e9..5cfd3d3a9 100644
--- a/core/systems/test/page.ex
+++ b/core/systems/test/page.ex
@@ -4,6 +4,7 @@ defmodule Systems.Test.Page do
"""
use CoreWeb, :live_view
use CoreWeb.Layouts.Workspace.Component, :test_page
+ use Systems.Observatory.Public
alias Systems.{
Test
diff --git a/core/systems/workflow/_public.ex b/core/systems/workflow/_public.ex
new file mode 100644
index 000000000..6e72585f3
--- /dev/null
+++ b/core/systems/workflow/_public.ex
@@ -0,0 +1,219 @@
+defmodule Systems.Workflow.Public do
+ import Ecto.Query, warn: false
+ alias Ecto.Multi
+ alias Core.Repo
+
+ alias Frameworks.Signal
+
+ alias Systems.Workflow
+ alias Systems.Document
+ alias Systems.Alliance
+ alias Systems.Feldspar
+ alias Systems.Lab
+ alias Systems.Benchmark
+ alias Systems.Project
+
+ def list_items(workflow, preload \\ [])
+ def list_items(%Workflow.Model{id: id}, preload), do: list_items(id, preload)
+
+ def list_items(workflow_id, preload) do
+ from(item in Workflow.ItemModel,
+ where: item.workflow_id == ^workflow_id,
+ order_by: {:asc, :position},
+ preload: ^preload
+ )
+ |> Repo.all()
+ end
+
+ def get!(id, preload \\ []) do
+ from(item in Workflow.Model, preload: ^preload)
+ |> Repo.get!(id)
+ end
+
+ def get_item!(id, preload \\ []) do
+ from(item in Workflow.ItemModel, preload: ^preload)
+ |> Repo.get!(id)
+ end
+
+ def get_item_by_tool_ref(tool_ref, preload \\ [])
+
+ def get_item_by_tool_ref(%Project.ToolRefModel{id: id}, preload) do
+ get_item_by_tool_ref(id, preload)
+ end
+
+ def get_item_by_tool_ref(tool_ref_id, preload) do
+ from(item in Workflow.ItemModel,
+ where: item.tool_ref_id == ^tool_ref_id,
+ preload: ^preload
+ )
+ |> Repo.one()
+ end
+
+ def get_item_by_tool!(field, tool_id, preload \\ [])
+
+ def get_item_by_tool!(field, tool_id, preload) when is_atom(field) do
+ get_item_by_tool!([field], tool_id, preload)
+ end
+
+ def get_item_by_tool!([hd | tl] = _keys, tool_id, preload) when is_integer(tool_id) do
+ query =
+ from(item in Workflow.ItemModel,
+ inner_join: tool_ref in Project.ToolRefModel,
+ on: tool_ref.id == item.tool_ref_id,
+ where: field(tool_ref, ^hd) == ^tool_id,
+ preload: ^preload
+ )
+
+ Enum.reduce(tl, query, fn key, query ->
+ query |> or_where([tool_ref], field(tool_ref, ^key) == ^tool_id)
+ end)
+ |> Repo.one!()
+ end
+
+ def add_item(workflow_id, %{} = item, director) when is_integer(workflow_id) do
+ get!(workflow_id)
+ |> add_item(item, director)
+ end
+
+ def add_item(%Workflow.Model{} = workflow, %{id: id, type: type} = _item, director)
+ when is_atom(director) do
+ Multi.new()
+ |> Multi.run(:position, fn _, _ ->
+ {:ok, item_count(workflow)}
+ end)
+ |> Multi.insert(:tool, prepare_tool(type, %{director: director}))
+ |> Multi.insert(:workflow_item, fn %{position: position, tool: tool} ->
+ tool_ref = prepare_tool_ref(id, type, tool)
+ prepare_item(workflow, position, tool_ref)
+ end)
+ |> Signal.Public.multi_dispatch({:workflow_item, :added})
+ |> Repo.transaction()
+ end
+
+ def item_count(%Workflow.Model{id: workflow_id}) do
+ from(item in Workflow.ItemModel,
+ where: item.workflow_id == ^workflow_id,
+ select: count(item.id)
+ )
+ |> Repo.one()
+ end
+
+ def prepare_item(%Workflow.Model{} = workflow, position, tool_ref) do
+ %Workflow.ItemModel{}
+ |> Workflow.ItemModel.changeset(%{position: position})
+ |> Ecto.Changeset.put_assoc(:workflow, workflow)
+ |> Ecto.Changeset.put_assoc(:tool_ref, tool_ref)
+ end
+
+ def prepare_tool_ref(special, tool_type, tool) do
+ %Project.ToolRefModel{}
+ |> Project.ToolRefModel.changeset(%{special: special})
+ |> Ecto.Changeset.put_assoc(tool_type, tool)
+ end
+
+ defp prepare_tool(:alliance_tool, %{} = attrs), do: Alliance.Public.prepare_tool(attrs)
+ defp prepare_tool(:document_tool, %{} = attrs), do: Document.Public.prepare_tool(attrs)
+ defp prepare_tool(:feldspar_tool, %{} = attrs), do: Feldspar.Public.prepare_tool(attrs)
+ defp prepare_tool(:lab_tool, %{} = attrs), do: Lab.Public.prepare_tool(attrs)
+ defp prepare_tool(:benchmark_tool, %{} = attrs), do: Benchmark.Public.prepare_tool(attrs)
+
+ def delete(%Workflow.ItemModel{workflow_id: workflow_id} = item) do
+ Multi.new()
+ |> Multi.delete(:workflow_item, item)
+ |> Multi.run(:items, fn _, _ ->
+ {:ok, list_items(workflow_id)}
+ end)
+ |> Multi.run(:order_and_update, fn _, %{items: items} ->
+ {:ok, rearrange(items)}
+ end)
+ |> Signal.Public.multi_dispatch({:workflow_item, :deleted})
+ |> Repo.transaction()
+ end
+
+ def update_position(%Workflow.ItemModel{workflow_id: workflow_id, position: old}, new)
+ when old == new,
+ do: {:ok, %{items: list_items(workflow_id)}}
+
+ def update_position(%Workflow.ItemModel{id: id, workflow_id: workflow_id, position: old}, new) do
+ Multi.new()
+ |> Multi.run(:items, fn _, _ ->
+ {:ok, list_items(workflow_id)}
+ end)
+ |> Multi.run(:validate_old_position, fn _, %{items: items} ->
+ validate_old_position(items, id, old)
+ end)
+ |> Multi.run(:validate_new_position, fn _, %{items: items} ->
+ validate_new_position(items, new)
+ end)
+ |> Multi.run(:order_and_update, fn _, %{items: items} ->
+ {:ok, rearrange(items, old, new)}
+ end)
+ |> Signal.Public.multi_dispatch({:workflow, :rearranged}, %{id: id, workflow_id: workflow_id})
+ |> Repo.transaction()
+ end
+
+ def validate_old_position(items, id, pos) do
+ validate_old_position(items, id, pos, Enum.count(items))
+ end
+
+ def validate_old_position([%{id: id_, position: pos_} | _], id, pos, count)
+ when id == id_ and pos == pos_ do
+ if pos >= 0 and pos < count do
+ {:ok, true}
+ else
+ {:error, :out_of_bounds}
+ end
+ end
+
+ def validate_old_position([%{id: id_} | _], id, _, _) when id == id_, do: {:error, :out_of_sync}
+
+ def validate_old_position([_ | tl], id, pos, count),
+ do: validate_old_position(tl, id, pos, count)
+
+ def validate_old_position([], _, _, _), do: {:error, :item_not_found}
+
+ def validate_new_position(items, pos) do
+ if pos >= 0 and pos < Enum.count(items) do
+ {:ok, true}
+ else
+ {:error, :out_of_bounds}
+ end
+ end
+
+ def rearrange(items, old, new) do
+ {item, items} = List.pop_at(items, old)
+
+ items
+ |> List.insert_at(new, item)
+ |> rearrange()
+ end
+
+ def rearrange(items) do
+ items
+ |> Enum.with_index()
+ |> Enum.map(&prepare_update_position/1)
+ |> Enum.map(&Repo.update/1)
+ end
+
+ def prepare_update_position({%Workflow.ItemModel{} = item, index}) do
+ Workflow.ItemModel.changeset(item, %{position: index})
+ end
+end
+
+defimpl Core.Persister, for: Systems.Workflow.Model do
+ def save(_task, changeset) do
+ case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :workflow) do
+ {:ok, %{workflow: workflow}} -> {:ok, workflow}
+ _ -> {:error, changeset}
+ end
+ end
+end
+
+defimpl Core.Persister, for: Systems.Workflow.ItemModel do
+ def save(_task, changeset) do
+ case Frameworks.Utility.EctoHelper.update_and_dispatch(changeset, :workflow_item) do
+ {:ok, %{workflow_item: workflow_item}} -> {:ok, workflow_item}
+ _ -> {:error, changeset}
+ end
+ end
+end
diff --git a/core/systems/workflow/_switch.ex b/core/systems/workflow/_switch.ex
new file mode 100644
index 000000000..67ff08bf3
--- /dev/null
+++ b/core/systems/workflow/_switch.ex
@@ -0,0 +1,46 @@
+defmodule Systems.Workflow.Switch do
+ use Frameworks.Signal.Handler
+
+ alias Frameworks.Signal
+
+ alias Systems.{
+ Workflow,
+ Project
+ }
+
+ @impl true
+ def intercept(
+ {:tool_ref, _} = signal,
+ %{tool_ref: %Project.ToolRefModel{} = tool_ref} = message
+ ) do
+ workflow_item = Workflow.Public.get_item_by_tool_ref(tool_ref, [:workflow, :tool_ref])
+
+ dispatch!(
+ {:workflow_item, signal},
+ Map.merge(message, %{workflow_item: workflow_item})
+ )
+ end
+
+ @impl true
+ def intercept(
+ {:workflow_item, _} = signal,
+ %{workflow_item: %{workflow_id: workflow_id}} = message
+ ) do
+ workflow = Workflow.Public.get!(workflow_id)
+
+ dispatch!(
+ {:workflow, signal},
+ Map.merge(message, %{workflow: workflow})
+ )
+ end
+
+ @impl true
+ def intercept({:workflow, :rearranged} = signal, %{workflow_id: workflow_id} = message) do
+ workflow = Workflow.Public.get!(workflow_id)
+
+ dispatch!(
+ {:workflow, signal},
+ Map.merge(message, %{workflow: workflow})
+ )
+ end
+end
diff --git a/core/systems/workflow/builder_view.ex b/core/systems/workflow/builder_view.ex
new file mode 100644
index 000000000..ef375a8b6
--- /dev/null
+++ b/core/systems/workflow/builder_view.ex
@@ -0,0 +1,123 @@
+defmodule Systems.Workflow.BuilderView do
+ use CoreWeb, :live_component
+
+ import Frameworks.Pixel.SidePanel
+
+ alias Systems.{
+ Workflow
+ }
+
+ import Workflow.ItemViews
+
+ @impl true
+ def update(%{action: "delete", item: item}, socket) do
+ Workflow.Public.delete(item)
+ {:ok, socket}
+ end
+
+ @impl true
+ def update(%{action: "up", item: %{position: position} = item}, socket) do
+ {:ok, _} = Workflow.Public.update_position(item, position - 1)
+ {:ok, socket}
+ end
+
+ @impl true
+ def update(%{action: "down", item: %{position: position} = item}, socket) do
+ {:ok, _} = Workflow.Public.update_position(item, position + 1)
+ {:ok, socket}
+ end
+
+ @impl true
+ def update(
+ %{
+ id: id,
+ workflow: %{items: items} = workflow,
+ config: config,
+ user: user,
+ uri_origin: uri_origin
+ },
+ socket
+ ) do
+ ordering_enabled? = Enum.count(items) > 1
+
+ {
+ :ok,
+ socket
+ |> assign(
+ id: id,
+ workflow: workflow,
+ config: config,
+ user: user,
+ uri_origin: uri_origin,
+ ordering_enabled?: ordering_enabled?
+ )
+ |> order_items()
+ |> update_item_types()
+ }
+ end
+
+ @impl true
+ def handle_event(
+ "add",
+ %{"item" => item_id},
+ %{assigns: %{workflow: %{id: id}, config: %{director: director}}} = socket
+ ) do
+ item = get_library_item(socket, item_id)
+ {:ok, _} = Workflow.Public.add_item(id, item, director)
+
+ {
+ :noreply,
+ socket
+ }
+ end
+
+ defp order_items(%{assigns: %{workflow: workflow}} = socket) do
+ assign(socket, ordered_items: Workflow.Model.ordered_items(workflow))
+ end
+
+ defp update_item_types(%{assigns: %{ordered_items: ordered_items}} = socket) do
+ item_types = Enum.map(ordered_items, &get_title(&1, socket))
+ assign(socket, item_types: item_types)
+ end
+
+ defp get_title(%{tool_ref: %{special: special}}, %{
+ assigns: %{config: %{library: %{items: library_items}}}
+ }) do
+ %{title: title} = Enum.find(library_items, &(&1.id == special))
+ title
+ end
+
+ defp get_library_item(socket, item_id) when is_binary(item_id) do
+ get_library_item(socket, String.to_existing_atom(item_id))
+ end
+
+ defp get_library_item(%{assigns: %{config: %{library: %{items: items}}}}, item_id)
+ when is_atom(item_id) do
+ Enum.find(items, &(&1.id == item_id))
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+
+ """
+ end
+end
diff --git a/core/systems/workflow/item_cell.ex b/core/systems/workflow/item_cell.ex
new file mode 100644
index 000000000..6cefde422
--- /dev/null
+++ b/core/systems/workflow/item_cell.ex
@@ -0,0 +1,197 @@
+defmodule Systems.Workflow.ItemCell do
+ use CoreWeb, :live_component
+
+ alias Frameworks.Concept
+
+ alias Systems.{
+ Project,
+ Workflow
+ }
+
+ @impl true
+ def update(
+ %{
+ id: id,
+ type: type,
+ item: item,
+ parent: parent,
+ relative_position: relative_position,
+ user: user,
+ uri_origin: uri_origin,
+ ordering_enabled?: ordering_enabled?
+ },
+ socket
+ ) do
+ {
+ :ok,
+ socket
+ |> assign(
+ id: id,
+ type: type,
+ item: item,
+ parent: parent,
+ relative_position: relative_position,
+ user: user,
+ uri_origin: uri_origin,
+ ordering_enabled?: ordering_enabled?
+ )
+ |> update_item_view()
+ |> update_item_form()
+ |> update_tool_form()
+ |> update_ready()
+ |> update_buttons()
+ }
+ end
+
+ @impl true
+ def handle_event(action, _params, %{assigns: %{parent: parent, item: item}} = socket) do
+ update_target(parent, %{module: __MODULE__, action: action, item: item})
+ {:noreply, socket}
+ end
+
+ defp update_item_view(%{assigns: %{item: %{title: _title}}} = socket) do
+ item_view = %{
+ function: &Workflow.ItemViews.collapsed/1,
+ props: %{
+ # title: title,
+ inner_block: nil
+ }
+ }
+
+ socket |> assign(item_view: item_view)
+ end
+
+ defp update_item_form(%{assigns: %{id: id, item: %{tool_ref: tool_ref} = item}} = socket) do
+ group_enabled? =
+ Project.ToolRefModel.flatten(tool_ref)
+ |> Concept.ToolModel.group_enabled?()
+
+ item_form = %{
+ id: "#{id}_item_form",
+ module: Workflow.ItemForm,
+ entity: item,
+ group_enabled?: group_enabled?
+ }
+
+ socket |> assign(item_form: item_form)
+ end
+
+ defp update_tool_form(
+ %{
+ assigns: %{
+ id: id,
+ item: %{id: item_id, tool_ref: tool_ref},
+ user: user,
+ uri_origin: uri_origin
+ }
+ } = socket
+ ) do
+ tool = Project.ToolRefModel.flatten(tool_ref)
+ tool_form_module = Concept.ToolModel.form(tool)
+
+ callback_path = ~p"/assignment/callback/#{item_id}"
+ callback_url = uri_origin <> callback_path
+
+ tool_form = %{
+ id: "#{id}_tool_form",
+ module: tool_form_module,
+ entity: tool,
+ callback_url: callback_url,
+ user: user
+ }
+
+ socket |> assign(tool_form: tool_form)
+ end
+
+ defp update_ready(%{assigns: %{item: item}} = socket) do
+ ready? = Workflow.ItemModel.ready?(item)
+ assign(socket, ready?: ready?)
+ end
+
+ defp update_buttons(socket) do
+ up_button = %{
+ action: %{type: :send, event: "up"},
+ face: %{type: :icon, icon: :arrow_up}
+ }
+
+ down_button = %{
+ action: %{type: :send, event: "down"},
+ face: %{type: :icon, icon: :arrow_down}
+ }
+
+ delete_button = %{
+ action: %{type: :send, event: "delete"},
+ face: %{type: :icon, icon: :delete_red}
+ }
+
+ collapse_button = %{
+ action: %{type: :fake},
+ face: %{
+ type: :label,
+ label: dgettext("eyra-ui", "collapse.button"),
+ text_color: "text-primary",
+ icon: :chevron_up
+ }
+ }
+
+ expand_button = %{
+ action: %{type: :fake},
+ face: %{
+ type: :label,
+ label: dgettext("eyra-ui", "expand.button"),
+ text_color: "text-primary",
+ icon: :chevron_down
+ }
+ }
+
+ assign(socket,
+ up_button: up_button,
+ down_button: down_button,
+ delete_button: delete_button,
+ collapse_button: collapse_button,
+ expand_button: expand_button
+ )
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+
+
+
+
+ <%= @config.list.title %>
+ <%= @config.list.description %>
+ <.spacing value="M" />
+ <.list items={@ordered_items} types={@item_types} ordering_enabled?={@ordering_enabled?} user={@user} uri_origin={@uri_origin} parent={%{type: __MODULE__, id: @id}} />
+
+
+
+ <.side_panel id={:library} parent={:item_builder}>
+
+ <.library {@config.library} />
+
+
+
+
+ """
+ end
+end
diff --git a/core/systems/workflow/item_form.ex b/core/systems/workflow/item_form.ex
new file mode 100644
index 000000000..8b64ea522
--- /dev/null
+++ b/core/systems/workflow/item_form.ex
@@ -0,0 +1,97 @@
+defmodule Systems.Workflow.ItemForm do
+ use CoreWeb.LiveForm
+
+ import Frameworks.Pixel.Form
+
+ alias Systems.{
+ Workflow
+ }
+
+ @impl true
+ def update(
+ %{id: id, entity: entity, group_enabled?: group_enabled?},
+ socket
+ ) do
+ changeset = Workflow.ItemModel.changeset(entity, %{})
+
+ {
+ :ok,
+ socket
+ |> assign(
+ id: id,
+ entity: entity,
+ changeset: changeset,
+ group_enabled?: group_enabled?
+ )
+ |> update_group_options()
+ |> update_selected_group()
+ }
+ end
+
+ defp update_selected_group(%{assigns: %{entity: %{group: group}}} = socket) do
+ assign(socket, selected_group: group)
+ end
+
+ defp update_group_options(socket) do
+ group_options = Workflow.Platforms.labels()
+ assign(socket, group_options: group_options)
+ end
+
+ # Handle Events
+ @impl true
+ def handle_event("select-option", %{"id" => group}, %{assigns: %{entity: entity}} = socket) do
+ {
+ :noreply,
+ socket
+ |> assign(selected_group: group)
+ |> save(entity, %{group: group})
+ }
+ end
+
+ @impl true
+ def handle_event("save", %{"item_model" => attrs}, %{assigns: %{entity: entity}} = socket) do
+ {
+ :noreply,
+ socket
+ |> save(entity, attrs)
+ }
+ end
+
+ @impl true
+ def handle_event("change", _params, socket) do
+ {
+ :noreply,
+ socket
+ }
+ end
+
+ # Saving
+
+ def save(socket, %Workflow.ItemModel{} = entity, attrs) do
+ changeset = Workflow.ItemModel.changeset(entity, attrs)
+
+ socket
+ |> save(changeset)
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <%= @type %>
+ <%= if @ready? do %>
+
+ <% end %>
+ <%= if @relative_position != :top do %>
+
+ <% end %>
+ <% end %>
+
+
+
+
+
+ <% end %>
+
+ <%= if @ordering_enabled? do %>
+ <%= if @relative_position != :bottom do %>
+
+ <.live_component {@item_form} />
+ <%= if @tool_form do %>
+ <.live_component {@tool_form} />
+ <.spacing value="XS" />
+ <% end %>
+
+
+
+
+
+ <.function_component {@item_view} />
+
+
+
+
+
+ <.form id={@id} :let={form} for={@changeset} phx-change="save" phx-target={@myself} >
+ <%= if @group_enabled? do %>
+ <.dropdown
+ form={form}
+ field={:group}
+ options={@group_options}
+ label_text={dgettext("eyra-workflow", "item.group.label")}
+ target={@myself}
+ />
+ <% end %>
+ <.text_input form={form} field={:title} label_text={dgettext("eyra-workflow", "item.title.label")} />
+ <.text_input form={form} field={:description} label_text={dgettext("eyra-workflow", "item.description.label")} />
+
+
+ """
+ end
+end
diff --git a/core/systems/workflow/item_model.ex b/core/systems/workflow/item_model.ex
new file mode 100644
index 000000000..b9a1c3a96
--- /dev/null
+++ b/core/systems/workflow/item_model.ex
@@ -0,0 +1,71 @@
+defmodule Systems.Workflow.ItemModel do
+ use Ecto.Schema
+ use Frameworks.Utility.Schema
+ alias Frameworks.Concept
+
+ import Ecto.Changeset
+
+ alias Systems.{
+ Project,
+ Workflow
+ }
+
+ schema "workflow_items" do
+ field(:group, :string)
+ field(:position, :integer)
+ field(:title, :string)
+ field(:description, :string)
+
+ belongs_to(:workflow, Workflow.Model)
+ belongs_to(:tool_ref, Project.ToolRefModel)
+
+ timestamps()
+ end
+
+ @fields ~w(group position title description)a
+ @required_fields @fields
+
+ def changeset(item, params) do
+ item
+ |> cast(params, @fields)
+ end
+
+ def validate(changeset) do
+ changeset
+ |> validate_required(@required_fields)
+ end
+
+ def ready?(item) do
+ changeset =
+ changeset(item, %{})
+ |> validate()
+
+ changeset.valid?() && tool_ready?(item)
+ end
+
+ defp tool_ready?(%{tool_ref: tool_ref}) do
+ Project.ToolRefModel.flatten(tool_ref)
+ |> Concept.ToolModel.ready?()
+ end
+
+ def status(%Workflow.ItemModel{} = item) do
+ if ready?(item) do
+ :incomplete
+ else
+ :ready
+ end
+ end
+
+ def preload_graph(:down),
+ do:
+ preload_graph([
+ :tool_ref
+ ])
+
+ 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 flatten(%{tool_ref: tool_ref}), do: Project.ToolRefModel.flatten(tool_ref)
+end
diff --git a/core/systems/workflow/item_views.ex b/core/systems/workflow/item_views.ex
new file mode 100644
index 000000000..b4e8c41d2
--- /dev/null
+++ b/core/systems/workflow/item_views.ex
@@ -0,0 +1,172 @@
+defmodule Systems.Workflow.ItemViews do
+ use CoreWeb, :html
+
+ alias Frameworks.Pixel.Panel
+ alias Frameworks.Pixel.Align
+
+ import CoreWeb.UI.StepIndicator
+
+ alias Systems.{
+ Workflow,
+ Project
+ }
+
+ import Project.ToolRefView
+
+ attr(:title, :string, required: true)
+ attr(:description, :string, required: true)
+ attr(:items, :list, required: true)
+
+ def library(assigns) do
+ ~H"""
+
+ <%= @title %>
+ <%= @description %>
+ <.spacing value="M" />
+
+ """
+ end
+
+ attr(:id, :string, required: true)
+ attr(:title, :string, required: true)
+ attr(:description, :string, required: true)
+
+ def library_item(assigns) do
+ ~H"""
+
+ <%= for item <- @items do %>
+ <.library_item {item} />
+ <% end %>
+
+
+
+ <%= @title %>
+ <.spacing value="XS" />
+ <%= @description %>
+ <.spacing value="M" />
+ <.wrap>
+
+
+
+
+ """
+ end
+
+ attr(:items, :list, required: true)
+ attr(:selected_item_id, :integer, required: true)
+
+ def work_list(assigns) do
+ ~H"""
+
+ <%= for {item, index} <- Enum.with_index(@items) do %>
+ <.work_item {item} index={index} selected?={item.id == @selected_item_id} />
+ <% end %>
+
+ """
+ end
+
+ attr(:id, :any, required: true)
+ attr(:title, :map, required: true)
+ attr(:icon, :string, required: true)
+ attr(:status, :atom, default: :pending)
+ attr(:index, :integer, required: true)
+ attr(:selected?, :boolean, default: true)
+ attr(:event, :string, default: "work_item_selected")
+
+ def work_item(assigns) do
+ ~H"""
+
+
+ """
+ end
+
+ attr(:item, :map, required: true)
+ attr(:task, :map, required: true)
+
+ def launcher(%{item: %{tool_ref: tool_ref}} = assigns) when not is_nil(tool_ref) do
+ ~H"""
+
+
+
+
+
+ <%= if @status == :pending do %>
+ <.step_indicator bg_color="bg-grey4" text={@index+1} />
+ <% else %>
+ <%= @title %>
+
+ <%= if @icon do %>
+
+
+
+
+ <% end %>
+
+
+
+ <% end %>
+
+ <.tool_ref_view tool_ref={@item.tool_ref} task={@task} />
+
+ """
+ end
+
+ defp relative_position(0, _count), do: :top
+ defp relative_position(position, count) when position == count - 1, do: :bottom
+ defp relative_position(_position, _count), do: :middle
+
+ attr(:items, :list, required: true)
+ attr(:types, :list, required: true)
+ attr(:user, :map, required: true)
+ attr(:uri_origin, :string, required: true)
+ attr(:ordering_enabled?, :boolean, default: false)
+ attr(:parent, :map, required: true)
+
+ def list(assigns) do
+ ~H"""
+
+ <%= if @ordering_enabled? do %>
+
+ <%= dgettext("eyra-workflow", "item.list.hint") %>
+
+ <% end %>
+ <%= for {item, index} <- Enum.with_index(@items) do %>
+ <.live_component
+ id={"item-cell-#{item.id}"}
+ module={Workflow.ItemCell}
+ type={Enum.at(@types, index)}
+ item={item}
+ user={@user}
+ uri_origin={@uri_origin}
+ parent={@parent}
+ relative_position={relative_position(item.position, Enum.count(@items))}
+ ordering_enabled?={@ordering_enabled?}
+ />
+ <% end %>
+
+ """
+ end
+
+ attr(:title, :string, default: nil)
+ slot(:inner_block, default: nil)
+
+ def collapsed(assigns) do
+ ~H"""
+
+
+ <%= if @title do %>
+ <%= @title %>
+ <.spacing value="M" />
+ <% end %>
+ <%= render_slot(@inner_block) %>
+
+ """
+ end
+end
diff --git a/core/systems/workflow/model.ex b/core/systems/workflow/model.ex
new file mode 100644
index 000000000..f93c19026
--- /dev/null
+++ b/core/systems/workflow/model.ex
@@ -0,0 +1,49 @@
+defmodule Systems.Workflow.Model do
+ use Ecto.Schema
+ use Frameworks.Utility.Schema
+
+ import Ecto.Changeset
+
+ alias Systems.{
+ Workflow
+ }
+
+ schema "workflows" do
+ field(:type, Ecto.Enum, values: [:single_task, :multi_task])
+ has_many(:items, Workflow.ItemModel, foreign_key: :workflow_id)
+ timestamps()
+ end
+
+ @fields ~w(type)a
+ @required_fields @fields
+
+ def changeset(model, params \\ %{}) do
+ model
+ |> cast(params, @fields)
+ |> validate_required(@required_fields)
+ end
+
+ def preload_graph(:down),
+ do:
+ preload_graph([
+ :items
+ ])
+
+ def preload_graph(:items), do: [items: Workflow.ItemModel.preload_graph(:down)]
+
+ def ready?(%{items: items}) do
+ Enum.reduce(items, false, fn item, acc ->
+ acc && Workflow.ItemModel.ready?(item)
+ end)
+ end
+
+ def flatten(%{items: nil}), do: []
+
+ def flatten(%{items: items}) do
+ Enum.map(items, &Workflow.ItemModel.flatten/1)
+ end
+
+ def ordered_items(%Workflow.Model{items: items}) do
+ Enum.sort_by(items, & &1.position)
+ end
+end
diff --git a/core/systems/data_donation/platforms.ex b/core/systems/workflow/platforms.ex
similarity index 68%
rename from core/systems/data_donation/platforms.ex
rename to core/systems/workflow/platforms.ex
index 63d2c6946..b03b2eb74 100644
--- a/core/systems/data_donation/platforms.ex
+++ b/core/systems/workflow/platforms.ex
@@ -1,4 +1,4 @@
-defmodule Systems.DataDonation.Platforms do
+defmodule Systems.Workflow.Platforms do
@moduledoc """
Defines DDP platforms that are supported out of the box.
"""
@@ -7,9 +7,12 @@ defmodule Systems.DataDonation.Platforms do
[
:facebook,
:instagram,
+ :tiktok,
:twitter,
:google,
:youtube,
- :whatsapp
+ :whatsapp,
+ :apple,
+ :samsung
]}
end
diff --git a/core/test/core/accounts/signal_handlers_test.exs b/core/test/core/accounts/signal_handlers_test.exs
index 0e7f06fcf..7b7d24e85 100644
--- a/core/test/core/accounts/signal_handlers_test.exs
+++ b/core/test/core/accounts/signal_handlers_test.exs
@@ -7,7 +7,7 @@ defmodule Core.Accounts.SignalHandlersTest do
describe "user_created" do
test "sends mail" do
user = Factories.build(:member)
- SignalHandlers.dispatch(:user_created, %{user: user})
+ SignalHandlers.intercept({:user, :created}, %{user: user})
assert_email_delivered_with(subject: "Welcome")
end
end
diff --git a/core/test/core/accounts_test.exs b/core/test/core/accounts_test.exs
index 436c37142..72da0f41f 100644
--- a/core/test/core/accounts_test.exs
+++ b/core/test/core/accounts_test.exs
@@ -587,7 +587,7 @@ defmodule Core.AccountsTest do
{:ok, _} = Accounts.update_user_profile(user_changeset, profile_changeset)
assert user |> Accounts.get_profile() |> Map.get(:fullname) == "Update Test"
- assert_signal_dispatched(:user_profile_updated)
+ assert_signal_dispatched({:user_profile, :updated})
end
end
diff --git a/core/test/core/apns/signal_handlers_test.exs b/core/test/core/apns/signal_handlers_test.exs
index aef60d771..3bc5665bc 100644
--- a/core/test/core/apns/signal_handlers_test.exs
+++ b/core/test/core/apns/signal_handlers_test.exs
@@ -20,7 +20,10 @@ defmodule Core.APNS.SignalHandlers.Test do
Core.APNS.MockBackend
|> expect(:send_notification, fn _notif -> :ok end)
- SignalHandlers.dispatch(:new_notification, %{box: box, data: %{title: "Hello Test Message"}})
+ SignalHandlers.intercept(:new_notification, %{
+ box: box,
+ data: %{title: "Hello Test Message"}
+ })
end
end
end
diff --git a/core/test/core/authorization_test.exs b/core/test/core/authorization_test.exs
index d43e6d7fe..a4c524141 100644
--- a/core/test/core/authorization_test.exs
+++ b/core/test/core/authorization_test.exs
@@ -105,4 +105,127 @@ defmodule Core.AuthorizationTest do
Authorization.assign_role(principal, node_id, :owner)
assert Authorization.can_access?(principal, node_id, Systems.Benchmark.ToolPage) == true
end
+
+ test "link/1 succeeds tuple with parent/child" do
+ %{id: parent_id} = parent = Authorization.create_node!()
+ %{id: child_id} = child = Authorization.create_node!()
+
+ Authorization.link({parent, child})
+
+ assert %Core.Authorization.Node{
+ id: ^child_id,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ } = Repo.get!(Core.Authorization.Node, child.id) |> Repo.preload(:parent)
+ end
+
+ test "link/1 succeeds tuple with parent/childs" do
+ %{id: parent_id} = parent = Authorization.create_node!()
+ %{id: child_id1} = child1 = Authorization.create_node!()
+ %{id: child_id2} = child2 = Authorization.create_node!()
+
+ Authorization.link({parent, [child1, child2]})
+
+ assert %Core.Authorization.Node{
+ id: ^child_id1,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ } = Repo.get!(Core.Authorization.Node, child1.id) |> Repo.preload(:parent)
+
+ assert %Core.Authorization.Node{
+ id: ^child_id2,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ } = Repo.get!(Core.Authorization.Node, child2.id) |> Repo.preload(:parent)
+ end
+
+ test "link/1 succeeds tuple with parent/tuple-list" do
+ %{id: parent_id} = parent = Authorization.create_node!()
+ %{id: child_id_a} = child_a = Authorization.create_node!()
+ %{id: child_id_a_a} = child_a_a = Authorization.create_node!()
+ %{id: child_id_a_b} = child_a_b = Authorization.create_node!()
+
+ Authorization.link({parent, {child_a, [child_a_a, child_a_b]}})
+
+ assert %Core.Authorization.Node{
+ id: ^child_id_a_a,
+ parent: %Core.Authorization.Node{
+ id: ^child_id_a,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ }
+ } = Repo.get!(Core.Authorization.Node, child_a_a.id) |> Repo.preload(parent: [:parent])
+
+ assert %Core.Authorization.Node{
+ id: ^child_id_a_b,
+ parent: %Core.Authorization.Node{
+ id: ^child_id_a,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ }
+ } = Repo.get!(Core.Authorization.Node, child_a_b.id) |> Repo.preload(parent: [:parent])
+ end
+
+ test "link/1 succeeds tuple with parent/tuple-list with nil value" do
+ %{id: parent_id} = parent = Authorization.create_node!()
+ %{id: child_id_a} = child_a = Authorization.create_node!()
+ %{id: child_id_a_a} = child_a_a = Authorization.create_node!()
+
+ Authorization.link({parent, {child_a, [child_a_a, nil]}})
+
+ assert %Core.Authorization.Node{
+ id: ^child_id_a_a,
+ parent: %Core.Authorization.Node{
+ id: ^child_id_a,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ }
+ } = Repo.get!(Core.Authorization.Node, child_a_a.id) |> Repo.preload(parent: [:parent])
+ end
+
+ test "link/1 succeeds tuple with parent/tuple-item" do
+ %{id: parent_id} = parent = Authorization.create_node!()
+ %{id: child_id_a} = child_a = Authorization.create_node!()
+ %{id: child_id_a_a} = child_a_a = Authorization.create_node!()
+
+ Authorization.link({parent, {child_a, child_a_a}})
+
+ assert %Core.Authorization.Node{
+ id: ^child_id_a_a,
+ parent: %Core.Authorization.Node{
+ id: ^child_id_a,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ }
+ } = Repo.get!(Core.Authorization.Node, child_a_a.id) |> Repo.preload(parent: [:parent])
+ end
+
+ test "link/1 succeeds tuple with parent/tuple-item with nil value" do
+ %{id: parent_id} = parent = Authorization.create_node!()
+ %{id: child_id_a} = child_a = Authorization.create_node!()
+
+ Authorization.link({parent, {child_a, nil}})
+
+ assert %Core.Authorization.Node{
+ id: ^child_id_a,
+ parent: %Core.Authorization.Node{
+ id: ^parent_id,
+ parent_id: nil
+ }
+ } = Repo.get!(Core.Authorization.Node, child_a.id) |> Repo.preload(parent: [:parent])
+ end
end
diff --git a/core/test/core/mailer/signal_handlers_test.exs b/core/test/core/mailer/signal_handlers_test.exs
index 20d840a99..1b58eb664 100644
--- a/core/test/core/mailer/signal_handlers_test.exs
+++ b/core/test/core/mailer/signal_handlers_test.exs
@@ -13,7 +13,10 @@ defmodule Core.Mailer.SignalHandlers.Test do
end
test "send mail to user", %{box: box, user: _user} do
- SignalHandlers.dispatch(:new_notification, %{box: box, data: %{title: "Hello Test Message"}})
+ SignalHandlers.intercept(:new_notification, %{
+ box: box,
+ data: %{title: "Hello Test Message"}
+ })
assert_email_delivered_with(text_body: ~r/Test Message/)
end
diff --git a/core/test/core/surfconext_test.exs b/core/test/core/surfconext_test.exs
index 24e7f68bc..c9dbde49f 100644
--- a/core/test/core/surfconext_test.exs
+++ b/core/test/core/surfconext_test.exs
@@ -86,7 +86,7 @@ defmodule Core.SurfConext.Test do
{:ok, surf_user} = Core.SurfConext.register_user(sso_info)
- message = assert_signal_dispatched(:user_created)
+ message = assert_signal_dispatched({:user, :created})
assert message == %{user: surf_user.user}
end
diff --git a/core/test/core/web_push/signal_handlers_test.exs b/core/test/core/web_push/signal_handlers_test.exs
index eb8dd9619..ddeaed553 100644
--- a/core/test/core/web_push/signal_handlers_test.exs
+++ b/core/test/core/web_push/signal_handlers_test.exs
@@ -15,7 +15,7 @@ defmodule Core.WebPush.SignalHandler.Test do
end
test "send web-push for new notifications", %{box: box, subscription: subscription} do
- SignalHandlers.dispatch(:new_notification, %{box: box, data: %{title: "Hello Test"}})
+ SignalHandlers.intercept(:new_notification, %{box: box, data: %{title: "Hello Test"}})
assert_enqueued(
worker: Worker,
diff --git a/core/test/frameworks/signal/test_helper.ex b/core/test/frameworks/signal/test_helper.ex
index 51ff31096..48759d3d7 100644
--- a/core/test/frameworks/signal/test_helper.ex
+++ b/core/test/frameworks/signal/test_helper.ex
@@ -10,7 +10,7 @@ defmodule Frameworks.Signal.TestHelper do
defmacro refute_signal_dispatched(signal) do
quote bind_quoted: [signal: signal] do
- refute_received({:signal_test, {^signal, _}})
+ refute_received({:signal_test, {^signal, _}}, 1000)
end
end
@@ -20,7 +20,7 @@ defmodule Frameworks.Signal.TestHelper do
end
end
- def dispatch(signal, message) do
+ def intercept(signal, message) do
send(self(), {:signal_test, {signal, message}})
end
end
diff --git a/core/test/systems/alliance/_public_test.exs b/core/test/systems/alliance/_public_test.exs
new file mode 100644
index 000000000..871b3cdec
--- /dev/null
+++ b/core/test/systems/alliance/_public_test.exs
@@ -0,0 +1,66 @@
+defmodule Systems.Alliance.PublicTest do
+ use Core.DataCase
+
+ alias Core.Repo
+
+ alias Systems.{
+ Alliance
+ }
+
+ describe "alliance_tools" do
+ alias Core.Factories
+
+ @update_attrs %{url: "http://eyra.co/fake_alliance"}
+
+ test "list_tools/0 returns all alliance_tools" do
+ alliance_tool = Factories.insert!(:alliance_tool)
+
+ assert Alliance.Public.list_tools()
+ |> Enum.map(& &1.id)
+ |> MapSet.new()
+ |> MapSet.member?(alliance_tool.id)
+ end
+
+ test "get_tool!/1 returns the alliance_tool with given id" do
+ alliance_tool = Factories.insert!(:alliance_tool)
+
+ assert Alliance.Public.get_tool!(alliance_tool.id).id ==
+ alliance_tool.id
+ end
+
+ test "create_tool/1 with valid data creates a alliance_tool" do
+ auth_node = Factories.insert!(:auth_node)
+
+ assert {:ok, %Alliance.ToolModel{} = _alliance_tool} =
+ Alliance.Public.prepare_tool(%{}, auth_node) |> Repo.insert()
+ end
+
+ test "update_tool/2 with valid data updates the alliance_tool" do
+ alliance_tool = Factories.insert!(:alliance_tool)
+
+ assert {:ok, %{tool: alliance_tool}} =
+ Alliance.Public.update_tool(
+ alliance_tool,
+ :auto_save,
+ @update_attrs
+ )
+
+ assert alliance_tool.url == "http://eyra.co/fake_alliance"
+ end
+
+ test "delete_tool/1 deletes the alliance_tool" do
+ alliance_tool = Factories.insert!(:alliance_tool)
+ assert {:ok, %{}} = Alliance.Public.delete_tool(alliance_tool)
+
+ assert_raise Ecto.NoResultsError, fn ->
+ Alliance.Public.get_tool!(alliance_tool.id)
+ end
+ end
+
+ test "change_tool/1 returns a alliance_tool changeset" do
+ alliance_tool = Factories.insert!(:alliance_tool)
+
+ assert %Ecto.Changeset{} = Alliance.Public.change_tool(alliance_tool, :mount)
+ end
+ end
+end
diff --git a/core/test/systems/survey/tool_model_test.exs b/core/test/systems/alliance/tool_model_test.exs
similarity index 58%
rename from core/test/systems/survey/tool_model_test.exs
rename to core/test/systems/alliance/tool_model_test.exs
index 8d541493b..ed1f41dab 100644
--- a/core/test/systems/survey/tool_model_test.exs
+++ b/core/test/systems/alliance/tool_model_test.exs
@@ -1,8 +1,8 @@
-defmodule Systems.Survey.ToolModelTest do
+defmodule Systems.Alliance.ToolModelTest do
use Core.DataCase, async: true
alias Systems.{
- Survey
+ Alliance
}
describe "validate_url" do
@@ -15,35 +15,39 @@ defmodule Systems.Survey.ToolModelTest do
] do
test "allow #{url}" do
changeset =
- Survey.ToolModel.changeset(%Survey.ToolModel{}, :auto_save, %{survey_url: unquote(url)})
+ Alliance.ToolModel.changeset(%Alliance.ToolModel{}, :auto_save, %{
+ url: unquote(url)
+ })
assert changeset.valid?, changeset.errors
end
end
for url <- [
- "http://example.org/survey?a=",
- "http://example.org/survey?c=+ content.title +
+ Verwijderen
+
+ Advertentie
+
+ Advertenties worden op de marktplaats gezet om deelnemers te werven. Bekijk de voorbeeld pagina om te zien hoe deelnemers jouw advertentie pagina zien als ze op jouw marktplaats tegel klikken.
+
+ Opdracht
+
+ Opdracht
+
+ Criteria
+
+
+ Op basis van de geselecteerde criteria, kunnen 0 van de 0 mensen in de pool meedoen aan jouw studie.
+
+
+ Inclusie
+
+ Selecteer hieronder de inclusiecriteria voor je studie. Geen selectie betekend dat iedereen in de pool mee mag doen.
+
+ Geslacht
+
+ M
+
+ V
+
+ X
+
+ Exclusie
+
+ Wil je deelnemers die je in voorgaande campagnes geworven hebt excluderen? Selecteer dan één of meerdere campagnes hieronder.
+
+ Campagnes
+
+ Nog geen voorgaande campagnes beschikbaar
+
+ Voortgang
+
+ Deelnames
+ 1/1
+ Deelgenomen: 1
+
+ Bezig: 0
+
+ Vrij: 0
+
+ Bijdragen
+
+
+ Deelgenomen
+ 0
+
+ Afgekeurd 0
+
+ Goedgekeurd
+ 1
+ Deelnemer
+ |
+ Goedgekeurd
+ | |
---|---|---|
+ Subject 1 + | + Vandaag om 18:28 + |
+ tabbar.item.assignment.forward
+
+ tabbar.item.submission.forward
+
+ tabbar.item.monitor.forward
+