Skip to content

Commit b8a163d

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
2 parents 4c4d519 + 85b1e4a commit b8a163d

27 files changed

Lines changed: 645 additions & 121 deletions

api/src/org/labkey/api/exp/OntologyManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,7 @@ public static void deleteAllObjects(Container c, User user) throws ValidationExc
13381338
String deleteObjPropSql = "DELETE FROM " + getTinfoObjectProperty() + " WHERE ObjectId IN (SELECT ObjectId FROM " + getTinfoObject() + " WHERE Container = ?)";
13391339
executor.execute(deleteObjPropSql, c);
13401340
String deleteObjSql = "DELETE FROM " + getTinfoObject() + " WHERE Container = ?";
1341+
_log.info("Deleting from exp.object in container {}", c);
13411342
executor.execute(deleteObjSql, c);
13421343

13431344
// delete property validator references on property descriptors

api/src/org/labkey/api/exp/api/ExperimentService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.labkey.api.exp.query.ExpRunTable;
6262
import org.labkey.api.exp.query.ExpSampleTypeTable;
6363
import org.labkey.api.exp.query.ExpSchema;
64+
import org.labkey.api.exp.query.ExpUnreferencedSampleFilesTable;
6465
import org.labkey.api.exp.query.SampleStatusTable;
6566
import org.labkey.api.gwt.client.AuditBehaviorType;
6667
import org.labkey.api.gwt.client.model.GWTDomain;
@@ -661,6 +662,8 @@ static void validateParentAlias(Map<String, String> aliasMap, Set<String> reserv
661662

662663
SampleStatusTable createSampleStatusTable(ExpSchema expSchema, ContainerFilter cf);
663664

665+
ExpUnreferencedSampleFilesTable createUnreferencedSampleFilesTable(ExpSchema expSchema, ContainerFilter cf);
666+
664667
FilteredTable<ExpSchema> createFieldsTable(ExpSchema expSchema, ContainerFilter cf);
665668

666669
FilteredTable<ExpSchema> createPhiFieldsTable(ExpSchema expSchema, ContainerFilter cf);

api/src/org/labkey/api/exp/query/ExpDataTable.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ enum Column
3636
SourceProtocolApplication,
3737
SourceApplicationInput,
3838
DataFileUrl,
39+
ReferenceCount,
3940
Run,
4041
RunApplication,
4142
RunApplicationOutput,

api/src/org/labkey/api/exp/query/ExpMaterialTable.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ enum Column
4343
Inputs,
4444
IsAliquot,
4545
IsPlated,
46+
LastIndexed,
4647
LSID,
4748
MaterialExpDate,
4849
MaterialSourceId,

api/src/org/labkey/api/exp/query/ExpSchema.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.labkey.api.security.User;
4949
import org.labkey.api.security.permissions.InsertPermission;
5050
import org.labkey.api.security.permissions.ReadPermission;
51+
import org.labkey.api.settings.AppProps;
5152
import org.labkey.api.util.StringExpression;
5253
import org.labkey.api.view.ActionURL;
5354
import org.labkey.api.view.ViewContext;
@@ -71,6 +72,7 @@ public class ExpSchema extends AbstractExpSchema
7172
public static final String SAMPLE_STATE_TYPE_TABLE = "SampleStateType";
7273
public static final String SAMPLE_TYPE_CATEGORY_TABLE = "SampleTypeCategoryType";
7374
public static final String MEASUREMENT_UNITS_TABLE = "MeasurementUnits";
75+
public static final String SAMPLE_FILES_TABLE = "UnreferencedSampleFiles";
7476

7577
public static final SchemaKey SCHEMA_EXP = SchemaKey.fromParts(ExpSchema.SCHEMA_NAME);
7678
public static final SchemaKey SCHEMA_EXP_DATA = SchemaKey.fromString(SCHEMA_EXP, ExpSchema.NestedSchemas.data.name());
@@ -221,6 +223,20 @@ public TableInfo createTable(ExpSchema expSchema, String queryName, ContainerFil
221223
return expSchema.setupTable(result);
222224
}
223225
},
226+
UnreferencedSampleFiles
227+
{
228+
@Override
229+
public TableInfo createTable(ExpSchema expSchema, String queryName, ContainerFilter cf)
230+
{
231+
return ExperimentService.get().createUnreferencedSampleFilesTable(expSchema, cf);
232+
}
233+
234+
@Override
235+
public boolean includeTable()
236+
{
237+
return AppProps.getInstance().isOptionalFeatureEnabled(SAMPLE_FILES_TABLE);
238+
}
239+
},
224240
SampleStatus
225241
{
226242
@Override
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.labkey.api.exp.query;
2+
3+
import org.labkey.api.data.ContainerFilterable;
4+
import org.labkey.api.data.TableInfo;
5+
6+
public interface ExpUnreferencedSampleFilesTable extends ContainerFilterable, TableInfo
7+
{
8+
}

api/src/org/labkey/api/files/FileContentService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ default void fireFileDeletedEvent(@NotNull Path deleted, @Nullable User user, @N
326326
*/
327327
SQLFragment listFilesQuery(@NotNull User currentUser);
328328

329+
SQLFragment listSampleFilesQuery(@NotNull User currentUser);
330+
329331
void setWebfilesEnabled(boolean enabled, User user);
330332

331333
/**
@@ -349,6 +351,14 @@ enum PathType { full, serverRelative, folderRelative }
349351
*/
350352
void ensureFileData(@NotNull ExpDataTable table);
351353

354+
/**
355+
* Fix the container column in the exp.data table for files that were moved as part of a sample move operation
356+
* but did not have their containers updated
357+
* @param admin The user doing the repair
358+
* @return Number of duplicate rows removed from exp.data table
359+
*/
360+
int fixContainerForExpDataFiles(User admin);
361+
352362
/**
353363
* Allows a module to register a directory pattern to be checked in the files webpart in order to zip the matching directory before uploading.
354364
* @param directoryPattern DirectoryPattern

api/src/org/labkey/api/files/FileListener.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,9 @@ default void fileDeleted(@NotNull Path deleted, @Nullable User user, @Nullable C
9494
* </ul>
9595
*/
9696
SQLFragment listFilesQuery();
97+
98+
@Nullable default SQLFragment listSampleFilesQuery()
99+
{
100+
return null;
101+
}
97102
}

api/src/org/labkey/api/files/TableUpdaterFileListener.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package org.labkey.api.files;
1717

1818
import org.apache.commons.lang3.StringUtils;
19-
import org.apache.logging.log4j.LogManager;
2019
import org.apache.logging.log4j.Logger;
2120
import org.jetbrains.annotations.NotNull;
2221
import org.jetbrains.annotations.Nullable;
@@ -34,6 +33,7 @@
3433
import org.labkey.api.data.dialect.SqlDialect;
3534
import org.labkey.api.security.User;
3635
import org.labkey.api.util.FileUtil;
36+
import org.labkey.api.util.logging.LogHelper;
3737

3838
import java.io.File;
3939
import java.nio.file.Files;
@@ -45,13 +45,13 @@
4545

4646
/**
4747
* FileListener implementation that can update tables that store file paths in various flavors (URI, standard OS
48-
* paths, etc).
48+
* paths, etc.).
4949
* User: jeckels
5050
* Date: 11/7/12
5151
*/
5252
public class TableUpdaterFileListener implements FileListener
5353
{
54-
private static final Logger LOG = LogManager.getLogger(TableUpdaterFileListener.class);
54+
protected static final Logger LOG = LogHelper.getLogger(TableUpdaterFileListener.class, "File listener activity");
5555

5656
public static final String TABLE_ALIAS = "x";
5757

@@ -273,7 +273,7 @@ public int fileMoved(@NotNull Path src, @NotNull Path dest, @Nullable User user,
273273
singleEntrySQL.append(")");
274274

275275
int rows = schema.getScope().executeWithRetry(tx -> new SqlExecutor(schema).execute(singleEntrySQL));
276-
LOG.info("Updated " + rows + " row in " + _table + " for move from " + src + " to " + dest);
276+
LOG.info("Updated {} row in {} for move from {} to {}", rows, _table, src, dest);
277277

278278
// Handle updating child paths, unless we know that the entry is a file. If it's not (either it's a
279279
// directory or it doesn't exist), then try to fix up child records
@@ -305,7 +305,7 @@ public int fileMoved(@NotNull Path src, @NotNull Path dest, @Nullable User user,
305305
childPathsSQL.append(whereClause);
306306
childRowsUpdated += new SqlExecutor(schema).execute(childPathsSQL);
307307

308-
LOG.info("Updated " + childRowsUpdated + " child paths in " + _table + " rows for move from " + src + " to " + dest);
308+
LOG.info("Updated {} child paths in {} rows for move from {} to {}", childRowsUpdated, _table, src, dest);
309309
return childRowsUpdated;
310310
}
311311
return 0;
@@ -353,10 +353,10 @@ public Collection<File> listFiles(@Nullable Container container)
353353
@Override
354354
public SQLFragment listFilesQuery()
355355
{
356-
return listFilesQuery(false, null);
356+
return listFilesQuery(false, null, false);
357357
}
358358

359-
public SQLFragment listFilesQuery(boolean skipCreatedModified, String filePath)
359+
public SQLFragment listFilesQuery(boolean skipCreatedModified, CharSequence filePath, boolean extractName)
360360
{
361361
SQLFragment selectFrag = new SQLFragment();
362362
selectFrag.append("SELECT\n");
@@ -395,6 +395,16 @@ else if (_table.getColumn("Folder") != null)
395395

396396
selectFrag.append(" ").appendIdentifier(_pathColumn.getSelectIdentifier()).append(" AS FilePath,\n");
397397

398+
if (extractName)
399+
{
400+
SqlDialect dialect = _table.getSchema().getSqlDialect();
401+
SQLFragment fileNameFrag = new SQLFragment();
402+
fileNameFrag.append("regexp_replace(").appendIdentifier(_pathColumn.getSelectIdentifier()).append(", ");
403+
fileNameFrag.append(dialect.getStringHandler().quoteStringLiteral(".*/")).append(", ");
404+
fileNameFrag.append(dialect.getStringHandler().quoteStringLiteral("")).append(")");
405+
selectFrag.append(" ").append(fileNameFrag).append(" AS FilePathShort,\n");
406+
}
407+
398408
if (_keyColumn != null)
399409
selectFrag.append(" ").appendIdentifier(_keyColumn.getSelectIdentifier()).append(" AS SourceKey,\n");
400410
else
@@ -408,6 +418,8 @@ else if (_table.getColumn("Folder") != null)
408418

409419
if (StringUtils.isEmpty(filePath))
410420
selectFrag.append(" IS NOT NULL\n");
421+
else if (filePath instanceof SQLFragment)
422+
selectFrag.append(" = ").append(filePath).append("\n");
411423
else
412424
selectFrag.append(" = ").appendStringLiteral(filePath, _table.getSchema().getSqlDialect()).append("\n");
413425

api/webapp/clientapi/dom/DataRegion.js

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ if (!LABKEY.DataRegions) {
493493
* Non-configurable Options
494494
*/
495495
this.selectionModified = false;
496+
this.selectionLoading = false;
496497

497498
if (this.panelConfigurations === undefined) {
498499
this.panelConfigurations = {};
@@ -1171,19 +1172,11 @@ if (!LABKEY.DataRegions) {
11711172
config.selectionKey = this.selectionKey;
11721173
config.scope = config.scope || me;
11731174

1174-
config = _chainSelectionCountCallback(this, config);
1175+
// set loading flag for DataRegion, will be cleared in callback
1176+
this.selectionLoading = true;
1177+
_updateSelectedCountMessage(this);
11751178

1176-
var failure = LABKEY.Utils.getOnFailure(config);
1177-
if ($.isFunction(failure)) {
1178-
config.failure = failure;
1179-
}
1180-
else {
1181-
config.failure = function(error) {
1182-
let msg = 'Error setting selection';
1183-
if (error && error.exception) msg += ': ' + error.exception;
1184-
me.addMessage(msg, 'selection');
1185-
};
1186-
}
1179+
config = _chainSelectionCountCallback(this, config);
11871180

11881181
if (config.selectionKey) {
11891182
LABKEY.DataRegion.setSelected(config);
@@ -3058,12 +3051,39 @@ if (!LABKEY.DataRegions) {
30583051

30593052
var _chainSelectionCountCallback = function(region, config) {
30603053

3061-
var success = LABKEY.Utils.getOnSuccess(config);
3054+
const failure = LABKEY.Utils.getOnFailure(config);
3055+
config.failure = function(error) {
3056+
region.selectionLoading = false;
3057+
3058+
let msg = 'Error setting selection';
3059+
if (error && error.exception) msg += ': ' + error.exception;
3060+
config.scope.addMessage(msg, 'selection');
3061+
3062+
if ($.isFunction(failure)) {
3063+
failure.call(config.scope, error);
3064+
}
3065+
}
30623066

30633067
// On success, update the current selectedCount on this DataRegion and fire the 'selectchange' event
3064-
config.success = function(data) {
3068+
const success = LABKEY.Utils.getOnSuccess(config);
3069+
config.success = function(data, response) {
3070+
3071+
// Workaround for GitHub Issue 778 where the response payload is JSON but the response has been
3072+
// configured by the server as non-JSON.
3073+
if (!data && response?.responseText) {
3074+
try {
3075+
data = JSON.parse(response.responseText);
3076+
} catch (e) {
3077+
const msg = 'failed to parse response';
3078+
console.error(msg, e, response);
3079+
config.failure.call(config.scope, { exception: msg });
3080+
return;
3081+
}
3082+
}
3083+
30653084
region.removeMessage('selection');
30663085
region.selectionModified = true;
3086+
region.selectionLoading = false;
30673087
region.selectedCount = data.count;
30683088
_onSelectionChange(region);
30693089

@@ -3363,7 +3383,10 @@ if (!LABKEY.DataRegions) {
33633383
var _buttonSelectionBind = function(region, cls, fn) {
33643384
var partEl = region.msgbox.getParent().find('div[data-msgpart="selection"]');
33653385
partEl.find('.labkey-button' + cls).off('click').on('click', $.proxy(function() {
3366-
fn.call(this);
3386+
// if one of the buttons is clicked while another action is loading selections, skip the click (the button should also be disabled)
3387+
if (!region.selectionLoading) {
3388+
fn.call(this);
3389+
}
33673390
}, region));
33683391
};
33693392

@@ -3592,18 +3615,20 @@ if (!LABKEY.DataRegions) {
35923615

35933616
var _showSelectMessage = function(region, msg) {
35943617
if (region.showRecordSelectors) {
3618+
const cls = 'labkey-button ' + (region.selectionLoading ? 'disabled ' : '');
3619+
35953620
if (_isShowSelectAll(region)) {
3596-
msg += "&nbsp;<span class='labkey-button select-all'>" + _getSelectAllText(region) + "</span>";
3621+
msg += "&nbsp;<span class='" + cls + " select-all'>" + _getSelectAllText(region) + "</span>";
35973622
}
35983623

3599-
msg += "&nbsp;" + "<span class='labkey-button select-none'>Select None</span>";
3624+
msg += "&nbsp;" + "<span class='" + cls + " select-none'>Select None</span>";
36003625
var showOpts = [];
36013626
if (region.showRows !== 'all' && !_isMaxRowsAllRows(region))
3602-
showOpts.push("<span class='labkey-button show-all'>Show All</span>");
3627+
showOpts.push("<span class='" + cls + " show-all'>Show All</span>");
36033628
if (region.showRows !== 'selected')
3604-
showOpts.push("<span class='labkey-button show-selected'>Show Selected</span>");
3629+
showOpts.push("<span class='" + cls + " show-selected'>Show Selected</span>");
36053630
if (region.showRows !== 'unselected')
3606-
showOpts.push("<span class='labkey-button show-unselected'>Show Unselected</span>");
3631+
showOpts.push("<span class='" + cls + " show-unselected'>Show Unselected</span>");
36073632
msg += "&nbsp;&nbsp;" + showOpts.join(" ");
36083633
}
36093634

@@ -4081,6 +4106,21 @@ if (!LABKEY.DataRegions) {
40814106
_setParameters(region, params, [OFFSET_PREFIX].concat(skipPrefixes));
40824107
};
40834108

4109+
var _updateSelectedCountMessage = function(region) {
4110+
// If not all rows are visible and some rows are selected, show selection message
4111+
if (region.totalRows && 0 !== region.selectedCount && !region.complete) {
4112+
var msg;
4113+
if (region.selectedCount === region.totalRows) {
4114+
msg = 'All <span class="labkey-strong">' + region.totalRows.toLocaleString() + '</span> rows selected.';
4115+
} else if (region.selectionLoading) {
4116+
msg = 'Selected <i class="fa fa-spinner fa-pulse" ></i> of ' + region.totalRows.toLocaleString() + ' rows.';
4117+
} else {
4118+
msg = 'Selected <span class="labkey-strong">' + region.selectedCount.toLocaleString() + '</span> of ' + region.totalRows.toLocaleString() + ' rows.';
4119+
}
4120+
_showSelectMessage(region, msg);
4121+
}
4122+
}
4123+
40844124
var _updateRequiresSelectionButtons = function(region, selectedCount) {
40854125

40864126
// update the 'select all on page' checkbox state
@@ -4100,13 +4140,7 @@ if (!LABKEY.DataRegions) {
41004140
}
41014141
});
41024142

4103-
// If not all rows are visible and some rows are selected, show selection message
4104-
if (region.totalRows && 0 !== region.selectedCount && !region.complete) {
4105-
var msg = (region.selectedCount === region.totalRows) ?
4106-
'All <span class="labkey-strong">' + region.totalRows.toLocaleString() + '</span> rows selected.' :
4107-
'Selected <span class="labkey-strong">' + region.selectedCount.toLocaleString() + '</span> of ' + region.totalRows.toLocaleString() + ' rows.';
4108-
_showSelectMessage(region, msg);
4109-
}
4143+
_updateSelectedCountMessage(region);
41104144

41114145
// Issue 10566: for javascript perf on IE stash the requires selection buttons
41124146
if (!region._requiresSelectionButtons) {
@@ -4446,6 +4480,11 @@ if (!LABKEY.DataRegions) {
44464480
};
44474481

44484482
LABKEY.DataRegion.selectAll = function(config) {
4483+
// GitHub Issue 778: Track selection loading state on DataRegion so we can disable buttons and prevent another click
4484+
var region = config.scope;
4485+
region.selectionLoading = true;
4486+
_updateSelectedCountMessage(region);
4487+
44494488
var params = {};
44504489
if (!config.url) {
44514490
// DataRegion doesn't have selectAllURL so generate url and query parameters manually
@@ -4485,8 +4524,8 @@ if (!LABKEY.DataRegions) {
44854524
url: config.url,
44864525
method: 'POST',
44874526
params: params,
4488-
success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
4489-
failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
4527+
success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), region),
4528+
failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), region, true)
44904529
});
44914530
};
44924531

0 commit comments

Comments
 (0)