Skip to content

Commit 97bc661

Browse files
authored
Merge pull request #3706 from kbase/develop
Develop -> Main for 5.5.0 release
2 parents 3c88614 + fe5f7bc commit 97bc661

File tree

15 files changed

+2662
-1405
lines changed

15 files changed

+2662
-1405
lines changed

RELEASE_NOTES.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,79 @@ The Narrative Interface allows users to craft KBase Narratives using a combinati
44

55
This is built on the Jupyter Notebook v6.5.7 and IPython 8.28.x (more notes will follow).
66

7+
## Version 5.5.0
8+
- UIP-52 - fix integration tests
9+
- PTV-1913 - force backup authentication cookie to reset on load
10+
- URO-362 - add text for DOI requests
11+
- URO-366 - users should be able to paste usernames into the share search field
12+
- URO-352 - add support for DTS manifest.json importer files for bulk import
13+
- adjust the readonly / writeable display toggle icon
14+
15+
- Python dependencies updated to the following versions:
16+
- coverage: 7.10.1
17+
- pytest: 8.4.1
18+
- pytest-cov: 6.2.1
19+
- pytest-recording: 0.13.4
20+
- ruff: 0.12.7
21+
- beautifulsoup4: 4.13.4
22+
- certifi: 2025.7.14
23+
- cryptography: 45.0.5
24+
- jsonschema: 4.25.0
25+
- markdown: 3.8.2
26+
- pillow: 11.3.0
27+
- plotly: 6.2.0
28+
- pycurl: 7.45.6
29+
- pygments: 2.19.2
30+
- pyopenssl: 25.1.0
31+
- rsa: 4.9.1
32+
- setuptools: 80.9.0
33+
- sympy: 1.14.0
34+
- ipywidgets: 8.1.7
35+
- jinja2: 3.1.6
36+
- pandas: 2.3.1
37+
- pymongo: 4.13.2
38+
- requests: 2.32.4
39+
- statsmodels: 0.14.5
40+
- tornado: 6.5.1
41+
42+
- Javascript dependencies updated to the following versions:
43+
- dompurify: 3.2.6
44+
- follow-redirects: 1.15.11
45+
- plotly.js-dist-min: 3.1.0
46+
- @babel/traverse: 7.28.0
47+
- @eslint/eslintrc: 3.3.1
48+
- @eslint/js: 9.33.0
49+
- @wdio/browserstack-service: 9.19.1
50+
- @wdio/cli: 9.19.1
51+
- @wdio/local-runner: 9.19.1
52+
- @wdio/mocha-framework: 9.19.1
53+
- @wdio/spec-reporter: 9.19.1
54+
- autoprefixer: 10.4.21
55+
- axios: 1.11.0
56+
- chromedriver: 139.0.0
57+
- commander: 14.0.0
58+
- cssnano: 7.1.0
59+
- eslint: 9.33.0
60+
- eslint-config-prettier: 10.1.8
61+
- glob: 11.0.3
62+
- globals: 16.3.0
63+
- jquery-migrate: 3.5.2
64+
- lint-staged: 16.1.5
65+
- postcss: 8.5.6
66+
- postcss-cli: 11.0.1
67+
- prettier: 3.6.2
68+
- puppeteer: 24.16.1
69+
- sass: 1.90.0
70+
- selenium-standalone: 10.0.2
71+
- selenium-webdriver: 4.35.0
72+
- stylelint: 16.23.1
73+
- stylelint-config-recommended: 17.0.0
74+
- stylelint-config-standard: 39.0.0
75+
- terser: 5.43.1
76+
77+
778
## Version 5.4.3
8-
URO-363 - add note to the sharing panel to contact KBase about DOIs
79+
- URO-363 - add note to the sharing panel to contact KBase about DOIs
980

1081
- Python `requirements-general.txt` and `requirements.txt` merged into a single file so that all runtime deps are installed in one place.
1182

kbase-extension/kbase_templates/narrative_header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
</div>
2828
<div>Created by: <span id="kb-narr-creator"></span>
2929
<span id="kb-view-mode">
30-
<span class="fa fa-pencil"></span>
30+
<span class="fa fa-eye-slash"></span>
3131
</span>
3232
</div>
3333
</div>

kbase-extension/static/kbase/config/staging_upload.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
"id": "import_specification",
5353
"name": "Import Specification"
5454
},
55+
{
56+
"id": "dts_manifest",
57+
"name": "Data Transfer Service Manifest"
58+
},
5559
{
5660
"id": "decompress",
5761
"name": "Decompress/Unpack"
@@ -62,13 +66,15 @@
6266
"fastq_reads_interleaved",
6367
"fastq_reads_noninterleaved",
6468
"gff_metagenome",
69+
"gff_genome",
6570
"assembly",
6671
"genbank_genome"
6772
],
6873
"app_info": {
6974
"web_upload": {
7075
"app_id": "kb_uploadmethods/upload_web_file"
7176
},
77+
"dts_manifest": { },
7278
"import_specification": { },
7379
"test_fastq_reads": {
7480
"app_id": "NarrativeTest/example_reads_upload",

kbase-extension/static/kbase/js/api/StagingServiceClient.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ define(['RestAPIClient'], (RestAPIClient) => {
2121
rename: { method: 'post', path: 'rename/${path}' },
2222
decompress: { method: 'patch', path: 'decompress/${path}' },
2323
importer_mappings: { method: 'get', path: 'importer_mappings/?${file_list}' },
24-
bulkSpecification: { method: 'get', path: 'bulk_specification/?files=${files}' },
24+
bulkSpecification: {
25+
method: 'get',
26+
path: 'bulk_specification/?files=${files}&${flag}',
27+
},
2528
write_bulk_specification: { method: 'post', path: 'write_bulk_specification/' },
2629
},
2730
});

kbase-extension/static/kbase/js/kbaseNarrative.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,12 @@ define([
360360
shareWidget.refresh();
361361
}
362362
shareDialog.show();
363+
364+
// After a few seconds, focus the share field.
365+
setTimeout(() => {
366+
$('.select2-search__field').focus();
367+
}, 2000);
368+
363369
});
364370
};
365371

kbase-extension/static/kbase/js/widgets/narrative_core/kbaseNarrativeWorkspace.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ define([
358358
// mechanism should be disabled (and the button hidden as well.)
359359
const icon = $('#kb-view-mode span');
360360
icon.toggleClass('fa-eye', this.uiMode === 'view');
361-
icon.toggleClass('fa-pencil', this.uiMode === 'edit');
361+
icon.toggleClass('fa-eye-slash', this.uiMode === 'edit');
362362
Jupyter.narrative.readonly = this.uiMode === 'view';
363363

364364
// Warning, do not look for the code for this ... it will burn your

kbase-extension/static/kbase/js/widgets/narrative_core/upload/importSetup.js

Lines changed: 110 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ define([
1414

1515
/**
1616
* This makes a call to the Staging Service to fetch information from bulk specification files.
17-
* This then gets processed through `processSpreadsheetFileData` before being returned.
17+
* This then gets processed through `processBulkImportSpecData` before being returned.
1818
* @param {Array[string]} files - array of file path names to treat as import specifications
1919
* @returns Promise that resolves into information that can be used to open a bulk import cell.
2020
* This has the format:
@@ -32,45 +32,18 @@ define([
3232
* }
3333
* }
3434
* @throws Errors.ImportSetupError if an error occurs in either data fetching from the Staging
35-
* Service, or in the initial parsing done by `processSpreadsheetFileData`
35+
* Service, or in the initial parsing done by `processBulkImportSpecData`
3636
*/
37-
function getSpreadsheetFileInfo(files) {
38-
if (!files || files.length === 0) {
39-
return Promise.resolve({});
40-
}
41-
const stagingUrl = Config.url('staging_api_url');
42-
const stagingServiceClient = new StagingServiceClient({
43-
root: stagingUrl,
44-
token: Runtime.make().authToken(),
45-
});
46-
47-
// This is overkill, but a little future proofing. We have to make a GET call with an
48-
// undetermined number of files, so if there are more than we can allow in the URL, gotta break that
49-
// into multiple calls.
37+
function getBulkImportFileInfo(xsvFiles, dtsFiles) {
38+
// dtsFiles gets wiped out by the spec requester.
39+
const dtsFileList = [...dtsFiles];
5040

51-
// a little cheating here to figure out the length allowance. Maybe it should be in the client?
52-
const maxQueryLength = 2048 - stagingUrl.length - '/bulk_specification/?files='.length;
53-
const bulkSpecProms = [];
54-
55-
while (files.length) {
56-
const fileBatch = [];
57-
let remainingLength = maxQueryLength;
58-
while (
59-
files.length &&
60-
remainingLength - files[0].length - 1 >= 0 // -1 is for the comma
61-
) {
62-
const nextFile = files.shift();
63-
fileBatch.push(nextFile);
64-
remainingLength -= nextFile.length + 1;
65-
}
66-
bulkSpecProms.push(
67-
stagingServiceClient.bulkSpecification({
68-
files: encodeURIComponent(fileBatch.join(',')),
69-
})
70-
);
71-
}
41+
// noting here that these are effectively jQuery promises, so we can't just
42+
// make this an async/await function, but need to wrap with Promise.all.
43+
const xsvFileProms = requestBulkImportSpec(xsvFiles);
44+
const dtsFileProms = requestBulkImportSpec(dtsFiles, 'dts');
7245

73-
return Promise.all(bulkSpecProms)
46+
return Promise.all([...xsvFileProms, ...dtsFileProms])
7447
.then((result) => {
7548
// join results of all calls together
7649
const errors = [];
@@ -92,6 +65,13 @@ define([
9265
} else {
9366
allCalls.types[dataType] = callResult.types[dataType];
9467
allCalls.files[dataType] = callResult.files[dataType];
68+
// These files have the username in there. We don't want that.
69+
// So need to compare after stripping them out.
70+
const fileName = allCalls.files[dataType].file;
71+
const pathIdx = fileName.indexOf('/');
72+
const strippedFile =
73+
pathIdx !== -1 ? fileName.substring(pathIdx + 1) : fileName;
74+
allCalls.files[dataType].isDts = dtsFileList.includes(strippedFile);
9575
}
9676
});
9777
return allCalls;
@@ -123,10 +103,54 @@ define([
123103
);
124104
})
125105
.then((result) => {
126-
return processSpreadsheetFileData(result);
106+
return processBulkImportSpecData(result);
127107
});
128108
}
129109

110+
function requestBulkImportSpec(files, flag = '') {
111+
/**
112+
* This returns an array of jQuery Promises, which is what
113+
* the staging service client uses, so it can't be (easily)
114+
* cast into async/await.
115+
*/
116+
if (!files || files.length === 0) {
117+
return [];
118+
}
119+
const stagingUrl = Config.url('staging_api_url');
120+
const stagingServiceClient = new StagingServiceClient({
121+
root: stagingUrl,
122+
token: Runtime.make().authToken(),
123+
});
124+
// This is overkill, but a little future proofing. We have to make a GET call with an
125+
// undetermined number of files, so if there are more than we can allow in the URL, gotta break that
126+
// into multiple calls.
127+
128+
// a little cheating here to figure out the length allowance. Maybe it should be in the client?
129+
const path = '/bulk_specification/?' + flag + 'files=&';
130+
const maxQueryLength = 2048 - stagingUrl.length - path.length;
131+
const bulkSpecProms = [];
132+
133+
while (files.length) {
134+
const fileBatch = [];
135+
let remainingLength = maxQueryLength;
136+
while (
137+
files.length &&
138+
remainingLength - files[0].length - 1 >= 0 // -1 is for the comma
139+
) {
140+
const nextFile = files.shift();
141+
fileBatch.push(nextFile);
142+
remainingLength -= nextFile.length + 1;
143+
}
144+
bulkSpecProms.push(
145+
stagingServiceClient.bulkSpecification({
146+
files: encodeURIComponent(fileBatch.join(',')),
147+
flag,
148+
})
149+
);
150+
}
151+
return bulkSpecProms;
152+
}
153+
130154
/**
131155
* This function does some preprocessing on the spreadsheet file data. Specifically,
132156
* those parameters that are static dropdowns or checkboxes need to translate their input
@@ -153,10 +177,14 @@ define([
153177
* TODO: also return the fetched app specs to avoid fetching them twice?
154178
* @param {Object} data
155179
*/
156-
async function processSpreadsheetFileData(data) {
180+
async function processBulkImportSpecData(data) {
157181
// map from given datatype to app id.
158182
// if any data types are missing, record that
159183
// if any data types are not bulk import ready, record that, too.
184+
185+
if (Object.keys(data.types).length === 0 && Object.keys(data.files).length === 0) {
186+
return data;
187+
}
160188
const appIdToType = {};
161189
const dataTypeErrors = [];
162190
Object.keys(data.types).forEach((dataType) => {
@@ -253,19 +281,58 @@ define([
253281
{}
254282
);
255283

284+
/*
285+
* Map from datatype to all file parameters. These are found (as in the bulk import cell)
286+
* by looking for those params that are dynamic dropdowns that look at ftp_staging.
287+
*/
288+
const typeToFileParams = Object.entries(appIdToType).reduce(
289+
(_typeToFileParams, [appId, dataType]) => {
290+
const spec = appIdToSpec[appId].appSpec;
291+
const specParams = spec.parameters.filter((param) => {
292+
return (
293+
param.dynamic_dropdown_options &&
294+
param.dynamic_dropdown_options.data_source === 'ftp_staging'
295+
);
296+
});
297+
_typeToFileParams[dataType] = specParams.map((param) => param.id);
298+
return _typeToFileParams;
299+
},
300+
{}
301+
);
302+
256303
/*
257304
* Now, update all parameters in place.
258305
* For each set of parameters in each type, look at the translated spec parameters.
259306
* If any of those are in the given parameter set, do the translation.
307+
*
308+
* If the datatype comes from a DTS manifest file, adjust the file paths to always be
309+
* relative to the subdirectory of that file.
260310
*/
261311
Object.values(appIdToType).forEach((dataType) => {
262312
const specParams = typeToAlteredParams[dataType];
313+
const fileParams = typeToFileParams[dataType];
314+
let filePrefix = '';
315+
if (data.files[dataType].isDts) {
316+
const file = data.files[dataType].file;
317+
const parts = file.split('/');
318+
if (parts.length > 2) {
319+
filePrefix = parts.slice(1, -1).join('/') + '/';
320+
}
321+
}
322+
263323
data.types[dataType] = data.types[dataType].map((parameterSet) => {
264324
Object.keys(parameterSet).forEach((paramId) => {
265325
const value = parameterSet[paramId];
266326
if (specParams[paramId] && value in specParams[paramId]) {
267327
parameterSet[paramId] = specParams[paramId][value];
268328
}
329+
if (
330+
data.files[dataType].isDts &&
331+
fileParams.includes(paramId) &&
332+
parameterSet[paramId] !== null
333+
) {
334+
parameterSet[paramId] = filePrefix + parameterSet[paramId];
335+
}
269336
});
270337
return parameterSet;
271338
});
@@ -390,6 +457,7 @@ define([
390457
const bulkFiles = {};
391458
const singleFiles = [];
392459
const xsvFiles = [];
460+
const dtsFiles = [];
393461
fileInfo.forEach((file) => {
394462
const importType = file.type;
395463
if (bulkIds.has(importType)) {
@@ -404,11 +472,13 @@ define([
404472
bulkFiles[importType].files.push(file.name);
405473
} else if (importType === 'import_specification') {
406474
xsvFiles.push(file.name);
475+
} else if (importType === 'dts_manifest') {
476+
dtsFiles.push(file.name);
407477
} else {
408478
singleFiles.push(file);
409479
}
410480
});
411-
return getSpreadsheetFileInfo(xsvFiles)
481+
return getBulkImportFileInfo(xsvFiles, dtsFiles)
412482
.then((result) => {
413483
if (result.types) {
414484
Object.keys(result.types).forEach((dataType) => {

0 commit comments

Comments
 (0)