diff --git a/5.2curlcommands.md b/5.2curlcommands.md index d5dd239..fb8c03f 100644 --- a/5.2curlcommands.md +++ b/5.2curlcommands.md @@ -754,7 +754,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin "toolName":"jupyterPreviewer", "scope":"file", "types":["preview"], - "toolUrl":"https://gdcc.github.io/dataverse-previewers/previewers/betatest/JupyterPreview.html", + "toolUrl":"https://qualitativedatarepository/dataverse-previewers/previewers/betatest/JupyterPreview.html", "toolParameters": { "queryParameters":[ {"fileid":"{fileId}"}, @@ -781,7 +781,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin "toolName":"mapShpPreviewer", "scope":"file", "types":["preview"], - "toolUrl":"qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapShpPreview.html", + "toolUrl":"https://qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapShpPreview.html", "toolParameters": { "queryParameters":[ {"fileid":"{fileId}"}, @@ -810,7 +810,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin "toolName":"mapShpPreviewer", "scope":"file", "types":["preview"], - "toolUrl":"qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapRasterPreview.html", + "toolUrl":"https://qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapRasterPreview.html", "toolParameters": { "queryParameters":[ {"fileid":"{fileId}"}, diff --git a/6.1curlcommands.md b/6.1curlcommands.md index ca3b245..6bd7ccb 100644 --- a/6.1curlcommands.md +++ b/6.1curlcommands.md @@ -2008,7 +2008,61 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin These previewers support the [REFI-QDA standard](https://www.qdasoftware.org/) format for qualitative research files. They allow viewing of the authors, codes, sources, annotations, and relationships listed in the codebook, with added functionality to view code use frequency and filter by the entries in these tables. The REFI-QDA Project previewer -also allows viewing the annotation anchor text retrieved from the zipped source files. +also allows viewing the annotation anchor text retrieved from the zipped source files and download of the original and text versions of source files. +Further, for users who can edit the dataset, the Codebook and Project Previewers allow creation of "redacted" versions that become publicly previewable +even when the datafiles themselves are restricted. The "View *" previewers will only show when the user can download the datafile, while the "View Redacted *" previewers +will show all the time. As the first available previewer shows as the default one in the dataset files table, it is recommended that these previewers +be installed in the order shown below (so the full previewers are the default for those who can download the datafile). + + + +```bash +curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \ +'{ + "displayName":"View REFI-QDA Codebook", + "description":"View the contents of a REFI-QDA codebook file.", + "toolName":"refiqdaCodebookPreviewer", + "scope":"file", + "types":["preview"], + "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDAPreview.html", + "toolParameters": { + "queryParameters":[ + {"fileid":"{fileId}"}, + {"siteUrl":"{siteUrl}"}, + {"datasetid":"{datasetId}"}, + {"datasetversion":"{datasetVersion}"}, + {"locale":"{localeCode}"} + ] + }, + "contentType":"text/x-xml-refiqda", + "allowedApiCalls": [ + { + "name": "retrieveFileContents", + "httpMethod": "GET", + "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=true", + "timeOut": 3600 + }, + { + "name": "downloadFile", + "httpMethod": "GET", + "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=false", + "timeOut": 3600 + }, + { + "name": "uploadRedactedFile", + "httpMethod": "POST", + "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdc/1.0", + "timeOut": 3600 + }, + { + "name": "getDatasetVersionMetadata", + "httpMethod": "GET", + "urlTemplate": "/api/v1/datasets/{datasetId}/versions/{datasetVersion}", + "timeOut": 3600 + } + ] +}' +``` ```bash curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \ @@ -2018,7 +2072,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin "toolName":"refiqdaProjectPreviewer", "scope":"file", "types":["preview"], - "toolUrl":"https://gdcc.github.io/dataverse-previewers/previewers/betatest/REFIQDPXPreview.html", + "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDPXPreview.html", "toolParameters": { "queryParameters":[ {"fileid":"{fileId}"}, @@ -2042,6 +2096,12 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=false", "timeOut": 3600 }, + { + "name": "uploadRedactedFile", + "httpMethod": "POST", + "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdpx/1.0", + "timeOut": 3600 + }, { "name": "getDatasetVersionMetadata", "httpMethod": "GET", @@ -2055,12 +2115,62 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin ```bash curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \ '{ - "displayName":"View REFI-QDA Codebook", - "description":"View the contents of a REFI-QDA codebook file.", - "toolName":"refiqdaCodebookPreviewer", + "displayName":"View Redacted REFI-QDA Project", + "description":"View the contents of a redacted EFI-QDA project file.", + "toolName":"refiqdaRedactedProjectPreviewer", "scope":"file", "types":["preview"], - "toolUrl":"https://gdcc.github.io/dataverse-previewers/previewers/betatest/REFIQDAPreview.html", + "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDPXPreview.html", + "toolParameters": { + "queryParameters":[ + {"fileid":"{fileId}"}, + {"siteUrl":"{siteUrl}"}, + {"datasetid":"{datasetId}"}, + {"datasetversion":"{datasetVersion}"}, + {"locale":"{localeCode}"} + ] + }, + "contentType":"application/x-zip-refiqda", + "requirements": { + "auxFilesExist": [ + { + "formatTag": "qdpx", + "formatVersion": "1.0" + } + ] + }, + "allowedApiCalls": [ + { + "name": "retrieveFileContents", + "httpMethod": "GET", + "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdpx/1.0?gbrecs=true", + "timeOut": 3600 + }, + { + "name": "downloadFile", + "httpMethod": "GET", + "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdpx/1.0?gbrecs=false", + "timeOut": 3600 + }, + { + "name": "getDatasetVersionMetadata", + "httpMethod": "GET", + "urlTemplate": "/api/v1/datasets/{datasetId}/versions/{datasetVersion}", + "timeOut": 3600 + } + ] +}' +``` + +```bash +curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \ +'{ + "displayName":"View Redacted REFI-QDA Codebook", + "description":"View the contents of a redacted REFI-QDA codebook file.", + "toolName":"refiqdaRedactedCodebookPreviewer", + "scope":"file", + "types":["preview"], + "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDAPreview.html", "toolParameters": { "queryParameters":[ {"fileid":"{fileId}"}, @@ -2071,17 +2181,25 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin ] }, "contentType":"text/x-xml-refiqda", + "requirements": { + "auxFilesExist": [ + { + "formatTag": "qdc", + "formatVersion": "1.0" + } + ] + }, "allowedApiCalls": [ { "name": "retrieveFileContents", "httpMethod": "GET", - "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=true", + "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdc/1.0?gbrecs=true", "timeOut": 3600 }, { "name": "downloadFile", "httpMethod": "GET", - "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=false", + "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdc/1.0?gbrecs=false", "timeOut": 3600 }, { diff --git a/previewers/betatest/REFIQDAPreview.html b/previewers/betatest/REFIQDAPreview.html index 6afce8b..5f569ae 100644 --- a/previewers/betatest/REFIQDAPreview.html +++ b/previewers/betatest/REFIQDAPreview.html @@ -6,9 +6,8 @@ REFI-QDA Preview - - - + + diff --git a/previewers/betatest/REFIQDPXPreview.html b/previewers/betatest/REFIQDPXPreview.html index 426d8cc..9d2c632 100644 --- a/previewers/betatest/REFIQDPXPreview.html +++ b/previewers/betatest/REFIQDPXPreview.html @@ -9,15 +9,15 @@ + - - - + + ').attr('id', 'waiting'); - $('').width('15%').attr('src','images/Loading_icon.gif').attr('id','throbber').appendTo(wait); - $('').text('Retrieving File...').appendTo(wait); - wait.appendTo($('.preview')); + addStandardPreviewHeader(file, title, authors); + options = { + "stripIgnoreTag": true, + "stripIgnoreTagBody": ['script', 'head'] + }; // Custom rules + wait = $('
').attr('id', 'waiting'); + $('').width('15%').attr('src', 'images/Loading_icon.gif').attr('id', 'throbber').appendTo(wait); + $('').text('Retrieving File...').appendTo(wait); + wait.appendTo($('.preview')); - fetch(fileUrl) - .then(response => response.text()) - .then(data => parseData(data)); + if (fileUrl.includes('auxiliary/qdpx')) { + redactedMode = true; + } else { + redactedMode = false; + } + fetch(fileUrl) + .then(response => response.text()) + .then(data => parseData(data, file)); } diff --git a/previewers/betatest/js/refiqdacore.js b/previewers/betatest/js/refiqdacore.js index 8d40f2a..749dbf9 100644 --- a/previewers/betatest/js/refiqdacore.js +++ b/previewers/betatest/js/refiqdacore.js @@ -12,6 +12,7 @@ var sourceDataTable; var setDataTable; var tables = new Array(); +let file; var textSourceCache = new Map(); // Cache for loaded text files $(document).ready(function() { @@ -23,44 +24,6 @@ function translateBaseHtmlPage() { $('.refiqdaPreviewText').text(refiqdaPreviewText); } -function addSelectAllAndUnselectButtons(dataTable, tableContainer, tableId) { - // Add "Unselect All" button next to the table title - const unselectAllButton = $('') - .addClass('unselect-all-btn') - .hide() - .on('click', function() { - dataTable.rows({ selected: true }).deselect(); - }); - tableContainer.find('h2').first().append(unselectAllButton); - - // Add "Select All" checkbox to the table header - const selectAllCheckbox = $(''); - $('#' + tableId + ' thead tr').prepend($('').append(selectAllCheckbox)); - $('#' + tableId + ' tbody tr').prepend($('')); // Placeholder for alignment - - selectAllCheckbox.on('click', function() { - if (this.checked) { - dataTable.rows().select(); - } else { - dataTable.rows().deselect(); - } - }); - - // Show/hide "Unselect All" button and manage "Select All" checkbox state - dataTable.on('select deselect', function() { - const selectedRows = dataTable.rows({ selected: true }).count(); - const totalRows = dataTable.rows().count(); - - if (selectedRows > 0) { - unselectAllButton.show(); - } else { - unselectAllButton.hide(); - } - - selectAllCheckbox.prop('checked', selectedRows === totalRows); - }); -} - var zipUrl = ''; //zipUrl is set in refiqdpx.js - the zip file case @@ -68,6 +31,8 @@ function isZipMode() { return typeof zipUrl !== 'undefined' && zipUrl !== null && zipUrl !== ''; } +var redactedMode; + var wait; var cy; @@ -84,7 +49,8 @@ function findDataAttribute(name, attrNamedNodeMap) { // Start parsing project file // This function just adds a loading icon and initial text to the page and then calls parseData2 -function parseData(data) { +function parseData(data, filejson) { + file=filejson; $('#waiting').remove(); wait = $('
').attr('id', 'waiting'); $('').width('15%').attr('src', 'images/Loading_icon.gif').appendTo(wait); @@ -101,7 +67,9 @@ function parseData2(data) { parser = new DOMParser(); xmlDoc = parser.parseFromString(data, "text/xml"); - + if(redactedMode) { + let redactedNotice = $('

').addClass('redacted-notice').text("Note: This is a redacted, public view of the restricted QDAS file").appendTo($(".preview")); + } //Add a Filter By option let filterBlock = $('
').width(tableWidth).appendTo($(".preview")); filterBlock.append($("

").html("Enable Filtering By")); @@ -295,26 +263,34 @@ function parseData2(data) { let pdfSel = selection.pdfSelection; let textSel = selection.plainTextSelection; let selectionName = pdfSel.getAttribute("name"); + + if(!selectionName) { + selectionName = "(Hover for more info)"; + } guid = pdfSel.getAttribute("guid"); codes = getCodeNames(pdfSel); // Codes are on the PDF selection + let sourceGuid = source.getAttribute("guid"); + selectionMatches = sourceMatches + pdfSel.getAttribute("creatingUser") + pdfSel.getAttribute("modifyingUser") + textSel.getAttribute("creatingUser") + textSel.getAttribute("modifyingUser") + - getCodeRelatedGUIDs(pdfSel); - - let sourceGuid = source.getAttribute("guid"); + getCodeRelatedGUIDs(pdfSel) + sourceGuid; displayName = createMergedSelectionWithTooltip(selectionName, pdfSel, textSel, sourceGuid); } else { // Handle regular selection node let selectionName = selection.getAttribute("name"); + if(!selectionName) { + selectionName = "(Hover for more info)"; + } guid = selection.getAttribute("guid"); codes = getCodeNames(selection); - selectionMatches = sourceMatches + selection.getAttribute("creatingUser") + selection.getAttribute("modifyingUser") + getCodeRelatedGUIDs(selection); - let sourceGuid = source.getAttribute("guid"); + selectionMatches = sourceMatches + selection.getAttribute("creatingUser") + selection.getAttribute("modifyingUser") + getCodeRelatedGUIDs(selection) + sourceGuid; + + displayName = selectionName; // Default display name if (selection.nodeName === "PDFSelection") { @@ -409,7 +385,19 @@ function parseData2(data) { }); sourceDataTable = new DataTable(".sourcetable", { - select: $('#filterby').val() == 'Sources' + select: $('#filterby').val() == 'Sources', + order: [[0, 'asc']], + columnDefs: [ + { + render: function(data, type, row) { + if (type === 'display' && data !== null && data.length > 50) { + return '' + data.substr(0, 50) + '...'; + } + return data; + }, + targets: 1 + } + ] }); if (typeof downloadFile === 'function') { @@ -446,14 +434,29 @@ function parseData2(data) { desc = desc[0].childNodes[0]; } let matches = ''; - if (note.getAttribute("creatingUser")) { - matches = matches + note.getAttribute("creatingUser"); + let name = ''; + let creatingUserGuid = note.getAttribute("creatingUser"); + let modifyingUserGuid = note.getAttribute("modifyingUser"); + let userNames = new Set(); + + if (creatingUserGuid) { + matches += creatingUserGuid; + let user = userMap.get(creatingUserGuid); + if (user) { + userNames.add(user.getAttribute("name")); + } + } + + if (modifyingUserGuid) { + matches += modifyingUserGuid; + let user = userMap.get(modifyingUserGuid); + if (user) { + userNames.add(user.getAttribute("name")); } - if (note.getAttribute("modifyingUser")) { - matches = matches + note.getAttribute("modifyingUser"); } + name = Array.from(userNames).join(', '); - let tr = addRow(noteTable, note.getAttribute("name"), ptc, desc, userMap.get(note.getAttribute("creatingUser")).getAttribute("name")); + let tr = addRow(noteTable, note.getAttribute("name"), ptc, desc, name); tr.attr('data-guid', note.getAttribute("guid")); tr.attr('data-matches', matches); @@ -468,6 +471,62 @@ function parseData2(data) { tables.push(noteDataTable); } + let variables = xmlDoc.getElementsByTagName("Variable"); + let cases = xmlDoc.getElementsByTagName("Case"); + + if (variables.length > 0 && cases.length > 0) { + let variableMap = new Map(); + let variableHeaders = ["Source"]; // First column is the source document + + for (let variable of variables) { + let guid = variable.getAttribute("guid"); + let name = variable.getAttribute("name"); + // Store the variable name and its column index in the table + variableMap.set(guid, { name: name, index: variableHeaders.length }); + variableHeaders.push(name); + } + + let caseBlock = $('
').width(tableWidth).appendTo($(".preview")); + caseBlock.append($("

").html("Cases")); + let caseTable = createTable("Cases", ...variableHeaders).appendTo(caseBlock); + caseTable.addClass("casetable compact stripe"); + + for (let caseNode of cases) { + let rowData = new Array(variableHeaders.length).fill(""); // Initialize row with empty strings + + // Find the source document for the case + let sourceRef = caseNode.getElementsByTagName("SourceRef")[0]; + if (sourceRef) { + let sourceGuid = sourceRef.getAttribute("targetGUID"); + let source = sourceMap.get(sourceGuid); + if (source) { + rowData[0] = createSourceReference(source, zipUrl); + } + } + + // Populate variable values for the case + let variableValues = caseNode.getElementsByTagName("VariableValue"); + for (let varValue of variableValues) { + let varRef = varValue.getElementsByTagName("VariableRef")[0]; + let textValue = varValue.getElementsByTagName("TextValue")[0]; + if (varRef && textValue) { + let varGuid = varRef.getAttribute("targetGUID"); + let variableInfo = variableMap.get(varGuid); + if (variableInfo) { + rowData[variableInfo.index] = textValue.textContent; + } + } + } + addRow(caseTable, ...rowData); + } + + // Initialize DataTable for cases, but don't add to filterable tables + new DataTable(".casetable", { + select: false // This table should not be selectable + }); + } + + let sets = xmlDoc.getElementsByTagName("Set"); if (sets != null && sets.length > 0) { $('#filterby').append($('