Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/build-windows-executable-app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ jobs:
- name: Clean up unnecessary Python files
shell: pwsh
run: |
$sitePkgs = "py/Lib/site-packages"
$sitePkgs = "python-${{ env.PYTHON_VERSION }}/Lib/site-packages"

# Remove __pycache__ directories
Get-ChildItem -Path $sitePkgs -Recurse -Directory -Filter "__pycache__" | Remove-Item -Recurse -Force
Expand Down Expand Up @@ -311,6 +311,10 @@ jobs:
Copy-Item "openms-bin/${file}.exe" -Destination "streamlit_exe/${file}.exe"
}

- name: Remove server address for local Windows deployment
run: |
(Get-Content streamlit_exe/.streamlit/config.toml) -notmatch '^address' | Set-Content streamlit_exe/.streamlit/config.toml

- name: Generate Readme.txt
shell: bash
run: |
Expand Down
9 changes: 2 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
# Requirements file generated with python=3.11
# Requirements file generated with python=3.12; tested with python=3.11
python-version: ["3.11"]
steps:
- uses: actions/checkout@v4
- uses: conda-incubator/setup-miniconda@v3
- uses: actions/setup-python@v4
Comment on lines 14 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all workflow action references and locate unpinned versions
rg -n '^\s*-\s*uses:\s*actions/(checkout|setup-python)@' .github/workflows

Repository: OpenMS/quantms-web

Length of output: 1951


🌐 Web query:

actions/setup-python@v4 deprecated end of life status 2024 2025 2026

💡 Result:

As of June 16, 2026, the actions/setup-python@v4 action is not officially marked as deprecated, but it is considered a legacy major version [1][2]. The official recommendation is to use the latest major version, which is v6 (as of the latest release v6.2.0 in January 2026) [1][3]. While v4 may continue to function in many workflows, it relies on older underlying technology (such as older Node.js runtimes) compared to current releases [1][4][3]. Key points regarding the status of setup-python: - Versioning and Support: GitHub Actions maintainers follow a practice of releasing new major versions to accommodate breaking changes, such as upgrades to the underlying Node.js runtime [1][3]. v4 was superseded by v5 (introduced around late 2023) and subsequently v6 [5][3]. - Deprecation Policy: GitHub typically manages the lifecycle of its actions by phasing out older major versions as newer ones become the standard [6]. Users are encouraged to migrate to the latest major version to ensure compatibility with updated GitHub runner environments, which may eventually drop support for the Node.js versions required by legacy actions [1][4][3]. - Usage: If your workflows are currently using @v4, they should be updated to @v6 to leverage the latest features, security patches, and compatibility improvements [1][3]. There is no specific EOL (End of Life) date publicly posted for v4 in the same manner as OS images (like Ubuntu 20.04) [7], but it is effectively obsolete for new or maintained projects [5][3].

Citations:


🌐 Web query:

GitHub Actions best practices pin commit SHA major version tags security

💡 Result:

The most effective security best practice for GitHub Actions is to pin every action to a full-length commit SHA [1][2][3]. Unlike version tags or branch names, which are mutable labels that can be redirected to malicious code by an attacker with repository access, a full-length commit SHA is cryptographically immutable and guarantees that the exact code you intend to run is executed every time [1][4][5][6]. Security Implications Using version tags (e.g., @v4) or branches (e.g., @main) creates a continuous, implicit trust relationship with the upstream repository [7]. If an attacker compromises the maintainer's account or gains write access to the repository, they can silently force-push updates to existing tags [1][8][6]. Downstream consumers relying on those tags will unknowingly execute the malicious code on their next workflow run, even if their own workflow files remain unchanged [4][7][9]. This has been a recurring vector for supply chain attacks in the GitHub Actions ecosystem [10][8][7]. Implementation and Maintenance Best Practices To manage the security and maintenance burden of SHA pinning: 1. Pin to SHA with a Human-Readable Comment: To maintain clarity, always include the version tag in a comment after the SHA [10]. This allows developers to understand which version they are using while gaining the security of immutability [4][10]. Example: uses: actions/checkout@3df4ab1 # v4.0.0 2. Automate Dependency Updates: Do not manually manage SHAs for all actions. Use dependency management tools like Dependabot or Renovate to automatically create pull requests that update your pinned SHAs [11][1][10]. For example, Renovate supports specific presets (e.g., helpers:pinGitHubActionDigests) that keep these hashes current [11][10]. 3. Enforce via Organization Policy: GitHub provides organization and repository-level policies that can block the use of actions not pinned to a full-length commit SHA [12][2][5]. Enabling this policy is a highly recommended security hardening step, as it forces all workflows to comply with immutable referencing [12][10]. 4. Audit the Dependency Tree: Remember that an action you use may rely on other actions internally. If you are an action maintainer, you should also pin your internal dependencies to full commit SHAs to ensure your consumers remain secure and compliant with organization-level policies [5]. By treating version tags only as discovery labels rather than trust anchors, you significantly reduce the risk of supply chain compromise in your CI/CD pipelines [3].

Citations:


Update to latest version and pin GitHub Actions to commit SHAs.

Lines 14–15 use outdated action major versions: actions/checkout@v4 and actions/setup-python@v4 are superseded by v4 for checkout and v6 for setup-python (current as of 2026). Additionally, both are unpinned version tags, which introduces supply-chain risk; version tags are mutable and can be redirected to malicious code. Use the latest major versions pinned to full commit SHAs with a version comment for clarity, e.g., actions/checkout@<commit-sha> # v4.x.x and actions/setup-python@<commit-sha> # v6.x.x.

🧰 Tools
🪛 actionlint (1.7.12)

[error] 15-15: the runner of "actions/setup-python@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 zizmor (1.25.2)

[warning] 14-14: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 14-14: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 14 - 15, Update the GitHub Actions in
the CI workflow to use the latest versions pinned to commit SHAs for security.
Replace the `actions/checkout@v4` action with the latest v4 commit SHA (pinned
with a version comment like `# v4.x.x`). Replace the `actions/setup-python@v4`
action with the latest v6 commit SHA (since v6 is the current version as of
2026) and also include a version comment like `# v6.x.x`. This mitigates
supply-chain risk by preventing version tags from being redirected to malicious
code while ensuring you are using the latest stable releases of these actions.

Source: Linters/SAST tools

with:
activate-environment: openms
python-version: ${{ matrix.python-version }}
channels: defaults,bioconda,conda-forge

- name: Install OpenMS
run: |
conda install openms -y
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test-win-exe-w-embed-py.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ jobs:
cp default-parameters.json streamlit_exe
cp ${{ env.APP_NAME }}.bat streamlit_exe

- name: Remove server address for local Windows deployment
run: |
(Get-Content streamlit_exe/.streamlit/config.toml) -notmatch '^address' | Set-Content streamlit_exe/.streamlit/config.toml

- name: Generate Readme.txt
shell: bash
run: |
Expand Down
1 change: 0 additions & 1 deletion .streamlit/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ developmentMode = false
address = "0.0.0.0"
maxUploadSize = 200 #MB
port = 8501 # should be same as configured in deployment repo
address = "0.0.0.0"
enableCORS = false
enableXsrfProtection = false

Expand Down
34 changes: 18 additions & 16 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,35 @@
with open("settings.json", "r") as f:
st.session_state.settings = json.load(f)

# Initialize session state for workspace
if "chosen-workspace" not in st.session_state:
if "workspace" in st.session_state:
st.session_state["chosen-workspace"] = str(st.session_state.workspace.stem)
else:
st.session_state["chosen-workspace"] = "default"

if __name__ == '__main__':
pages = {
str(st.session_state.settings["app-name"]) : [
st.Page(Path("content", "quickstart.py"), title="Quickstart", icon="👋"),
st.Page(Path("content", "documentation.py"), title="Documentation", icon="📖"),
],
"pyOpenMS Toolbox": [
st.Page(Path("content", "digest.py"), title="In Silico Digest", icon="✂️"),
st.Page(Path("content", "peptide_mz_calculator.py"), title="m/z Calculator", icon="⚖️"),
st.Page(Path("content", "isotope_pattern_generator.py"), title="Isotopic Pattern Calculator", icon="📶"),
st.Page(Path("content", "fragmentation.py"), title="Fragment Ion Generation", icon="💥"),
],
"TOPP Workflow Framework": [
st.Page(Path("content", "topp_workflow_file_upload.py"), title="File Upload", icon="📁"),
st.Page(Path("content", "topp_workflow_parameter.py"), title="Configure", icon="⚙️"),
st.Page(Path("content", "topp_workflow_execution.py"), title="Run", icon="🚀"),
st.Page(Path("content", "topp_workflow_results.py"), title="Results", icon="📊"),
],
"pyOpenMS Workflow" : [
st.Page(Path("content", "file_upload.py"), title="File Upload", icon="📂"),
st.Page(Path("content", "raw_data_viewer.py"), title="View MS data", icon="👀"),
st.Page(Path("content", "run_example_workflow.py"), title="Run Workflow", icon="⚙️"),
st.Page(Path("content", "download_section.py"), title="Download Results", icon="⬇️"),
# st.Page(Path("content", "topp_workflow_results.py"), title="Results", icon="📊"),
],
"Others Topics": [
st.Page(Path("content", "simple_workflow.py"), title="Simple Workflow", icon="⚙️"),
st.Page(Path("content", "run_subprocess.py"), title="Run Subprocess", icon="🖥️"),
"Results": [
st.Page(Path("content", "results_database_search.py"), title="Database Search", icon="🔬"),
st.Page(Path("content", "results_rescoring.py"), title="Rescoring", icon="📈"),
st.Page(Path("content", "results_filtered.py"), title="Filtered PSMs", icon="🎯"),
st.Page(Path("content", "results_abundance.py"), title="Abundance", icon="📋"),
st.Page(Path("content", "results_volcano.py"), title="Volcano", icon="🌋"),
st.Page(Path("content", "results_pca.py"), title="PCA", icon="📊"),
st.Page(Path("content", "results_heatmap.py"), title="Heatmap", icon="🔥"),
# st.Page(Path("content", "results_library.py"), title="Spectral Library", icon="📚"),
st.Page(Path("content", "results_pathway.py"), title="Pathway Analysis", icon="🧪"),
]
}

Expand Down
132 changes: 132 additions & 0 deletions content/results_abundance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""Abundance (ProteomicsLFQ) Results Page."""
import streamlit as st
import pandas as pd
import numpy as np
from pathlib import Path
from scipy.stats import ttest_ind
from src.common.common import page_setup
from statsmodels.stats.multitest import multipletests
from src.common.results_helpers import get_workflow_dir, get_abundance_data

params = page_setup()
st.title("Abundance Quantification")

st.markdown(
"""
View protein and PSM-level quantification from **ProteomicsLFQ**.
This page calculates differential expression statistics between sample groups.
"""
)

if "workspace" not in st.session_state:
st.warning("Please initialize your workspace first.")
st.stop()

results_dir = Path(st.session_state["workspace"]) / "topp-workflow" / "results" / "quant_results"
consensus_out = results_dir / "openms_design_protein_openms.csv"

@st.cache_data
def load_data(file_path):
return pd.read_csv(file_path, sep="\t", comment="#", engine="python")

if consensus_out.exists():

# df = load_data(consensus_out)
# # ratio column removal
# df = df.loc[:, ~df.columns.str.contains('ratio', case=False)]

pre_processing_tab, protein_tab = st.tabs(["Pre-processing", "Protein Table"])

with pre_processing_tab:
# result = get_abundance_data(st.session_state["workspace"])
# DEBUG: 상세 원인 출력 (임시)
try:
result = get_abundance_data(st.session_state["workspace"])
except Exception as e:
st.exception(e)
result = None

if result is None:
ws = st.session_state.get("workspace")
st.error("Debug: get_abundance_data returned None")
st.write("workspace:", ws)
wf = Path(ws) / "topp-workflow"
st.write("workflow dir exists:", wf.exists(), "->", wf)
qdir = wf / "results" / "quant_results"
st.write("quant_dir exists:", qdir.exists(), "->", qdir)
if qdir.exists():
st.write("csv files:", sorted([p.name for p in qdir.glob('*.csv')]))
# show cached param snapshot if available
try:
from src.workflow.ParameterManager import ParameterManager
pm = ParameterManager(wf)
st.write("parameters keys (sample):", list(pm.get_parameters_from_json().items())[:20])
except Exception as e:
st.write("Param manager error:", e)
st.stop()

if result is None:
st.info("💡 Please complete the configuration in the 'Configure' page to see results.")
st.stop()
Comment on lines +43 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Debug/diagnostic code left in production and unreachable code block.

Lines 43-66 contain extensive debug output (st.exception, st.write, st.error("Debug: ...")) that should be removed or gated behind a debug flag for production. Additionally, lines 68-70 are unreachable because st.stop() on line 66 terminates execution.

♻️ Proposed cleanup
     with pre_processing_tab:
-        # result = get_abundance_data(st.session_state["workspace"])
-        # DEBUG: 상세 원인 출력 (임시)
-        try:
-            result = get_abundance_data(st.session_state["workspace"])
-        except Exception as e:
-            st.exception(e)
-            result = None
-
-        if result is None:
-            ws = st.session_state.get("workspace")
-            st.error("Debug: get_abundance_data returned None")
-            st.write("workspace:", ws)
-            wf = Path(ws) / "topp-workflow"
-            st.write("workflow dir exists:", wf.exists(), "->", wf)
-            qdir = wf / "results" / "quant_results"
-            st.write("quant_dir exists:", qdir.exists(), "->", qdir)
-            if qdir.exists():
-                st.write("csv files:", sorted([p.name for p in qdir.glob('*.csv')]))
-            # show cached param snapshot if available
-            try:
-                from src.workflow.ParameterManager import ParameterManager
-                pm = ParameterManager(wf)
-                st.write("parameters keys (sample):", list(pm.get_parameters_from_json().items())[:20])
-            except Exception as e:
-                st.write("Param manager error:", e)
-            st.stop()
-
+        result = get_abundance_data(st.session_state["workspace"])
+        
         if result is None:
             st.info("💡 Please complete the configuration in the 'Configure' page to see results.")
             st.stop()
🧰 Tools
🪛 Ruff (0.15.17)

[warning] 45-45: Do not catch blind exception: Exception

(BLE001)


[warning] 64-64: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/results_abundance.py` around lines 43 - 70, The code contains
extensive debug diagnostics (st.write, st.error with "Debug:" prefix,
ParameterManager inspection) that should not be in production, and the if result
is None block on lines 68-70 is unreachable because st.stop() on line 66
terminates execution. Remove all debug output statements from the exception
handler and the detailed diagnostic checks (Path/workflow directory checks, csv
file listing, and ParameterManager inspection). Keep the essential exception
logging and remove the first st.stop() call so the code proceeds to the proper
user-facing info message when result is None from the get_abundance_data call.


pivot_df, expr_df, group_map = result

st.write("### Final Results (Group row removed, Stats added)")
st.dataframe(pivot_df.head(10))


with protein_tab:
st.markdown("### Protein-Level Abundance Table")

st.info(
"This protein-level table is generated by grouping all PSMs that map to the "
"same protein and aggregating their intensities across samples.\n\n"
"Additionally, log2 fold change and p-values are calculated between sample groups."
)

# Display group comparison info
groups = sorted(set(group_map.values()))
if len(groups) >= 2:
group1, group2 = sorted(groups)[:2]
st.info(f"Statistical comparison: **{group2} vs {group1}**")

exclude_cols = ["protein", "log2FC", "p-value", "p-adj",
"n_proteins", "n_peptides", "protein_score"]

# Get sample columns (between stats and PeptideSequence)
sample_cols = [c for c in pivot_df.columns if c
not in exclude_cols and "ratio" not in c.lower()]

# Create bar chart column with log2-transformed values
pivot_df["Intensity"] = pivot_df[sample_cols].apply(
lambda row: [np.log2(v + 1) for v in row], axis=1
)

# Reorder columns: place Intensity after p-value
display_cols = ["protein", "log2FC", "p-value", "Intensity"] + sample_cols
available_cols = [c for c in display_cols if c in pivot_df.columns]

st.dataframe(
pivot_df[available_cols].sort_values("p-value"),
column_config={
"Intensity": st.column_config.BarChartColumn(
"Intensity",
help="Sample intensities (log2 scale)",
width="small",
y_min=0,
),
},
width="stretch"
)
else:
st.warning(f"File not found: {consensus_out}")

st.markdown("---")
st.markdown("**Next steps:** Explore statistical visualizations")
col1, col2, col3 = st.columns(3)
with col1:
st.page_link("content/results_volcano.py", label="Volcano Plot", icon="🌋")
with col2:
st.page_link("content/results_pca.py", label="PCA", icon="📊")
with col3:
st.page_link("content/results_heatmap.py", label="Heatmap", icon="🔥")
79 changes: 79 additions & 0 deletions content/results_database_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Database Search (Comet) Results Page."""
import streamlit as st
from pathlib import Path
from src.common.common import page_setup
from src.common.results_helpers import get_workflow_dir
from openms_insight import Table, Heatmap, LinePlot, SequenceView, StateManager

params = page_setup()
st.title("Database Search Results")

st.markdown(
"""
View peptide-spectrum matches (PSMs) identified by **Comet** database search.
Click on a PSM to view the annotated spectrum and peptide sequence.
"""
)

st.info(
"**Score:** The e-value (expectation value) represents the expected number of random PSMs "
"with an equal or better score. Lower values indicate higher confidence identifications."
)

if "workspace" not in st.session_state:
st.warning("Please initialize your workspace first.")
st.stop()

workflow_dir = get_workflow_dir(st.session_state["workspace"])
comet_dir = workflow_dir / "results" / "comet_results"
cache_dir = workflow_dir / "results" / "insight_cache"

if not comet_dir.exists():
st.info("No database search results available yet. Please run the workflow first.")
st.page_link("content/workflow_run.py", label="Go to Run", icon="🚀")
st.stop()

comet_files = sorted(comet_dir.glob("*.idXML"))

if not comet_files:
st.warning("No identification output files found.")
st.stop()

selected_file = st.selectbox(
"Select identification result file",
comet_files,
format_func=lambda x: x.name
)

cache_id_prefix = selected_file.stem

# Check if cache exists
if not (cache_dir / f"table_{cache_id_prefix}").is_dir():
st.warning("Visualization cache not found. Please re-run the workflow.")
st.stop()
Comment on lines +51 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preflight cache validation is incomplete across the OpenMS Insight pages. Each page validates only table_* before instantiating Heatmap, SequenceView, and LinePlot, so partial/missing cache artifacts can still trigger runtime failures.

  • content/results_database_search.py#L51-L53: validate all required cache IDs (table_, heatmap_, seqview_, lineplot_) before component initialization.
  • content/results_filtered.py#L51-L53: add the same full cache preflight check before component initialization.
  • content/results_rescoring.py#L52-L54: add the same full cache preflight check before component initialization.
📍 Affects 3 files
  • content/results_database_search.py#L51-L53 (this comment)
  • content/results_filtered.py#L51-L53
  • content/results_rescoring.py#L52-L54
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/results_database_search.py` around lines 51 - 53, The cache preflight
validation is incomplete across all three pages and only checks for the table_
cache prefix, leaving the visualization components vulnerable to runtime
failures from missing cache artifacts. In content/results_database_search.py at
lines 51-53, expand the cache validation check to validate all four required
cache ID prefixes (table_, heatmap_, seqview_, and lineplot_) instead of only
table_. Apply the identical full preflight cache check validation at
content/results_filtered.py lines 51-53 before component initialization. Apply
the same complete cache validation check at content/results_rescoring.py lines
52-54 before component initialization. Each location should verify the existence
of all four cache prefixes in a single unified check before any Heatmap,
SequenceView, or LinePlot components are instantiated.


# Initialize state manager for cross-component linking
state_manager = StateManager()

# Load components from cache (no data parameter needed)
table = Table(cache_id=f"table_{cache_id_prefix}", cache_path=str(cache_dir))
heatmap = Heatmap(cache_id=f"heatmap_{cache_id_prefix}", cache_path=str(cache_dir))
seq_view = SequenceView(cache_id=f"seqview_{cache_id_prefix}", cache_path=str(cache_dir))
line_plot = LinePlot(cache_id=f"lineplot_{cache_id_prefix}", cache_path=str(cache_dir))

# Render components
st.subheader("PSM Overview")
heatmap(state_manager=state_manager, height=350)

st.subheader("PSM Table")
table(state_manager=state_manager, height=533)

st.subheader("Peptide Sequence")
seq_view(key=f"seqview_{cache_id_prefix}", state_manager=state_manager, height=533)

st.subheader("MS2 Spectrum")
line_plot(key=f"lineplot_{cache_id_prefix}", state_manager=state_manager, height=450, sequence_view_key=f"seqview_{cache_id_prefix}")

st.markdown("---")
st.markdown("**Next step:** View rescoring results")
st.page_link("content/results_rescoring.py", label="Go to Rescoring", icon="📈")
Loading