cozy-dataproxy-lib
Version:
Library meant to be by Cozy Cloud's DataProxy apps for data manipulation
377 lines (291 loc) • 13.7 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.normalizeSearchResult = exports.getCleanedFilePath = exports.enrichResultsWithDocs = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _cozyClient = require("cozy-client");
var _cozyMinilog = _interopRequireDefault(require("cozy-minilog"));
var _consts = require("../consts");
var _queries = require("../queries");
var _types = require("../types");
var _utils = require("./utils");
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
var log = (0, _cozyMinilog.default)('🗂️ [Indexing]');
var normalizeSearchResult = function normalizeSearchResult(client, searchResults, query) {
var doc = getCleanedFilePath(searchResults.doc);
var slug = getSearchResultSlug(client, doc);
var url = buildOpenURL(client, doc, slug);
var secondaryUrl = buildSecondaryURL(client, doc, url);
var title = getSearchResultTitle(doc);
var subTitle = getSearchResultSubTitle(client, {
fields: searchResults.fields,
doc: doc,
query: query
});
var normalizedRes = {
doc: doc,
slug: slug,
title: title,
subTitle: subTitle,
url: url,
secondaryUrl: secondaryUrl
};
return normalizedRes;
};
exports.normalizeSearchResult = normalizeSearchResult;
var getCleanedFilePath = function getCleanedFilePath(doc) {
if (!(0, _types.isIOCozyFile)(doc)) {
return doc;
}
var path = doc.path;
if (!path) {
// Paths should be completed for both files and directories, at indexing time
log.warn("No path found for ".concat(doc._id, "}"));
return doc;
}
var newPath = path;
if (path.endsWith("/".concat(doc.name))) {
// Remove the name from the path, which is added at indexing time to search on it
newPath = path.slice(0, -doc.name.length - 1);
}
if (!newPath) {
// Special case for root path
newPath = '/';
}
return _objectSpread(_objectSpread({}, doc), {}, {
path: newPath
});
};
exports.getCleanedFilePath = getCleanedFilePath;
var getSearchResultTitle = function getSearchResultTitle(doc) {
if ((0, _types.isIOCozyFile)(doc)) {
return doc.name;
}
if ((0, _types.isIOCozyContact)(doc)) {
return doc.displayName || doc.fullname || null;
}
if ((0, _types.isIOCozyApp)(doc)) {
return doc.name;
}
return null;
};
var getSearchResultSubTitle = function getSearchResultSubTitle(client, params) {
var fields = params.fields,
doc = params.doc,
query = params.query;
if ((0, _types.isIOCozyFile)(doc)) {
var _doc$path;
return (_doc$path = doc.path) !== null && _doc$path !== void 0 ? _doc$path : null;
}
if ((0, _types.isIOCozyContact)(doc)) {
var matchingValue; // Several document fields might match a search query. Let's take the first one different from name, assuming a relevance order
var matchingField = fields.find(function (field) {
return field !== 'displayName' && field !== 'fullname';
});
if (!matchingField) {
return null;
}
if (matchingField.includes('[]:')) {
var tokens = matchingField.split('[]:');
if (tokens.length !== 2) {
return null;
}
var arrayAttributeName = tokens[0];
var valueAttribute = tokens[1];
var array = doc[arrayAttributeName];
var matchingArrayItem = Array.isArray(array) && array.find(function (item) {
var value = typeof item === 'object' && item !== null && valueAttribute in item && item[valueAttribute];
return typeof value === 'string' && value.includes(query);
});
if (!matchingArrayItem) {
return null;
}
matchingValue = matchingArrayItem[valueAttribute];
} else {
matchingValue = doc[matchingField];
}
if (matchingValue === null || matchingValue === undefined) return null;
if (typeof matchingValue !== 'string' && typeof matchingValue !== 'number' && typeof matchingValue !== 'boolean') return null;
return matchingValue.toString();
}
if (doc._type === _consts.APPS_DOCTYPE) {
try {
var locale = client.getInstanceOptions().locale || 'en';
if (doc.locales[locale]) {
return doc.locales[locale].short_description;
}
} catch (_unused) {
return doc.name;
}
}
return null;
};
var getSearchResultSlug = function getSearchResultSlug(client, doc) {
if ((0, _types.isIOCozyFile)(doc)) {
if (_cozyClient.models.file.isNote(doc)) {
var _doc$cozyMetadata;
var cozyUrl = client.getStackClient().uri;
var createdOn = (_doc$cozyMetadata = doc.cozyMetadata) === null || _doc$cozyMetadata === void 0 ? void 0 : _doc$cozyMetadata.createdOn;
var isSharedNote = createdOn && createdOn !== "".concat(cozyUrl, "/"); // In case of a shared note, the cozyURL must be the one from the instance who the created it,
// and should include the docID coming from this instance.
// As we do not have this info, we need to first open the note on Drive, which will handle it
// and make the correct redirection.
return isSharedNote ? 'drive' : 'notes';
}
return 'drive';
}
if ((0, _types.isIOCozyContact)(doc)) {
return 'contacts';
}
if ((0, _types.isIOCozyApp)(doc)) {
return doc.slug;
}
return null;
};
var buildOpenURL = function buildOpenURL(client, doc, slug) {
// TODO: extract some of this common logic with Drive in cozy-client
var urlHash = '';
if ((0, _types.isIOCozyFile)(doc)) {
var isDir = doc.type === _consts.TYPE_DIRECTORY;
var dirId = isDir ? doc._id : doc.dir_id;
var folderURLHash = "/folder/".concat(dirId);
if (_cozyClient.models.file.isNote(doc)) {
// A note might be opened by Drive if it is shared
urlHash = slug === 'notes' ? "/n/".concat(doc._id) : "/note/".concat(doc._id);
} else if (_cozyClient.models.file.shouldBeOpenedByOnlyOffice(doc)) {
urlHash = "/onlyoffice/".concat(doc._id, "?redirectLink=drive").concat(folderURLHash);
} else if (isDir) {
urlHash = folderURLHash;
} else {
urlHash = "".concat(folderURLHash, "/file/").concat(doc._id);
}
if ((0, _types.isIOCozySharedDriveFile)(doc)) {
urlHash = "/shareddrive/".concat(doc.driveId, "/").concat(dirId);
if (doc.type === _consts.TYPE_FILE) {
urlHash += "/file/".concat(doc._id);
}
}
}
if ((0, _types.isIOCozyContact)(doc)) {
urlHash = "/".concat(doc._id);
}
if (!slug) {
return null;
}
return (0, _cozyClient.generateWebLink)({
cozyUrl: client.getStackClient().uri,
slug: slug,
subDomainType: client.getInstanceOptions().subdomain,
hash: urlHash,
pathname: '',
searchParams: []
});
};
var buildSecondaryURL = function buildSecondaryURL(client, doc, url) {
if (!(0, _types.isIOCozyFile)(doc) || !url) {
return null;
}
var folderURLHash = "/folder/".concat(doc.dir_id);
if ((0, _types.isIOCozySharedDriveFile)(doc)) {
folderURLHash = "/shareddrive/".concat(doc.driveId, "/").concat(doc.dir_id); // FIXME this url hash for shared drives should be in cozy-client
}
return (0, _cozyClient.generateWebLink)({
cozyUrl: client.getStackClient().uri,
slug: 'drive',
subDomainType: client.getInstanceOptions().subdomain,
hash: folderURLHash,
pathname: '',
searchParams: []
});
};
var enrichResultsWithDocs = /*#__PURE__*/function () {
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client, results) {
var _docs;
var enrichedResults, resultsByDoctype, docs, _i, _Object$keys, doctype, ids, startQuery, fromStore, queryDocs, endQuery, docsMap, filteredResults, _iterator, _step, _res$id, res, id, doc;
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
enrichedResults = (0, _toConsumableArray2.default)(results); // Group by doctype
resultsByDoctype = results.reduce(function (acc, _ref2) {
var id = _ref2.id,
doctype = _ref2.doctype;
if (!acc[doctype]) {
acc[doctype] = [];
}
acc[doctype].push(id);
return acc;
}, {});
docs = [];
_i = 0, _Object$keys = Object.keys(resultsByDoctype);
case 4:
if (!(_i < _Object$keys.length)) {
_context.next = 18;
break;
}
doctype = _Object$keys[_i];
ids = resultsByDoctype[doctype];
startQuery = performance.now();
fromStore = false; // We used to query from store as it was much more efficient, but now we query directly from PouchDB
// which should be fast enough after performances improvements in cozy-pouch-link
_context.next = 11;
return (0, _queries.queryDocsByIds)(client, doctype, ids, {
fromStore: false
});
case 11:
queryDocs = _context.sent;
endQuery = performance.now();
docs = docs.concat(queryDocs);
if ((0, _utils.isDebug)()) {
log.debug("Query took ".concat((endQuery - startQuery).toFixed(2), " ms to retrieve ").concat(ids.length, " ").concat(doctype, " from store: ").concat(fromStore));
}
case 15:
_i++;
_context.next = 4;
break;
case 18:
docsMap = new Map((_docs = docs) === null || _docs === void 0 ? void 0 : _docs.map(function (doc) {
return [doc._id, doc];
}));
filteredResults = [];
_iterator = _createForOfIteratorHelper(enrichedResults);
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
res = _step.value;
id = (_res$id = res.id) === null || _res$id === void 0 ? void 0 : _res$id.toString(); // Because of flexsearch Id typing
doc = docsMap.get(id);
if (!doc) {
// TODO: remove missing docs from search index
log.error("".concat(id, " is found in search but not in local data"));
} else {
res.doc = doc;
filteredResults.push(res);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return _context.abrupt("return", filteredResults);
case 23:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return function enrichResultsWithDocs(_x, _x2) {
return _ref.apply(this, arguments);
};
}();
exports.enrichResultsWithDocs = enrichResultsWithDocs;