трейсы
This commit is contained in:
@@ -439,13 +439,13 @@ async function getNavCountsInner(context: WebPartContext): Promise<INavCounts> {
|
||||
|
||||
const [turn, rework] = await Promise.all([
|
||||
sharedPendingIndex(context, 'myTurn', MY_TURN_STATUSES),
|
||||
getOwnRequestsByStatus(context, types, REQUEST_STATUS.returned, 1),
|
||||
countOwnRequestsByStatus(context, types, REQUEST_STATUS.returned),
|
||||
]);
|
||||
|
||||
return {
|
||||
approvals: turn.index.filter((r) => r.myStepStatus === REQUEST_STATUS.onApproval).length,
|
||||
execution: turn.index.filter((r) => r.myStepStatus === REQUEST_STATUS.onExecution).length,
|
||||
rework: rework.totalRows,
|
||||
rework,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -727,14 +727,23 @@ async function finalizeApprovalTarget(
|
||||
|
||||
// Заголовок/статус/дата заявки — отдельным запросом к списку ДАННЫХ (см.
|
||||
// комментарий у функции: через Lookup-expand это не отдаётся).
|
||||
//
|
||||
// Автор берётся ЗДЕСЬ ЖЕ через expand. Раньше он дочитывался отдельным
|
||||
// проходом (hydrateAuthors) и только по строкам видимой страницы — чтобы не
|
||||
// тянуть его на весь индекс. Но экономилась при этом не сеть, а payload:
|
||||
// запрос к списку данных всё равно делается, и expand не добавляет к нему
|
||||
// ни одного round-trip'а, тогда как отдельный проход стоил целый — замер
|
||||
// 2026-08-20 показал 262 мс ровно на критическом пути вкладки
|
||||
// «На согласовании» (Search → строки очереди → автор).
|
||||
const ticketFilter = ticketIds.map((id) => `Id eq ${id}`).join(' or ');
|
||||
const ticketRows = (await list.items
|
||||
.filter(ticketFilter)
|
||||
.select('Id', 'RequestUniqueCode', 'Title', 'RequestStatus', 'Created')
|
||||
.select('Id', 'RequestUniqueCode', 'Title', 'RequestStatus', 'Created', 'Author/EMail', 'Author/Title')
|
||||
.expand('Author')
|
||||
.top(ticketIds.length)()) as Array<Record<string, unknown>>;
|
||||
|
||||
debug.rows = ticketRows.map((r) => ({
|
||||
...toMyRequest(r, undefined, type, 'approval', target),
|
||||
...toMyRequest(r, r.Author as { EMail?: string; Title?: string } | undefined, type, 'approval', target),
|
||||
myStepStatus: stepStatusByTicket.get(r.Id as number),
|
||||
}));
|
||||
|
||||
@@ -749,16 +758,23 @@ async function finalizeApprovalTarget(
|
||||
}
|
||||
}
|
||||
|
||||
// Дочитывает Author/EMail + Author/Title в списке ДАННЫХ каждого типа —
|
||||
// только по переданным строкам (обычно уже нарезанная страница из ≤20), не по
|
||||
// всему найденному срезу. Мутирует переданные объекты (проставляет authorEmail/authorName).
|
||||
// Дочитывает Author/EMail + Author/Title в списке ДАННЫХ каждого типа — только
|
||||
// по переданным строкам. Мутирует переданные объекты (проставляет
|
||||
// authorEmail/authorName).
|
||||
//
|
||||
// Строки, у которых автор УЖЕ известен, пропускаются: и 'own' (loadByIds), и
|
||||
// 'approval' (finalizeApprovalTarget) теперь получают его тем же запросом, что
|
||||
// и остальные поля, поэтому на обычных путях сюда доходить нечему и запрос не
|
||||
// уходит вовсе. Функция оставлена страховкой на случай источника строк без
|
||||
// автора — молча показывать пустого инициатора хуже, чем один лишний запрос.
|
||||
async function hydrateAuthors(
|
||||
context: WebPartContext,
|
||||
types: IRequestType[],
|
||||
rows: IMyRequest[]
|
||||
): Promise<void> {
|
||||
if (rows.length === 0) return;
|
||||
return traced('дочитывание автора (REST)', () => hydrateAuthorsInner(context, types, rows));
|
||||
const missing = rows.filter((r) => !r.authorName && !r.authorEmail);
|
||||
if (missing.length === 0) return;
|
||||
return traced('дочитывание автора (REST)', () => hydrateAuthorsInner(context, types, missing));
|
||||
}
|
||||
|
||||
async function hydrateAuthorsInner(
|
||||
@@ -862,6 +878,10 @@ async function fetchAllSearchRows(
|
||||
let startRow = 0;
|
||||
|
||||
for (;;) {
|
||||
// Постраничной метки здесь больше нет: замер 2026-08-20 показал, что запрос
|
||||
// ровно один (847 мс у вызывающего против 846 мс у страницы), то есть
|
||||
// медленный сам Search, а не перебор StartRow. Вопрос закрыт, а метка
|
||||
// занимала место в строке сводки, дублируя метку вызывающего.
|
||||
const results = await sp.search({
|
||||
Querytext: kql,
|
||||
RowLimit: SEARCH_PAGE_SIZE,
|
||||
@@ -1310,6 +1330,36 @@ export async function getOwnRequestsByStatus(
|
||||
status: string,
|
||||
page: number,
|
||||
query: string = ''
|
||||
): Promise<IOwnRequestsPage> {
|
||||
return ownRequestsByStatus(context, types, status, page, query, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Только СКОЛЬКО таких заявок — для счётчика «Доработка» в боковом меню.
|
||||
*
|
||||
* Отличие от getOwnRequestsByStatus одно: не дочитываются поля найденных заявок
|
||||
* (loadByIds). Счётчику они не нужны — ему нужен totalRows, — а замер 2026-08-20
|
||||
* показал, что этот шаг стоит ~200-300 мс и идёт СТРОГО ПОСЛЕ Search, то есть
|
||||
* целиком лежит на критическом пути открытия портала.
|
||||
*
|
||||
* Условия отбора и довесок по свежести — те же самые, поэтому счётчик и вкладка
|
||||
* по-прежнему не расходятся (ради чего они и считаются одним кодом).
|
||||
*/
|
||||
export async function countOwnRequestsByStatus(
|
||||
context: WebPartContext,
|
||||
types: IRequestType[],
|
||||
status: string
|
||||
): Promise<number> {
|
||||
return (await ownRequestsByStatus(context, types, status, 1, '', false)).totalRows;
|
||||
}
|
||||
|
||||
async function ownRequestsByStatus(
|
||||
context: WebPartContext,
|
||||
types: IRequestType[],
|
||||
status: string,
|
||||
page: number,
|
||||
query: string,
|
||||
withRows: boolean
|
||||
): Promise<IOwnRequestsPage> {
|
||||
if (types.length === 0) return { rows: [], totalRows: 0 };
|
||||
|
||||
@@ -1339,7 +1389,7 @@ export async function getOwnRequestsByStatus(
|
||||
const totalRows = results.TotalRows || 0;
|
||||
const entries = hitsToEntries(results, types);
|
||||
const byKey =
|
||||
entries.length > 0
|
||||
withRows && entries.length > 0
|
||||
? await traced('дочитывание полей по Id (REST)', () => loadByIds(context, entries, 'own'))
|
||||
: new Map<string, IMyRequest>();
|
||||
const searchRows = entries.map((e) => byKey.get(requestKey(e.target.listId, e.id))).filter((r): r is IMyRequest => !!r);
|
||||
@@ -1357,7 +1407,10 @@ export async function getOwnRequestsByStatus(
|
||||
.reduce((acc, r) => acc.concat(r), [] as IMyRequest[])
|
||||
.filter((r) => r.status === status);
|
||||
|
||||
const searchKeys = new Set(searchRows.map((r) => rowKey(r)));
|
||||
// Ключи для дедупа берём из ХИТОВ индекса, а не из дочитанных строк: пара
|
||||
// «партиция + Id» известна уже по ответу Search, и режиму счётчика (withRows
|
||||
// = false) не приходится ради этого дочитывать поля.
|
||||
const searchKeys = new Set(entries.map((e) => requestKey(e.target.listId, e.id)));
|
||||
const onlyFresh = freshRows.filter((r) => !searchKeys.has(rowKey(r)));
|
||||
|
||||
const merged = onlyFresh
|
||||
|
||||
Reference in New Issue
Block a user