cspace-ui
Version:
CollectionSpace user interface for browsers
1,145 lines (1,093 loc) • 48.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.normalizeListFieldValue = exports.normalizeGroupCondition = exports.normalizeFieldValue = exports.normalizeFieldCondition = exports.normalizeCondition = exports.normalizeBooleanCondition = exports.isFieldAutocomplete = exports.groupConditionToNXQL = exports.getSubrecordSearchName = exports.getSearchableRecordTypes = exports.getPreviousPageSearchDescriptor = exports.getOperatorsForDataType = exports.getNextPageSearchDescriptor = exports.getListTypeFromResult = exports.getFirstItem = exports.fieldConditionToNXQL = exports.extractAdvancedSearchGroupedTerms = exports.deriveSearchType = exports.dateStartTimestamp = exports.dateEndTimestamp = exports.dataTypeSupportsMultipleValues = exports.createSortDirHandler = exports.createSortByHandler = exports.createPageSizeChangeHandler = exports.createPageChangeHandler = exports.createCounter = exports.convertAdvancedSearchConditionToNXQL = exports.clearAdvancedSearchConditionValues = exports.booleanConditionToNXQL = exports.advancedSearchConditionToNXQL = void 0;
exports.normalizePageSize = normalizePageSize;
exports.normalizeRangeFieldCondition = void 0;
exports.normalizeSearchQueryParams = normalizeSearchQueryParams;
exports.valueToNXQL = exports.structuredDateFieldConditionToNXQL = exports.searchDescriptorToLocation = exports.rangeFieldConditionToNXQL = exports.patternValueToNXQL = exports.pathToNXQL = exports.operatorToNXQL = exports.operatorSupportsMultipleValues = exports.operatorExpectsValue = exports.normalizeStringFieldValue = 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 _dataTypes = require("../constants/dataTypes");
var _searchNames = require("../constants/searchNames");
var _searchOperators = require("../constants/searchOperators");
var _xmlNames = require("../constants/xmlNames");
var _structuredDateFields = require("../constants/structuredDateFields");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const opsByDataTypeMapNew = {
[_dataTypes.DATA_TYPE_STRING]: [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_CONTAIN, _searchOperators.OP_NOT_CONTAIN, _searchOperators.OP_MATCH, _searchOperators.OP_NOT_MATCH, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_INT]: [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL, _searchOperators.OP_GTE, _searchOperators.OP_LTE, _searchOperators.OP_GT, _searchOperators.OP_LT, _searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE],
[_dataTypes.DATA_TYPE_FLOAT]: [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL, _searchOperators.OP_GTE, _searchOperators.OP_LTE, _searchOperators.OP_GT, _searchOperators.OP_LT, _searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE],
[_dataTypes.DATA_TYPE_BOOL]: [_searchOperators.OP_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_DATE]: [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ,
// OP_CONTAIN, TODO: needs backend update
// OP_NOT_CONTAIN, TODO: needs backend update
_searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL, _searchOperators.OP_GTE, _searchOperators.OP_LTE, _searchOperators.OP_GT, _searchOperators.OP_LT, _searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE],
[_dataTypes.DATA_TYPE_DATETIME]: [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ,
// OP_CONTAIN, TODO: needs backend update
// OP_NOT_CONTAIN, TODO: needs backend update
_searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL, _searchOperators.OP_GTE, _searchOperators.OP_LTE, _searchOperators.OP_GT, _searchOperators.OP_LT, _searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE],
[_dataTypes.DATA_TYPE_STRUCTURED_DATE]: [_searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL, _searchOperators.OP_GTC, _searchOperators.OP_LTC, _searchOperators.OP_GT, _searchOperators.OP_LT, _searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE, _searchOperators.OP_COMPLETE, _searchOperators.OP_NOT_COMPLETE]
};
const opsByDataTypeMap = {
[_dataTypes.DATA_TYPE_STRING]: [_searchOperators.OP_CONTAIN, _searchOperators.OP_NOT_CONTAIN, _searchOperators.OP_MATCH, _searchOperators.OP_NOT_MATCH, _searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE, _searchOperators.OP_GT, _searchOperators.OP_GTE, _searchOperators.OP_LT, _searchOperators.OP_LTE, _searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_INT]: [_searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE, _searchOperators.OP_GT, _searchOperators.OP_GTE, _searchOperators.OP_LT, _searchOperators.OP_LTE, _searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_FLOAT]: [_searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE, _searchOperators.OP_GT, _searchOperators.OP_GTE, _searchOperators.OP_LT, _searchOperators.OP_LTE, _searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_BOOL]: [_searchOperators.OP_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_DATE]: [_searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE, _searchOperators.OP_GT, _searchOperators.OP_GTE, _searchOperators.OP_LT, _searchOperators.OP_LTE, _searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_DATETIME]: [_searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE, _searchOperators.OP_GT, _searchOperators.OP_GTE, _searchOperators.OP_LT, _searchOperators.OP_LTE, _searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL],
[_dataTypes.DATA_TYPE_STRUCTURED_DATE]: [_searchOperators.OP_RANGE, _searchOperators.OP_NOT_RANGE, _searchOperators.OP_CONTAIN, _searchOperators.OP_NOT_CONTAIN, _searchOperators.OP_GT, _searchOperators.OP_GTC, _searchOperators.OP_LT, _searchOperators.OP_LTC, _searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NOT_COMPLETE, _searchOperators.OP_COMPLETE]
};
const autocompleteOps = [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_CONTAIN, _searchOperators.OP_NOT_CONTAIN, _searchOperators.OP_MATCH, _searchOperators.OP_NOT_MATCH, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL];
const controlledListOps = [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL];
const getOperatorsForDataType = (dataType = _dataTypes.DATA_TYPE_STRING, isControlled, isNewSearchForm, isAutocomplete) => {
if (isAutocomplete && isNewSearchForm) return autocompleteOps;
if (isControlled) return controlledListOps;
const opsByDataType = isNewSearchForm ? opsByDataTypeMapNew[dataType] : opsByDataTypeMap[dataType];
return opsByDataType !== null && opsByDataType !== void 0 ? opsByDataType : [];
};
exports.getOperatorsForDataType = getOperatorsForDataType;
const operatorExpectsValue = op => op !== _searchOperators.OP_NULL && op !== _searchOperators.OP_NOT_NULL && op !== _searchOperators.OP_COMPLETE && op !== _searchOperators.OP_NOT_COMPLETE;
exports.operatorExpectsValue = operatorExpectsValue;
const operatorSupportsMultipleValues = op =>
// There is no need to support multiple values with greater than/less than operators, since they
// are redundant. The range search operator could conceivably have multiple values (non-
// overlapping ranges), but the range search input doesn't support multiple values right now.
// This could be implemented if it's needed.
// The below operators allow multiple values.
op === _searchOperators.OP_EQ || op === _searchOperators.OP_CONTAIN || op === _searchOperators.OP_MATCH || op === _searchOperators.OP_RANGE || op === _searchOperators.OP_NOT_EQ || op === _searchOperators.OP_NOT_CONTAIN || op === _searchOperators.OP_NOT_MATCH || op === _searchOperators.OP_NOT_RANGE;
exports.operatorSupportsMultipleValues = operatorSupportsMultipleValues;
const dataTypeSupportsMultipleValues = dataType =>
// Booleans only have two possible values, so null (don't care) or a single desired
// value is sufficient to describe all searches, and there's no need to allow multiple
// values.
dataType !== _dataTypes.DATA_TYPE_BOOL;
exports.dataTypeSupportsMultipleValues = dataTypeSupportsMultipleValues;
const getDataType = (fieldDescriptor, path) => (0, _get.default)(fieldDescriptor, ['document', ...path.split('/'), _configHelpers.configKey, 'dataType']);
const getSearchCompareField = (fieldDescriptor, path) => (0, _get.default)(fieldDescriptor, ['document', ...path.split('/'), _configHelpers.configKey, 'searchCompareField']);
const getSearchValueTransform = (fieldDescriptor, path) => (0, _get.default)(fieldDescriptor, ['document', ...path.split('/'), _configHelpers.configKey, 'searchTransform']);
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) {
childConditions = childConditions
// Gotta do this mutual recursion
// eslint-disable-next-line no-use-before-define
.map(childCondition => normalizeCondition(fieldDescriptor, childCondition)).filter(childCondition => !!childCondition);
}
if (childConditions && childConditions.size > 0) {
if (childConditions.size > 1) {
return condition.set('value', childConditions);
}
return childConditions.get(0);
}
return null;
};
exports.normalizeBooleanCondition = normalizeBooleanCondition;
const normalizeGroupCondition = (fieldDescriptor, condition) => {
const path = condition.get('path');
if (!path) {
return null;
}
let childCondition = condition.get('value');
if (!childCondition) {
return null;
}
const childOp = childCondition.get('op');
if (childOp !== _searchOperators.OP_AND && childOp !== _searchOperators.OP_OR) {
// Ensure the child is a boolean operation.
childCondition = _immutable.default.Map({
op: _searchOperators.OP_AND,
value: _immutable.default.List.of(childCondition)
});
}
// Normalize the child conditions of the child boolean operation.
let boolChildConditions = childCondition.get('value');
if (!boolChildConditions) {
return null;
}
boolChildConditions = boolChildConditions
// Gotta do this mutual recursion
// eslint-disable-next-line no-use-before-define
.map(boolChildCondition => normalizeCondition(fieldDescriptor, boolChildCondition)).filter(boolChildCondition => !!boolChildCondition);
if (boolChildConditions.size === 0) {
return null;
}
return condition.set('value', childCondition.set('value', boolChildConditions));
};
exports.normalizeGroupCondition = normalizeGroupCondition;
const normalizeRangeFieldCondition = (fieldDescriptor, condition) => {
const path = condition.get('path');
if (!path) {
return null;
}
let value = condition.get('value');
if (value) {
if (!_immutable.default.List.isList(value)) {
value = _immutable.default.List.of(value);
}
const startValue = normalizeStringFieldValue(value.get(0));
const endValue = normalizeStringFieldValue(value.get(1));
if (!startValue && !endValue) {
return null;
}
const op = condition.get('op');
const dataType = getDataType(fieldDescriptor, path);
if (!startValue) {
let newOp;
if (dataType === _dataTypes.DATA_TYPE_STRUCTURED_DATE) {
newOp = op === _searchOperators.OP_NOT_RANGE ? _searchOperators.OP_GT : _searchOperators.OP_LTC;
} else {
newOp = op === _searchOperators.OP_NOT_RANGE ? _searchOperators.OP_GT : _searchOperators.OP_LTE;
}
return _immutable.default.Map({
op: newOp,
path: condition.get('path'),
value: endValue
});
}
if (!endValue) {
let newOp;
if (dataType === _dataTypes.DATA_TYPE_STRUCTURED_DATE) {
newOp = op === _searchOperators.OP_NOT_RANGE ? _searchOperators.OP_LT : _searchOperators.OP_GTC;
} else {
newOp = op === _searchOperators.OP_NOT_RANGE ? _searchOperators.OP_LT : _searchOperators.OP_GTE;
}
return _immutable.default.Map({
op: newOp,
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 path = condition.get('path');
if (!path) {
return null;
}
const value = normalizeFieldValue(condition.get('value'));
if (value) {
return condition.set('value', value);
}
if (operatorExpectsValue(condition.get('op'))) {
// The operator expects a value, but none were supplied. Remove the condition.
return null;
}
return condition.delete('value');
};
exports.normalizeFieldCondition = normalizeFieldCondition;
function normalizePageSize(pageSize, {
min = 1,
max = 2500,
fallback = 20
} = {}) {
const normalized = parseInt(pageSize, 10);
if (Number.isNaN(normalized) || normalized < min) {
return fallback;
}
if (normalized > max) {
return max;
}
return normalized;
}
function normalizeSearchQueryParams(query, preferredPageSize, defaultPageSize, maxPageSize = 2500) {
const normalizedQuery = {
...query
};
let changed = false;
// Normalize page size
const fallback = preferredPageSize || defaultPageSize || 20;
const normalizedPageSize = normalizePageSize(query.size, {
min: 1,
max: maxPageSize,
fallback
});
// here is necessary to check using regex instead of Number.isNaN(parseInt())
// because the parseInt('123asd') still parses successfully even though parameter is not a number.
if (!/^-?\d+$/.test(query.size) || parseInt(query.size, 10) !== normalizedPageSize) {
normalizedQuery.size = normalizedPageSize.toString();
changed = true;
}
// Normalize page number
const pageNum = parseInt(query.p, 10);
if (Number.isNaN(pageNum) || pageNum < 1) {
normalizedQuery.p = '1';
changed = true;
} else if (pageNum.toString() !== query.p) {
normalizedQuery.p = pageNum.toString();
changed = true;
}
return {
normalizedQuery,
changed
};
}
const isFieldAutocomplete = (fieldDescriptor, path) => {
var _viewType;
let viewType = (0, _get.default)(fieldDescriptor, [_configHelpers.configKey, 'view', 'type']);
if (path) {
viewType = (0, _get.default)(fieldDescriptor, ['document', ...path.split('/'), _configHelpers.configKey, 'view', 'type']);
}
return ((_viewType = viewType) === null || _viewType === void 0 ? void 0 : _viewType.toJSON()) === 'AutocompleteInput';
};
exports.isFieldAutocomplete = isFieldAutocomplete;
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:
case _searchOperators.OP_NOT_RANGE:
return normalizeRangeFieldCondition(fieldDescriptor, condition);
case _searchOperators.OP_GROUP:
return normalizeGroupCondition(fieldDescriptor, condition);
default:
return normalizeFieldCondition(fieldDescriptor, condition);
}
}
return null;
};
exports.normalizeCondition = normalizeCondition;
const patternValueToNXQL = value => {
if (!value) {
return value;
}
return value.replace(/(^|(\\\\)+|[^\\])\*+/g, '$1%');
};
exports.patternValueToNXQL = patternValueToNXQL;
const isComparisonOp = op => op === _searchOperators.OP_LT || op === _searchOperators.OP_LTE || op === _searchOperators.OP_GT || op === _searchOperators.OP_GTE || op === _searchOperators.OP_RANGE || op === _searchOperators.OP_NOT_RANGE;
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',
[_searchOperators.OP_NULL]: 'IS NULL',
[_searchOperators.OP_NOT_EQ]: '<>',
[_searchOperators.OP_NOT_MATCH]: 'NOT ILIKE',
[_searchOperators.OP_NOT_RANGE]: 'NOT BETWEEN',
[_searchOperators.OP_NOT_NULL]: 'IS NOT NULL'
};
const pathToNXQL = (fieldDescriptor, path) => {
const [partName, ...pathInPartArray] = path.split('/');
const nxqlPartName = partName.substr(0, 4) === `${_xmlNames.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 `${nxqlPartName}:${nxqlPath}`;
};
exports.pathToNXQL = pathToNXQL;
const operatorToNXQL = operator => operatorToNXQLMap[operator];
exports.operatorToNXQL = operatorToNXQL;
const dateStartTimestamp = value => value.indexOf('T') < 0 ? `${value}T00:00:00.000` : value;
exports.dateStartTimestamp = dateStartTimestamp;
const dateEndTimestamp = value => value.indexOf('T') < 0 ? `${value}T23:59:59.999` : value;
exports.dateEndTimestamp = dateEndTimestamp;
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 "${nxqlValue}"`;
}
if (dataType === _dataTypes.DATA_TYPE_DATE) {
nxqlValue = data;
// Append zero time part to date-only timestamps.
nxqlValue = nxqlValue.includes('T') ? nxqlValue : `${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 : `${nxqlValue}Z`;
return `TIMESTAMP "${nxqlValue}"`;
}
if (dataType === _dataTypes.DATA_TYPE_INT || 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, counter) => {
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-next-line no-use-before-define
advancedSearchConditionToNXQL(fieldDescriptor, childCondition, counter)).join(` ${nxqlOp} `);
return `(${nxql})`;
}
return '';
};
exports.booleanConditionToNXQL = booleanConditionToNXQL;
const correlatePath = (nxql, nxqlPath, counter) => {
if (!nxqlPath.endsWith('*')) {
if (nxqlPath.includes('*')) {
const index = nxqlPath.lastIndexOf('*');
return correlatePath(nxql, nxqlPath.substring(0, index + 1), counter);
}
return nxql;
}
const correlationNumber = counter.next();
const correlatedPath = `${nxqlPath}${correlationNumber}`;
const nxqlPathMatchString = nxqlPath.replace(/\*/g, '\\*\\d*');
const correlatedNXQL = nxql.replace(new RegExp(nxqlPathMatchString, 'g'), correlatedPath);
const index = nxqlPath.lastIndexOf('*', nxqlPath.length - 2);
return correlatePath(correlatedNXQL, nxqlPath.substring(0, index + 1), counter);
};
const groupConditionToNXQL = (fieldDescriptor, condition, counter) => {
const path = condition.get('path');
const childCondition = condition.get('value');
const nxqlPath = pathToNXQL(fieldDescriptor, path);
// Gotta do this mutual recursion
// eslint-disable-next-line no-use-before-define
const nxql = advancedSearchConditionToNXQL(fieldDescriptor, childCondition, counter);
return correlatePath(nxql, nxqlPath, counter);
};
exports.groupConditionToNXQL = groupConditionToNXQL;
const structuredDateFieldConditionToNXQL = (fieldDescriptor, condition, counter) => {
// 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.
// All conditions that are converted to and/or conditions on the earliest and latest scalar dates
// need to be correlated to the same group instance, because the field may be repeating. This is
// done by wrapping them in a group operator.
const path = condition.get('path');
const operator = condition.get('op');
const value = condition.get('value');
const earliestScalarDatePath = `${path}${_structuredDateFields.EARLIEST_SCALAR_DATE_FIELD}`;
const latestScalarDatePath = `${path}${_structuredDateFields.LATEST_SCALAR_DATE_FIELD}`;
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({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_AND,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_LTE,
value: rangeEnd
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_GT,
// Not GTE, because latest scalar date has one day added.
value: rangeStart
}]
}
});
} else if (operator === _searchOperators.OP_NULL) {
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_AND,
value: _structuredDateFields.ALL_FIELDS.map(field => ({
path: `${path}${field}`,
op: _searchOperators.OP_NULL
}))
}
});
} else if (operator === _searchOperators.OP_NOT_NULL) {
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_OR,
value: _structuredDateFields.ALL_FIELDS.map(field => ({
path: `${path}${field}`,
op: _searchOperators.OP_NOT_NULL
}))
}
});
} else if (operator === _searchOperators.OP_NOT_RANGE) {
// The structured date range does not overlap the value range.
// This will be the logical negation of OP_RANGE.
const rangeStart = value.get(0);
const rangeEnd = value.get(1);
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_OR,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_GT,
value: rangeEnd
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_LTE,
// Not LT, because latest scalar date has one day added.
value: rangeStart
}]
}
});
} else if (operator === _searchOperators.OP_CONTAIN) {
// The structured date range contains the value date.
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
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_NOT_CONTAIN) {
// The structured date range does not contain the value date.
// This will be the logical negation of OP_CONTAIN.
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_OR,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_GT,
value
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_LTE,
// Not LT, 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({
path,
op: _searchOperators.OP_GROUP,
value: {
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_NOT_EQ) {
// Either the earliest or latest dates of the structured date are not equal to the
// value date.
// This will be the logical negation of OP_EQ.
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_OR,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_NOT_EQ,
value
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_NOT_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 latest date in the structured date is before the value date, i.e. no part of the
// structured date exists on or after the value.
convertedCondition = _immutable.default.fromJS({
path: latestScalarDatePath,
op: _searchOperators.OP_LTE,
// Not LT, because latest scalar date has one day added.
value
});
} else if (operator === _searchOperators.OP_LTC) {
// 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 earliest date in the structured date is after the value date, i.e. no part of the
// structured date exists on or before the value.
convertedCondition = _immutable.default.fromJS({
path: earliestScalarDatePath,
op: _searchOperators.OP_GT,
value
});
} else if (operator === _searchOperators.OP_GTC) {
// 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
});
} else if (operator === _searchOperators.OP_COMPLETE) {
// Both the earliest and latest scalar date are not null.
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_AND,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_NOT_NULL
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_NOT_NULL
}]
}
});
} else if (operator === _searchOperators.OP_NOT_COMPLETE) {
// Either the earliest or latest scalar date is null.
// This will be the logical negation of OP_COMPLETE.
convertedCondition = _immutable.default.fromJS({
path,
op: _searchOperators.OP_GROUP,
value: {
op: _searchOperators.OP_OR,
value: [{
path: earliestScalarDatePath,
op: _searchOperators.OP_NULL
}, {
path: latestScalarDatePath,
op: _searchOperators.OP_NULL
}]
}
});
}
process.env.NODE_ENV !== "production" ? (0, _warning.default)(convertedCondition, `The operator ${operator} is not supported for structured date fields. Search condition will be ignored.`) : void 0;
return convertedCondition
// Gotta do this mutual recursion
// eslint-disable-next-line no-use-before-define
? advancedSearchConditionToNXQL(fieldDescriptor, convertedCondition, counter) : null;
};
exports.structuredDateFieldConditionToNXQL = structuredDateFieldConditionToNXQL;
const resolveSearchCompareField = (fieldDescriptor, condition) => {
const op = condition.get('op');
const path = condition.get('path');
if (isComparisonOp(op)) {
const searchCompareField = getSearchCompareField(fieldDescriptor, path);
if (searchCompareField) {
return condition.set('path', searchCompareField);
}
}
return condition;
};
const rangeFieldConditionToNXQL = (fieldDescriptor, rangeFieldCondition, counter) => {
const condition = resolveSearchCompareField(fieldDescriptor, rangeFieldCondition);
const path = condition.get('path');
const dataType = getDataType(fieldDescriptor, path);
if (dataType === _dataTypes.DATA_TYPE_STRUCTURED_DATE) {
return structuredDateFieldConditionToNXQL(fieldDescriptor, condition, counter);
}
const operator = condition.get('op');
const values = condition.get('value');
const nxqlPath = pathToNXQL(fieldDescriptor, path);
const nxqlOp = operatorToNXQL(operator);
let startValue = values.get(0);
let endValue = values.get(1);
if (dataType === _dataTypes.DATA_TYPE_DATETIME) {
// } || dataType === DATA_TYPE_DATE) {
startValue = dateStartTimestamp(startValue);
endValue = dateEndTimestamp(endValue);
}
const nxqlValue = [startValue, endValue].map(value => valueToNXQL(value, path, fieldDescriptor)).join(' AND ');
return `${nxqlPath} ${nxqlOp} ${nxqlValue}`;
};
exports.rangeFieldConditionToNXQL = rangeFieldConditionToNXQL;
const fieldConditionToNXQL = (fieldDescriptor, fieldCondition, counter) => {
const condition = resolveSearchCompareField(fieldDescriptor, fieldCondition);
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), counter)).join(' OR ');
return `(${orClauses})`;
}
if (dataType === _dataTypes.DATA_TYPE_STRUCTURED_DATE) {
return structuredDateFieldConditionToNXQL(fieldDescriptor, condition, counter);
}
if (dataType === _dataTypes.DATA_TYPE_DATETIME) {
// } || dataType === DATA_TYPE_DATE) {
if (operator === _searchOperators.OP_EQ || operator === _searchOperators.OP_NOT_EQ) {
return rangeFieldConditionToNXQL(fieldDescriptor, condition.set('op', operator === _searchOperators.OP_EQ ? _searchOperators.OP_RANGE : _searchOperators.OP_NOT_RANGE).set('value', _immutable.default.List([value, value])), counter);
}
if (operator === _searchOperators.OP_GT || operator === _searchOperators.OP_LTE) {
value = dateEndTimestamp(value);
} else {
value = dateStartTimestamp(value);
}
}
const isAutocomplete = isFieldAutocomplete(fieldDescriptor, path);
if (isAutocomplete) {
if (operator === _searchOperators.OP_CONTAIN) {
operator = _searchOperators.OP_MATCH;
value = `%'%${value}%'%`;
} else if (operator === _searchOperators.OP_NOT_CONTAIN) {
operator = _searchOperators.OP_NOT_MATCH;
value = `%'%${value}%'%`;
} else if (operator === _searchOperators.OP_MATCH || operator === _searchOperators.OP_NOT_MATCH) {
value = `%'${value}'%`;
}
} else if (operator === _searchOperators.OP_CONTAIN) {
operator = _searchOperators.OP_MATCH;
value = `%${value}%`;
} else if (operator === _searchOperators.OP_NOT_CONTAIN) {
operator = _searchOperators.OP_NOT_MATCH;
value = `%${value}%`;
}
if (operator === _searchOperators.OP_MATCH || operator === _searchOperators.OP_NOT_MATCH) {
value = patternValueToNXQL(value);
}
const nxqlPath = pathToNXQL(fieldDescriptor, path);
const nxqlOp = operatorToNXQL(operator);
if (typeof value === 'undefined') {
return `${nxqlPath} ${nxqlOp}`;
}
const nxqlValue = valueToNXQL(value, path, fieldDescriptor);
return `${nxqlPath} ${nxqlOp} ${nxqlValue}`;
};
exports.fieldConditionToNXQL = fieldConditionToNXQL;
const createCounter = () => {
let nextNum = 1;
return {
next: () => {
const result = nextNum;
nextNum += 1;
return result;
}
};
};
exports.createCounter = createCounter;
const advancedSearchConditionToNXQL = (fieldDescriptor, condition, counter) => {
if (condition) {
const operator = condition.get('op');
switch (operator) {
case _searchOperators.OP_AND:
case _searchOperators.OP_OR:
return booleanConditionToNXQL(fieldDescriptor, condition, counter);
case _searchOperators.OP_RANGE:
case _searchOperators.OP_NOT_RANGE:
return rangeFieldConditionToNXQL(fieldDescriptor, condition, counter);
case _searchOperators.OP_GROUP:
return groupConditionToNXQL(fieldDescriptor, condition, counter);
default:
return fieldConditionToNXQL(fieldDescriptor, condition, counter);
}
}
return null;
};
exports.advancedSearchConditionToNXQL = advancedSearchConditionToNXQL;
const convertAdvancedSearchConditionToNXQL = (fieldDescriptor, condition) => advancedSearchConditionToNXQL(fieldDescriptor, condition, createCounter());
/**
* Converts a search descriptor to a React Router location.
*/
exports.convertAdvancedSearchConditionToNXQL = convertAdvancedSearchConditionToNXQL;
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: `?${queryString}`
};
};
/**
* Attempt to derive list type and search type from a search's name and descriptor.
*
* @param {*} config the cspace configuration
* @param {*} searchName the name of the search
* @param {*} searchDescriptor the search descriptor
* @returns an object with the listType and searchType set
*/
exports.searchDescriptorToLocation = searchDescriptorToLocation;
const deriveSearchType = (config, searchName, searchDescriptor) => {
let listType = 'common';
let searchType = 'default';
if (searchDescriptor) {
const recordType = searchDescriptor.get('recordType');
const subresource = searchDescriptor.get('subresource');
// there are a few search apis which return different lists, so we account for them first
// because they should only need the listType.
// Note that we use the name of the key for the listType (role, account, etc).
// todo: it would be nice to update the backend to send a single paginated list type instead
if (_searchNames.SEARCH_RESULT_ACCOUNT_PAGE === searchName) {
listType = 'account';
} else if (_searchNames.SEARCH_RESULT_AUTH_ROLE_PAGE === searchName) {
listType = 'role';
} else if (subresource) {
listType = (0, _get.default)(config, ['subresources', subresource, 'listType']) || 'common';
} else if ((0, _get.default)(config, ['recordTypes', recordType, 'serviceConfig', 'features', 'updatedSearch'])) {
listType = 'search';
searchType = 'advanced';
}
}
return {
listType,
searchType
};
};
exports.deriveSearchType = deriveSearchType;
const getListTypeFromResult = (config, searchResult) => {
let listType;
if (searchResult) {
listType = Object.keys((0, _get.default)(config, ['listTypes'])).find(key => {
const listNodeName = (0, _get.default)(config, ['listTypes', key, 'listNodeName']);
return searchResult.has(listNodeName);
});
}
return listType || 'common';
};
/**
* Returns a search descriptor that describes the next page of the search described by a given
* descriptor.
*/
exports.getListTypeFromResult = getListTypeFromResult;
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 = (config, listData, listType = '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/${csid}/${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;
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] = {
...recordTypeConfig,
vocabularies: filteredVocabularies
};
}
}
} else {
// Allow other types. These may get filtered down further by child components.
filteredRecordTypes[recordType] = recordTypeConfig;
}
});
return filteredRecordTypes;
};
exports.getSearchableRecordTypes = getSearchableRecordTypes;
const clearAdvancedSearchConditionValues = condition => {
if (!condition) {
return condition;
}
const op = condition.get('op');
const value = condition.get('value');
if (op === _searchOperators.OP_AND || op === _searchOperators.OP_OR) {
if (_immutable.default.List.isList(value)) {
return condition.set('value', value.map(childCondition => clearAdvancedSearchConditionValues(childCondition)));
}
return condition.set('value', clearAdvancedSearchConditionValues(value));
}
if (op === _searchOperators.OP_GROUP) {
return condition.set('value', clearAdvancedSearchConditionValues(value));
}
return condition.delete('value');
};
/**
* Creates a reusable page size change handler that updates URL query parameters and navigation
* history. This function handles the common pattern of resetting to page 1 and updating the
* page size in the URL.
*/
exports.clearAdvancedSearchConditionValues = clearAdvancedSearchConditionValues;
const createPageSizeChangeHandler = ({
history,
location,
dispatch,
setPreferredPageSize,
maxPageSize = 2500,
minPageSize = 1
}) => pageSize => {
// Normalize page size
const normalizedPageSize = normalizePageSize(pageSize, {
min: minPageSize,
max: maxPageSize,
fallback: minPageSize
});
if (dispatch && setPreferredPageSize) {
dispatch(() => setPreferredPageSize(normalizedPageSize));
}
if (history && location) {
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
query.p = '1';
query.size = normalizedPageSize.toString();
const queryString = _qs.default.stringify(query);
history.push({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
}
return normalizedPageSize;
};
/**
* Creates a reusable page change handler that updates URL query parameters and navigation
* history. This function handles navigation between pages while preserving other query
* parameters.
*/
exports.createPageSizeChangeHandler = createPageSizeChangeHandler;
const createPageChangeHandler = ({
history,
location,
zeroIndexed = true
}) => page => {
if (!history || !location) {
return;
}
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
// Convert page number based on indexing (URL uses 1-based, internal logic may use 0-based)
query.p = zeroIndexed ? (page + 1).toString() : page.toString();
const queryString = _qs.default.stringify(query);
history.push({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
};
exports.createPageChangeHandler = createPageChangeHandler;
const extractAdvancedSearchGroupedTerms = searchQuery => {
if ((searchQuery === null || searchQuery === void 0 ? void 0 : searchQuery.get('op')) === _searchOperators.OP_AND) {
const conditions = searchQuery.get('value');
return {
searchTerms: conditions.get(0),
limitBy: conditions.get(1)
};
}
return {
searchTerms: searchQuery,
limitBy: null
};
};
/**
* Handler which updates the sortBy portion of the sort string. This preserves
* the sort direction previously applied to the query.
*/
exports.extractAdvancedSearchGroupedTerms = extractAdvancedSearchGroupedTerms;
const createSortByHandler = ({
history,
location
}) => sort => {
if (!history || !location) {
return;
}
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
if (query) {
var _currentSort$split$;
const {
sort: currentSort
} = query;
const sortDir = (_currentSort$split$ = currentSort === null || currentSort === void 0 ? void 0 : currentSort.split(' ')[1]) !== null && _currentSort$split$ !== void 0 ? _currentSort$split$ : undefined;
query.sort = sortDir === undefined ? sort : `${sort} ${sortDir}`;
const queryString = _qs.default.stringify(query);
history.push({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
}
};
/**
* Handler which updates the sort direction portion of the sort string. This
* inverts based on the existence of the previous sort direction as undefined/null
* is used for ascending.
*
* Note: if no parameters are present, the API defaults to updatedAt descending.
*/
exports.createSortByHandler = createSortByHandler;
const createSortDirHandler = ({
history,
location,
defaultSortBy = 'updatedAt',
defaultSortDir = 'desc'
}) => () => {
if (!history || !location) {
return;
}
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
if (query) {
var _sort$split;
const {
sort
} = query;
const [sortColumn, sortDir] = (_sort$split = sort === null || sort === void 0 ? void 0 : sort.split(' ')) !== null && _sort$split !== void 0 ? _sort$split : [defaultSortBy, defaultSortDir];
query.sort = sortDir === undefined ? `${sortColumn} desc` : sortColumn;
const queryString = _qs.default.stringify(query);
history.push({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
}
};
exports.createSortDirHandler = createSortDirHandler;