157b3e782017ad059b88a62fa75a349874188d4d chmalee Fri Feb 14 12:23:00 2025 -0800 Calculate hubSpace directory size in UI as size of all child files, refs #31058 diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js index 726990fa523..8a4da8837fa 100644 --- src/hg/js/hgMyData.js +++ src/hg/js/hgMyData.js @@ -111,31 +111,32 @@ if (thisQuota + hubCreate.uiState.userQuota > hubCreate.uiState.maxQuota) { uppy.info(`Error: this file batch exceeds your quota. Please delete some files to make space or email genome-www@soe.ucsc.edu if you feel you need more space.`); doUpload = false; } return doUpload; }, }); var hubCreate = (function() { let uiState = { // our object for keeping track of the current UI and what to do userUrl: "", // the web accesible path where the uploads are stored for this user hubNameDefault: "", isLoggedIn: "", maxQuota: 0, userQuota: 0, - userFiles: {}, + userFiles: {}, // same as uiData.userFiles on page load + filesHash: {}, // for each file, userFiles.fullPath is the key, and then the userFiles.fileList data as the value, with an extra key for the child fullPaths if the file is a directory }; function getTusdEndpoint() { // this variable is set by hgHubConnect and comes from hg.conf value return tusdEndpoint; } let extensionMap = { "bigBed": [".bb", ".bigbed"], "bam": [".bam"], "vcf": [".vcf"], "vcfTabix": [".vcf.gz", "vcf.bgz"], "bigWig": [".bw", ".bigwig"], "hic": [".hic"], "cram": [".cram"], @@ -451,32 +452,78 @@ // match the background color of the normal rows: rowNode.style.backgroundColor = "#f9f9f9"; let thead = document.querySelector(".dt-scroll-headInner > table:nth-child(1) > thead:nth-child(1)"); if (thead.childNodes.length === 1) { thead.appendChild(rowClone); } else { thead.replaceChild(rowClone, thead.lastChild); } // remove the row row.remove(); // now do a regular order table.order([{name: "uploadTime", dir: "desc"}]); } } + function parseFileListIntoHash(fileList) { + // Hash the uiState fileList by the fullPath, and also store the children + // for each directory + // first go through and copy all of the data and make the empty + // children array for each directory + fileList.forEach(function(fileData) { + uiState.filesHash[fileData.fullPath] = fileData; + if (fileData.fileType === "dir") { + uiState.filesHash[fileData.fullPath].children = []; + } + }); + // use a second pass to go through and set the children + // since we may not have encountered them yet in the above loop + fileList.forEach(function(fileData) { + if (fileData.fileType !== "dir" || fileData.parentDir !== "") { + // compute the key from the fullPath: + let parts = fileData.fullPath.split("/"); + let keyName = parts.slice(0,-1).join("/"); + if (keyName in uiState.filesHash) { + uiState.filesHash[keyName].children.push(fileData); + } + } + }); + } + + function getChildRows(dirFullPath, childRowArray) { + // Recursively return all of the child rows for a given path + let childRows = uiState.filesHash[dirFullPath].children; + childRows.forEach(function(rowData) { + if (rowData.fileType !== "dir") { + childRowArray.push(rowData); + } else { + childRowArray.concat(getChildRows(rowData.fullPath, childRowArray)); + } + }); + } + function dataTablePrintSize(data, type, row, meta) { + if (row.fileType !== "dir") { return prettyFileSize(data); + } else { + let childRows = []; + getChildRows(row.fullPath, childRows); + let sum = childRows.reduce( (accumulator, currentValue) => { + return accumulator + currentValue.fileSize; + }, 0); + return prettyFileSize(sum); + } } function dataTablePrintGenome(data, type, row, meta) { if (data.startsWith("hub_")) return data.split("_").slice(2).join("_"); return data; } function dataTablePrintAction(rowData) { /* Return a node for rendering the actions column */ if (rowData.fileType === "dir") { let folderIcon = document.createElement("i"); folderIcon.style.display = "inline-block"; folderIcon.style.backgroundImage = "url(\"../images/folderC.png\")"; folderIcon.style.backgroundPosition = "left center"; @@ -526,55 +573,55 @@ } function addFileToHub(rowData) { // a file has been uploaded and a hub has been created, present a modal // to choose which hub to associate this track to // backend wise: move the file into the hub directory // update the hubSpace row with the hub name // frontend wise: move the file row into a 'child' of the hub row console.log(`sending addToHub req for ${rowData.fileName} to `); cart.setCgi("hgHubConnect"); cart.send({addToHub: {hubName: "", dataFile: ""}}); cart.flush(); } - // hash of file paths to their objects, starts as uiState.userFiles - let filesHash = {}; function addNewUploadedHubToTable(hub) { // hub is a list of objects representing the file just uploaded, the associated // hub.txt, and directory. Make a new row for each in the filesTable, except for // maybe the hub directory row and hub.txt which we may have already seen before let table = $("#filesTable").DataTable(); let justUploaded = {}; // hash of contents of hub but keyed by fullPath let hubDirData = {}; // the data for the parentDir of the uploaded file for (let obj of hub) { if (!obj.parentDir) { hubDirData = obj; } let rowObj; - if (!(obj.fullPath in filesHash)) { + if (!(obj.fullPath in uiState.filesHash)) { justUploaded[obj.fullPath] = obj; rowObj = table.row.add(obj); - filesHash[obj.fullPath] = obj; uiState.fileList.push(obj); + // NOTE: we don't add the obj to the filesHash until after we're done + // so we don't need to reparse all files each time we add one } } // show all the new rows we just added, note the double draw, we need // to have the new rows rendered to do the order because the order // will copy the actual DOM node + parseFileListIntoHash(uiState.fileList); dataTableShowDir(table, hubDirData.fileName, hubDirData.fullPath); table.draw(); dataTableCustomOrder(table, hubDirData); table.draw(); } function doRowSelect(ev, table, indexes) { let row = table.row(indexes); let rowTr = row.node(); if (rowTr) { let rowCheckbox = rowTr.childNodes[0].firstChild; if (rowTr.classList.contains("parentRow")) { // we need to manually check the children table.rows((idx,rowData) => rowData.fullPath.startsWith(row.data().fullPath) && rowData.parentDir === row.data().fileName).every(function(rowIdx, tableLoop, rowLoop) { if (ev.type === "select") { @@ -634,33 +681,33 @@ orderable: false, targets: 0, render: DataTable.render.select(), }, { orderable: false, targets: 1, data: "action", title: "", render: function(data, type, row) { if (type === "display") { return dataTablePrintAction(row); } return ''; } }, { targets: 3, - render: function(data, type, row) { + render: function(data, type, row, meta) { if (type === "display") { - return dataTablePrintSize(data); + return dataTablePrintSize(data, type, row, meta); } return data; } }, { targets: 5, render: function(data, type, row) { if (type === "display") { return dataTablePrintGenome(data); } return data; } }, { // The upload time column @@ -742,45 +789,43 @@ document.getElementById("rootBreadcrumb").addEventListener("click", function(e) { dataTableShowTopLevel(table); dataTableCustomOrder(table); dataTableEmptyBreadcrumb(table); table.draw(); }); } else { table.buttons(".uploadButton").disable(); } table.on("select", function(e, dt, type, indexes) { doRowSelect(e, dt, indexes); }); table.on("deselect", function(e, dt, type, indexes) { doRowSelect(e, dt, indexes); }); - _.each(d, function(f) { - filesHash[f.fullPath] = f; - }); return table; } function init() { cart.setCgi('hgMyData'); cart.debug(debugCartJson); // TODO: write functions for // creating default trackDbs // editing trackDbs // get the state from the history stack if it exists if (typeof uiData !== 'undefined' && typeof uiState.userFiles !== 'undefined') { _.assign(uiState, uiData.userFiles); + parseFileListIntoHash(uiState.fileList); } // first add the top level directories/files let table = showExistingFiles(uiState.fileList); // TODO: add event handlers for editing defaults, grouping into hub $("#newTrackHubDialog").dialog({ modal: true, autoOpen: false, title: "Create new track hub", closeOnEscape: true, minWidth: 400, minHeight: 120 }); // create a custom uppy plugin to batch change the type and db fields class BatchChangePlugin extends Uppy.BasePlugin { @@ -1048,30 +1093,30 @@ "fileName": "hub.txt", "fileSize": 0, "fileType": "hub.txt", "genome": metadata.genome, "parentDir": metadata.parentDir, "fullPath": metadata.parentDir + "/hub.txt", }; parentDirObj = { "uploadTime": now.toLocaleString(), "lastModified": d.toLocaleString(), "fileName": metadata.parentDir, "fileSize": 0, "fileType": "dir", "genome": metadata.genome, "parentDir": "", - "fullPath": metadata.parentDir + "/", + "fullPath": metadata.parentDir, }; // package the three objects together as one "hub" and display it let hub = [parentDirObj, hubTxtObj, newReqObj]; addNewUploadedHubToTable(hub); }); uppy.on('complete', (result) => { history.replaceState(uiState, "", document.location.href); console.log("replace history with uiState"); }); } return { init: init, uiState: uiState, }; }());