Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
92 changes: 92 additions & 0 deletions CHANGE-LOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,97 @@
# OpenStudyBuilder (OSB) Commits changelog

## V 2.5

New Features and Enhancements
============

### Fixes and Enhancements

- The menu Library > Concepts > Activities > Activity Instances, now support editing of core attributes and associated activity items.
- The menu Library > Concepts > Activities > Activity Instances, now support defining the Activity Instance Class Categoric Finding including relationship to Categoric Response terms.
- Tables action buttons now expand on hover.
- Online help pop-ups are now movable by drag and drop (like dialog windows).

### New Feature

- The new "CRF Library Versions" report in NeoDash compares two versions of CRF forms in the CRF library.
- The system now supports the ability to develop UI and API extensions that can be deployed as an integrated part of the OSB system. See more details in the following readme files within the GitHub repository:
https://github.com/NovoNordisk-OpenSource/openstudybuilder-solution/tree/main/clinical-mdr-api/exte…
https://github.com/NovoNordisk-OpenSource/openstudybuilder-solution/tree/main/studybuilder/src/exte…

### Performance Improvements
- Faster UI performance of Detailed SoA page.
- Faster saving of audit trail information when creating/editing/deleting study selections, e.g. Study Activities, Visits, Epochs, Arms etc.
- Faster page load when opening detail page of activity instance classes.
- Faster loading and filtering of Library tables: Codelists, Activities.

### End-to-End Automated test enhancements
- Various code improvements to ensure easier maintenance and overall tests stability.
- Library > Concepts > Activities > Activities: Defined and implemented tests for multiple instance allowed checkbox.
- Studies > Define Study > Study Structure > Study Arms: Defined and implemented tests for study arm label.
- Studies > Define Study > Study Activities > Study Activities: Updated tests to reflect change to placeholder handling actions.
- Studies > Define Study > Study Activities > SoA > Detailed view: Defined and implemented tests for data exports.
- Studies > Define Study > Study Activities > SoA > Protocol view: Defined and implemented tests for data exports.
- Studies > Define Study > Study Interventions > Study Compounds: Defined and implemented tests for Study Compunds.
- Studies > Define Study > Study Interventions > Study Compound Dosings: Defined and implemented tests for Study Compunds Dosings.
- Studies > Study List > Copy Study: Updated tests to use custom test study with fully defined structured created via API.
- Reports > Neodash: Defined and implemened tests for CRFs versioning.

Solved Bugs
============

### API

- Fix ODM Item to Term relationship

### About Page

- UI fetches cached (i.e. old) SBOMs by default

### Library

**API**

- CT Terms order not retained
- Fix Odm Vendor Extension versioning

**Concepts > Activity Instances > Overview Page**

- Broken layout when topic code is very long

**Data Collection Standards -> CRF Builder**

- When you provide a wrong value for an attribute of a vendor extension, then we you save the item is created but the error is displayed, creating duplicate

**Data Collection Standards > CRF Builder > CRF Items**

- Unable to assign term in Step 3

**Data Collection Standards > CRF Builder > Items**

- Broken pagination for Units table
- CRF Item History page is not displaying the Number of Row

### Studies

**Define Study -> Study Structure -> Disease Milestones**

- Error thrown when performing search

**Define Study > Study Activities**

- Cannot edit subgroup of activity placeholder
- Issue with searching activity by name

**Define Study > Study Activities > Detailed SoA**

- Unable to reorder activities in 'patient reported outcome' sub-group

**Define Study > Study Structure > Study Visits**

- Unique visit number is not always unique


## V 2.4

New Features and Enhancements
Expand Down
1 change: 1 addition & 0 deletions clinical-mdr-api/.coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ include =
clinical_mdr_api/*
consumer_api/*
common/*
extensions/*
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Intergration API Test Specialist
name: Integration API Test Specialist
description: API testing agent for creating and updating integration tests for the FastAPI service backed by Neo4j, based on staged git changes.
---

Expand Down
1 change: 1 addition & 0 deletions clinical-mdr-api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ linting_report.txt
.coverage*
/reports
/consumer_api/reports
/extensions/reports
traceability.html
Consumer_API_Traceability.html
47 changes: 39 additions & 8 deletions clinical-mdr-api/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,18 @@ dist = "python setup.py bdist_wheel"
package = "pip wheel -r requirements.txt -w dist"
testunit = "pytest -s --cov-report html:reports/coverage-unit-html --cov-report xml:reports/coverage.xml --cov-append --cov --junitxml=reports/unit_report.xml clinical_mdr_api/tests/unit/ common/tests/unit"
testint = "pytest -s -n 4 --dist loadfile --cov-report html:reports/coverage-int-html --cov-report xml:reports/coverage.xml --cov-append --cov --junitxml=reports/int_report.xml clinical_mdr_api/tests/integration/"
testauth = "pytest -s --cov-report html:reports/coverage-auth-html --cov-report xml:reports/coverage.xml --cov-append --cov --junitxml=reports/auth_report.xml clinical_mdr_api/tests/auth/ common/tests/auth"
testauth = "pytest -s --cov-report html:reports/coverage-auth-html --cov-report xml:reports/coverage.xml --cov-append --cov --junitxml=reports/auth_report.xml clinical_mdr_api/tests/auth/ common/tests/auth extensions/tests/auth/"
test-telemetry = "pytest -s --cov-report html:reports/coverage-telemetry-html --cov-report xml:reports/coverage.xml --cov-append --cov --junitxml=reports/telemetry_report.xml clinical_mdr_api/tests/telemetry/"
testunitallure = "pytest -s --cov-report html:reports/coverage-unit --cov-report xml:reports/coverage.xml --cov-append --cov=clinical_mdr_api --junitxml=reports/unit_report.xml --alluredir reports/allure-results clinical_mdr_api/tests/unit/"
testintallure = "pytest -s -n 4 --dist loadfile --cov-report html:reports/coverage-int --cov-report xml:reports/coverage.xml --cov-append --cov=clinical_mdr_api --junitxml=reports/int_report.xml --alluredir reports/allure-results clinical_mdr_api/tests/integration/"
lint = "pylint -j 0 clinical_mdr_api consumer_api common"
mypy = "mypy clinical_mdr_api consumer_api common"
sblint = "python -m sblint.main clinical_mdr_api consumer_api common"
black = "python -m black clinical_mdr_api consumer_api common"
isort = "python -m isort clinical_mdr_api consumer_api common"
lint = "pylint -j 0 clinical_mdr_api consumer_api common extensions"
mypy = "mypy clinical_mdr_api consumer_api common extensions"
sblint = "python -m sblint.main clinical_mdr_api consumer_api common extensions"
black = "python -m black clinical_mdr_api consumer_api common extensions"
isort = "python -m isort clinical_mdr_api consumer_api common extensions"
format = """sh -c "
python -m isort clinical_mdr_api consumer_api common \
&& python -m black clinical_mdr_api consumer_api common \
python -m isort clinical_mdr_api consumer_api common extensions \
&& python -m black clinical_mdr_api consumer_api common extensions \
"
"""
openapi = "python generate_openapi_json.py"
Expand Down Expand Up @@ -128,3 +128,34 @@ consumer-api-schemathesis = """
consumer_api/openapi.json
"""
consumer-api-traceability = "python consumer_api/requirements/generate_traceability.py"

extensions-api-dev = "uvicorn --host=0.0.0.0 --port=8009 extensions.extensions_api:app --reload"
extensions-openapi = "python generate_openapi.py extensions.extensions_api:app extensions/openapi.json extensions/apiVersion"
extensions-lint = "pylint extensions"
extensions-test = """sh -c "
PRODEX_API_MOCK=True pytest -s -n 1 --dist loadfile \
--cov-report html:extensions/reports/coverage-extensions-html \
--cov-report xml:extensions/reports/coverage.xml \
--cov-append --cov \
--junitxml=extensions/reports/test_report.xml \
--ignore=extensions/tests/auth \
extensions/tests extensions/*/tests
"
"""
extensions-testauth = "pytest -s -n 1 --dist loadfile --cov-report html:extensions/reports/coverage-auth-html --cov-report xml:extensions/reports/coverage.xml --cov-append --cov --junitxml=extensions/reports/auth_report.xml extensions/tests/auth"
extensions-schemathesis = """
schemathesis
run
--experimental=openapi-3.1
--checks=all
--base-url=http://localhost:8009
--request-timeout=30000
--hypothesis-max-examples=100
--hypothesis-verbosity=normal
--hypothesis-deadline=None
--hypothesis-suppress-health-check=too_slow,filter_too_much,data_too_large
--junit-xml=extensions/reports/schemathesis_report.xml
--report=extensions/reports/schemathesis_report.tgz
--show-trace
extensions/openapi.json
"""
2 changes: 1 addition & 1 deletion clinical-mdr-api/apiVersion
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0.569
3.0.595
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,13 @@ def get_activity_item_classes(
results, meta = db.cypher_query(final_query, params=params)

return [dict(zip(meta, row)) for row in results], total

def get_all_version_numbers(self, uid: str) -> list[str]:
"""Get all version numbers of a given activity instance class"""

rs, _ = db.cypher_query(
"MATCH (:ActivityInstanceClassRoot {uid: $uid})-[hv:HAS_VERSION]-(:ActivityInstanceClassValue) RETURN DISTINCT hv.version",
params={"uid": uid},
)

return [row[0] for row in rs]
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ActivityItemClassValue,
)
from clinical_mdr_api.domain_repositories.models.controlled_terminology import (
CTCodelistRoot,
CTTermRoot,
)
from clinical_mdr_api.domain_repositories.models.generic import (
Expand Down Expand Up @@ -482,6 +483,16 @@ def patch_mappings(self, uid: str, variable_class_uids: list[str]) -> None:
variable_class = VariableClass.nodes.get(uid=variable_class)
root.maps_variable_class.connect(variable_class)

@sb_clear_cache(caches=["cache_store_item_by_uid"])
def patch_valid_codelist_mappings(
self, uid: str, valid_codelist_uids: list[str]
) -> None:
root = ActivityItemClassRoot.nodes.get(uid=uid)
root.has_valid_codelist_for_items.disconnect_all()
for codelist_uid in valid_codelist_uids:
codelist = CTCodelistRoot.nodes.get(uid=codelist_uid)
root.has_valid_codelist_for_items.connect(codelist)

def _maintain_parameters(
self,
versioned_object: ActivityItemClassAR,
Expand Down Expand Up @@ -620,6 +631,45 @@ def get_referenced_codelist_and_term_uids(
codelists_and_terms[cl_uid] = term_uids
return codelists_and_terms

def get_valid_codelists_and_terms(
self, activity_item_class_uid: str, ct_catalogue_name: str | None
) -> dict[str, list[str] | None]:
if ct_catalogue_name:
extra_filter_kwargs = {
"has_valid_codelist_for_items__has_codelist__name": ct_catalogue_name,
}
else:
extra_filter_kwargs = {}
codelist_uids = (
ActivityItemClassRoot()
.nodes.filter(
uid=activity_item_class_uid,
**extra_filter_kwargs,
)
.traverse(
Path(
value="has_valid_codelist_for_items",
include_rels_in_return=False,
include_nodes_in_return=False,
),
)
.unique_variables("has_valid_codelist_for_items")
.intermediate_transform(
{
"codelist_uid": {
"source": NodeNameResolver("has_valid_codelist_for_items"),
"source_prop": "uid",
"include_in_return": True,
},
},
distinct=True,
)
.all()
)
# Return a dictionary of codelist uids and filtered terms
# Note that for now, this model does not cater for filtering down to a subset of terms
return {uid: None for uid in codelist_uids}

def get_activity_instance_classes_using_item(
self,
activity_item_class_uid: str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ def _create_new_value_node(self, ar: ActivityInstanceAR) -> ActivityInstanceValu
else False
)
activity_item_node = ActivityItem(
is_adam_param_specific=is_adam_param_specific
is_adam_param_specific=is_adam_param_specific,
text_value=item.text_value,
)
activity_item_node.save()
activity_item_node.has_activity_item_class.connect(activity_item_class)
Expand Down Expand Up @@ -162,6 +163,7 @@ def _has_item_data_changed(self, ar_items, value_item_nodes):
"class": item.activity_item_class_uid,
"units": {unit.uid for unit in item.unit_definitions},
"terms": {(term.uid, term.codelist_uid) for term in item.ct_terms},
"text_value": item.text_value,
}
)

Expand All @@ -186,6 +188,7 @@ def _has_item_data_changed(self, ar_items, value_item_nodes):
(ct_term["uid"], ct_term["codelist_uid"])
for ct_term in ct_terms
},
"text_value": activity_item_node.text_value,
}
)
for item in ar_activity_items:
Expand Down Expand Up @@ -424,6 +427,7 @@ def _create_aggregate_root_instance_from_cypher_result(
)
for unit in activity_item.get("unit_definitions")
],
text_value=activity_item.get("text_value"),
)
for activity_item in input_dict.get("activity_items", [])
],
Expand Down Expand Up @@ -502,6 +506,7 @@ def _create_aggregate_root_instance_from_version_root_relationship_and_value(
activity_item_class_name=activity_item_class_root.has_latest_value.get_or_none().name,
ct_terms=ct_terms,
unit_definitions=unit_definitions,
text_value=activity_item.text_value,
)
)
activity_groupings_nodes = value.has_activity.all()
Expand Down Expand Up @@ -632,6 +637,7 @@ def _create_ar(
activity_item_class_name=activity_item["activity_item_class_name"],
ct_terms=ct_terms,
unit_definitions=unit_definitions,
text_value=activity_item.get("text_value"),
)
)
activity_groupings = []
Expand Down Expand Up @@ -738,7 +744,8 @@ def specific_alias_clause(self, **kwargs) -> str:
RETURN {uid: term_root.uid, name: term_name_value.name, codelist_uid: codelist_root.uid, submission_value: ct_codelist_term.submission_value}
},
unit_definitions: [(activity_item)-[:HAS_UNIT_DEFINITION]->(unit_definition_root:UnitDefinitionRoot)-[:LATEST]->(unit_definition_value:UnitDefinitionValue)-[:HAS_CT_DIMENSION]-(:CTTermRoot)-[:HAS_NAME_ROOT]->(CTTermNamesRoot)-[:LATEST]->(dimension_value:CTTermNameValue) | {uid: unit_definition_root.uid, name: unit_definition_value.name, dimension_name: dimension_value.name}],
is_adam_param_specific: activity_item.is_adam_param_specific
is_adam_param_specific: activity_item.is_adam_param_specific,
text_value: activity_item.text_value
}] AS activity_items,
head([(concept_value)-[:HAS_ACTIVITY]->(activity_grouping)<-[:HAS_GROUPING]-(activity_value) | activity_value.name]) as activity_name,
apoc.coll.toSet([(concept_value)-[:HAS_ACTIVITY]->(activity_grouping:ActivityGrouping)
Expand Down Expand Up @@ -978,7 +985,8 @@ def get_activity_instance_overview(
-[:HAS_CT_DIMENSION]-(:CTTermContext)-[:HAS_SELECTED_TERM]-(:CTTermRoot)-[:HAS_NAME_ROOT]->(CTTermNamesRoot)-[:LATEST]->(dimension_value:CTTermNameValue)
| {uid: unit_definition_root.uid, name: unit_definition_value.name, dimension_name: dimension_value.name}
],
is_adam_param_specific: activity_item.is_adam_param_specific
is_adam_param_specific: activity_item.is_adam_param_specific,
text_value: activity_item.text_value
}
]) AS activity_items
CALL {
Expand Down
Loading