Skip to content

Commit c40e090

Browse files
committed
Merge branch 'release_26.0' into dev
2 parents c997879 + 0f1d5e9 commit c40e090

58 files changed

Lines changed: 1205 additions & 191 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client/src/components/Tool/ToolCard.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,16 @@ const { isOnlyPreference } = useStorageLocationConfiguration();
117117
const { currentUser, isAnonymous } = storeToRefs(useUserStore());
118118
const { isLoaded: isConfigLoaded, config } = storeToRefs(useConfigStore());
119119
const hasUser = computed(() => !isAnonymous.value);
120-
const versions = computed(() => props.options.versions);
121-
const showVersions = computed(() => props.options.versions?.length > 1);
120+
const versions = computed(() => props.options.versions ?? []);
121+
const hiddenVersions = computed(() => props.options.hidden_versions ?? []);
122+
const visibleVersions = computed(() => {
123+
const filtered = versions.value.filter((v) => !hiddenVersions.value.includes(v));
124+
if (props.version && !filtered.includes(props.version) && versions.value.includes(props.version)) {
125+
filtered.push(props.version);
126+
}
127+
return filtered;
128+
});
129+
const showVersions = computed(() => visibleVersions.value.length > 1);
122130
123131
const storageLocationModalTitle = computed(() => {
124132
if (isOnlyPreference.value) {
@@ -165,7 +173,7 @@ onBeforeMount(() => {
165173
<ToolVersionsButton
166174
v-if="showVersions"
167175
:version="props.version"
168-
:versions="versions"
176+
:versions="visibleVersions"
169177
@onChangeVersion="onChangeVersion" />
170178
<ToolOptionsButton
171179
:id="props.id"

client/src/components/Upload/model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface UploadRowModel {
4141
targetHistoryId?: string;
4242
toPosixLines: boolean;
4343
id?: string;
44+
autoDecompress: boolean;
4445
}
4546

4647
export const defaultModel: UploadRowModel = {
@@ -62,4 +63,5 @@ export const defaultModel: UploadRowModel = {
6263
spaceToTab: false,
6364
status: "init",
6465
toPosixLines: true,
66+
autoDecompress: true,
6567
};

client/src/composables/zipExplorer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ export function useZipExplorer() {
193193
ext: defaultModel.extension,
194194
space_to_tab: defaultModel.spaceToTab,
195195
to_posix_lines: defaultModel.toPosixLines,
196+
auto_decompress: defaultModel.autoDecompress,
196197
deferred: defaultModel.deferred ?? false,
197198
};
198199
uploadItems.push(uploadItem);

client/src/utils/upload-queue.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,11 @@ describe("UploadQueue", () => {
221221
history_id: "historyId",
222222
targets: [
223223
{
224-
auto_decompress: false,
224+
auto_decompress: true,
225225
destination: { type: "hdas" },
226226
elements: [
227227
{
228-
auto_decompress: false,
228+
auto_decompress: true,
229229
dbkey: "?",
230230
deferred: true,
231231
ext: "auto",
@@ -236,7 +236,7 @@ describe("UploadQueue", () => {
236236
url: "http://test.me.0",
237237
},
238238
{
239-
auto_decompress: false,
239+
auto_decompress: true,
240240
dbkey: "?",
241241
deferred: true,
242242
ext: "auto",
@@ -247,7 +247,7 @@ describe("UploadQueue", () => {
247247
url: "http://test.me.1",
248248
},
249249
{
250-
auto_decompress: false,
250+
auto_decompress: true,
251251
dbkey: "?",
252252
deferred: true,
253253
ext: "auto",

client/src/utils/upload.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,13 @@ describe("createUrlUploadItem", () => {
427427
expect(item.deferred).toBe(true);
428428
expect(item.dbkey).toBe("hg38");
429429
});
430+
431+
test("trims surrounding whitespace from URL", () => {
432+
const item = createUrlUploadItem(" http://example.com/data.bed\n", "historyId");
433+
434+
expect(item.url).toBe("http://example.com/data.bed");
435+
expect(item.name).toBe("data.bed");
436+
});
430437
});
431438

432439
describe("parseContentToUploadItems", () => {
@@ -462,6 +469,22 @@ describe("parseContentToUploadItems", () => {
462469
);
463470
});
464471

472+
test("throws on network URL with empty DNS labels", () => {
473+
expect(() => parseContentToUploadItems("https://.../SRR1957099.fastq.gz", "historyId")).toThrow(
474+
"Invalid URL: https://.../SRR1957099.fastq.gz",
475+
);
476+
});
477+
478+
test("accepts Galaxy file-source URIs", () => {
479+
const content = "gxfiles://myftp/file.txt\ndrs://example.org/abc\nzenodo://record/123";
480+
const items = parseContentToUploadItems(content, "historyId");
481+
482+
expect(items).toHaveLength(3);
483+
expect((items[0] as { url: string }).url).toBe("gxfiles://myftp/file.txt");
484+
expect((items[1] as { url: string }).url).toBe("drs://example.org/abc");
485+
expect((items[2] as { url: string }).url).toBe("zenodo://record/123");
486+
});
487+
465488
test("handles whitespace around URLs", () => {
466489
const items = parseContentToUploadItems(" http://example.com/file.txt \n ", "historyId");
467490

@@ -537,6 +560,12 @@ describe("buildUploadPayload", () => {
537560

538561
expect(() => buildUploadPayload(items)).toThrow("Invalid URL: not-a-valid-url");
539562
});
563+
564+
test("rejects network URL with empty DNS labels", () => {
565+
const items: ApiUploadItem[] = [createUrlUploadItem("https://.../SRR1957099.fastq.gz", "historyId")];
566+
567+
expect(() => buildUploadPayload(items)).toThrow("Invalid URL: https://.../SRR1957099.fastq.gz");
568+
});
540569
});
541570

542571
// ============================================================================

client/src/utils/upload.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import type { SupportedCollectionType, UploadCollectionConfig } from "@/composab
5858
import type { NewUploadItem } from "@/composables/upload/uploadItemTypes";
5959
import { getAppRoot } from "@/onload/loadConfig";
6060
import { errorMessageAsString } from "@/utils/simple-error";
61-
import { isUrl } from "@/utils/url";
61+
import { isUrl, isValidUrl } from "@/utils/url";
6262

6363
import { createTusUpload, type FileStream, type NamedBlob, type UploadableFile } from "./tusUpload";
6464

@@ -127,6 +127,8 @@ interface UploadItemCommon {
127127
deferred: boolean;
128128
/** Optional hash values for verification */
129129
hashes?: FetchDatasetHash[];
130+
/** Whether to auto-decompress the upload */
131+
auto_decompress: boolean;
130132
}
131133

132134
/** Upload item from a local file */
@@ -349,6 +351,7 @@ export function createFileUploadItem(
349351
to_posix_lines: options.to_posix_lines ?? uploadItemDefaults.to_posix_lines,
350352
deferred: options.deferred ?? uploadItemDefaults.deferred,
351353
hashes: options.hashes,
354+
auto_decompress: true,
352355
};
353356
}
354357

@@ -385,6 +388,7 @@ export function createPastedUploadItem(
385388
to_posix_lines: options.to_posix_lines ?? uploadItemDefaults.to_posix_lines,
386389
deferred: options.deferred ?? uploadItemDefaults.deferred,
387390
hashes: options.hashes,
391+
auto_decompress: true,
388392
};
389393
}
390394

@@ -410,12 +414,13 @@ export function createUrlUploadItem(
410414
historyId: string,
411415
options: Partial<Omit<UrlUploadItem, "src" | "url" | "historyId">> = {},
412416
): UrlUploadItem {
417+
const trimmedUrl = url.trim();
413418
// Extract filename from URL if not provided
414-
const defaultName = url.split("/").pop()?.split("?")[0] || DEFAULT_FILE_NAME;
419+
const defaultName = trimmedUrl.split("/").pop()?.split("?")[0] || DEFAULT_FILE_NAME;
415420

416421
return {
417422
src: "url",
418-
url,
423+
url: trimmedUrl,
419424
historyId,
420425
name: options.name ?? defaultName,
421426
size: options.size ?? 0,
@@ -425,6 +430,7 @@ export function createUrlUploadItem(
425430
to_posix_lines: options.to_posix_lines ?? uploadItemDefaults.to_posix_lines,
426431
deferred: options.deferred ?? uploadItemDefaults.deferred,
427432
hashes: options.hashes,
433+
auto_decompress: true,
428434
};
429435
}
430436

@@ -449,6 +455,7 @@ export function toApiUploadItem(item: NewUploadItem): ApiUploadItem {
449455
to_posix_lines: item.toPosixLines,
450456
deferred: item.deferred,
451457
hashes: item.hashes,
458+
auto_decompress: true,
452459
};
453460

454461
switch (item.uploadMode) {
@@ -510,7 +517,7 @@ export function parseContentToUploadItems(
510517
// If first line is a URL, treat all lines as URLs
511518
if (isUrl(firstLine)) {
512519
return lines.filter(Boolean).map((urlLine) => {
513-
if (!isUrl(urlLine)) {
520+
if (!isValidUrl(urlLine)) {
514521
throw new Error(`Invalid URL: ${urlLine}`);
515522
}
516523
return createUrlUploadItem(urlLine, historyId, options);
@@ -536,7 +543,7 @@ function buildDataElement(item: ApiUploadItem): ApiDataElement {
536543
name: normalizeFileName(item.name),
537544
space_to_tab: item.space_to_tab,
538545
to_posix_lines: item.to_posix_lines,
539-
auto_decompress: false,
546+
auto_decompress: true,
540547
deferred: item.deferred,
541548
};
542549

@@ -598,7 +605,7 @@ function validateItemContent(item: ApiUploadItem): void {
598605
if (!item.url || item.url.trim().length === 0) {
599606
throw new Error(`No URL for upload item: ${item.name}`);
600607
}
601-
if (!isUrl(item.url)) {
608+
if (!isValidUrl(item.url)) {
602609
throw new Error(`Invalid URL: ${item.url}`);
603610
}
604611
break;
@@ -674,7 +681,7 @@ export function buildUploadPayload(items: ApiUploadItem[], options: BuildPayload
674681
history_id: historyId,
675682
targets: [
676683
{
677-
auto_decompress: false,
684+
auto_decompress: true,
678685
destination: { type: "hdas" },
679686
elements,
680687
},

client/src/utils/upload/itemMappers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export function mapToPasteUrlUpload(item: PasteUrlItem, targetHistoryId: string)
7171
spaceToTab: item.spaceToTab,
7272
toPosixLines: item.toPosixLines,
7373
deferred: item.deferred,
74-
url: item.url,
74+
url: item.url.trim(),
7575
};
7676
}
7777

client/src/utils/url.test.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it, test } from "vitest";
22

3-
import { addSearchParams, isUrl } from "./url";
3+
import { addSearchParams, isUrl, isValidNetworkUrl, isValidUrl } from "./url";
44

55
describe("test url utilities", () => {
66
it("adding parameters to url", async () => {
@@ -14,4 +14,59 @@ describe("test url utilities", () => {
1414
expect(isUrl("ftp://")).toBeTruthy();
1515
expect(isUrl("http://")).toBeTruthy();
1616
});
17+
18+
test("network url validation accepts well-formed hosts", () => {
19+
expect(isValidNetworkUrl("https://example.com/x")).toBe(true);
20+
expect(isValidNetworkUrl("http://sub.example.org/path?q=1")).toBe(true);
21+
expect(isValidNetworkUrl("ftp://ftp.example.org/a")).toBe(true);
22+
});
23+
24+
test("network url validation rejects empty DNS labels", () => {
25+
expect(isValidNetworkUrl("https://.../foo")).toBe(false);
26+
expect(isValidNetworkUrl("https://./foo")).toBe(false);
27+
expect(isValidNetworkUrl("https://..example.com/x")).toBe(false);
28+
expect(isValidNetworkUrl("https:///foo")).toBe(false);
29+
});
30+
31+
test("network url validation rejects non-network schemes", () => {
32+
expect(isValidNetworkUrl("gxfiles://foo")).toBe(false);
33+
expect(isValidNetworkUrl("drs://example.org/123")).toBe(false);
34+
expect(isValidNetworkUrl("not-a-url")).toBe(false);
35+
});
36+
37+
test("isValidUrl accepts custom Galaxy file-source schemes", () => {
38+
expect(isValidUrl("gxfiles://myftp/file.txt")).toBe(true);
39+
expect(isValidUrl("gxuserfiles://mysource/x")).toBe(true);
40+
expect(isValidUrl("drs://example.org/abc")).toBe(true);
41+
expect(isValidUrl("zenodo://record/123")).toBe(true);
42+
expect(isValidUrl("invenio://record/123")).toBe(true);
43+
expect(isValidUrl("dataverse://doi/x")).toBe(true);
44+
expect(isValidUrl("base64://aGVsbG8=")).toBe(true);
45+
});
46+
47+
test("isValidUrl accepts well-formed network urls", () => {
48+
expect(isValidUrl("https://example.com/")).toBe(true);
49+
expect(isValidUrl("https://example.com/file.txt")).toBe(true);
50+
expect(isValidUrl("ftp://ftp.example.org/a")).toBe(true);
51+
});
52+
53+
test("isValidUrl rejects network urls with empty DNS labels", () => {
54+
expect(isValidUrl("https://.../foo")).toBe(false);
55+
expect(isValidUrl("https://./foo")).toBe(false);
56+
expect(isValidUrl("https:///foo")).toBe(false);
57+
});
58+
59+
test("isValidUrl rejects unknown schemes and non-urls", () => {
60+
expect(isValidUrl("")).toBe(false);
61+
expect(isValidUrl("not-a-url")).toBe(false);
62+
expect(isValidUrl("xyz://foo")).toBe(false);
63+
});
64+
65+
test("isValidUrl tolerates surrounding whitespace", () => {
66+
expect(isValidUrl(" https://example.com/x ")).toBe(true);
67+
expect(isValidUrl("\thttps://example.com/x\n")).toBe(true);
68+
expect(isValidUrl(" gxfiles://myftp/file.txt ")).toBe(true);
69+
expect(isValidUrl(" ")).toBe(false);
70+
expect(isValidUrl(" https://.../x ")).toBe(false);
71+
});
1772
});

0 commit comments

Comments
 (0)