@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
644 lines • 32.7 kB
JavaScript
import { CommonConfig } from "../../../common-config";
import { CommonLogger } from "../../../common-logger";
import { chunkArray, firstIndexOf, firstOrNull, makeUniqueArray, toHash } from "../../../helpers/collections.base";
import { EnsureViewFields, GetOrderByFromCaml, RemoveOrderByFromCaml, getFieldOutputType } from "../../../helpers/sharepoint";
import { isDate, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrNaN, isNullOrUndefined, isNumeric } from "../../../helpers/typecheckers";
import { encodeURIComponentEX } from "../../../helpers/url";
import { jsonTypes } from "../../../types/rest.types";
import { GetJson } from "../../rest";
import { GetSiteUrl, __getSPRestErrorData } from "../common";
import { GetListFields, GetListRestUrl } from "../list";
import { SPServerLocalTimeToUTCSync } from "../web";
import { GetItemsById } from "./GetListItemsById";
import { SkipFields, __fixGetListItemsResults } from "./common";
const logger = new CommonLogger("utils/sharepoint.rest/listutils/GetListItemsByCaml");
//Lookup threshold limit is 12 but we use a smaller number here to ensure there are no issues
const lookupOrUserFieldLimit = 11;
/** returns the items or NULL, never errors out since it is used in aggregator.
* set throwErrors = true if you want errors to be thrown instead of returning null.
* camlQuery: one of:
* - view.ViewQuery
* - wrapped view.ViewQuery in a <View>: "<View Scope='RecursiveAll'><Query></Query><RowLimit>1000</RowLimit></View>" or send rowLimit in options.
*/
export async function GetListItemsByCaml(siteUrl, listIdOrTitle, camlQuery, options) {
siteUrl = GetSiteUrl(siteUrl);
if (!camlQuery.toLowerCase().startsWith("<view")) {
camlQuery = `<View Scope='RecursiveAll'><Query>${camlQuery}</Query></View>`;
}
let xmlParser = new DOMParser();
let xmlDoc = xmlParser.parseFromString(camlQuery, "text/xml");
let viewNode = xmlDoc.querySelector("View, view");
let queryNode = viewNode && viewNode.querySelector("Query, query");
let rowLimitNode = viewNode && viewNode.querySelector("RowLimit, rowlimit");
let viewFieldsNode = viewNode && viewNode.querySelector("ViewFields, viewfields");
let parseRowLimitNodeResult = _parseRowLimitNode(rowLimitNode, viewNode, options.rowLimit);
if (!isNullOrUndefined(parseRowLimitNodeResult)) {
options.rowLimit = parseRowLimitNodeResult.rowLimit;
camlQuery = parseRowLimitNodeResult.camlQuery;
}
let removeSingleAndConditionsResult = _removeSingleAndConditions(viewNode, queryNode);
if (!isNullOrUndefined(removeSingleAndConditionsResult)) {
camlQuery = removeSingleAndConditionsResult.camlQuery;
}
let getAllItems = isNullOrEmptyString(options.rowLimit) || options.rowLimit < 1;
let totalItemsInList = 99999;
let maxBatchSize = 5000;
let batchSize = options.rowLimit > 0 && options.rowLimit < maxBatchSize ? options.rowLimit : maxBatchSize;
let totalNumberOfItemsToGet = getAllItems ? totalItemsInList : options.rowLimit > maxBatchSize ? options.rowLimit : batchSize;
let requestUrl = `${GetListRestUrl(siteUrl, listIdOrTitle)}/GetItems?$expand=FieldValuesAsText`;
let allListFieldsHash = toHash(await GetListFields(siteUrl, listIdOrTitle), f => f.InternalName);
let expandFields = [];
let orderByStatement = GetOrderByFromCaml(camlQuery);
if (isNullOrUndefined(options.columns)) {
options.columns = [];
}
let columns = options.columns;
columns = _ensureOrderByColumns(orderByStatement, columns);
columns = _ensureViewFields(viewFieldsNode, columns, allListFieldsHash);
columns = _normalizeColumns(columns, allListFieldsHash);
if (columns.length > options.columns.length) {
logger.warn(`added ${columns.length - options.columns.length} to query`);
}
options.columns = columns;
// Store the result of processing the request
let rtrnProcessRequestResult;
let lookupOrUserFieldsInColumns = options.columns.filter((columnName) => {
let field = allListFieldsHash[columnName];
return _isLookupOrUserField(field);
});
let isThrottled = lookupOrUserFieldsInColumns.length >= lookupOrUserFieldLimit;
try {
if (isThrottled) {
//ISSUE: 1565
rtrnProcessRequestResult = await _processLookupThresholdCamlRequest(orderByStatement, allListFieldsHash, options, camlQuery, requestUrl, expandFields, siteUrl, totalNumberOfItemsToGet, batchSize);
}
else {
rtrnProcessRequestResult = await _processNormalCamlRequest(orderByStatement, allListFieldsHash, options, camlQuery, requestUrl, expandFields, siteUrl, totalNumberOfItemsToGet, batchSize);
}
const { items, needContentTypes, postProcessOrderBy } = rtrnProcessRequestResult;
let itemsResult = __fixGetListItemsResults(siteUrl, listIdOrTitle, items, options.foldersBehaviour);
let itemsWithOutContentType = [];
if (needContentTypes) {
itemsResult.forEach((item) => {
if (isNullOrUndefined(item["ContentType"])) {
itemsWithOutContentType.push(item.Id);
}
else if (!isNullOrUndefined(item["ContentType"].Name)) {
item["ContentType"] = item["ContentType"].Name;
}
});
}
if (itemsWithOutContentType.length > 0) {
logger.time("Getting content types");
//Issue 1465 content types no longer come back from get items request...
//Make a separate request to get this info
let ctypes = (await GetItemsById(siteUrl, listIdOrTitle, itemsWithOutContentType, {
expand: ['ContentType/Name'],
select: ['ContentType/Name', 'Id'],
jsonMetadata: jsonTypes.nometadata
}));
itemsResult.forEach(item => {
if (!isNullOrUndefined(ctypes[item.Id])) {
item["ContentType"] = ctypes[item.Id].ContentType.Name;
}
});
logger.timeEnd("Getting content types");
}
if (postProcessOrderBy) {
//re-apply sort
if (CommonConfig.i.IsLocalDev) {
logger.table(itemsResult.map(i => {
let row = {
Id: i.Id,
Title: i.Title
};
orderByStatement.forEach(s => { row[`${s.Name} ${s.IsAscending ? 'asc' : 'desc'}`] = i[s.Name]; });
return row;
}), "before sort", true);
}
itemsResult.sort((a, b) => {
for (let i = 0; i < orderByStatement.length; i++) {
let ob = orderByStatement[i];
let v1 = a[ob.Name];
let v2 = b[ob.Name];
if (v1 === v2) {
//these are equal - continue to second sort statement
}
else {
if (ob.IsAscending)
return (v1 > v2) ? 1 : -1;
else
return (v2 > v1) ? 1 : -1;
}
}
return 0;
});
if (CommonConfig.i.IsLocalDev) {
logger.table(itemsResult.map(i => {
let row = {
Id: i.Id,
Title: i.Title
};
orderByStatement.forEach(s => { row[`${s.Name} ${s.IsAscending ? 'asc' : 'desc'}`] = i[s.Name]; });
return row;
}), "after sort", true);
}
}
return itemsResult;
}
catch (ex) {
console.log(`isThrottled: ${isThrottled}`);
}
return null;
}
// (window as any).runtTest = true;
// window.setTimeout(() => {
// if ((window as any).runtTest == false) return;
// (window as any).runtTest = false;
// GetListItemsByCaml(null, "aec7756b-daa0-4da1-88ba-c66cb572e816", "", {
// columns: [],
// refreshCache: true
// }).then((r) => {
// });
// }, 500);
//ISSUE: 1565
async function _processLookupThresholdCamlRequest(orderByStatement, allListFieldsHash, options, camlQuery, requestUrl, expandFields, siteUrl, totalNumberOfItemsToGet, batchSize) {
let rtrnProcessRequestResult = {
items: [],
postProcessOrderBy: false,
needContentTypes: false
};
let orderByFields = orderByStatement.map((orderByField) => {
let field = allListFieldsHash[orderByField.Name];
return field;
});
let lookupOrUserFieldsInOrderBy = orderByFields.filter((orderByField) => {
return _isLookupOrUserField(orderByField);
});
let lookupOrUserFieldsToChunk = options.columns.filter((columnName) => {
let field = allListFieldsHash[columnName];
if (_isLookupOrUserField(field)) {
let col = firstOrNull(lookupOrUserFieldsInOrderBy, (orderByField) => {
return field.InternalName !== orderByField.InternalName;
});
return col === null;
}
return false;
});
let otherFieldNames = options.columns.filter((columnName) => {
let field = allListFieldsHash[columnName];
return !isNullOrUndefined(field) && !_isLookupOrUserField(field);
});
//The number of lookup columns in each request is based on the lookupOrUserFieldLimit.
//Lookup fields in the order by statement must be sent with each request.
//For example, we have 20 lookup columns and 3 of them are in the order by statement.
//The 3 order by lookup columns are sent with each request. The remaining lookups are split into chunks.
//The request will split into 3 requests
//The first request will have 11 lookup columns (8 standard + 3 order by lookup columns)
//The second request will have 11 lookup columns (8 standard + 3 order by lookup columns)
//The third request will have 4 lookup columns (1 standard + 3 order by lookup columns)
let requestChunks = chunkArray(lookupOrUserFieldsToChunk, lookupOrUserFieldLimit - lookupOrUserFieldsInOrderBy.length);
let otherFieldsChunkSize = Math.ceil(otherFieldNames.length / requestChunks.length);
let otherFieldsChunks = chunkArray(otherFieldNames, otherFieldsChunkSize);
requestChunks.forEach((chunk, index) => {
//Add all order by fields to each request, this will include the lookup
//fields from the order by statement that we left room for previously
requestChunks[index] = chunk.concat(orderByFields.map((orderByField) => {
return orderByField.InternalName;
}));
//Add the other fields but split them across the requests so we don't request duplicate data
if (otherFieldsChunks[index]) {
requestChunks[index] = chunk.concat(otherFieldsChunks[index]);
}
});
//requestChunks should now have about the same number of fields in each chunk and each
//chunk will have all the order by fields.
let queries = requestChunks.map((requestChunk) => {
let camlQueryClone = camlQuery;
let processColumnsResult = _processColumns(requestChunk, expandFields, allListFieldsHash);
//Id field must be included in oder to merge the items correctly
let viewFields = processColumnsResult.viewFields;
if (viewFields.length && !viewFields.includes("Id")) {
viewFields.push("Id");
}
let selectFields = processColumnsResult.selectFields;
if (selectFields.length && !selectFields.includes("Id")) {
selectFields.push("Id");
}
rtrnProcessRequestResult.needContentTypes = rtrnProcessRequestResult.needContentTypes || processColumnsResult.needContentTypes;
expandFields = processColumnsResult.expandFields;
if (isNotEmptyArray(viewFields)) {
camlQueryClone = EnsureViewFields(camlQueryClone, viewFields, true);
}
// if (isDebug()) {
// let lfields = viewFields.filter((fieldName) => {
// let field = allListFieldsHash[fieldName];
// return _isLookupOrUserField(field);
// });
// let xmlDoc = new DOMParser().parseFromString(camlQueryClone, "text/xml");
// let viewNode = xmlDoc.querySelector("View, view");
// let viewFieldsNode = viewNode && viewNode.querySelector("ViewFields, viewfields");
// let viewFields2 = Array.from(viewFieldsNode.children).map((viewFieldNode) => {
// let name = viewFieldNode.getAttribute("Name") || viewFieldNode.getAttribute("name");
// return name;
// });
// let lfields2 = viewFields2.filter((fieldName) => {
// let field = allListFieldsHash[fieldName];
// return _isLookupOrUserField(field);
// });
// if (lfields2.length !== lfields.length) {
// logger.warn("Lookup fields in caml query do not match look up fields in view fields.");
// logger.warn(`Lookup fields in caml query: ${lfields2}`)
// logger.warn(`Lookup fields in in view fields: ${lfields}`)
// }
// }
// Prepare the REST URL with select fields
let restUrlWithSelect = requestUrl;
if (expandFields.length > 0) {
restUrlWithSelect += ',' + expandFields.join(',');
}
// Include the lookup user select fields with the other select fields
restUrlWithSelect += '&$select=' + selectFields.join(',');
return { camlQueryClone, restUrlWithSelect, viewFields };
});
let mergedItems = null;
for (let i = 0; i < queries.length; i++) {
const { camlQueryClone, restUrlWithSelect, viewFields } = queries[i];
let loopResult = await processRequestResult(requestUrl, restUrlWithSelect, camlQueryClone, { ...options, columns: viewFields }, siteUrl, totalNumberOfItemsToGet, batchSize, orderByStatement);
if (isNullOrUndefined(mergedItems)) {
mergedItems = loopResult.items;
}
else {
for (let restItemIndex = 0; restItemIndex < loopResult.items.length; restItemIndex++) {
//The item chunks (loopResult) should be in the same order as mergedItems because each request should have the
//same number of items returned in the same order. The only difference between requests is which fields are present.
//So request 1 will have the 1st set of fields, request 2 will have the 2nd set. etc.
//This means that the restItemIndex should be the same as existingItemIndex for each iteration. But, we do these extra
//checks just in case.
let restItem = loopResult.items[restItemIndex];
let existingItem = mergedItems[restItemIndex];
let existingItemIndex = restItemIndex;
if (isNullOrUndefined(existingItem) || existingItem.Id !== restItem.Id) {
existingItemIndex = firstIndexOf(mergedItems, (mergedItem) => {
return mergedItem.Id === restItem.Id;
});
}
if (existingItemIndex === -1) {
//We shouldn't get here. Each chunk should have the same items in the same order.
logger.warn("_processLookupThresholdCamlRequest results are out of sync");
mergedItems.push(restItem);
}
else {
let existingItem = mergedItems[existingItemIndex];
let FieldValuesAsText = {
...(existingItem.FieldValuesAsText || {}),
...(restItem.FieldValuesAsText || {})
};
let FieldValuesForEdit = {
...(existingItem.FieldValuesForEdit || {}),
...(restItem.FieldValuesForEdit || {})
};
existingItem = { ...existingItem, ...restItem };
existingItem.FieldValuesAsText = FieldValuesAsText;
existingItem.FieldValuesForEdit = FieldValuesForEdit;
mergedItems[existingItemIndex] = existingItem;
}
}
}
// only need to put this true if it happens once
if (loopResult.postProcessOrderBy) {
rtrnProcessRequestResult.postProcessOrderBy = true;
}
}
rtrnProcessRequestResult.items = mergedItems;
return rtrnProcessRequestResult;
}
async function _processNormalCamlRequest(orderByStatement, allListFieldsHash, options, camlQuery, requestUrl, expandFields, siteUrl, totalNumberOfItemsToGet, batchSize) {
let processColumnsResult = _processColumns(options.columns, expandFields, allListFieldsHash);
let viewFields = processColumnsResult.viewFields;
let selectFields = processColumnsResult.selectFields;
expandFields = processColumnsResult.expandFields;
let needContentTypes = processColumnsResult.needContentTypes;
if (isNotEmptyArray(viewFields)) {
camlQuery = EnsureViewFields(camlQuery, viewFields, true);
}
// Prepare the REST URL with select fields
let restUrlWithSelect = requestUrl;
if (expandFields.length > 0) {
restUrlWithSelect += ',' + expandFields.join(',');
}
// Include the lookup user select fields with the other select fields
restUrlWithSelect += '&$select=' + selectFields.join(',');
// Process the request and get the result
let result = await processRequestResult(requestUrl, restUrlWithSelect, camlQuery, options, siteUrl, totalNumberOfItemsToGet, batchSize, orderByStatement);
return {
...result,
needContentTypes
};
}
function _ensureViewFields(viewFieldsNode, columns, allListFieldsHash) {
//ISSUE: 1565
//Cases
//1. Empty view fields element so ALL columns will be requested. We must include all list fields in
//columns parsing so that we get an accurate count of how many lookup/user fields there are
//2. View fields element with a some field names that must also be included in the column parsing so we add them here.
if (isNullOrUndefined(viewFieldsNode) && isNullOrEmptyArray(columns)) {
Object.keys(allListFieldsHash).forEach((fieldName) => {
let field = allListFieldsHash[fieldName];
if (!isNullOrUndefined(field)
&& !field.Hidden
&& !SkipFields.includes(fieldName.toLowerCase())) {
columns.push(field.InternalName);
}
});
}
else if (!isNullOrUndefined(viewFieldsNode)) {
let fieldRefNodes = Array.from(viewFieldsNode.querySelectorAll("FieldRef"));
if (isNotEmptyArray(fieldRefNodes)) {
fieldRefNodes.forEach((fieldRefNode) => {
let name = fieldRefNode.getAttribute("Name") || fieldRefNode.getAttribute("name");
if (!isNullOrEmptyString(name)) {
columns.push(name);
}
});
}
}
return columns;
}
function _ensureOrderByColumns(orderByStatement, columns) {
//Issue 548: Ensure that the order by field is in the view fields and columns collection so
//that paging works correctly when there is an order by clause
if (orderByStatement.length > 0 && !isNullOrEmptyString(orderByStatement[0].Name)) {
// just add them to columns, and we will add them to view fields below.
// camlQuery = EnsureViewFields(camlQuery, orderByStatement.orderBy.map(o => o.Name), false);
orderByStatement.forEach(o => {
if (columns.indexOf(o.Name) === -1) {
columns.push(o.Name);
}
});
}
return columns;
}
function _normalizeColumns(columns, allListFieldsHash) {
return makeUniqueArray(columns.map((column) => {
//some columns will come in lower case (from dvp), normalize them so that we can remove duplicates
let name = Object.keys(allListFieldsHash).filter((fieldName) => {
return fieldName.toLowerCase() === column.toLowerCase();
})[0];
if (!isNullOrEmptyString(name)) {
let field = allListFieldsHash[name];
return field;
}
return null;
}).filter((field) => {
return !isNullOrUndefined(field);
}).map((field) => {
return field.InternalName;
}).concat(['Title', 'Id', 'FileLeafRef', 'FileDirRef', 'FileRef', 'FileSystemObjectType']));
}
function _removeSingleAndConditions(viewNode, queryNode) {
if (!isNullOrUndefined(viewNode.querySelector("And, and"))) {
try {
//Issue 8063: calendar list, will wrongly add a wrapping <and> statement for a single condition
//will result in error 500
//ie: <View Scope='RecursiveAll'><Query><Where><And><Eq><FieldRef Name=\"Title\" /><Value Type=\"Text\">Holiday</Value></Eq></And></Where>
//</Query><RowLimit>5000</RowLimit></View>
let whereNode = queryNode && queryNode.querySelector("Where, where");
if (!isNullOrUndefined(whereNode)) {
let firstCondition = whereNode.firstElementChild;
if (firstCondition.tagName.toLowerCase() === "and" && firstCondition.children.length < 2) {
//this is the bug, <and> tag must have 2 conditions - get rid of it!
whereNode.innerHTML = firstCondition.innerHTML;
return {
camlQuery: viewNode.outerHTML
};
}
}
}
catch (e) { }
}
return null;
}
function _parseRowLimitNode(rowLimitNode, viewNode, rowLimit) {
if (!isNullOrUndefined(rowLimitNode)) {
let value = rowLimitNode.textContent;
//if not provided by options - use it
if (isNullOrNaN(rowLimit) && isNumeric(value) && Number(value) > 0) {
rowLimit = Number(value);
}
//remove it
viewNode.removeChild(rowLimitNode);
return {
camlQuery: viewNode.outerHTML,
rowLimit: rowLimit
};
}
return null;
}
function _processColumns(columns, expandFields, allListFieldsHash) {
let thresholdLimitLookupCount = 0;
let thresholdLimitLookupHit = false;
let selectFields = [];
let viewFields = [];
let needContentTypes = false;
//column parsing
columns.forEach(viewField => {
let viewFieldLower = viewField.toLowerCase();
if (viewFieldLower === 'contenttype' || viewFieldLower === 'contenttypeid') {
needContentTypes = true;
}
else if (viewFieldLower === '_moderationstatus') {
selectFields.push(`FieldValuesAsText/${viewField}`);
viewFields.push('_moderationstatus');
}
else if (viewFieldLower === '_moderationcomments') {
selectFields.push('OData_' + viewField);
viewFields.push('_moderationcomments');
}
else if (viewFieldLower === "filesystemobjecttype") {
selectFields.push("FileSystemObjectType");
}
else if (viewFieldLower === "fileref" || viewFieldLower === "filedirref") {
//treat them similar to lookup fields
selectFields.push(viewField);
selectFields.push(`FieldValuesAsText/${viewField}`);
viewFields.push(`${viewField}`);
viewFields.push(`${viewField}Id`);
}
else {
//prefer to get columns not from FieldValuesAsText, unless special data type requires it (date, lookup, boolean, etc...)
//make the select url shorter
let foundField = allListFieldsHash[viewField];
if (foundField) {
let foundFieldInternalName = foundField.InternalName;
viewFields.push(foundFieldInternalName);
let urlField = null;
if (foundField.TypeAsString === "URL" && foundField.InternalName === "URL") {
urlField = foundField;
}
else if ((viewFieldLower === "urlnomenu"
|| viewFieldLower === "urlwmenu2"
|| viewFieldLower === "urlwmenu")
&& foundField.TypeAsString === "Computed"
&& !isNullOrUndefined(allListFieldsHash["URL"])
&& allListFieldsHash["URL"].TypeAsString === "URL") {
urlField = allListFieldsHash["URL"];
}
if (!isNullOrUndefined(urlField) && urlField.TypeAsString === "URL") {
viewFields.push(urlField.InternalName);
selectFields.push(urlField.InternalName);
selectFields.push(`FieldValuesAsText/${urlField.InternalName}`);
}
else {
//Issue 828, 336
if (foundFieldInternalName.startsWith("_")) {
foundFieldInternalName = `OData_${foundFieldInternalName}`;
}
let outputType = getFieldOutputType(foundField);
switch (outputType) {
case "Lookup":
case "LookupMulti":
case "User":
case "UserMulti":
thresholdLimitLookupCount += 1;
if (thresholdLimitLookupCount >= lookupOrUserFieldLimit) {
thresholdLimitLookupHit = true;
}
//lookup raw values comes with Id appended
selectFields.push(`FieldValuesAsText/${foundFieldInternalName}`);
selectFields.push(`${foundFieldInternalName}Id`);
break;
case "Boolean":
case "Attachments":
case "AllDayEvent":
case "Recurrence":
selectFields.push(`FieldValuesAsText/${foundFieldInternalName}`);
selectFields.push(foundFieldInternalName);
break;
default:
selectFields.push(foundFieldInternalName);
break;
}
}
}
else {
selectFields.push(viewField);
}
}
});
if (needContentTypes) {
expandFields.push("ContentType");
selectFields.push("ContentType/Name");
selectFields.push("ContentTypeId");
viewFields.push("ContentTypeId");
}
return {
thresholdLimitLookupHit,
viewFields: makeUniqueArray(viewFields),
selectFields: makeUniqueArray(selectFields),
expandFields: makeUniqueArray(expandFields),
needContentTypes
};
}
function _isLookupOrUserField(field) {
if (isNullOrUndefined(field)) {
return false;
}
let outputType = getFieldOutputType(field);
switch (outputType) {
case "Lookup":
case "LookupMulti":
case "User":
case "UserMulti":
return true;
default:
return false;
}
}
async function processRequestResult(requestUrl, restUrlWithSelect, camlQuery, options, siteUrl, totalNumberOfItemsToGet, batchSize, orderByStatement) {
//issue 6150: if there are too many fields, url will be too long. just get all columns without adding a $select
if (restUrlWithSelect.length < 2000)
requestUrl = restUrlWithSelect;
let query = { ViewXml: camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`) };
if (!isNullOrEmptyString(options.FolderServerRelativeUrl))
query.FolderServerRelativeUrl = options.FolderServerRelativeUrl;
let data = { query: query };
let items = [];
//let triedWithoutViewFields = false;
let postProcessOrderBy = false;
do {
try {
let requestResult = await GetJson(requestUrl, JSON.stringify(data), {
allowCache: options.refreshCache !== true,
postCacheKey: JSON.stringify(data.query),
jsonMetadata: jsonTypes.nometadata,
spWebUrl: siteUrl
});
if (requestResult && requestResult.value)
items.push(...requestResult.value);
let itemsLeftToGet = totalNumberOfItemsToGet - items.length;
if (itemsLeftToGet > 0 && //we need more items
requestResult.value.length === batchSize //we might have more on server since we got the full batch size we asked for
) {
let lastItem = items[items.length - 1];
let lastItemId = lastItem.Id;
let pagingInfoSort = "";
//Issue 7542 need to add order by value to the paging info if it is in the query
if (!postProcessOrderBy && orderByStatement && orderByStatement.length) {
let orderFieldName = orderByStatement[0].Name;
let orderFieldValue = lastItem[orderFieldName];
//if field is ID - do not do it.
if (orderFieldName.toLowerCase() !== "id" && !isNullOrUndefined(orderFieldValue)) {
//if value is date - we need it in ISO and in this format: yyyyMMdd HH:mm:ss
try {
//Numbers cast to date properly but they are not dates. ignore number values.
let orderFieldValueAsDate = isNumeric(orderFieldValue) ? null : new Date(orderFieldValue);
if (isDate(orderFieldValueAsDate)) {
try {
//issue 7599 date only field on different time zone...
orderFieldValue = SPServerLocalTimeToUTCSync(siteUrl, orderFieldValueAsDate).replace(/T/i, " ").replace(/Z/i, " ");
}
catch (e) { }
}
}
catch (e) { }
pagingInfoSort = `&p_${orderFieldName}=${encodeURIComponentEX(orderFieldValue)}`;
}
}
data.query.ListItemCollectionPosition = {
"PagingInfo": `Paged=TRUE${pagingInfoSort}&p_ID=${lastItemId}`
};
if (itemsLeftToGet < batchSize) //the last batch should be smaller, update the row limit
data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${itemsLeftToGet}</RowLimit></View>`);
}
else
data = null;
}
catch (e) {
if (__getSPRestErrorData(e).code.indexOf('SPQueryThrottledException')) {
//test again - on awaiting score view, this will work but will ONLY return the order by fields...
// if (!triedWithoutViewFields) {
// //Our own issues list had too many lookup fields.
// logger.info("Query throttled, trying again without view fields... some fields might be missing from results.");
// triedWithoutViewFields = true;
// camlQuery = EnsureViewFields(camlQuery, orderByStatement.map(o => o.Name), false, true);
// data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`);
// }
// else
if (!postProcessOrderBy && orderByStatement.length > 0) {
logger.warn("Query throttled, trying again without order by...");
postProcessOrderBy = true;
camlQuery = RemoveOrderByFromCaml(camlQuery);
camlQuery = EnsureViewFields(camlQuery, [], false, true);
data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`);
}
else
throw e; //still throttled? might be due to filter query
}
else
throw e; //different error
}
} while (!isNullOrEmptyString(data));
return { items: items, postProcessOrderBy: postProcessOrderBy };
}
//# sourceMappingURL=GetListItemsByCaml.js.map