Compare commits

..
Author SHA1 Message Date
Tim Lorsbach 2a8974b1b8 App Integration Secret Packages
API CI / api-tests (pull_request) Failing after 39s
CI / test (pull_request) Failing after 43s
2026-11-11 20:07:07 +01:00
Tim Lorsbach 20b3e73347 v1.21
API CI / api-tests (pull_request) Failing after 44s
CI / test (pull_request) Failing after 43s
2026-11-10 21:00:59 +01:00
Tim Lorsbach 8a1ed2fc6d Import Export cycle of probs 2026-11-10 19:57:57 +01:00
Tim Lorsbach a6b901aa47 merged pathways
API CI / api-tests (pull_request) Failing after 39s
CI / test (pull_request) Failing after 42s
2026-11-10 07:30:27 +01:00
Tim Lorsbach 423ddf16d1 Adjust data_pool resolution for app integrations
API CI / api-tests (pull_request) Failing after 34s
CI / test (pull_request) Failing after 36s
2026-11-09 23:16:01 +01:00
Tim Lorsbach a7b2e60660 fixed width for news
API CI / api-tests (pull_request) Failing after 40s
CI / test (pull_request) Failing after 36s
2026-11-09 22:52:15 +01:00
jebusandTim Lorsbach ae3a164e74 Feat/bb4g (#2)
CI / test (pull_request) Failing after 40s
API CI / api-tests (pull_request) Failing after 41s
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: #2
2026-11-10 10:29:05 +13:00
Tim Lorsbach 2e2abf0287 ...
API CI / api-tests (pull_request) Failing after 32s
CI / test (pull_request) Failing after 33s
2026-10-21 11:59:11 +02:00
12 changed files with 187 additions and 56 deletions
@@ -0,0 +1,19 @@
# Generated by Django 6.0.3 on 2026-11-09 21:43
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bayer', '0006_remove_newsentry_url'),
]
operations = [
migrations.AlterField(
model_name='newsentry',
name='publication_date',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date'),
),
]
-1
View File
@@ -272,7 +272,6 @@ def visualize_pes(request):
return HttpResponse(image_data, content_type="image/png")
@package_permission_required()
def news_detail(request, news_entry_uuid):
current_user = _anonymous_or_real(request)
context = get_base_context(request, current_user)
+1 -1
View File
@@ -141,7 +141,7 @@ class BB4G(Classifier):
def run(self, eP: EnviPyDTO, *args, **kwargs) -> RunResult:
# Ensure Service is running
# self.start()
self.start()
smiles = [c.smiles for c in eP.get_compounds()]
preds = self._post(smiles)
+5
View File
@@ -2159,6 +2159,7 @@ class CreateEdge(Schema):
products: str | None = None # Node URIs comma sep
multistep: str | None = None
edgeReason: str | None = None
probability: float | None = None
@router.post(
@@ -2226,6 +2227,10 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
multi_step=multi_step,
)
if e.probability:
new_e.kv["probability"] = e.probability
new_e.save()
# Update depths as sideeffect of above operation
pw.update_depths()
+9 -2
View File
@@ -376,9 +376,12 @@ class PackageManager(object):
groups = GroupManager.get_groups(user)
# EDIT START
if package.classification_level == Package.Classification.SECRET:
if package.data_pool not in groups:
if user.principal_type == user.PrincipalType.APP:
if not user.app_details.secret_allowed or package.data_pool != user.app_details.data_pool:
return False
elif package.data_pool not in groups:
return False
# EDIT END
@@ -440,6 +443,10 @@ class PackageManager(object):
@staticmethod
def check_package_classification(user, pack: Package):
if pack.classification_level == Package.Classification.SECRET:
if user.principal_type == user.PrincipalType.APP:
if pack.data_pool == user.app_details.data_pool:
return pack
if pack.data_pool.user_member.filter(id=user.id).exists():
return pack
+102 -15
View File
@@ -2071,7 +2071,10 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
def empty_due_to_threshold(self):
return self.kv.get("empty_due_to_threshold", False)
def d3_json(self):
def merged_d3_json(self):
return self.d3_json(merged=True)
def d3_json(self, merged=False):
# Ideally it would be something like this but
# to reduce crossing in edges do a DFS
# nodes = [n.d3_json() for n in self.nodes]
@@ -2079,15 +2082,68 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
nodes = []
processed = set()
structure_to_combined_nodes = dict()
node_to_combined_node = dict()
queue = list()
for n in self.root_nodes:
queue.append(n)
# Add unconnected nodes
for n in self.nodes.order_by("url"):
if len(n.out_edges.all()) == 0:
if n not in queue:
queue.append(n)
class CombinedNode(object):
def __init__(self, representative: "Node"):
self.representative = representative
self.nodes = set([self.representative])
def ids(self):
return [n.id for n in self.nodes]
def d3_json(self):
d3_json = self.representative.d3_json()
compound = self.representative.default_node_label.compound
d3_json["name"] = compound.get_name()
d3_json["smiles"] = compound.default_structure.smiles
d3_json["node_label_id"] = compound.default_structure.url
d3_json["image"] = f"{compound.default_structure.url}?image=svg",
d3_json["image_svg"] = IndigoUtils.mol_to_svg(
compound.default_structure.molfile
if compound.default_structure.molfile is not None
and compound.default_structure.molfile.strip()
else compound.default_structure.smiles,
width=40,
height=40,
),
return d3_json
def __repr__(self):
return f"CombinedNode({self.nodes})"
if merged:
for n in self.nodes:
def_struct = n.default_node_label.compound.default_structure
if def_struct in structure_to_combined_nodes:
structure_to_combined_nodes[def_struct].nodes.add(n)
else:
cn = CombinedNode(n)
structure_to_combined_nodes[def_struct] = cn
# for n in self.nodes.order_by("url"):
# if len(n.out_edges.all()) == 0:
# if n not in queue:
# queue.append(n)
for cn in structure_to_combined_nodes.values():
for n in cn.nodes:
node_to_combined_node[n] = cn
queue = list(structure_to_combined_nodes.values())
else:
for n in self.nodes:
queue.append(n)
# Add unconnected nodes
for n in self.nodes.order_by("url"):
if len(n.out_edges.all()) == 0:
if n not in queue:
queue.append(n)
for i in queue:
processed.add(i)
@@ -2097,25 +2153,56 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
processed.add(current)
nodes.append(current.d3_json())
for e in self.edges.filter(start_nodes=current).order_by("url").distinct():
if merged:
q = Q(start_nodes__in=current.ids())
else:
q = Q(start_nodes=current)
for e in self.edges.filter(q).order_by("url").distinct():
for prod in e.end_nodes.all().order_by("url"):
if prod not in queue and prod not in processed:
queue.append(prod)
if merged:
def_struct = n.default_node_label.compound.default_structure
if def_struct in structure_to_combined_nodes:
structure_to_combined_nodes[def_struct].nodes.add(n)
else:
cn = CombinedNode(n)
structure_to_combined_nodes[def_struct] = cn
if cn not in queue and cn not in processed:
queue.append(cn)
else:
if prod not in queue and prod not in processed:
queue.append(prod)
# We shouldn't lose or make up nodes...
if len(nodes) != len(self.nodes):
logger.debug(
f"{self.get_name()}: Num Nodes {len(nodes)} vs. DB Nodes {len(self.nodes)}"
)
if not merged:
if len(nodes) != len(self.nodes):
logger.debug(
f"{self.get_name()}: Num Nodes {len(nodes)} vs. DB Nodes {len(self.nodes)}"
)
links = [e.d3_json() for e in self.edges]
# D3 links Nodes based on indices in nodes array
combined_node_depth_to_idx = dict()
node_url_to_idx = dict()
for i, n in enumerate(nodes):
n["id"] = i
node_url_to_idx[n["url"]] = i
# Add potential missing mappings
if merged:
cn_idx_mapping = dict()
for n in nodes:
for cn in processed:
for sub_n in cn.nodes:
if n["url"] == sub_n.url:
cn_idx_mapping[cn] = n["id"]
for n in processed:
for sub_n in n.nodes:
node_url_to_idx[sub_n.url] = cn_idx_mapping[n]
adjusted_links = []
for link in links:
# Check if we'll need pseudo nodes
+1
View File
@@ -2313,6 +2313,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
context["pathway"] = current_pathway
context["current_object"] = current_pathway
context["merged"] = request.GET.get("merged", False) == "true"
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
-1
View File
@@ -15,7 +15,6 @@ dependencies = [
"django-polymorphic>=4.1.0",
"enviformer",
"envipy-additional-information",
"envipy-ambit>=0.1.0",
"envipy-plugins",
"epam-indigo>=1.30.1",
"gunicorn>=23.0.0",
+2 -1
View File
@@ -266,7 +266,7 @@
<div id="services-container" class="flex justify-center gap-4">
{% for ne in news_entries %}
<div class="card-body flex flex-col h-full justify-between p-5">
<div class="card-body flex flex-col h-full justify-between p-5 w-96"> <!-- Fixed width applied via w-96 -->
<div>
<div class="flex items-center gap-2 mb-2">
<div class="bg-primary/10 text-primary rounded-lg p-1.5">
@@ -296,6 +296,7 @@
{% endfor %}
</div>
<div class="mt-6 text-right">
<a
href="https://community.envipath.org/c/announcements/10"
+46 -1
View File
@@ -227,6 +227,29 @@
Reaction Names
</a>
</li>
<li>
<a href="{% if merged %}{{ current_object.url }}{% else %}{{ current_object.url }}?merged=true{% endif %}" id="merged-toggle-button" class="cursor-pointer">
<svg
id="merged-icon"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-eye"
>
<path
d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"
/>
<circle cx="12" cy="12" r="3" />
</svg>
Merged View
</a>
</li>
{% if pathway.setting.model.app_domain %}
<li>
<a id="app-domain-toggle-button" class="cursor-pointer">
@@ -544,8 +567,11 @@
</div>
{% endif %}
</div>
{% if merged %}
{{ pathway.merged_d3_json|json_script:"pathway" }}
{% else %}
{{ pathway.d3_json|json_script:"pathway" }}
{% endif %}
<script>
// Global switch for compound names view
var compoundNamesViewEnabled = false;
@@ -557,6 +583,8 @@
var timeseriesViewEnabled = false;
// Predicted Property View
var predictedPropertyViewEnabled = false;
// Merged View
var mergedViewEnabled = new URLSearchParams(window.location.search).get("merged") !== null;
function goFullscreen(id) {
var element = document.getElementById(id);
@@ -702,6 +730,23 @@
});
}
const mergedViewBtn = document.getElementById(
"merged-toggle-button",
);
if (mergedViewBtn) {
const icon = document.getElementById("merged-icon");
if (mergedViewEnabled) {
// Change to eye-off icon
icon.innerHTML =
'<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/>';
} else {
// Change back to eye icon
icon.innerHTML =
'<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>';
}
};
// Timeseries toggle
const timeseriesBtn = document.getElementById("timeseries-toggle-button");
if (timeseriesBtn) {
+2
View File
@@ -214,6 +214,7 @@ class EdgeExportSchema(RefEdgeExportSchema):
start_nodes: List[RefNodeExportSchema]
end_nodes: List[RefNodeExportSchema]
scenarios: List[RefScenarioExportSchema]
kv: Optional[dict]
class PathwayExportSchema(RefPathwayExportSchema):
@@ -623,6 +624,7 @@ class PackageImporter:
new_e.description = e.description
new_e.aliases = e.aliases
new_e.edge_label = self._cache[e.edge_label.uuid]
new_e.kv = e.kv
new_e.save()
for sn in e.start_nodes:
Generated
-34
View File
@@ -802,7 +802,6 @@ dependencies = [
{ name = "django-polymorphic" },
{ name = "enviformer" },
{ name = "envipy-additional-information" },
{ name = "envipy-ambit" },
{ name = "envipy-plugins" },
{ name = "epam-indigo" },
{ name = "gunicorn" },
@@ -859,7 +858,6 @@ requires-dist = [
{ name = "emcee", marker = "extra == 'pepper-plugin'", specifier = ">=3.1.6" },
{ name = "enviformer", git = "ssh://git@git.envipath.com/enviPath/enviformer.git?rev=main" },
{ name = "envipy-additional-information", git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git?branch=develop" },
{ name = "envipy-ambit", git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" },
{ name = "envipy-plugins", git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git?rev=v0.1.0" },
{ name = "epam-indigo", specifier = ">=1.30.1" },
{ name = "gunicorn", specifier = ">=23.0.0" },
@@ -899,14 +897,6 @@ dependencies = [
{ name = "pydantic" },
]
[[package]]
name = "envipy-ambit"
version = "0.1.0"
source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git#0c424ba153105a54f758569a784d5871989338d8" }
dependencies = [
{ name = "jpype1" },
]
[[package]]
name = "envipy-plugins"
version = "0.1.0"
@@ -1190,30 +1180,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" },
]
[[package]]
name = "jpype1"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/49/6090a131d84b22c6aae13b1853092028b060fd17da1af87c0e42ad69d50f/jpype1-1.6.0.tar.gz", hash = "sha256:2d46b2a14f8f0e6f17d8aa22b4fc3a64b2790851ebf1409ad79a37c698fd6e9a", size = 1057888, upload-time = "2025-07-07T14:04:11.244Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/88/2156a27251492169f947850293d9870d49d784f1355fad7e920095636721/jpype1-1.6.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5454322887505097c46f41521becf31cda2303f54e35cb42e20f1a180055a558", size = 582187, upload-time = "2025-07-07T13:50:39.034Z" },
{ url = "https://files.pythonhosted.org/packages/6b/5d/51bde8695cc3eabcc465779f6dc23b149194bbcdcb8d221a9572422053f4/jpype1-1.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cf646459023a0dd71b3cc3aeeb977225a93f94c97839bb4e6146977b166c7caf", size = 468341, upload-time = "2025-07-07T13:50:40.739Z" },
{ url = "https://files.pythonhosted.org/packages/7a/a1/50cdc2bf9e51c0828faabec6fad50d61aada5a57d0bc435f4db0c16eb2ca/jpype1-1.6.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:7a98b34bc68a117881382dffa9b02d5e1d0d94325f85d7f564c33cc9e9d2916b", size = 511724, upload-time = "2025-07-07T13:50:42.479Z" },
{ url = "https://files.pythonhosted.org/packages/c4/a4/6038228e44cbcdb8f96ff2518cd6c577730f31fb62e9f46de7efdfabc2de/jpype1-1.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b56d7b662ce353c7f876e9ff3d4e775918348340795801e1f00c3b6241006264", size = 495897, upload-time = "2025-07-07T13:50:44.018Z" },
{ url = "https://files.pythonhosted.org/packages/8c/e4/af69d43779da0e49d37d7fd8a4a01bede03f6a3bab450eab1c10cf48e20e/jpype1-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf41134bc59c28de2721614ed00a34f76f69f2b525583a5c42fee5c7bae05d40", size = 355702, upload-time = "2025-07-07T13:50:45.368Z" },
{ url = "https://files.pythonhosted.org/packages/3f/1b/7167cfb702cfbcb17b0973150608f9cb92c3f409ebaaff655f7389617766/jpype1-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ce1da9ff01010a4529de194aecc053bfc8ab25440ea6b63579ef8c8d381da21d", size = 582384, upload-time = "2025-07-07T13:50:47.418Z" },
{ url = "https://files.pythonhosted.org/packages/c0/48/8ae425d8842bc76eb51052bbea5941dac230c8f6b58614ef2b7cb2da1a74/jpype1-1.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a387430cd4f3f68761013dca7f71b79d8d64b47dfd56eedcbd44c894d98d7f2", size = 468237, upload-time = "2025-07-07T13:50:48.925Z" },
{ url = "https://files.pythonhosted.org/packages/ef/2f/dbdc15aa99404dda0315a9883add7422c9ac7acce28ce24b66c89052c2fb/jpype1-1.6.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:6c9d99f41a5d05a8a2b6aeda796f646904c1c2b68aed0cc9a3bbc165483d9e1e", size = 511905, upload-time = "2025-07-07T13:50:50.87Z" },
{ url = "https://files.pythonhosted.org/packages/00/0d/50fcdd6d3bb42a87cbda8b04fc72283a9c6d955ada0d011190ce31e0e027/jpype1-1.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:55ba7257988a69bee84f7cd1444131725d5447999149fdafdba25bf46e4b1a3f", size = 496001, upload-time = "2025-07-07T13:50:52.711Z" },
{ url = "https://files.pythonhosted.org/packages/4f/b8/25214a9ebecbd31447b685500dca5fe2ac070df38358d6823f591a8ce52e/jpype1-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:ef8dd72442d91cde7e5eefef24ab71323cdd4137283a59f79d265a6620f7d0ab", size = 355727, upload-time = "2025-07-07T13:51:00.402Z" },
{ url = "https://files.pythonhosted.org/packages/29/a0/b2347572998020de8ce3ee0a285a1b84b052b522e8d452ee9ad330570918/jpype1-1.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc0c1c8b989b9857f87648980a746084b323858b0450225eab3642bb674645d6", size = 471150, upload-time = "2025-07-07T13:50:54.254Z" },
{ url = "https://files.pythonhosted.org/packages/7c/62/5e0f64a185f37303d0dd07e7b7a927cd5237fdc3c8176add949ed8cafaa2/jpype1-1.6.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cbbba16381ccd23530ad4ef09f7b2b1b12bd7fd0f6ca9fe4c7b4e3da8fe4cf63", size = 514015, upload-time = "2025-07-07T13:50:56.299Z" },
{ url = "https://files.pythonhosted.org/packages/72/68/84050ed2679e2d2ee2030a60d3e5a59a29d0533cd03fd8d9891e36592b0f/jpype1-1.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1a3814e4f65d67e36bdb03b8851d5ece8d7a408aa3a24251ea0609bb8fba77dd", size = 497229, upload-time = "2025-07-07T13:50:57.842Z" },
]
[[package]]
name = "jsonref"
version = "1.1.0"