Skip to content

Commit af8bf81

Browse files
Merge 26.3 to develop
2 parents 4b9d25d + 3d13f02 commit af8bf81

26 files changed

+4062
-198
lines changed

SkylineToolsStore/src/org/labkey/skylinetoolsstore/SkylineToolsStoreController.java

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import java.net.URLDecoder;
101101
import java.nio.file.Files;
102102
import java.nio.file.Path;
103+
import java.nio.file.StandardCopyOption;
103104
import java.text.DateFormat;
104105
import java.text.SimpleDateFormat;
105106
import java.util.ArrayList;
@@ -176,9 +177,10 @@ protected SkylineTool getToolFromZip(MultipartFile zip) throws IOException
176177
{
177178
ZipEntry zipEntry;
178179
while ((zipEntry = zipStream.getNextEntry()) != null &&
179-
(tool == null || tool.getIcon() == null))
180+
(tool == null || toolIcon == null))
180181
{
181-
if (zipEntry.getName().toLowerCase().startsWith("tool-inf/"))
182+
String entryLower = zipEntry.getName().toLowerCase();
183+
if (entryLower.startsWith("tool-inf/") && !entryLower.startsWith("tool-inf/docs/"))
182184
{
183185
String lowerBaseName = new File(zipEntry.getName()).getName().toLowerCase();
184186

@@ -229,14 +231,46 @@ protected byte[] unzip(ZipInputStream stream)
229231
}
230232
}
231233

234+
protected static boolean extractDocsFromZip(Path zipFile, Path containerDir) throws IOException
235+
{
236+
Path docsDir = containerDir.resolve("docs");
237+
boolean extracted = false;
238+
try (ZipFile zf = new ZipFile(zipFile.toFile()))
239+
{
240+
Enumeration<? extends ZipEntry> entries = zf.entries();
241+
while (entries.hasMoreElements())
242+
{
243+
ZipEntry entry = entries.nextElement();
244+
String name = entry.getName();
245+
if (!name.toLowerCase().startsWith("tool-inf/docs/") || entry.isDirectory())
246+
continue;
247+
// Strip "tool-inf/docs/" prefix to get relative path within docs dir
248+
String relativePath = name.substring("tool-inf/docs/".length());
249+
if (relativePath.isEmpty())
250+
continue;
251+
Path destPath = docsDir.resolve(relativePath).normalize();
252+
// Zip-slip protection
253+
if (!destPath.startsWith(docsDir.normalize()))
254+
throw new IOException("Zip entry outside target directory: " + name);
255+
Files.createDirectories(destPath.getParent());
256+
try (InputStream in = zf.getInputStream(entry))
257+
{
258+
Files.copy(in, destPath, StandardCopyOption.REPLACE_EXISTING);
259+
}
260+
extracted = true;
261+
}
262+
}
263+
return extracted;
264+
}
265+
232266
public static File makeFile(Container c, String filename)
233267
{
234-
return new File(getLocalPath(c), FileUtil.makeLegalName(filename));
268+
return getLocalPath(c).resolve(FileUtil.makeLegalName(filename)).toFile();
235269
}
236270

237-
public static File getLocalPath(Container c)
271+
public static Path getLocalPath(Container c)
238272
{
239-
return FileContentService.get().getFileRootPath(c, FileContentService.ContentType.files).toFile();
273+
return FileContentService.get().getFileRootPath(c, FileContentService.ContentType.files);
240274
}
241275

242276
protected Container makeContainer(Container parent, String folderName, List<User> users, Role role) throws IOException
@@ -383,7 +417,7 @@ public static ArrayList<String> getToolRelevantUsers(SkylineTool tool, Role[] ro
383417
return new ArrayList<>(users);
384418
}
385419

386-
public static HashMap<String, String> getSupplementaryFiles(SkylineTool tool)
420+
public static HashMap<String, String> getSupplementaryFiles(SkylineTool tool) throws IOException
387421
{
388422
// Store supporting files in map <url, icon url>
389423
final String[] knownExtensions = {"pdf", "zip"};
@@ -401,15 +435,15 @@ public static HashMap<String, String> getSupplementaryFiles(SkylineTool tool)
401435
return suppFiles;
402436
}
403437

404-
public static HashSet<String> getSupplementaryFileBasenames(SkylineTool tool)
438+
public static HashSet<String> getSupplementaryFileBasenames(SkylineTool tool) throws IOException
405439
{
406440
HashSet<String> suppFiles = new HashSet<>();
407-
File localToolDir = getLocalPath(tool.lookupContainer());
408-
for (String suppFile : localToolDir.list())
441+
Path localToolDir = getLocalPath(tool.lookupContainer());
442+
try (var stream = Files.list(localToolDir))
409443
{
410-
final String basename = new File(suppFile).getName();
411-
if (!basename.startsWith(".") && !basename.equals(tool.getZipName()) && !basename.equals("icon.png"))
412-
suppFiles.add(suppFile);
444+
stream.map(p -> p.getFileName().toString())
445+
.filter(name -> !name.startsWith(".") && !name.equals(tool.getZipName()) && !name.equals("icon.png") && !name.equals("docs"))
446+
.forEach(suppFiles::add);
413447
}
414448
return suppFiles;
415449
}
@@ -583,9 +617,19 @@ else if (!getContainer().hasPermission(getUser(), InsertPermission.class))
583617
{
584618
Container c = makeContainer(getContainer(), folderName, toolOwnersUsers, RoleManager.getRole(EditorRole.class));
585619
copyContainerPermissions(existingVersionContainer, c);
586-
zip.transferTo(makeFile(c, zip.getOriginalFilename()));
620+
File storedZip = makeFile(c, zip.getOriginalFilename());
621+
zip.transferTo(storedZip);
587622
tool.writeIconToFile(makeFile(c, "icon.png"), "png");
588623

624+
// Extract docs from tool-inf/docs/ in the ZIP; carry forward from previous version if absent
625+
boolean hasDocs = extractDocsFromZip(storedZip.toPath(), getLocalPath(c));
626+
if (!hasDocs && existingVersionContainer != null)
627+
{
628+
Path oldDocs = getLocalPath(existingVersionContainer).resolve("docs");
629+
if (Files.isDirectory(oldDocs))
630+
FileUtil.copyDirectory(oldDocs, getLocalPath(c).resolve("docs"));
631+
}
632+
589633
if (copyFiles != null && existingVersionContainer != null)
590634
for (String copyFile : copyFiles)
591635
FileUtils.copyFile(makeFile(existingVersionContainer, copyFile), makeFile(c, copyFile), true);

SkylineToolsStore/src/org/labkey/skylinetoolsstore/model/SkylineTool.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import org.apache.commons.lang3.StringUtils;
44
import org.labkey.api.data.Container;
55
import org.labkey.api.data.Entity;
6+
import org.labkey.api.files.FileContentService;
67
import org.labkey.api.settings.AppProps;
78
import org.labkey.api.util.Pair;
9+
import org.labkey.api.webdav.WebdavService;
810
import org.labkey.skylinetoolsstore.SkylineToolsStoreController;
911

12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
1015
import javax.imageio.ImageIO;
1116
import java.io.BufferedReader;
1217
import java.io.ByteArrayInputStream;
@@ -293,6 +298,21 @@ public String getFolderUrl()
293298
return AppProps.getInstance().getContextPath() + "/files" + lookupContainer().getPath() + "/";
294299
}
295300

301+
public boolean hasDocumentation()
302+
{
303+
Path localPath = SkylineToolsStoreController.getLocalPath(lookupContainer());
304+
return localPath != null && Files.exists(localPath.resolve("docs/index.html"));
305+
}
306+
307+
public String getDocsUrl()
308+
{
309+
org.labkey.api.util.Path path = WebdavService.getPath()
310+
.append(lookupContainer().getParsedPath())
311+
.append(FileContentService.FILES_LINK)
312+
.append(new org.labkey.api.util.Path("docs", "index.html"));
313+
return AppProps.getInstance().getContextPath() + path.encode();
314+
}
315+
296316
public String getIconUrl()
297317
{
298318
return (SkylineToolsStoreController.makeFile(lookupContainer(), "icon.png").exists()) ?

SkylineToolsStore/src/org/labkey/skylinetoolsstore/view/SkylineToolDetails.jsp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<%@ page import="org.apache.commons.lang3.StringUtils" %>
2+
<%@ page import="org.labkey.api.data.Container" %>
3+
<%@ page import="org.labkey.api.data.ContainerManager" %>
24
<%@ page import="org.labkey.api.portal.ProjectUrls" %>
35
<%@ page import="org.labkey.api.security.permissions.DeletePermission" %>
46
<%@ page import="org.labkey.api.security.permissions.InsertPermission" %>
@@ -298,8 +300,16 @@ a { text-decoration: none; }
298300
<% } %>
299301
</div>
300302

303+
<%
304+
Container supportContainer = getContainer().getChild("Support");
305+
Container toolSupportBoard = supportContainer != null ? supportContainer.getChild(tool.getName()) : null;
306+
if (toolSupportBoard == null)
307+
toolSupportBoard = ContainerManager.getForPath("/home/support");
308+
%>
309+
<% if (toolSupportBoard != null) { %>
301310
<button id="tool-support-board-btn" class="banner-button-small">Support Board</button>
302-
<% addHandler("tool-support-board-btn", "click", "window.open(" + q(urlProvider(ProjectUrls.class).getBeginURL(getContainer().getChild("Support").getChild(tool.getName()))) + ", '_blank', 'noopener,noreferrer')"); %>
311+
<% addHandler("tool-support-board-btn", "click", "window.open(" + q(urlProvider(ProjectUrls.class).getBeginURL(toolSupportBoard)) + ", '_blank', 'noopener,noreferrer')"); %>
312+
<% } %>
303313
</div>
304314
<% if (toolEditor) { %>
305315
<div class="menuMouseArea sprocket">
@@ -332,9 +342,20 @@ a { text-decoration: none; }
332342
</div>
333343
</div>
334344

335-
<% if (suppIter.hasNext()) { %>
345+
<%
346+
boolean hasDocumentation = tool.hasDocumentation();
347+
%>
348+
<% if (hasDocumentation || suppIter.hasNext()) { %>
336349
<div id="documentationbox" class="itemsbox">
337350
<legend>Documentation</legend>
351+
<% if (hasDocumentation) { %>
352+
<div class="barItem">
353+
<a href="<%=h(tool.getDocsUrl())%>" target="_blank" rel="noopener noreferrer">
354+
<img src="<%= h(imgDir) %>link.png" alt="Documentation" />
355+
<span>Online Documentation</span>
356+
</a>
357+
</div>
358+
<% } %>
338359
<%
339360
while (suppIter.hasNext()) {
340361
Map.Entry suppPair = (Map.Entry)suppIter.next();

SkylineToolsStore/src/org/labkey/skylinetoolsstore/view/SkylineToolsStoreWebPart.jsp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
.content {margin: 8px 12px 0 0; padding:0; text-align:justify;}
5959
.toolButtons {margin-top: 12px;}
6060
.styled-button{
61+
display:inline-flex;
62+
align-items:center;
6163
box-shadow:rgba(0,0,0,0.0.1) 0 1px 0 0;
6264
background-color:#5B74A8;
6365
border:1px solid #29447E;
@@ -72,6 +74,8 @@
7274
cursor:pointer;
7375
}
7476
.styled-button:hover{background-color:#1e90ff; color:#f5f5dc;}
77+
a.styled-button{text-decoration:none; color:#fff;}
78+
a.styled-button:visited{color:#fff;}
7579
.toolOwners {width: 80%; min-width: 300px;}
7680
.ui-menu {width:240px;}
7781
.dropMenu {position: absolute;}
@@ -153,6 +157,8 @@
153157
// Get supporting files in map <url, icon url>
154158
HashMap<String, String> suppFiles = SkylineToolsStoreController.getSupplementaryFiles(tool);
155159
Iterator suppIter = suppFiles.entrySet().iterator();
160+
boolean hasDocs = tool.hasDocumentation();
161+
int docCount = suppFiles.size() + (hasDocs ? 1 : 0);
156162
157163
final String curToolOwners = StringUtils.join(SkylineToolsStoreController.getToolOwners(tool), ", ");
158164
toolOwners.put(tool.getRowId(), curToolOwners);
@@ -200,17 +206,23 @@
200206

201207
<div class="toolButtons">
202208

203-
<button type="button" id="download-tool-btn-<%=tool.getRowId()%>" class="styled-button">Download</button>
204-
<% addHandler("download-tool-btn-" + tool.getRowId(), "click", "window.location.href = " + q(urlFor(SkylineToolsStoreController.DownloadToolAction.class).addParameter("id", tool.getRowId()))); %>
209+
<%=link(unsafe("Download<span class=\"visually-hidden\">&nbsp;" + h(tool.getName()) + "</span>")).href(urlFor(SkylineToolsStoreController.DownloadToolAction.class).addParameter("id", tool.getRowId()).toString()).clearClasses().addClass("styled-button")%>
205210
<%
206-
if (suppFiles.size() == 1) {
211+
if (docCount == 1 && hasDocs) {
212+
%>
213+
<%=link(unsafe("Documentation<span class=\"visually-hidden\">&nbsp;" + h(tool.getName()) + "</span>")).href(tool.getDocsUrl()).clearClasses().addClass("styled-button").target("_blank").rel("noopener noreferrer")%>
214+
<%
215+
} else if (docCount == 1) {
207216
Map.Entry suppPair = (Map.Entry)suppIter.next();
208217
%>
209-
<a href="<%=h(suppPair.getKey())%>"><button type="button" class="styled-button">Documentation</button></a>
210-
<% } else if (suppFiles.size() > 1) { %>
218+
<%=link(unsafe("Documentation<span class=\"visually-hidden\">&nbsp;" + h(tool.getName()) + "</span>")).href(suppPair.getKey().toString()).clearClasses().addClass("styled-button")%>
219+
<% } else if (docCount > 1) { %>
211220
<div class="menuMouseArea">
212-
<button type="button" class="styled-button">Documentation</button>
221+
<button type="button" class="styled-button">Documentation<span class="visually-hidden"><%=h(tool.getName())%></span></button>
213222
<ul class="dropMenu">
223+
<% if (hasDocs) { %>
224+
<li><a href="<%=h(tool.getDocsUrl())%>" target="_blank" rel="noopener noreferrer"><img class="menuIconImg" src="<%= h(imgDir) %>link.png" alt="Documentation">Online Documentation</a></li>
225+
<% } %>
214226
<%
215227
while (suppIter.hasNext()) {
216228
Map.Entry suppPair = (Map.Entry)suppIter.next();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
-- Add publication tracking columns to DatasetStatus
3+
ALTER TABLE panoramapublic.DatasetStatus ADD COLUMN PotentialPublicationId VARCHAR(255);
4+
ALTER TABLE panoramapublic.DatasetStatus ADD COLUMN PublicationType VARCHAR(50);
5+
ALTER TABLE panoramapublic.DatasetStatus ADD COLUMN PublicationMatchInfo TEXT;
6+
ALTER TABLE panoramapublic.DatasetStatus ADD COLUMN Citation TEXT;
7+
ALTER TABLE panoramapublic.DatasetStatus ADD COLUMN UserDismissedPublication TIMESTAMP;

panoramapublic/resources/schemas/panoramapublic.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,21 @@
864864
<column columnName="LastReminderDate" />
865865
<column columnName="ExtensionRequestedDate" />
866866
<column columnName="DeletionRequestedDate"/>
867+
<column columnName="PotentialPublicationId">
868+
<description>Publication ID (PubMed ID, PMC ID) suggestion based on NCBI search</description>
869+
</column>
870+
<column columnName="PublicationType">
871+
<description>Type of publication ID: PMID, PMC</description>
872+
</column>
873+
<column columnName="PublicationMatchInfo">
874+
<description>Information about which fields (e.g. PXD, Panorama link, title etc.) matched</description>
875+
</column>
876+
<column columnName="Citation">
877+
<description>NLM citation string for the potential publication match</description>
878+
</column>
879+
<column columnName="UserDismissedPublication">
880+
<description>Date when the user dismissed the publication suggestion for this dataset</description>
881+
</column>
867882
</columns>
868883
</table>
869884
</tables>

0 commit comments

Comments
 (0)