UNPKG

cspace-ui

Version:
836 lines (792 loc) 36.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.valueToNXQL = exports.structuredDateFieldConditionToNXQL = exports.searchDescriptorToLocation = exports.rangeFieldConditionToNXQL = exports.patternValueToNXQL = exports.pathToNXQL = exports.operatorToNXQL = exports.operatorSupportsMultipleValues = exports.operatorExpectsValue = exports.normalizeStringFieldValue = exports.normalizeRangeFieldCondition = exports.normalizeListFieldValue = exports.normalizeGroupCondition = exports.normalizeFieldValue = exports.normalizeFieldCondition = exports.normalizeCondition = exports.normalizeBooleanCondition = exports.groupConditionToNXQL = exports.getSubrecordSearchName = exports.getSearchableRecordTypes = exports.getPreviousPageSearchDescriptor = exports.getOperatorsForDataType = exports.getNextPageSearchDescriptor = exports.getListType = exports.getFirstItem = exports.fieldConditionToNXQL = exports.dateStartTimestamp = exports.dateEndTimestamp = exports.dataTypeSupportsMultipleValues = exports.createCounter = exports.convertAdvancedSearchConditionToNXQL = exports.clearAdvancedSearchConditionValues = exports.booleanConditionToNXQL = exports.advancedSearchConditionToNXQL = 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 _searchOperators = require("../constants/searchOperators"); var _xmlNames = require("../constants/xmlNames"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const opsByDataType = { [_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] }; // For controlled lists, comparison/range operators will not necessarily produce results that // users expect, since they are comparing database values/ref names, not display names. Don't // show those operators on controlled list fields, until we have a way to deal with this. const controlledListOps = [_searchOperators.OP_EQ, _searchOperators.OP_NOT_EQ, _searchOperators.OP_NULL, _searchOperators.OP_NOT_NULL]; const getOperatorsForDataType = (dataType = _dataTypes.DATA_TYPE_STRING, isControlled) => isControlled ? controlledListOps : opsByDataType[dataType] || []; 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; 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('*')) { 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}/dateEarliestScalarValue`; const latestScalarDatePath = `${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({ 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_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); } } 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}` }; }; 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 = (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'); }; exports.clearAdvancedSearchConditionValues = clearAdvancedSearchConditionValues;