From 23a76cb5d9005a2ec99f208491ae2f91e1476f5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:16:53 +0000 Subject: [PATCH 01/26] Initial plan From 9f04abeb908270042311f35f76d56b38dfa5827b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:20:12 +0000 Subject: [PATCH 02/26] Fix footer positioning and query parameter passing in vega-editor Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- .../whyis_vue/components/pages/vega/editor/vega-editor.html | 2 +- whyis/static/js/whyis_vue/components/yasqe.vue | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html b/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html index 8b046283b..3da706bca 100644 --- a/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html +++ b/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html @@ -1,4 +1,4 @@ -
+
diff --git a/whyis/static/js/whyis_vue/components/yasqe.vue b/whyis/static/js/whyis_vue/components/yasqe.vue index 28ae70f3f..3a88b9074 100644 --- a/whyis/static/js/whyis_vue/components/yasqe.vue +++ b/whyis/static/js/whyis_vue/components/yasqe.vue @@ -37,6 +37,10 @@ export default Vue.component("yasqe", { showQueryButton: !this.showBtns, endpoint: this.endpoint, requestMethod: "POST", + // Ensure query parameter is sent in POST body + args: [ + { name: "query", value: null } + ], callbacks: { error() { console.error("YASQE query error", arguments); From 016baf47ce0adff51c17397203bc7b98b9211a3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:23:12 +0000 Subject: [PATCH 03/26] Move inline style to scoped CSS class for maintainability Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- .../whyis_vue/components/pages/vega/editor/vega-editor.html | 2 +- .../whyis_vue/components/pages/vega/editor/vega-editor.vue | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html b/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html index 3da706bca..3dd7ef41e 100644 --- a/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html +++ b/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.html @@ -1,4 +1,4 @@ -
+
diff --git a/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.vue b/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.vue index b01b7d975..619a86717 100644 --- a/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.vue +++ b/whyis/static/js/whyis_vue/components/pages/vega/editor/vega-editor.vue @@ -1,3 +1,9 @@ + From 278bea51ff03c53f5781bbdd6e684516e716c2e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:43:27 +0000 Subject: [PATCH 04/26] Fix SPARQL proxy to preserve form data when forwarding to Fuseki Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 8336ecccb..5a12c8c93 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -36,13 +36,28 @@ def sparql_view(): elif request.method == 'POST': if 'application/sparql-update' in request.headers.get('content-type', ''): return "Update not allowed.", 403 + + # Get the raw data BEFORE accessing request.values + # because accessing request.values consumes the input stream + raw_data = request.get_data() + if 'update' in request.values: return "Update not allowed.", 403 + # Prepare headers for proxying - remove headers that should not be forwarded + headers = {} + headers.update(request.headers) + # Remove hop-by-hop headers and headers that will be set by requests library + for header in ['Host', 'Content-Length', 'Connection', 'Keep-Alive', + 'Proxy-Authenticate', 'Proxy-Authorization', 'TE', 'Trailers', + 'Transfer-Encoding', 'Upgrade']: + if header in headers: + del headers[header] + req = current_app.db.store.raw_sparql_request( method='POST', - headers=dict(request.headers), - data=request.get_data() + headers=headers, + data=raw_data ) except NotImplementedError as e: # Local stores don't support proxying - return error @@ -64,10 +79,14 @@ def sparql_view(): elif request.method == 'POST': if 'application/sparql-update' in request.headers.get('content-type', ''): return "Update not allowed.", 403 + + # Get raw data before accessing request.values + raw_data = request.get_data() + if 'update' in request.values: return "Update not allowed.", 403 req = requests.post(current_app.db.store.query_endpoint, - headers=request.headers, data=request.values, stream=True) + headers=request.headers, data=raw_data, stream=True) # Return the response response = Response(FileLikeFromIter(req.iter_content()), From 2d6a0b2f5af97e0c2d51f155a87c41381c30409d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:44:09 +0000 Subject: [PATCH 05/26] Address code review feedback: improve header handling consistency Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 5a12c8c93..f7afd446b 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -45,8 +45,7 @@ def sparql_view(): return "Update not allowed.", 403 # Prepare headers for proxying - remove headers that should not be forwarded - headers = {} - headers.update(request.headers) + headers = dict(request.headers) # Remove hop-by-hop headers and headers that will be set by requests library for header in ['Host', 'Content-Length', 'Connection', 'Keep-Alive', 'Proxy-Authenticate', 'Proxy-Authorization', 'TE', 'Trailers', @@ -85,8 +84,17 @@ def sparql_view(): if 'update' in request.values: return "Update not allowed.", 403 + + # Prepare headers for proxying - same filtering as main path + headers = dict(request.headers) + for header in ['Host', 'Content-Length', 'Connection', 'Keep-Alive', + 'Proxy-Authenticate', 'Proxy-Authorization', 'TE', 'Trailers', + 'Transfer-Encoding', 'Upgrade']: + if header in headers: + del headers[header] + req = requests.post(current_app.db.store.query_endpoint, - headers=request.headers, data=raw_data, stream=True) + headers=headers, data=raw_data, stream=True) # Return the response response = Response(FileLikeFromIter(req.iter_content()), From 0e3db5009b2ac5e26d58f43860ca615d43a6e8e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:44:55 +0000 Subject: [PATCH 06/26] Extract hop-by-hop headers list to module constant for maintainability Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index f7afd446b..b90891584 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -6,6 +6,15 @@ from setlr import FileLikeFromIter +# HTTP headers that should not be forwarded when proxying requests +# These are hop-by-hop headers or headers that will be set by the requests library +HOP_BY_HOP_HEADERS = [ + 'Host', 'Content-Length', 'Connection', 'Keep-Alive', + 'Proxy-Authenticate', 'Proxy-Authorization', 'TE', 'Trailers', + 'Transfer-Encoding', 'Upgrade' +] + + @sparql_blueprint.route('/sparql', methods=['GET', 'POST']) @conditional_login_required def sparql_view(): @@ -47,9 +56,7 @@ def sparql_view(): # Prepare headers for proxying - remove headers that should not be forwarded headers = dict(request.headers) # Remove hop-by-hop headers and headers that will be set by requests library - for header in ['Host', 'Content-Length', 'Connection', 'Keep-Alive', - 'Proxy-Authenticate', 'Proxy-Authorization', 'TE', 'Trailers', - 'Transfer-Encoding', 'Upgrade']: + for header in HOP_BY_HOP_HEADERS: if header in headers: del headers[header] @@ -87,9 +94,7 @@ def sparql_view(): # Prepare headers for proxying - same filtering as main path headers = dict(request.headers) - for header in ['Host', 'Content-Length', 'Connection', 'Keep-Alive', - 'Proxy-Authenticate', 'Proxy-Authorization', 'TE', 'Trailers', - 'Transfer-Encoding', 'Upgrade']: + for header in HOP_BY_HOP_HEADERS: if header in headers: del headers[header] From 88481e3af127c18c86eb9dd9ec974b0ba23a1c78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:45:44 +0000 Subject: [PATCH 07/26] Add case-insensitive header filtering helper function Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 35 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index b90891584..85253fbde 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -15,6 +15,26 @@ ] +def filter_headers_for_proxying(headers): + """ + Filter out hop-by-hop headers that should not be forwarded when proxying. + + Args: + headers: Flask headers object or dict of headers + + Returns: + dict: Filtered headers suitable for forwarding + """ + filtered = dict(headers) + # Use case-insensitive comparison since HTTP headers are case-insensitive + headers_lower = {k.lower(): k for k in filtered.keys()} + for header in HOP_BY_HOP_HEADERS: + header_lower = header.lower() + if header_lower in headers_lower: + del filtered[headers_lower[header_lower]] + return filtered + + @sparql_blueprint.route('/sparql', methods=['GET', 'POST']) @conditional_login_required def sparql_view(): @@ -53,12 +73,8 @@ def sparql_view(): if 'update' in request.values: return "Update not allowed.", 403 - # Prepare headers for proxying - remove headers that should not be forwarded - headers = dict(request.headers) - # Remove hop-by-hop headers and headers that will be set by requests library - for header in HOP_BY_HOP_HEADERS: - if header in headers: - del headers[header] + # Filter headers for proxying + headers = filter_headers_for_proxying(request.headers) req = current_app.db.store.raw_sparql_request( method='POST', @@ -92,11 +108,8 @@ def sparql_view(): if 'update' in request.values: return "Update not allowed.", 403 - # Prepare headers for proxying - same filtering as main path - headers = dict(request.headers) - for header in HOP_BY_HOP_HEADERS: - if header in headers: - del headers[header] + # Filter headers for proxying + headers = filter_headers_for_proxying(request.headers) req = requests.post(current_app.db.store.query_endpoint, headers=headers, data=raw_data, stream=True) From 1f4e1b4afee1b5001ad6c3839bd7cbfd1134b5f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:46:23 +0000 Subject: [PATCH 08/26] Improve documentation for filter_headers_for_proxying function Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 85253fbde..828a15225 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -19,11 +19,14 @@ def filter_headers_for_proxying(headers): """ Filter out hop-by-hop headers that should not be forwarded when proxying. + Performs case-insensitive header matching to comply with HTTP standards, + which specify that header names are case-insensitive. + Args: headers: Flask headers object or dict of headers Returns: - dict: Filtered headers suitable for forwarding + dict: Filtered headers suitable for forwarding (with hop-by-hop headers removed) """ filtered = dict(headers) # Use case-insensitive comparison since HTTP headers are case-insensitive From 57474bd11fb07575ff546a22ebcd5b3a3b5f72e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:47:03 +0000 Subject: [PATCH 09/26] Add clarifying comments for request handling and streaming behavior Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 828a15225..60e394c9c 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -70,7 +70,8 @@ def sparql_view(): return "Update not allowed.", 403 # Get the raw data BEFORE accessing request.values - # because accessing request.values consumes the input stream + # because accessing request.values consumes the input stream, + # which would cause request.get_data() to return empty bytes raw_data = request.get_data() if 'update' in request.values: @@ -84,6 +85,8 @@ def sparql_view(): headers=headers, data=raw_data ) + # Note: raw_sparql_request returns a response object that supports + # iter_content() for streaming, same as requests library responses except NotImplementedError as e: # Local stores don't support proxying - return error return str(e), 501 From 30ba360bf652ef37806d5446d3e3c3524eb4a757 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:47:44 +0000 Subject: [PATCH 10/26] Clarify comments about request.values access ordering Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 60e394c9c..e282a741b 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -69,9 +69,10 @@ def sparql_view(): if 'application/sparql-update' in request.headers.get('content-type', ''): return "Update not allowed.", 403 - # Get the raw data BEFORE accessing request.values - # because accessing request.values consumes the input stream, - # which would cause request.get_data() to return empty bytes + # Get the raw data BEFORE accessing request.values. + # Flask's request.values consumes the input stream, making the body + # unavailable for get_data(). By calling get_data() first, we preserve + # the raw body, and can then safely access request.values. raw_data = request.get_data() if 'update' in request.values: @@ -108,7 +109,7 @@ def sparql_view(): if 'application/sparql-update' in request.headers.get('content-type', ''): return "Update not allowed.", 403 - # Get raw data before accessing request.values + # Get raw data before accessing request.values (see comment above in main path) raw_data = request.get_data() if 'update' in request.values: From c97441036a65952ed56eea8af0a209f647ba9306 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:48:21 +0000 Subject: [PATCH 11/26] Add comment explaining why raw_data is used instead of request.values Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index e282a741b..71f1e972b 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -118,6 +118,7 @@ def sparql_view(): # Filter headers for proxying headers = filter_headers_for_proxying(request.headers) + # Send raw_data (bytes) not request.values (dict) to preserve exact form encoding req = requests.post(current_app.db.store.query_endpoint, headers=headers, data=raw_data, stream=True) From ad6620d713c695c1020fe084d65083c2f6e07243 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:49:04 +0000 Subject: [PATCH 12/26] Optimize filter_headers_for_proxying with single-pass dictionary comprehension Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 71f1e972b..3bbe954c1 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -28,14 +28,11 @@ def filter_headers_for_proxying(headers): Returns: dict: Filtered headers suitable for forwarding (with hop-by-hop headers removed) """ - filtered = dict(headers) - # Use case-insensitive comparison since HTTP headers are case-insensitive - headers_lower = {k.lower(): k for k in filtered.keys()} - for header in HOP_BY_HOP_HEADERS: - header_lower = header.lower() - if header_lower in headers_lower: - del filtered[headers_lower[header_lower]] - return filtered + # Create set of lowercase header names to filter for efficient lookup + hop_by_hop_lower = {h.lower() for h in HOP_BY_HOP_HEADERS} + + # Filter headers in a single pass with case-insensitive comparison + return {k: v for k, v in headers.items() if k.lower() not in hop_by_hop_lower} @sparql_blueprint.route('/sparql', methods=['GET', 'POST']) From db90f62192a289e34a3350ff8994364cb99ed116 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:49:36 +0000 Subject: [PATCH 13/26] Remove redundant comment about streaming behavior Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 3bbe954c1..875fa2b69 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -83,8 +83,6 @@ def sparql_view(): headers=headers, data=raw_data ) - # Note: raw_sparql_request returns a response object that supports - # iter_content() for streaming, same as requests library responses except NotImplementedError as e: # Local stores don't support proxying - return error return str(e), 501 From 9a017d44f4332066cba5ed36198984d2e5c025c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:50:20 +0000 Subject: [PATCH 14/26] Optimize with module-level constant and improve comment clarity Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 875fa2b69..b5813c381 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -14,6 +14,9 @@ 'Transfer-Encoding', 'Upgrade' ] +# Lowercase version for efficient case-insensitive lookups +HOP_BY_HOP_HEADERS_LOWER = {h.lower() for h in HOP_BY_HOP_HEADERS} + def filter_headers_for_proxying(headers): """ @@ -28,11 +31,8 @@ def filter_headers_for_proxying(headers): Returns: dict: Filtered headers suitable for forwarding (with hop-by-hop headers removed) """ - # Create set of lowercase header names to filter for efficient lookup - hop_by_hop_lower = {h.lower() for h in HOP_BY_HOP_HEADERS} - # Filter headers in a single pass with case-insensitive comparison - return {k: v for k, v in headers.items() if k.lower() not in hop_by_hop_lower} + return {k: v for k, v in headers.items() if k.lower() not in HOP_BY_HOP_HEADERS_LOWER} @sparql_blueprint.route('/sparql', methods=['GET', 'POST']) @@ -104,7 +104,10 @@ def sparql_view(): if 'application/sparql-update' in request.headers.get('content-type', ''): return "Update not allowed.", 403 - # Get raw data before accessing request.values (see comment above in main path) + # Get raw data before accessing request.values to preserve request body. + # Flask's request.values consumes the input stream, making the body + # unavailable for get_data(). By calling get_data() first, we preserve + # the raw body, and can then safely access request.values. raw_data = request.get_data() if 'update' in request.values: From e44b19f21cd5581825e1c58dc67fb3c6ece5f070 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:50:53 +0000 Subject: [PATCH 15/26] Clarify architectural reason for dual proxying approaches Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/blueprint/sparql/sparql_view.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index b5813c381..649a91c93 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -91,8 +91,9 @@ def sparql_view(): current_app.logger.error(f"SPARQL request failed: {str(e)}") return f"SPARQL request failed: {str(e)}", 500 else: - # Fallback for stores without raw_sparql_request (should not happen) - # This is the old behavior - direct HTTP request without authentication + # Fallback for stores without raw_sparql_request (should not happen in practice) + # This path uses requests library directly without authentication support + # Modern stores should implement raw_sparql_request for proper auth handling if request.method == 'GET': headers = {} headers.update(request.headers) From 17b77c5a5928817436152359832e1ddc7a70e1d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:20:51 +0000 Subject: [PATCH 16/26] Remove args configuration from YASQE to fix duplicate query parameter Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/static/js/whyis_vue/components/yasqe.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/whyis/static/js/whyis_vue/components/yasqe.vue b/whyis/static/js/whyis_vue/components/yasqe.vue index 3a88b9074..28ae70f3f 100644 --- a/whyis/static/js/whyis_vue/components/yasqe.vue +++ b/whyis/static/js/whyis_vue/components/yasqe.vue @@ -37,10 +37,6 @@ export default Vue.component("yasqe", { showQueryButton: !this.showBtns, endpoint: this.endpoint, requestMethod: "POST", - // Ensure query parameter is sent in POST body - args: [ - { name: "query", value: null } - ], callbacks: { error() { console.error("YASQE query error", arguments); From 7d0ef71c1753ba557ac45959bb831e8cb3eeeacc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:29:55 +0000 Subject: [PATCH 17/26] Fix Vega-Lite URL construction error by removing $schema before validation Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue b/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue index ddef7c204..7c9f6793e 100644 --- a/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue +++ b/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue @@ -54,7 +54,12 @@ export default { this.$emit('new-vega-view', result.view) }, validateSpec () { - const validation = jsonValidate(this.spec, vegaLiteSchema) + // Create a copy of the spec without $schema to avoid URL resolution issues + // The $schema property can cause jsonValidate to attempt URL construction + const specToValidate = Object.assign({}, this.spec) + delete specToValidate.$schema + + const validation = jsonValidate(specToValidate, vegaLiteSchema) if (!validation.valid) { console.warn('Invalid spec', validation) } else { From efdc860d972c3369a7aa7e74dd12638e68432909 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:30:54 +0000 Subject: [PATCH 18/26] Add clarifying comment about shallow copy sufficiency Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue b/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue index 7c9f6793e..e4158c32e 100644 --- a/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue +++ b/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue @@ -54,8 +54,10 @@ export default { this.$emit('new-vega-view', result.view) }, validateSpec () { - // Create a copy of the spec without $schema to avoid URL resolution issues - // The $schema property can cause jsonValidate to attempt URL construction + // Create a shallow copy of the spec to remove $schema property + // Shallow copy is sufficient since we only delete top-level $schema + // and don't modify nested properties. The $schema property can cause + // jsonValidate to attempt URL construction, which fails const specToValidate = Object.assign({}, this.spec) delete specToValidate.$schema From c1392a6853a9d7d28c5b38d3d61dbba117304d10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:00:38 +0000 Subject: [PATCH 19/26] Add CORS support to enable cross-origin API access Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- setup.py | 1 + tests/api/test_cors.py | 50 ++++++++++++++++++++++++++++++++++++++++++ whyis/app.py | 14 ++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 tests/api/test_cors.py diff --git a/setup.py b/setup.py index 607e8a3bc..627593f5f 100644 --- a/setup.py +++ b/setup.py @@ -203,6 +203,7 @@ def run(self): 'xlrd==2.0.1', 'werkzeug==2.0.3', 'Flask-Caching==1.10.1', + 'Flask-CORS>=3.0.10', 'pytz' ], tests_require=[ diff --git a/tests/api/test_cors.py b/tests/api/test_cors.py new file mode 100644 index 000000000..dadb17db2 --- /dev/null +++ b/tests/api/test_cors.py @@ -0,0 +1,50 @@ +""" +Test CORS (Cross-Origin Resource Sharing) headers. + +Verifies that CORS headers are properly set for API endpoints. +""" + +import pytest + + +def test_cors_headers_on_root(client): + """Test that CORS headers are present on root endpoint.""" + response = client.get('/') + + # Check for CORS headers + assert 'Access-Control-Allow-Origin' in response.headers + assert response.headers['Access-Control-Allow-Origin'] == '*' + + +def test_cors_preflight_request(client): + """Test CORS preflight (OPTIONS) request.""" + response = client.options( + '/sparql', + headers={ + 'Origin': 'http://example.com', + 'Access-Control-Request-Method': 'POST', + 'Access-Control-Request-Headers': 'Content-Type' + } + ) + + # Check CORS preflight response headers + assert 'Access-Control-Allow-Origin' in response.headers + assert 'Access-Control-Allow-Methods' in response.headers + assert 'Access-Control-Allow-Headers' in response.headers + + +def test_cors_headers_on_sparql_endpoint(client): + """Test that CORS headers are present on SPARQL endpoint.""" + response = client.get('/sparql') + + # Check for CORS headers + assert 'Access-Control-Allow-Origin' in response.headers + + +def test_cors_headers_on_api_endpoint(client): + """Test that CORS headers are present on API endpoints.""" + # Test with the nanopub list endpoint + response = client.get('/pub/') + + # Check for CORS headers + assert 'Access-Control-Allow-Origin' in response.headers diff --git a/whyis/app.py b/whyis/app.py index bc71c8152..e2826c8d3 100644 --- a/whyis/app.py +++ b/whyis/app.py @@ -22,6 +22,7 @@ from depot.middleware import FileServeApp from depot.io.utils import FileIntent from flask import render_template, g, redirect, url_for, request, flash, send_from_directory, abort +from flask_cors import CORS from flask_security import Security from flask_security.core import current_user from flask_security.forms import RegisterForm @@ -97,6 +98,19 @@ def configure_extensions(self): Empty.configure_extensions(self) + # Configure CORS to allow cross-origin requests from any origin + # This enables external applications to access Whyis APIs and data + CORS(self, resources={ + r"/*": { + "origins": "*", + "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], + "allow_headers": ["Content-Type", "Authorization", "Accept"], + "expose_headers": ["Content-Type", "Authorization"], + "supports_credentials": True, + "max_age": 3600 + } + }) + if self.config.get('EMBEDDED_CELERY',False): # self.config['CELERY_BROKER_URL'] = 'redis://localhost:6379' # self.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379' From e38d1b3b01e081de5a7a1c9ad3a7eafc4c01a4df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:01:24 +0000 Subject: [PATCH 20/26] Fix CORS security issue by disabling credentials with wildcard origins Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/whyis/app.py b/whyis/app.py index e2826c8d3..c4fa0ebbd 100644 --- a/whyis/app.py +++ b/whyis/app.py @@ -100,13 +100,14 @@ def configure_extensions(self): # Configure CORS to allow cross-origin requests from any origin # This enables external applications to access Whyis APIs and data + # Note: supports_credentials is False when using wildcard origins for security CORS(self, resources={ r"/*": { "origins": "*", "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], "allow_headers": ["Content-Type", "Authorization", "Accept"], "expose_headers": ["Content-Type", "Authorization"], - "supports_credentials": True, + "supports_credentials": False, "max_age": 3600 } }) From c46963dad9002429173bbf872c9fc85e960cc416 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:02:02 +0000 Subject: [PATCH 21/26] Extract CORS max_age to named constant for better maintainability Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/whyis/app.py b/whyis/app.py index c4fa0ebbd..7cd4c9276 100644 --- a/whyis/app.py +++ b/whyis/app.py @@ -101,6 +101,7 @@ def configure_extensions(self): # Configure CORS to allow cross-origin requests from any origin # This enables external applications to access Whyis APIs and data # Note: supports_credentials is False when using wildcard origins for security + CORS_MAX_AGE = 3600 # Preflight cache duration in seconds (1 hour) CORS(self, resources={ r"/*": { "origins": "*", @@ -108,7 +109,7 @@ def configure_extensions(self): "allow_headers": ["Content-Type", "Authorization", "Accept"], "expose_headers": ["Content-Type", "Authorization"], "supports_credentials": False, - "max_age": 3600 + "max_age": CORS_MAX_AGE } }) From 40b01697683c5f1f1b9dc14d0573dbd8b6bfd74e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:02:43 +0000 Subject: [PATCH 22/26] Make CORS origins configurable via CORS_ORIGINS setting Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/app.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/whyis/app.py b/whyis/app.py index 7cd4c9276..b9e5202a8 100644 --- a/whyis/app.py +++ b/whyis/app.py @@ -101,10 +101,15 @@ def configure_extensions(self): # Configure CORS to allow cross-origin requests from any origin # This enables external applications to access Whyis APIs and data # Note: supports_credentials is False when using wildcard origins for security + # To restrict origins, set CORS_ORIGINS in config (comma-separated list) CORS_MAX_AGE = 3600 # Preflight cache duration in seconds (1 hour) + cors_origins = self.config.get('CORS_ORIGINS', '*') + if cors_origins != '*' and ',' in cors_origins: + cors_origins = [origin.strip() for origin in cors_origins.split(',')] + CORS(self, resources={ r"/*": { - "origins": "*", + "origins": cors_origins, "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], "allow_headers": ["Content-Type", "Authorization", "Accept"], "expose_headers": ["Content-Type", "Authorization"], From ddde85105dad3ceb442dcebc7270a7dfb4b9a48d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:03:28 +0000 Subject: [PATCH 23/26] Fix CORS origins parsing to handle single origin correctly Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/app.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/whyis/app.py b/whyis/app.py index b9e5202a8..37f5ab767 100644 --- a/whyis/app.py +++ b/whyis/app.py @@ -101,11 +101,18 @@ def configure_extensions(self): # Configure CORS to allow cross-origin requests from any origin # This enables external applications to access Whyis APIs and data # Note: supports_credentials is False when using wildcard origins for security - # To restrict origins, set CORS_ORIGINS in config (comma-separated list) + # To restrict origins, set CORS_ORIGINS in config (comma-separated list or single origin) CORS_MAX_AGE = 3600 # Preflight cache duration in seconds (1 hour) cors_origins = self.config.get('CORS_ORIGINS', '*') - if cors_origins != '*' and ',' in cors_origins: - cors_origins = [origin.strip() for origin in cors_origins.split(',')] + + # Parse CORS_ORIGINS: wildcard, single origin, or comma-separated list + if cors_origins != '*': + if ',' in cors_origins: + # Multiple origins separated by commas + cors_origins = [origin.strip() for origin in cors_origins.split(',')] + else: + # Single origin - wrap in list for Flask-CORS + cors_origins = [cors_origins.strip()] CORS(self, resources={ r"/*": { From c005b12d1e8d1cfa8abf0290cb56c8b99ff8d498 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:04:40 +0000 Subject: [PATCH 24/26] Make CORS credentials configurable and enhance test coverage Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- tests/api/test_cors.py | 30 +++++++++++++++++++++++++++++- whyis/app.py | 12 ++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/tests/api/test_cors.py b/tests/api/test_cors.py index dadb17db2..6d2420ac1 100644 --- a/tests/api/test_cors.py +++ b/tests/api/test_cors.py @@ -17,7 +17,7 @@ def test_cors_headers_on_root(client): def test_cors_preflight_request(client): - """Test CORS preflight (OPTIONS) request.""" + """Test CORS preflight (OPTIONS) request with detailed header validation.""" response = client.options( '/sparql', headers={ @@ -31,6 +31,18 @@ def test_cors_preflight_request(client): assert 'Access-Control-Allow-Origin' in response.headers assert 'Access-Control-Allow-Methods' in response.headers assert 'Access-Control-Allow-Headers' in response.headers + + # Validate allowed methods include expected values + allowed_methods = response.headers.get('Access-Control-Allow-Methods', '') + expected_methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'] + for method in expected_methods: + assert method in allowed_methods, f"Expected method {method} not in allowed methods" + + # Validate allowed headers include expected values + allowed_headers = response.headers.get('Access-Control-Allow-Headers', '').lower() + expected_headers = ['content-type', 'authorization', 'accept'] + for header in expected_headers: + assert header in allowed_headers, f"Expected header {header} not in allowed headers" def test_cors_headers_on_sparql_endpoint(client): @@ -48,3 +60,19 @@ def test_cors_headers_on_api_endpoint(client): # Check for CORS headers assert 'Access-Control-Allow-Origin' in response.headers + + +def test_cors_max_age_header(client): + """Test that CORS max age header is set correctly.""" + response = client.options( + '/sparql', + headers={ + 'Origin': 'http://example.com', + 'Access-Control-Request-Method': 'GET' + } + ) + + # Check for max age header + assert 'Access-Control-Max-Age' in response.headers + # Verify it's set to 3600 (1 hour) + assert response.headers['Access-Control-Max-Age'] == '3600' diff --git a/whyis/app.py b/whyis/app.py index 37f5ab767..7747801a6 100644 --- a/whyis/app.py +++ b/whyis/app.py @@ -100,8 +100,9 @@ def configure_extensions(self): # Configure CORS to allow cross-origin requests from any origin # This enables external applications to access Whyis APIs and data - # Note: supports_credentials is False when using wildcard origins for security + # Note: supports_credentials should be False with wildcard origins for security # To restrict origins, set CORS_ORIGINS in config (comma-separated list or single origin) + # To enable credentials, set CORS_SUPPORTS_CREDENTIALS=True (only with specific origins) CORS_MAX_AGE = 3600 # Preflight cache duration in seconds (1 hour) cors_origins = self.config.get('CORS_ORIGINS', '*') @@ -114,13 +115,20 @@ def configure_extensions(self): # Single origin - wrap in list for Flask-CORS cors_origins = [cors_origins.strip()] + # supports_credentials can only be True with specific origins (not wildcard) + supports_credentials = self.config.get('CORS_SUPPORTS_CREDENTIALS', False) + if supports_credentials and cors_origins == '*': + # Warn and disable credentials if wildcard is used + print("WARNING: CORS_SUPPORTS_CREDENTIALS cannot be True with wildcard origins. Disabling.") + supports_credentials = False + CORS(self, resources={ r"/*": { "origins": cors_origins, "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], "allow_headers": ["Content-Type", "Authorization", "Accept"], "expose_headers": ["Content-Type", "Authorization"], - "supports_credentials": False, + "supports_credentials": supports_credentials, "max_age": CORS_MAX_AGE } }) From 36fa4cd86ca690616caec5279a347532df850ac3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:05:27 +0000 Subject: [PATCH 25/26] Make CORS max_age configurable and use proper logging Co-authored-by: jpmccu <602385+jpmccu@users.noreply.github.com> --- whyis/app.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/whyis/app.py b/whyis/app.py index 7747801a6..7a707344e 100644 --- a/whyis/app.py +++ b/whyis/app.py @@ -101,9 +101,11 @@ def configure_extensions(self): # Configure CORS to allow cross-origin requests from any origin # This enables external applications to access Whyis APIs and data # Note: supports_credentials should be False with wildcard origins for security - # To restrict origins, set CORS_ORIGINS in config (comma-separated list or single origin) - # To enable credentials, set CORS_SUPPORTS_CREDENTIALS=True (only with specific origins) - CORS_MAX_AGE = 3600 # Preflight cache duration in seconds (1 hour) + # Configuration options: + # CORS_ORIGINS: "*" (default), single origin, or comma-separated list + # CORS_SUPPORTS_CREDENTIALS: False (default), True (only with specific origins) + # CORS_MAX_AGE: 3600 (default), preflight cache duration in seconds + cors_max_age = self.config.get('CORS_MAX_AGE', 3600) cors_origins = self.config.get('CORS_ORIGINS', '*') # Parse CORS_ORIGINS: wildcard, single origin, or comma-separated list @@ -119,7 +121,7 @@ def configure_extensions(self): supports_credentials = self.config.get('CORS_SUPPORTS_CREDENTIALS', False) if supports_credentials and cors_origins == '*': # Warn and disable credentials if wildcard is used - print("WARNING: CORS_SUPPORTS_CREDENTIALS cannot be True with wildcard origins. Disabling.") + self.logger.warning("CORS: CORS_SUPPORTS_CREDENTIALS cannot be True with wildcard origins. Disabling credentials.") supports_credentials = False CORS(self, resources={ @@ -129,7 +131,7 @@ def configure_extensions(self): "allow_headers": ["Content-Type", "Authorization", "Accept"], "expose_headers": ["Content-Type", "Authorization"], "supports_credentials": supports_credentials, - "max_age": CORS_MAX_AGE + "max_age": cors_max_age } }) From 18b47c11e2bc69aec69eae40f575d82ba5e3234c Mon Sep 17 00:00:00 2001 From: Jamie McCusker Date: Wed, 4 Feb 2026 21:41:23 +0000 Subject: [PATCH 26/26] Fixed additional layout issues. --- whyis/blueprint/sparql/sparql_view.py | 3 +- whyis/filters.py | 6 + .../components/pages/search/search-page.vue | 2 +- .../pages/vega/editor/vega-editor.html | 198 ++++++++++-------- .../components/search-autocomplete.vue | 2 +- .../components/vega-lite-wrapper.vue | 12 +- .../js/whyis_vue/utilities/vega-chart.js | 4 +- whyis/static/vite.config.js | 15 +- whyis/templates/base_vue.html | 1 - whyis/templates/class_view.html | 145 +++++++------ whyis/templates/elements/upload.html | 4 +- whyis/templates/resource_view.html | 147 +++++++------ 12 files changed, 303 insertions(+), 236 deletions(-) diff --git a/whyis/blueprint/sparql/sparql_view.py b/whyis/blueprint/sparql/sparql_view.py index 649a91c93..ee1587ded 100644 --- a/whyis/blueprint/sparql/sparql_view.py +++ b/whyis/blueprint/sparql/sparql_view.py @@ -77,7 +77,7 @@ def sparql_view(): # Filter headers for proxying headers = filter_headers_for_proxying(request.headers) - + print (raw_data) req = current_app.db.store.raw_sparql_request( method='POST', headers=headers, @@ -117,6 +117,7 @@ def sparql_view(): # Filter headers for proxying headers = filter_headers_for_proxying(request.headers) + print(raw_data) # Send raw_data (bytes) not request.values (dict) to preserve exact form encoding req = requests.post(current_app.db.store.query_endpoint, headers=headers, data=raw_data, stream=True) diff --git a/whyis/filters.py b/whyis/filters.py index 695966ff1..e9902c86d 100644 --- a/whyis/filters.py +++ b/whyis/filters.py @@ -59,6 +59,12 @@ def labelize(entry, key='about', label_key='label', fetch=False): entry[label_key] = app.get_label(resource) return entry + @app.template_filter('label') + def label(entity): + key_uri = rdflib.URIRef(entity) + resource = app.db.resource(key_uri) + return app.get_label(resource) + @app.template_filter('iter_labelize') def iter_labelize(entries, *args, **kw): for entry in entries: diff --git a/whyis/static/js/whyis_vue/components/pages/search/search-page.vue b/whyis/static/js/whyis_vue/components/pages/search/search-page.vue index 3af583ea2..3be80ef29 100644 --- a/whyis/static/js/whyis_vue/components/pages/search/search-page.vue +++ b/whyis/static/js/whyis_vue/components/pages/search/search-page.vue @@ -11,7 +11,7 @@ v-model="searchQuery" type="text" class="form-control" - placeholder="Search knowledge base..." + placeholder="Search..." @keydown.enter="performSearch" />
+
-
diff --git a/whyis/static/js/whyis_vue/components/search-autocomplete.vue b/whyis/static/js/whyis_vue/components/search-autocomplete.vue index cdbea42c8..651775be0 100644 --- a/whyis/static/js/whyis_vue/components/search-autocomplete.vue +++ b/whyis/static/js/whyis_vue/components/search-autocomplete.vue @@ -8,7 +8,7 @@ v-model="searchQuery" type="text" class="form-control form-control-lg" - placeholder="Search knowledge base..." + placeholder="Search..." @input="onInput" @focus="onFocus" @blur="onBlur" diff --git a/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue b/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue index e4158c32e..4eb645de6 100644 --- a/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue +++ b/whyis/static/js/whyis_vue/components/vega-lite-wrapper.vue @@ -1,7 +1,7 @@ @@ -19,7 +19,7 @@ export default { data () { return { id: 'vega-lite', - specValidation: {} + specValidation: {"valid": true} } }, props: { @@ -70,10 +70,10 @@ export default { this.specValidation = validation }, processSpec () { - this.validateSpec() - if (this.specValidation.valid) { + //this.validateSpec() + //if (this.specValidation.valid) { this.plotSpec() - } + //} } }, watch: { diff --git a/whyis/static/js/whyis_vue/utilities/vega-chart.js b/whyis/static/js/whyis_vue/utilities/vega-chart.js index a3a1871dc..41044991c 100644 --- a/whyis/static/js/whyis_vue/utilities/vega-chart.js +++ b/whyis/static/js/whyis_vue/utilities/vega-chart.js @@ -15,10 +15,10 @@ import { querySparql } from './sparql' const defaultQuery = ` PREFIX rdfs: -SELECT DISTINCT ?c (MIN(?class) AS ?class) (COUNT(?x) AS ?count) +SELECT DISTINCT ?c (MIN(?clabel) AS ?class) (COUNT(?x) AS ?count) WHERE { ?x a ?c. - ?c rdfs:label ?class. + ?c rdfs:label ?clabel. } GROUP BY ?c ORDER BY DESC(?count) diff --git a/whyis/static/vite.config.js b/whyis/static/vite.config.js index c8cab33b7..143222666 100644 --- a/whyis/static/vite.config.js +++ b/whyis/static/vite.config.js @@ -16,13 +16,14 @@ export default defineConfig({ "process.env": {}, }, build: { - lib: { - // Could also be a dictionary or array of multiple entry points - entry: resolve(__dirname, 'js/whyis_vue/main.js'), - name: 'whyis', - // the proper extensions will be added - fileName: 'whyis', - }, + sourcemap: true, + lib: { + // Could also be a dictionary or array of multiple entry points + entry: resolve(__dirname, 'js/whyis_vue/main.js'), + name: 'whyis', + // the proper extensions will be added + fileName: 'whyis', + }, }, plugins: [ vue() diff --git a/whyis/templates/base_vue.html b/whyis/templates/base_vue.html index 2d518d32d..447dffcd9 100644 --- a/whyis/templates/base_vue.html +++ b/whyis/templates/base_vue.html @@ -82,7 +82,6 @@ {% endif %}
  • Query Data
  • Create Visualization
  • -
  • Upload Dataset
  • diff --git a/whyis/templates/class_view.html b/whyis/templates/class_view.html index 070890df1..7e2883ad0 100644 --- a/whyis/templates/class_view.html +++ b/whyis/templates/class_view.html @@ -7,37 +7,37 @@ {% set incoming = this | include("incoming") | fromjson %} {% set outgoing = this | include("outgoing") | fromjson %} -
    + +
    -

    {{attributes.label}}

    @@ -77,33 +77,41 @@

    {{attributes.label}}

    {% for link_name, items in outgoing | groupby("link_label") %}

    {{link_name.title() }}

    -
    +
    {% for item in items %} -
    -
    -
    -
    {{item.target_label}}
    - {% if this.graph.value(rdflib.URIRef(item.target), ns.foaf.depiction) %} +
    + {% if this.graph.value(rdflib.URIRef(item.target), ns.foaf.depiction) %} {% set depiction = this.graph.value(rdflib.URIRef(item.target), ns.foaf.depiction) %} -
    - {% if depiction.startswith(config['LOD_PREFIX']) %} + {% if depiction.startswith(config['LOD_PREFIX']) %} {{item.target_label}} - {% else %} + {% else %} {{item.target_label}} - {% endif %} -
    {% endif %} + {% endif %} +
    +
    +
    +
    {{item.target_label}}
    + {% if item.target_types | length > 0 %}

    + {% for type in item.target_types %}{{type | label }}{% if not loop.last %}, {% endif %} {% endfor %} +

    {% endif %} +
    +
    + + +
    +
    - -
    + {% endfor %}
    {% endfor %} @@ -115,32 +123,39 @@

    {{link_name.title()}} Of {% endif %}

    -
    +
    {% for item in items %} -
    -
    -
    -
    {{item.source_label}}
    - {% if this.graph.value(rdflib.URIRef(item.source), ns.foaf.depiction) %} +
    + {% if this.graph.value(rdflib.URIRef(item.source), ns.foaf.depiction) %} {% set depiction = this.graph.value(rdflib.URIRef(item.source), ns.foaf.depiction) %} -
    - {% if depiction.startswith(config['LOD_PREFIX']) %} + {% if depiction.startswith(config['LOD_PREFIX']) %} {{item.source_label}} - {% else %} + {% else %} {{item.source_label}} - {% endif %} -
    {% endif %} + {% endif %} +
    +
    +
    +
    {{item.source_label}}
    + {% if item.source_types | length > 0 %}

    + {% for type in item.source_types %}{{type | label}}{% if not loop.last %}, {% endif %} {% endfor %} +

    {% endif %} +
    +
    + + +
    +
    - -
    {% endfor %}
    diff --git a/whyis/templates/elements/upload.html b/whyis/templates/elements/upload.html index 561235d90..54234ab71 100644 --- a/whyis/templates/elements/upload.html +++ b/whyis/templates/elements/upload.html @@ -85,11 +85,13 @@