cspace-ui
Version:
CollectionSpace user interface for browsers
649 lines (508 loc) • 22.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getSearchableRecordTypes = exports.getSubrecordSearchName = exports.getFirstItem = exports.getPreviousPageSearchDescriptor = exports.getNextPageSearchDescriptor = exports.getListType = exports.searchDescriptorToLocation = exports.advancedSearchConditionToNXQL = exports.fieldConditionToNXQL = exports.rangeFieldConditionToNXQL = exports.structuredDateFieldConditionToNXQL = exports.booleanConditionToNXQL = exports.valueToNXQL = exports.operatorToNXQL = exports.pathToNXQL = exports.normalizePatternValue = exports.normalizeCondition = exports.normalizeFieldCondition = exports.normalizeRangeFieldCondition = exports.normalizeBooleanCondition = exports.normalizeFieldValue = exports.normalizeListFieldValue = exports.normalizeStringFieldValue = exports.normalizeTimestampRangeEndValue = exports.normalizeTimestampRangeStartValue = void 0;
var _immutable = _interopRequireDefault(require("immutable"));
var _get = _interopRequireDefault(require("lodash/get"));
var _moment = _interopRequireDefault(require("moment"));
var _qs = _interopRequireDefault(require("qs"));
var _warning = _interopRequireDefault(require("warning"));
var _permissionHelpers = require("./permissionHelpers");
var _configHelpers = require("./configHelpers");
var _recordDataHelpers = require("./recordDataHelpers");
var _dataTypes = require("../constants/dataTypes");
var _searchOperators = require("../constants/searchOperators");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const getDataType = (fieldDescriptor, path) => (0, _get.default)(fieldDescriptor, ['document', ...path.split('/'), _configHelpers.configKey, 'dataType']);
const getSearchValueTransform = (fieldDescriptor, path) => (0, _get.default)(fieldDescriptor, ['document', ...path.split('/'), _configHelpers.configKey, 'searchTransform']);
const normalizeTimestampRangeStartValue = value => {
if (value) {
const timestamp = value.trim();
if (timestamp) {
return timestamp.indexOf('T') < 0 ? "".concat(timestamp, "T00:00:00.000") : timestamp;
}
}
return null;
};
exports.normalizeTimestampRangeStartValue = normalizeTimestampRangeStartValue;
const normalizeTimestampRangeEndValue = value => {
if (value) {
const timestamp = value.trim();
if (timestamp) {
return timestamp.indexOf('T') < 0 ? "".concat(timestamp, "T23:59:59.999") : timestamp;
}
}
return null;
};
exports.normalizeTimestampRangeEndValue = normalizeTimestampRangeEndValue;
const normalizeStringFieldValue = value => {
let trimmed;
if (value) {
trimmed = value.trim();
}
return trimmed || null;
};
exports.normalizeStringFieldValue = normalizeStringFieldValue;
const normalizeListFieldValue = list => {
let filtered;
if (list) {
filtered = list.map(value => normalizeStringFieldValue(value)).filter(value => !!value);
}
if (!filtered || filtered.size === 0) {
return null;
}
if (filtered.size === 1) {
return filtered.first();
}
return filtered;
};
exports.normalizeListFieldValue = normalizeListFieldValue;
const normalizeFieldValue = value => _immutable.default.List.isList(value) ? normalizeListFieldValue(value) : normalizeStringFieldValue(value);
exports.normalizeFieldValue = normalizeFieldValue;
const normalizeBooleanCondition = (fieldDescriptor, condition) => {
let childConditions = condition.get('value');
if (childConditions) {
/* Gotta do this mutual recursion */
/* eslint-disable no-use-before-define */
childConditions = childConditions.map(childCondition => normalizeCondition(fieldDescriptor, childCondition)).filter(childCondition => !!childCondition);
/* eslint-enable no-use-before-define */
}
if (childConditions && childConditions.size > 0) {
if (childConditions.size > 1) {
return condition.set('value', childConditions);
}
return childConditions.get(0);
}
return null;
};
exports.normalizeBooleanCondition = normalizeBooleanCondition;
const normalizeRangeFieldCondition = (fieldDescriptor, condition) => {
const value = condition.get('value');
if (value) {
const path = condition.get('path');
const dataType = getDataType(fieldDescriptor, path);
let startValue = value.get(0);
let endValue = value.get(1);
if (dataType === _dataTypes.DATA_TYPE_DATETIME || dataType === _dataTypes.DATA_TYPE_DATE) {
startValue = normalizeTimestampRangeStartValue(startValue);
endValue = normalizeTimestampRangeEndValue(endValue);
} else {
startValue = normalizeStringFieldValue(startValue);
endValue = normalizeStringFieldValue(endValue);
}
if (!startValue && !endValue) {
return null;
}
if (!startValue) {
return _immutable.default.Map({
op: _searchOperators.OP_LTE,
path: condition.get('path'),
value: endValue
});
}
if (!endValue) {
return _immutable.default.Map({
op: _searchOperators.OP_GTE,
path: condition.get('path'),
value: startValue
});
}
return condition.set('value', _immutable.default.List([startValue, endValue]));
}
return null;
};
exports.normalizeRangeFieldCondition = normalizeRangeFieldCondition;
const normalizeFieldCondition = (fieldDescriptor, condition) => {
const value = normalizeFieldValue(condition.get('value'));
if (value) {
return condition.set('value', value);
}
return null;
};
exports.normalizeFieldCondition = normalizeFieldCondition;
const normalizeCondition = (fieldDescriptor, condition) => {
if (condition) {
const operator = condition.get('op');
switch (operator) {
case _searchOperators.OP_AND:
case _searchOperators.OP_OR:
return normalizeBooleanCondition(fieldDescriptor, condition);
case _searchOperators.OP_RANGE:
return normalizeRangeFieldCondition(fieldDescriptor, condition);
default:
return normalizeFieldCondition(fieldDescriptor, condition);
}
}
return null;
};
exports.normalizeCondition = normalizeCondition;
const normalizePatternValue = value => {
if (!value) {
return value;
}
return value.replace(/(^|(\\\\)+|[^\\])\*+/g, '$1%');
};
exports.normalizePatternValue = normalizePatternValue;
const operatorToNXQLMap = {
[_searchOperators.OP_AND]: 'AND',
[_searchOperators.OP_OR]: 'OR',
[_searchOperators.OP_EQ]: '=',
[_searchOperators.OP_LT]: '<',
[_searchOperators.OP_LTE]: '<=',
[_searchOperators.OP_GT]: '>',
[_searchOperators.OP_GTE]: '>=',
[_searchOperators.OP_MATCH]: 'ILIKE',
[_searchOperators.OP_RANGE]: 'BETWEEN'
};
const pathToNXQL = (fieldDescriptor, path) => {
const [partName, ...pathInPartArray] = path.split('/');
const nxqlPartName = partName.substr(0, 4) === "".concat(_recordDataHelpers.NS_PREFIX, ":") ? partName.substr(4) : partName;
const partDescriptor = (0, _get.default)(fieldDescriptor, ['document', partName]);
const nxqlPathInPartArray = [];
for (let i = 0; i < pathInPartArray.length; i += 1) {
const fieldName = pathInPartArray[i];
const fieldPath = pathInPartArray.slice(0, i + 1);
const fieldConfig = (0, _get.default)(partDescriptor, [...fieldPath, _configHelpers.configKey]);
const repeating = (0, _get.default)(fieldConfig, 'repeating');
nxqlPathInPartArray.push(repeating ? '*' : fieldName);
}
const nxqlPath = nxqlPathInPartArray.join('/');
return "".concat(nxqlPartName, ":").concat(nxqlPath);
};
exports.pathToNXQL = pathToNXQL;
const operatorToNXQL = operator => operatorToNXQLMap[operator];
exports.operatorToNXQL = operatorToNXQL;
const valueToNXQL = (value, path, fieldDescriptor) => {
const dataType = getDataType(fieldDescriptor, path) || _dataTypes.DATA_TYPE_STRING;
const searchValueTransform = getSearchValueTransform(fieldDescriptor, path);
let data = value;
if (searchValueTransform) {
data = searchValueTransform({
data: value
});
}
let nxqlValue;
if (dataType === _dataTypes.DATA_TYPE_DATETIME) {
// Timestamp values in searches must be given in UTC.
// Assume timezoneless values are given in local time.
nxqlValue = new Date(Date.parse(data)).toISOString();
return "TIMESTAMP \"".concat(nxqlValue, "\"");
}
if (dataType === _dataTypes.DATA_TYPE_DATE) {
nxqlValue = data; // Append zero time part to date-only timestamps.
nxqlValue = nxqlValue.includes('T') ? nxqlValue : "".concat(nxqlValue, "T00:00:00.000"); // Timestamp values in searches must be given in UTC.
// Assume timezoneless values are given in UTC.
nxqlValue = nxqlValue.endsWith('Z') ? nxqlValue : "".concat(nxqlValue, "Z");
return "TIMESTAMP \"".concat(nxqlValue, "\"");
}
if (dataType === _dataTypes.DATA_TYPE_INT) {
nxqlValue = parseInt(data, 10);
} else if (dataType === _dataTypes.DATA_TYPE_FLOAT) {
nxqlValue = parseFloat(data);
} else if (dataType === _dataTypes.DATA_TYPE_BOOL) {
const boolData = typeof data === 'string' && data === 'false' ? false : !!data;
nxqlValue = boolData ? 1 : 0;
} else {
nxqlValue = data;
}
return JSON.stringify(nxqlValue);
};
exports.valueToNXQL = valueToNXQL;
const booleanConditionToNXQL = (fieldDescriptor, condition) => {
const operator = condition.get('op');
const nxqlOp = operatorToNXQL(operator);
if (nxqlOp) {
const childConditions = condition.get('value');
const nxql = childConditions.map(childCondition =>
/* Gotta do this mutual recursion */
/* eslint-disable no-use-before-define */
advancedSearchConditionToNXQL(fieldDescriptor, childCondition))
/* eslint-enable no-use-before-define */
.join(" ".concat(nxqlOp, " "));
return "(".concat(nxql, ")");
}
return '';
};
exports.booleanConditionToNXQL = booleanConditionToNXQL;
const structuredDateFieldConditionToNXQL = (fieldDescriptor, condition) => {
// Convert structured date searches to searches on the earliest and latest scalar dates.
// The UI has historically added one day to the latest day when computing the latest scalar
// date. I don't know why. This has to be taken into account.
const path = condition.get('path');
const operator = condition.get('op');
const value = condition.get('value');
const earliestScalarDatePath = "".concat(path, "/dateEarliestScalarValue");
const latestScalarDatePath = "".concat(path, "/dateLatestScalarValue");
let convertedCondition;
if (operator === _searchOperators.OP_RANGE) {
// The structured date range overlaps the value range.
const rangeStart = value.get(0);
const rangeEnd = value.get(1);
convertedCondition = _immutable.default.fromJS({
op: _searchOperators.OP_AND,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_LTE,
value: rangeEnd
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_GT,
value: rangeStart
}]
});
} else if (operator === _searchOperators.OP_CONTAIN) {
// The structured date range contains the value date.
convertedCondition = _immutable.default.fromJS({
op: _searchOperators.OP_AND,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_LTE,
value
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_GT,
// Not GTE, because latest scalar date has one day added.
value
}]
});
} else if (operator === _searchOperators.OP_EQ) {
// The earliest and latest dates of the structured date are the same, and are equal to the
// value date.
convertedCondition = _immutable.default.fromJS({
op: _searchOperators.OP_AND,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_EQ,
value
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_EQ,
// GRRR. The latest scalar date has one day added.
value: (0, _moment.default)(value).add(1, 'day').format('YYYY-MM-DD')
}]
});
} else if (operator === _searchOperators.OP_LT) {
// The earliest date in the structured date is before the value date, i.e. some part of the
// structured date exists before the value.
convertedCondition = _immutable.default.fromJS({
path: earliestScalarDatePath,
op: _searchOperators.OP_LT,
value
});
} else if (operator === _searchOperators.OP_LTE) {
// The earliest date in the structured date is before or equal to the value date.
convertedCondition = _immutable.default.fromJS({
path: earliestScalarDatePath,
op: _searchOperators.OP_LTE,
value
});
} else if (operator === _searchOperators.OP_GT) {
// The latest date in the structured date is after the value date, i.e. some part of the
// structured date exists after the value.
convertedCondition = _immutable.default.fromJS({
path: latestScalarDatePath,
op: _searchOperators.OP_GT,
// GRRR. The latest scalar date has one day added.
value: (0, _moment.default)(value).add(1, 'day').format('YYYY-MM-DD')
});
} else if (operator === _searchOperators.OP_GTE) {
// The latest date in the structured date is after or equal to the value date.
convertedCondition = _immutable.default.fromJS({
path: latestScalarDatePath,
op: _searchOperators.OP_GT,
// Not GTE, because latest scalar date has one day added.
value
});
}
process.env.NODE_ENV !== "production" ? (0, _warning.default)(convertedCondition, "The operator ".concat(operator, " is not supported for structured date fields. Search condition will be ignored.")) : void 0;
return convertedCondition // eslint-disable-next-line no-use-before-define
? advancedSearchConditionToNXQL(fieldDescriptor, convertedCondition) : null;
};
exports.structuredDateFieldConditionToNXQL = structuredDateFieldConditionToNXQL;
const rangeFieldConditionToNXQL = (fieldDescriptor, condition) => {
const path = condition.get('path');
const dataType = getDataType(fieldDescriptor, path);
if (dataType === _dataTypes.DATA_TYPE_STRUCTURED_DATE) {
return structuredDateFieldConditionToNXQL(fieldDescriptor, condition);
}
const operator = condition.get('op');
const values = condition.get('value');
const nxqlPath = pathToNXQL(fieldDescriptor, path);
const nxqlOp = operatorToNXQL(operator);
const startValue = values.get(0);
const endValue = values.get(1);
const nxqlValue = [startValue, endValue].map(value => valueToNXQL(value, path, fieldDescriptor)).join(' AND ');
return "".concat(nxqlPath, " ").concat(nxqlOp, " ").concat(nxqlValue);
};
exports.rangeFieldConditionToNXQL = rangeFieldConditionToNXQL;
const fieldConditionToNXQL = (fieldDescriptor, condition) => {
const path = condition.get('path');
const dataType = getDataType(fieldDescriptor, path);
let operator = condition.get('op');
let value = condition.get('value');
if (_immutable.default.List.isList(value)) {
// Expand or'ed values.
const orClauses = value.map(valueInstance => fieldConditionToNXQL(fieldDescriptor, condition.set('value', valueInstance))).join(' OR ');
return "(".concat(orClauses, ")");
}
if (dataType === _dataTypes.DATA_TYPE_STRUCTURED_DATE) {
return structuredDateFieldConditionToNXQL(fieldDescriptor, condition);
}
if (operator === _searchOperators.OP_CONTAIN) {
operator = _searchOperators.OP_MATCH;
value = "%".concat(value, "%");
}
if (operator === _searchOperators.OP_MATCH) {
value = normalizePatternValue(value);
}
const nxqlPath = pathToNXQL(fieldDescriptor, path);
const nxqlOp = operatorToNXQL(operator);
const nxqlValue = valueToNXQL(value, path, fieldDescriptor);
return "".concat(nxqlPath, " ").concat(nxqlOp, " ").concat(nxqlValue);
};
exports.fieldConditionToNXQL = fieldConditionToNXQL;
const advancedSearchConditionToNXQL = (fieldDescriptor, condition) => {
if (condition) {
const operator = condition.get('op');
switch (operator) {
case _searchOperators.OP_AND:
case _searchOperators.OP_OR:
return booleanConditionToNXQL(fieldDescriptor, condition);
case _searchOperators.OP_RANGE:
return rangeFieldConditionToNXQL(fieldDescriptor, condition);
default:
return fieldConditionToNXQL(fieldDescriptor, condition);
}
}
return null;
};
/**
* Converts a search descriptor to a React Router location.
*/
exports.advancedSearchConditionToNXQL = advancedSearchConditionToNXQL;
const searchDescriptorToLocation = searchDescriptor => {
const recordType = searchDescriptor.get('recordType');
const vocabulary = searchDescriptor.get('vocabulary');
const csid = searchDescriptor.get('csid');
const subresource = searchDescriptor.get('subresource');
const searchQuery = searchDescriptor.get('searchQuery');
const pathParts = ['/list', recordType, vocabulary, csid, subresource];
const pathname = pathParts.filter(part => !!part).join('/');
const as = searchQuery.get('as');
const p = searchQuery.get('p');
const asParam = as ? JSON.stringify(as.toJS()) : undefined;
const pParam = typeof p === 'number' ? (p + 1).toString() : undefined;
const queryString = _qs.default.stringify(searchQuery.set('as', asParam).set('p', pParam).toJS());
return {
pathname,
search: "?".concat(queryString)
};
};
exports.searchDescriptorToLocation = searchDescriptorToLocation;
const getListType = (config, searchDescriptor) => {
if (searchDescriptor) {
const subresource = searchDescriptor.get('subresource');
if (subresource) {
return (0, _get.default)(config, ['subresources', subresource, 'listType']);
}
}
return 'common';
};
/**
* Returns a search descriptor that describes the next page of the search described by a given
* descriptor.
*/
exports.getListType = getListType;
const getNextPageSearchDescriptor = searchDescriptor => {
const p = searchDescriptor.getIn(['searchQuery', 'p']) || 0;
return searchDescriptor.setIn(['searchQuery', 'p'], p + 1);
};
/**
* Returns a search descriptor that describes the previous page of the search described by a given
* descriptor. Null is returned if the given descriptor is on the first page.
*/
exports.getNextPageSearchDescriptor = getNextPageSearchDescriptor;
const getPreviousPageSearchDescriptor = searchDescriptor => {
const p = searchDescriptor.getIn(['searchQuery', 'p']) || 0;
if (p <= 0) {
return null;
}
return searchDescriptor.setIn(['searchQuery', 'p'], p - 1);
};
/**
* Returns the first item in list result data.
*/
exports.getPreviousPageSearchDescriptor = getPreviousPageSearchDescriptor;
const getFirstItem = function getFirstItem(config, listData) {
let listType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'common';
if (!listData) {
return null;
}
const listTypeConfig = (0, _get.default)(config, ['listTypes', listType]);
if (!listTypeConfig) {
return null;
}
const {
listNodeName,
itemNodeName
} = listTypeConfig;
const items = listData.getIn([listNodeName, itemNodeName]);
const item = _immutable.default.List.isList(items) ? items.first() : items;
return item;
};
/**
* Returns a name for a search for subrecords.
*/
exports.getFirstItem = getFirstItem;
const getSubrecordSearchName = (csid, subrecordName) => "subrecord/".concat(csid, "/").concat(subrecordName);
exports.getSubrecordSearchName = getSubrecordSearchName;
const getSearchableRecordTypes = (getAuthorityVocabCsid, config, perms) => {
const {
recordTypes
} = config;
const filteredRecordTypes = {}; // Filter out record types for which the user doesn't have suffiencient permissions, and
// vocabularies that don't exist on the server.
Object.keys(recordTypes).forEach(recordType => {
const recordTypeConfig = recordTypes[recordType];
const serviceType = (0, _get.default)(recordTypeConfig, ['serviceConfig', 'serviceType']);
if (serviceType === 'object' || serviceType === 'procedure') {
// For objects and procedures, check if list permissions exist.
if ((0, _permissionHelpers.canList)(recordType, perms)) {
filteredRecordTypes[recordType] = recordTypeConfig;
}
} else if (serviceType === 'authority') {
// For authorities, check if list permissions exist, and if so, filter the vocabularies.
if ((0, _permissionHelpers.canList)(recordType, perms)) {
const vocabularies = recordTypeConfig.vocabularies;
if (vocabularies) {
const filteredVocabularies = {}; // Filter out vocabularies that are configured in the UI, but don't exist in the services
// layer. This will happen when a vocabulary has been configured in the UI, but hasn't
// been created/initialized through the REST API. This isn't necessarily a bug, because
// sometimes vocbularies are defined in the UI for convenience, but aren't expected to be
// created in all tenants.
Object.keys(vocabularies).forEach(vocabulary => {
const vocabularyConfig = vocabularies[vocabulary];
const {
type
} = vocabularyConfig; // The 'all' vocabulary always exists, so always let that through the filter. For
// others, check if a csid for the vocabulary was successfully retrieved from the
// services layer.
const exists = type === 'all' || getAuthorityVocabCsid(recordType, vocabulary);
if (exists) {
filteredVocabularies[vocabulary] = vocabularyConfig;
}
});
filteredRecordTypes[recordType] = Object.assign({}, recordTypeConfig, {
vocabularies: filteredVocabularies
});
}
}
} else {
// Allow other types. These may get filtered down further by child components.
filteredRecordTypes[recordType] = recordTypeConfig;
}
});
return filteredRecordTypes;
};
exports.getSearchableRecordTypes = getSearchableRecordTypes;