import json
import logging
import os
import uuid
from dataclasses import dataclass
from typing import Any, Optional
import streamlit as st
from streamlit_extras.let_it_rain import rain
from proteobench.io.parsing.utils import add_maxquant_fixed_modifications
from .inputs import generate_input_widget
[docs]
def generate_submission_ui_elements(variables, user_input) -> bool:
"""
Create the UI elements necessary for data submission,
including metadata uploader and comments section.
"""
submission_ready = False
try:
copy_dataframes_for_submission(variables)
submission_ready = True
except Exception:
st.error(":x: Please provide a result file", icon="🚨")
generate_metadata_uploader(variables, user_input)
return submission_ready
logger: logging.Logger = logging.getLogger(__name__)
[docs]
def load_user_parameters(variables, ionmodule, user_input) -> Any:
"""
Read and process the parameter files provided by the user.
Returns
-------
Any
The parsed parameters.
"""
params = None
try:
params = ionmodule.load_params_file(
user_input[variables.meta_data],
user_input["input_format"],
json_file=variables.additional_params_json,
)
st.session_state[variables.params_json_dict] = params.__dict__ if hasattr(params, "__dict__") else params
except KeyError:
st.error(
"Parsing of meta parameters file for this software is not supported yet.",
icon="🚨",
)
except Exception as e:
input_f = user_input["input_format"]
st.error(
"Unexpected error while parsing file. Make sure you provided a meta "
f"parameters file produced by {input_f}: {e}",
icon="🚨",
)
return params
[docs]
def generate_additional_parameters_fields_submission(
variables,
user_input,
) -> None:
"""
Create the additional parameters section of the form and initializes the parameter fields.
"""
st.markdown(variables.texts.ShortMessages.initial_parameters)
# Load JSON config
with open(variables.additional_params_json, encoding="utf-8") as file:
config = json.load(file)
# Check if parsed values exist in session state
_ = st.session_state.get(variables.params_json_dict, {})
st_col1, st_col2, st_col3 = st.columns(3)
input_param_len = int(len(config.items()) / 3)
for idx, (key, value) in enumerate(config.items()):
if key.lower() == "software_name":
editable = False
else:
editable = True
if idx < input_param_len:
with st_col1:
user_input[key] = generate_input_widget(
variables, user_input["input_format"], value, key, editable=editable
)
elif idx < input_param_len * 2:
with st_col2:
user_input[key] = generate_input_widget(
variables, user_input["input_format"], value, key, editable=editable
)
else:
with st_col3:
user_input[key] = generate_input_widget(
variables, user_input["input_format"], value, key, editable=editable
)
[docs]
def generate_confirmation_checkbox(check_submission: str) -> None:
"""
Create the confirmation checkbox for metadata correctness.
"""
st.session_state[check_submission] = st.checkbox(
"I confirm that the metadata is correct",
)
return True
[docs]
def submit_to_repository(
variables,
ionmodule,
user_input,
params_from_file,
params,
) -> Optional[str]:
"""
Handle the submission process of the benchmark results to the ProteoBench repository.
Parameters
----------
params : ProteoBenchParameters
The parameters for the submission.
Returns
-------
str, optional
The URL of the pull request if the submission was successful.
"""
st.session_state[variables.submit] = True
button_pressed = generate_submission_button(
button_submission_uuid=variables.button_submission_uuid
) # None or 'button_pressed'
if not button_pressed: # if button_pressed is None
return None
# MaxQuant fixed modification handling
if user_input["input_format"] == "MaxQuant":
st.session_state[variables.result_perf] = add_maxquant_fixed_modifications(
params, st.session_state[variables.result_perf]
)
# Overwrite the dataframes for submission
# ? done a second time?
copy_dataframes_for_submission(
variables=variables,
)
clear_highlight_column(all_datapoints_submission=variables.all_datapoints_submission)
pr_url = create_pull_request(
variables=variables,
ionmodule=ionmodule,
user_input=user_input,
params_from_file=params_from_file,
params=params,
)
if pr_url:
save_intermediate_submission_data(
variables=variables,
ionmodule=ionmodule,
user_input=user_input,
)
return pr_url
[docs]
def show_submission_success_message(variables, pr_url) -> None:
"""
Handle the UI updates and notifications after a successful submission.
Parameters
----------
pr_url : str
The URL of the pull request.
"""
if st.session_state[variables.submit]:
st.subheader("SUCCESS")
st.markdown(variables.texts.ShortMessages.submission_processing_warning)
try:
st.write(f"Follow your submission approval here: [{pr_url}]({pr_url})")
except UnboundLocalError:
pass
st.session_state[variables.submit] = False
rain(emoji="🎈", font_size=54, falling_speed=5, animation_length=1)
########################################################################################
# helper functions for:
# generate_submission_ui_elements
[docs]
def copy_dataframes_for_submission(variables) -> None:
"""
Create copies of the dataframes before submission.
"""
if st.session_state[variables.all_datapoints_submitted] is not None:
st.session_state[variables.all_datapoints_submission] = st.session_state[
variables.all_datapoints_submitted
].copy()
if st.session_state[variables.input_df] is not None:
st.session_state[variables.input_df_submission] = st.session_state[variables.input_df].copy()
if st.session_state[variables.result_perf] is not None:
st.session_state[variables.result_performance_submission] = st.session_state[variables.result_perf].copy()
# submit_to_repository
[docs]
def clear_highlight_column(all_datapoints_submission: str) -> None:
"""
Remove the highlight column from the submission data if it exists.
"""
if "Highlight" in st.session_state[all_datapoints_submission].columns:
st.session_state[all_datapoints_submission].drop("Highlight", inplace=True, axis=1)
[docs]
def compare_dictionaries(old_dict, new_dict):
"""
Generate a human-readable string describing differences between two dictionaries.
Parameters
----------
old_dict : dict
The old dictionary.
new_dict : dict
The new dictionary.
Returns
-------
str
The human-readable string describing the differences between the two dictionaries.
"""
changes = []
# Get all unique keys across both dictionaries
all_keys = set(old_dict.keys()).union(set(new_dict.keys()))
for key in all_keys:
old_value = old_dict.get(key, "[MISSING]")
new_value = new_dict.get(key, "[MISSING]")
if str(old_value) != str(new_value) and not (old_value is None and new_value == "[MISSING]"):
changes.append(f"- **{key}**: `{old_value}` → `{new_value}`")
if changes:
return "\n ### Parameter changes detected:\n" + "\n".join(changes)
else:
return "\n ### No parameter changes detected. \n"
[docs]
def create_pull_request(
variables,
ionmodule,
user_input,
params_from_file: dict[str, Any],
params: dataclass,
) -> Optional[str]:
"""
Submit the pull request with the benchmark results and returns the PR URL.
Parameters
----------
params : Any
The parameters object.
Returns
-------
Optional[str]
The URL of the pull request.
"""
user_comments = user_input["comments_for_submission"]
changed_params_str = compare_dictionaries(params_from_file, params.__dict__)
try:
pr_url = ionmodule.clone_pr(
st.session_state[variables.all_datapoints_submission],
params,
remote_git=variables.github_link_pr,
submission_comments=user_comments + "\n" + changed_params_str,
)
except Exception as e:
st.error(f"Unable to create the pull request: {e}", icon="🚨")
pr_url = None
if not pr_url:
del st.session_state[variables.submit]
return pr_url