@@ -539,11 +539,20 @@ function freeTextClause(query: string): string | undefined {
|
||||
}
|
||||
|
||||
// Ответ Search → пары (Id элемента, его тип по префиксу Path).
|
||||
//
|
||||
// Дедуп по (тип, Id) — ЗДЕСЬ, а не у вызывающей стороны: во всех запросах
|
||||
// стоит TrimDuplicates: false (нужно, чтобы найти черновики и «свои» копии —
|
||||
// стандартная дедупликация Search иногда схлопывает и их), а без неё индекс
|
||||
// может вернуть один и тот же элемент несколькими хитами (переиндексация,
|
||||
// несколько «версий» в crawl). Раньше хиты превращались в строки один-в-один,
|
||||
// и дубликат из индекса становился дубликатом строки в таблице — с одинаковым
|
||||
// React-ключом, то есть видимым «залипанием» одной и той же заявки дважды.
|
||||
function hitsToEntries(
|
||||
results: { PrimarySearchResults: unknown[] },
|
||||
types: IRequestType[]
|
||||
): Array<{ id: number; type: IRequestType }> {
|
||||
const urls = types.map((t) => ({ type: t, url: listAbsUrl(t).toLowerCase() }));
|
||||
const seen = new Set<string>();
|
||||
return (results.PrimarySearchResults as Array<Record<string, unknown>>)
|
||||
.map((r) => ({ id: Number(r.ListItemID), path: String(r.Path || '').toLowerCase() }))
|
||||
.filter((h) => !isNaN(h.id) && h.id > 0)
|
||||
@@ -551,7 +560,13 @@ function hitsToEntries(
|
||||
const matched = urls.filter((u) => h.path.indexOf(u.url) === 0)[0];
|
||||
return matched ? { id: h.id, type: matched.type } : undefined;
|
||||
})
|
||||
.filter((e): e is { id: number; type: IRequestType } => !!e);
|
||||
.filter((e): e is { id: number; type: IRequestType } => !!e)
|
||||
.filter((e) => {
|
||||
const key = `${e.type.id}-${e.id}`;
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Дочитать поля заявок по найденным Id (Search отдаёт не все колонки, и для
|
||||
|
||||
Reference in New Issue
Block a user