Skip to content

add genomics filter #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4b2ab88
update nplinker version
gcroci2 Jul 29, 2024
1f620f8
move callbacks to file
gcroci2 Jul 29, 2024
3771cd5
fix tests
gcroci2 Jul 29, 2024
8b90bfe
move layouts to specific file
gcroci2 Jul 29, 2024
b3088bb
remove unused init and comment
gcroci2 Jul 29, 2024
586205d
improve layout structure by removing the functions
gcroci2 Jul 29, 2024
ccf44c4
fix linting error
gcroci2 Jul 29, 2024
cbd1ab6
fix mypy issues
gcroci2 Jul 29, 2024
f344618
add gcfs filters to gm tab
gcroci2 Jul 30, 2024
7683c3a
add collapse functionality
gcroci2 Jul 30, 2024
67d8355
add tests for gm_filter
gcroci2 Jul 30, 2024
ef02754
use accordion instead of button
gcroci2 Aug 5, 2024
95d4457
use dmc grid for filter
gcroci2 Aug 5, 2024
76842a3
add the add filter functionality
gcroci2 Aug 5, 2024
1ef908d
start with one filter block and keep button only on the last ones
gcroci2 Aug 5, 2024
17b79ac
improve ids name, add default filter value, improve gcf ids hint
gcroci2 Aug 6, 2024
d0d3bef
use unique id for the first block
gcroci2 Aug 7, 2024
d87c0f7
keep previous dropdown and input text values when a new block is added
gcroci2 Aug 7, 2024
34fb975
change bigscape class to multivalue bgc class
gcroci2 Aug 7, 2024
5bcabfe
update tests
gcroci2 Aug 7, 2024
56d32a4
order BGC classes and add config
gcroci2 Aug 9, 2024
0e232f4
handle fake file upload
gcroci2 Aug 9, 2024
981817d
add type hints and doc stringas
gcroci2 Aug 9, 2024
c11da79
fix mypy error
gcroci2 Aug 9, 2024
18ebd60
fix tests
gcroci2 Aug 9, 2024
21173ac
fix import error
gcroci2 Aug 12, 2024
573ef52
add last suggestions
gcroci2 Aug 12, 2024
a77bea2
remove gm_text_input_ids_placeholder var
gcroci2 Aug 13, 2024
0644ed1
remove sys.path.append
gcroci2 Aug 13, 2024
d811c0d
fix tests
gcroci2 Aug 13, 2024
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
142 changes: 88 additions & 54 deletions app/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from dash import Input
from dash import Output
from dash import State
from dash import callback_context as ctx
from dash import clientside_callback
from dash import dcc
from dash import html
Expand Down Expand Up @@ -87,75 +88,108 @@ def display_file_contents(file_path): # noqa: D103


@app.callback(
Output("block-store", "data"),
Output("blocks-id", "data"),
Input({"type": "gm-add-button", "index": ALL}, "n_clicks"),
State("block-store", "data"),
State("blocks-id", "data"),
)
def add_block(n_clicks, blocks_data): # noqa: D103
def add_block(n_clicks, blocks_id): # noqa: D103
if not any(n_clicks):
raise dash.exceptions.PreventUpdate

# Create a unique ID for the new block
new_block_id = str(uuid.uuid4())
blocks_data.append(new_block_id)
return blocks_data


@app.callback(Output("blocks-container", "children"), Input("block-store", "data"))
def display_blocks(blocks_data): # noqa: D103
# Start with one block in the layout and then add additional blocks dynamically
blocks = [
dmc.Grid(
id={"type": "gm-block", "index": block_id},
children=[
dmc.GridCol(
dbc.Button(
[html.I(className="fas fa-plus")],
id={"type": "gm-add-button", "index": block_id},
className="btn-primary",
style={
"display": "block" if i == len(blocks_data) - 1 else "none"
}, # Show button only on the latest block
),
span=1,
blocks_id.append(new_block_id)
return blocks_id


@app.callback(
Output("blocks-container", "children"),
Input("blocks-id", "data"),
State("blocks-container", "children"),
)
def display_blocks(blocks_id, existing_blocks): # noqa: D103
new_block_id = blocks_id[-1]

new_block = dmc.Grid(
id={"type": "gm-block", "index": new_block_id},
children=[
dmc.GridCol(
dbc.Button(
[html.I(className="fas fa-plus")],
id={"type": "gm-add-button", "index": new_block_id},
className="btn-primary",
),
dmc.GridCol(
dcc.Dropdown(
options=[
{"label": "GCF ID", "value": "GCF_ID"},
{"label": "BiG-SCAPE Class", "value": "BSC_CLASS"},
],
value="GCF_ID",
placeholder="Enter one or more GCF IDs",
id={"type": "gm-dropdown-menu", "index": block_id},
clearable=False,
),
span=6,
span=1,
),
dmc.GridCol(
dcc.Dropdown(
options=[
{"label": "GCF ID", "value": "GCF_ID"},
{"label": "BGC Class", "value": "BGC_CLASS"},
],
value="GCF_ID",
id={"type": "gm-dropdown-menu", "index": new_block_id},
clearable=False,
),
dmc.GridCol(
span=6,
),
dmc.GridCol(
[
dmc.TextInput(
id={"type": "gm-dropdown-input", "index": block_id},
placeholder="",
id={"type": "gm-dropdown-ids-text-input", "index": new_block_id},
placeholder="1, 2, 3, ...",
className="custom-textinput",
),
span=5,
),
],
gutter="md",
)
for i, block_id in enumerate(blocks_data)
]
return blocks
dcc.Dropdown(
id={"type": "gm-dropdown-bgc-class-dropdown", "index": new_block_id},
options=[
{"label": "NRP", "value": "NRP"},
{"label": "Polyketide", "value": "POLYKETIDE"},
{"label": "RiPP", "value": "RIPP"},
{"label": "Terpene", "value": "TERPENE"},
{"label": "Saccharide", "value": "SAACCHARIDE"},
{"label": "Alkaloid", "value": "ALKALOID"},
{"label": "Other", "value": "OTHER"},
{"label": "Unknown", "value": "UNKNOWN"},
],
multi=True,
style={"display": "none"},
),
],
span=5,
),
],
gutter="md",
)

# Hide the add button on the previous last block
existing_blocks[-1]["props"]["children"][0]["props"]["children"]["props"]["style"] = {
"display": "none"
}

return existing_blocks + [new_block]


@app.callback(
Output({"type": "gm-dropdown-input", "index": MATCH}, "placeholder"),
Output({"type": "gm-dropdown-input", "index": MATCH}, "value"),
Output({"type": "gm-dropdown-ids-text-input", "index": MATCH}, "style"),
Output({"type": "gm-dropdown-bgc-class-dropdown", "index": MATCH}, "style"),
Output({"type": "gm-dropdown-ids-text-input", "index": MATCH}, "placeholder"),
Output({"type": "gm-dropdown-bgc-class-dropdown", "index": MATCH}, "placeholder"),
Output({"type": "gm-dropdown-ids-text-input", "index": MATCH}, "value"),
Output({"type": "gm-dropdown-bgc-class-dropdown", "index": MATCH}, "value"),
Input({"type": "gm-dropdown-menu", "index": MATCH}, "value"),
)
def update_placeholder(selected_value): # noqa: D103
if not ctx.triggered:
# Callback was not triggered by user interaction, don't change anything
raise dash.exceptions.PreventUpdate
if selected_value == "GCF_ID":
return "Enter one or more GCF IDs", ""
elif selected_value == "BSC_CLASS":
return "Enter one or more GCF BiG-SCAPE classes", ""
return "", ""
return {"display": "block"}, {"display": "none"}, "1, 2, 3, ...", "", "", []
elif selected_value == "BGC_CLASS":
return (
{"display": "none"},
{"display": "block"},
"",
"Select one or more BGC classes",
"",
[],
)
46 changes: 35 additions & 11 deletions app/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,20 @@

# ------------------ Tabs ------------------ #
# dropdown menu items
initial_block_id = str(uuid.uuid4())
gm_input_group = html.Div(
[
dcc.Store(id="block-store", data=[str(uuid.uuid4())]), # Start with one block
dcc.Store(id="blocks-id", data=[initial_block_id]), # Start with one block
html.Div(
id="blocks-container",
children=[
dmc.Grid(
id={"type": "gm-block", "index": str(uuid.uuid4())}, # Start with one block
id={"type": "gm-block", "index": initial_block_id}, # Start with one block
children=[
dmc.GridCol(
dbc.Button(
[html.I(className="fas fa-plus")],
id={"type": "gm-add-button", "index": str(uuid.uuid4())},
id={"type": "gm-add-button", "index": initial_block_id},
className="btn-primary",
),
span=1,
Expand All @@ -93,21 +94,44 @@
dcc.Dropdown(
options=[
{"label": "GCF ID", "value": "GCF_ID"},
{"label": "BiG-SCAPE Class", "value": "BSC_CLASS"},
{"label": "BGC Class", "value": "BGC_CLASS"},
],
value="GCF_ID",
placeholder="Enter one or more GCF IDs",
id={"type": "gm-dropdown-menu", "index": str(uuid.uuid4())},
id={"type": "gm-dropdown-menu", "index": initial_block_id},
clearable=False,
),
span=6,
),
dmc.GridCol(
dmc.TextInput(
id={"type": "gm-dropdown-input", "index": str(uuid.uuid4())},
placeholder="",
className="custom-textinput",
),
[
dmc.TextInput(
id={
"type": "gm-dropdown-ids-text-input",
"index": initial_block_id,
},
placeholder="1, 2, 3, ...",
className="custom-textinput",
),
dcc.Dropdown(
id={
"type": "gm-dropdown-bgc-class-dropdown",
"index": initial_block_id,
},
options=[
{"label": "NRP", "value": "NRP"},
{"label": "Polyketide", "value": "POLYKETIDE"},
{"label": "RiPP", "value": "RIPP"},
{"label": "Terpene", "value": "TERPENE"},
{"label": "Saccharide", "value": "SAACCHARIDE"},
{"label": "Alkaloid", "value": "ALKALOID"},
{"label": "Other", "value": "OTHER"},
{"label": "Unknown", "value": "UNKNOWN"},
],
placeholder="Select one or more BGC classes",
multi=True,
style={"display": "none"},
),
],
span=5,
),
],
Expand Down
69 changes: 25 additions & 44 deletions tests/test_callbacks.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from contextvars import copy_context
import uuid
import dash
import pytest
from dash._callback_context import context_value
from dash._utils import AttributeDict
from dash_uploader import UploadStatus
from app.callbacks import add_block
from app.callbacks import disable_tabs
from app.callbacks import gm_filter
from app.callbacks import upload_data
from . import DATA_DIR

Expand Down Expand Up @@ -36,48 +35,30 @@ def test_disable_tabs():
assert result[1] is False # MG tab should be enabled


@pytest.fixture
def mock_uuid(monkeypatch):
def mock_uuid4():
return "test-uuid"

monkeypatch.setattr(uuid, "uuid4", mock_uuid4)


@pytest.mark.parametrize(
"gcf_ids, gcf_bigscape, triggered_prop_id, expected_result",
"n_clicks, initial_blocks, expected_result",
[
([], ["block1"], pytest.raises(dash.exceptions.PreventUpdate)), # no buttons clicked
([1], ["block1", "block2"], ["block1", "block2", "test-uuid"]), # one button clicked once
(
"10, 34, 56",
"",
"gcf-ids-dropdown-input.value",
("10, 34, 56", ""),
), # gcf-ids-dropdown-input triggered
(
"",
"class1",
"gcf-bigscape-dropdown-input.value",
("", "class1"),
), # gcf-bigscape-dropdown-input triggered
(
"10, 34, 56",
"class1",
"gcf-ids-dropdown-clear.n_clicks",
("", "class1"),
), # gcf-ids-dropdown-clear triggered
(
"10, 34, 56",
"class1",
"gcf-bigscape-dropdown-clear.n_clicks",
("10, 34, 56", ""),
), # gcf-bigscape-dropdown-clear triggered
("", "", "no_triggering_context", ("", "")), # No triggering context
[1, 1, 1],
["block1", "block2"],
["block1", "block2", "test-uuid"],
), # three buttons, each clicked once
],
)
def test_gm_filter(gcf_ids, gcf_bigscape, triggered_prop_id, expected_result):
def run_callback():
gcf_ids_clear = None
gcf_bigscape_clear = None
if triggered_prop_id == "no_triggering_context":
context_value.set(AttributeDict(**{"triggered_inputs": []}))
else:
context_value.set(
AttributeDict(**{"triggered_inputs": [{"prop_id": triggered_prop_id}]})
)
return gm_filter(gcf_ids, gcf_ids_clear, gcf_bigscape, gcf_bigscape_clear)

ctx = copy_context()
output = ctx.run(run_callback)
assert output == expected_result
def test_add_block(mock_uuid, n_clicks, initial_blocks, expected_result):
if isinstance(expected_result, list):
result = add_block(n_clicks, initial_blocks)
assert result == expected_result
else:
with expected_result:
add_block(n_clicks, initial_blocks)
Loading