Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c83304c
Enhance visibility of empty fields (keep clickable, see example below…
Aug 15, 2025
069d72e
Opening accordion for all categories fields #2078
Oct 13, 2025
bd403e2
Grouping fields by a common header
Oct 13, 2025
58b98d0
processing both sub_items formats: a dictionary of the form {flat: [.…
Oct 20, 2025
ef34110
processing both sub_items formats: a dictionary of the form {flat: [.…
Oct 20, 2025
ed5dd82
categorizing fields on the summary page #2078
Nov 19, 2025
0dfde51
categorizing fields on the summary page on contributor page #2078
Nov 19, 2025
f1c4349
add css for review progress #2078
Nov 24, 2025
575b58a
functionality to calculate review progress #2078
Nov 24, 2025
76aaf71
connecting the review progress counting function #2078
Nov 24, 2025
30283e8
functionality to calculate review progress #2078
Nov 24, 2025
692baff
add css for review progress on contributor page #2078
Nov 24, 2025
80fbcc3
Fixed a bug with non-support of str format, changed the format to dic…
Dec 19, 2025
73f150b
Fixed call to reviewer page #2078
Dec 19, 2025
19ffe7c
Page marker used by peer_review/main.js to load the correct contribut…
Dec 19, 2025
318a400
add metadata module #2078
Dec 19, 2025
6bc10c5
Page marker used by peer_review/main.js to load the correct reviewer.…
Dec 19, 2025
e559c39
Added state check for using OK/Suggest/reject buttons #2078
Dec 19, 2025
615a8d9
correct field description search #2078
Dec 19, 2025
8eae6ca
correct fieldId search #2078
Dec 19, 2025
26ec7e3
Merge branch 'develop' into bugfix-2078-bulk-enhancements-to-finalize…
jh-RLI Mar 3, 2026
75fd6a3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 3, 2026
d888bf4
Merge branch 'develop' into bugfix-2078-bulk-enhancements-to-finalize…
jh-RLI Mar 3, 2026
e052a6f
Bluk fixes to make OPR work again ... soon
jh-RLI Mar 4, 2026
a85d069
#2078: Fis issues after poorly solved merge conflicts
jh-RLI Mar 5, 2026
d7958bd
#2078: Reverse engineer imported but missing functionality, restore …
jh-RLI Mar 5, 2026
fb16f88
Fix empty fields not clickable #2078
Mar 23, 2026
2400b43
Fix view of fileds under every tab #2078
Mar 24, 2026
5532add
Tread 0 as empty in review process so 0 in bounding box as default va…
Mar 24, 2026
30591d3
0 only for bindingBox empty value #2078
Mar 31, 2026
c9515d1
Fix variables for isEffectivelyEmpty #2078
Apr 1, 2026
61e9970
Remove layer License 1 #2078
Apr 1, 2026
8866286
Add text License 1 #2078
Apr 1, 2026
e320236
Remove outer source accordion and replace with text #2078
Apr 1, 2026
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
167 changes: 77 additions & 90 deletions dataedit/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ def merge_field_reviews(current_json, new_json):

Note:
If the same key is present in both the contributor's and
reviewer's reviews, the function will merge the field
evaluations. Otherwise, it will create a new entry in
the Review-Dict.
reviewer's reviews, the function will merge the field
evaluations. Otherwise, it will create a new entry in
the Review-Dict.
"""
merged_json = new_json.copy()
review_dict = {}
Expand Down Expand Up @@ -88,18 +88,7 @@ def merge_field_reviews(current_json, new_json):


def get_review_for_key(key, review_data):
"""
Retrieve the review for a specific key from the review data.

Args:
key (str): The key for which to retrieve the review.
review_data (dict): The review data containing
reviews for various keys.

Returns:
Any: The new value associated with the specified key
in the review data, or None if the key is not found.
"""
"""Retrieve the review for a specific key from the review data."""

for review in review_data["reviewData"]["reviews"]:
if review["key"] == key:
Expand All @@ -108,20 +97,9 @@ def get_review_for_key(key, review_data):


def recursive_update(metadata, review_data):
"""
Recursively updates metadata with new values from review_data,
skipping or removing fields with status 'rejected'.

Args:
metadata (dict): The original metadata dictionary to update.
review_data (dict): The review data containing the new values
for various keys.
"""Recursively updates metadata with new values from review_data.

Note:
The function iterates through the review data and for each key
updates the corresponding value in metadata if the new value is
present and is not an empty string, and if the field status is
not 'rejected'.
Skips or removes fields with status 'rejected'.
"""

def delete_nested_field(data: list | dict | None, keys: list[str]):
Expand Down Expand Up @@ -189,18 +167,7 @@ def delete_nested_field(data: list | dict | None, keys: list[str]):


def set_nested_value(metadata, keys, value):
"""
Set a nested value in a dictionary given a sequence of keys.

Args:
metadata (dict): The dictionary in which to set the value.
keys (list): A list of keys representing the path to the nested value.
value (Any): The value to set.

Note:
The function navigates through the dictionary using the keys
and sets the value at the position indicated by the last key in the list.
"""
"""Set a nested value in a dictionary given a sequence of keys."""

for key in keys[:-1]:
if key.isdigit():
Expand All @@ -213,73 +180,93 @@ def set_nested_value(metadata, keys, value):


def process_review_data(review_data, metadata, categories):
state_dict = {}
"""Attach reviewer fields (suggestions/comments/newValue) to metadata items.

# Initialize fields
for category in categories:
for item in metadata[category]:
item["reviewer_suggestion"] = ""
item["suggestion_comment"] = ""
item["additional_comment"] = ""
item["newValue"] = ""
The `metadata[category]` structures may be nested (dict/list) and can contain
non-dict leaf values (e.g. strings). We therefore walk the structure
recursively and only mutate leaf dicts that represent a field item.
"""

state_dict: dict[str, str | None] = {}

def iter_field_items(node):
"""Yield all leaf field-item dicts inside nested list/dict structures.

for review in review_data:
A leaf field item is a dict with a string key `field`.
"""
if isinstance(node, list):
for el in node:
yield from iter_field_items(el)
elif isinstance(node, dict):
if isinstance(node.get("field"), str):
yield node
else:
for v in node.values():
yield from iter_field_items(v)
# Ignore other node types (e.g. str/int/None)

# Initialize fields safely
for category in categories:
cat_node = metadata.get(category)
if cat_node is None:
continue
for item in iter_field_items(cat_node):
item.setdefault("reviewer_suggestion", "")
item.setdefault("suggestion_comment", "")
item.setdefault("additional_comment", "")
item.setdefault("newValue", "")

# Apply review values
for review in review_data or []:
field_key = review.get("key")
field_review = review.get("fieldReview")
category = review.get("category") # Get the category from the review
category = review.get("category")

state = None
reviewer_suggestion = ""
reviewer_suggestion_comment = ""
newValue = ""
additional_comment = ""

if isinstance(field_review, list):
# Sort and get the latest field review
sorted_field_review = sorted(
field_review, key=lambda x: x.get("timestamp"), reverse=True
)
latest_field_review = (
sorted_field_review[0] if sorted_field_review else None
field_review,
key=lambda x: (x.get("timestamp") or 0) if isinstance(x, dict) else 0,
reverse=True,
)
latest = sorted_field_review[0] if sorted_field_review else None
if isinstance(latest, dict):
state = latest.get("state")
reviewer_suggestion = latest.get("reviewerSuggestion") or ""
reviewer_suggestion_comment = latest.get("comment") or ""
newValue = latest.get("newValue") or ""
additional_comment = latest.get("additionalComment") or ""

if latest_field_review:
state = latest_field_review.get("state")
reviewer_suggestion = latest_field_review.get("reviewerSuggestion")
reviewer_suggestion_comment = latest_field_review.get("comment")
newValue = latest_field_review.get("newValue")
additional_comment = latest_field_review.get("additionalComment")
else:
state = None
reviewer_suggestion = ""
reviewer_suggestion_comment = ""
newValue = ""
additional_comment = ""
else:
elif isinstance(field_review, dict):
state = field_review.get("state")
reviewer_suggestion = field_review.get("reviewerSuggestion")
reviewer_suggestion_comment = field_review.get("comment")
newValue = field_review.get("newValue")
additional_comment = field_review.get("additionalComment")

# Update the item in the correct category
if category in metadata:
for item in metadata[category]:
if item["field"] == field_key:
item["reviewer_suggestion"] = reviewer_suggestion or ""
item["suggestion_comment"] = reviewer_suggestion_comment or ""
item["additional_comment"] = additional_comment or ""
item["newValue"] = newValue or ""
reviewer_suggestion = field_review.get("reviewerSuggestion") or ""
reviewer_suggestion_comment = field_review.get("comment") or ""
newValue = field_review.get("newValue") or ""
additional_comment = field_review.get("additionalComment") or ""

# Update the matching item in metadata for this category
if category in metadata and field_key:
for item in iter_field_items(metadata.get(category)):
if item.get("field") == field_key:
item["reviewer_suggestion"] = reviewer_suggestion
item["suggestion_comment"] = reviewer_suggestion_comment
item["additional_comment"] = additional_comment
item["newValue"] = newValue
break

state_dict[field_key] = state
if field_key:
state_dict[field_key] = state

return state_dict


def delete_peer_review(review_id):
"""
Remove Peer Review by review_id.
Args:
review_id (int): ID review.

Returns:
JsonResponse: JSON response about successful deletion or error.
"""
"""Remove Peer Review by review_id."""
if review_id:
peer_review = PeerReview.objects.filter(id=review_id).first()
if peer_review:
Expand Down
36 changes: 28 additions & 8 deletions dataedit/static/peer_review/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,40 @@

import * as common from "./peer_review.js";
import { selectState } from './peer_review.js';
window.selectState = selectState;
import { selectNextField, selectPreviousField } from './navigation.js';
import { setGetFieldState } from './state_current_review.js';

import { selectNextField } from './navigation.js'
window.selectNextField = selectNextField;
// Static imports avoid the "Failed to fetch" dynamic import errors
import { initReviewer } from './opr_reviewer.js';
import { initContributor } from './opr_contributor.js';

import { selectPreviousField } from './navigation.js'
// Expose functions to global window scope for HTML onclick events
window.selectState = selectState;
window.selectNextField = selectNextField;
window.selectPreviousField = selectPreviousField;

import { setGetFieldState } from './state_current_review.js';
import './opr_reviewer.js';
// Initialize the state getter
setGetFieldState((fieldKey) => {
return window.state_dict?.[fieldKey] ?? null;
});

document.addEventListener('DOMContentLoaded', function () {
common.initCurrentReview(config);
common.peerReview(config, true);
// Initialize common logic
// 'config' is defined in the HTML template
if (typeof config !== 'undefined') {
common.initCurrentReview(config);
common.peerReview(config, true);
}

// Initialize role-specific logic based on the HTML marker
const marker = document.getElementById('opr-page-marker');
const oprPage = marker?.dataset?.oprPage;

if (oprPage === 'reviewer') {
initReviewer();
} else if (oprPage === 'contributor') {
initContributor();
} else {
console.warn('OPR page marker not found or invalid; skipping role-specific initialization');
}
});
46 changes: 37 additions & 9 deletions dataedit/static/peer_review/navigation.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,47 @@
// SPDX-FileCopyrightText: 2025 Reiner Lemoine Institut
// SPDX-License-Identifier: AGPL-3.0-or-later
import {getCategoryToTabIdMapping, makeFieldList, selectField} from "./peer_review.js";

import { getCategoryToTabIdMapping, makeFieldList, selectField } from "./peer_review.js";

export function updateTabProgress() {
const allFields = document.querySelectorAll('.review__item');
const total = allFields.length;
let completed = 0;

allFields.forEach(field => {
// Check for any of the completed states (ok, rejected, suggestion)
if (field.classList.contains('field-ok') ||
field.classList.contains('field-rejected') ||
field.classList.contains('field-suggestion')) {
completed++;
}
});

const percentage = total === 0 ? 0 : Math.round((completed / total) * 100);

// Update the circular progress bar
const circle = document.getElementById('okProgressCircle');
const text = document.getElementById('okPercentageText');

if (circle && text) {
// 326.72 is 2*PI*r where r=52 (from your SVG)
const circumference = 326.72;
const offset = circumference - (percentage / 100) * circumference;
circle.style.strokeDashoffset = offset;
text.textContent = `${percentage}%`;
}
}

// Alias to fix the ReferenceError in summary.js
export const updatePercentageDisplay = updateTabProgress;

export function switchCategoryTab(category) {
const currentTab = document.querySelector('.tab-pane.active'); // Get the currently active tab
const currentTab = document.querySelector('.tab-pane.active');
const tabIdForCategory = getCategoryToTabIdMapping()[category];
console.log("tabID", tabIdForCategory);
if (currentTab.getAttribute('id') !== tabIdForCategory) {
// The clicked field does not belong to the current tab, switch to the next tab

if (currentTab && currentTab.getAttribute('id') !== tabIdForCategory) {
const targetTab = document.getElementById(tabIdForCategory);
if (targetTab) {
// The target tab exists, click the tab link to switch to it
targetTab.click();
}
}
Expand All @@ -22,9 +53,6 @@ export function selectNextField() {
selectField(fieldList, next);
}

/**
* Selects the HTML field element previous to the current one and clicks it
*/
export function selectPreviousField() {
var fieldList = makeFieldList();
var prev = fieldList.indexOf('field_' + window.selectedField) - 1;
Expand Down
Loading
Loading