оптимизация

This commit is contained in:
gorbenko.pavel
2026-08-18 17:19:38 +05:00
parent 960875dc38
commit fe03b9e640
4 changed files with 32 additions and 15 deletions
@@ -16,6 +16,7 @@ import { IMyRequest } from './models/IMyRequest';
import { DataTable, IDataTableColumn } from '@tnsu/ui-kit'; import { DataTable, IDataTableColumn } from '@tnsu/ui-kit';
import { REQUEST_STATUS, REQUEST_STATUS_CHOICES, IRequestType, StatusBadge } from '../../shared/approval'; import { REQUEST_STATUS, REQUEST_STATUS_CHOICES, IRequestType, StatusBadge } from '../../shared/approval';
import { onRequestsChanged } from '../../shared/requestEvents'; import { onRequestsChanged } from '../../shared/requestEvents';
import { traced } from './perfTrace';
import { useRoutedQuery } from '../../shared/usePersistedQuery'; import { useRoutedQuery } from '../../shared/usePersistedQuery';
import { IPage, pageKey, pageSignature, readPage, writePage } from './pageCache'; import { IPage, pageKey, pageSignature, readPage, writePage } from './pageCache';
import { Pager } from './Pager'; import { Pager } from './Pager';
@@ -394,7 +395,7 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
const load = async (forceRender = false): Promise<void> => { const load = async (forceRender = false): Promise<void> => {
try { try {
const fresh = await fetchPage(); const fresh = await traced(`ИТОГО таблица:${view}#${page}`, fetchPage);
if (cancelled) return; if (cancelled) return;
// Перерисовываем, только если данные реально изменились — иначе при // Перерисовываем, только если данные реально изменились — иначе при
@@ -4,6 +4,7 @@ import { getLinkedTypes, hydratePendingAuthors, searchMyRequests } from './myReq
import { IMyRequest } from './models/IMyRequest'; import { IMyRequest } from './models/IMyRequest';
import { IRequestType, StatusBadge } from '../../shared/approval'; import { IRequestType, StatusBadge } from '../../shared/approval';
import { onRequestsChanged } from '../../shared/requestEvents'; import { onRequestsChanged } from '../../shared/requestEvents';
import { traced } from './perfTrace';
import { useRoutedQuery } from '../../shared/usePersistedQuery'; import { useRoutedQuery } from '../../shared/usePersistedQuery';
import { pageKey, pageSignature, readPage, writePage } from './pageCache'; import { pageKey, pageSignature, readPage, writePage } from './pageCache';
import { Pager } from './Pager'; import { Pager } from './Pager';
@@ -184,7 +185,7 @@ export const SearchView: React.FC<ISearchViewProps> = ({
const load = async (forceRender = false): Promise<void> => { const load = async (forceRender = false): Promise<void> => {
try { try {
const fresh = await searchMyRequests(context, types, debouncedQuery); const fresh = await traced('ИТОГО поиск', () => searchMyRequests(context, types, debouncedQuery));
if (cancelled) return; if (cancelled) return;
applyKnownAuthors(fresh.rows, authors.current); applyKnownAuthors(fresh.rows, authors.current);
const prev = readPage(cacheKey); const prev = readPage(cacheKey);
@@ -232,6 +232,10 @@ export interface INavCounts {
// своим запросом — но параллельно, и ровно тем же вызовом, что наполняет саму // своим запросом — но параллельно, и ровно тем же вызовом, что наполняет саму
// вкладку: иначе счётчик и список могли бы разойтись. // вкладку: иначе счётчик и список могли бы разойтись.
export async function getNavCounts(context: WebPartContext): Promise<INavCounts> { export async function getNavCounts(context: WebPartContext): Promise<INavCounts> {
return traced('ИТОГО getNavCounts', () => getNavCountsInner(context));
}
async function getNavCountsInner(context: WebPartContext): Promise<INavCounts> {
const types = await getLinkedTypes(context); const types = await getLinkedTypes(context);
const [turn, rework] = await Promise.all([ const [turn, rework] = await Promise.all([
@@ -352,8 +356,15 @@ async function indexApprovalsForPartition(
const sp = spForSite(target.siteUrl, context); const sp = spForSite(target.siteUrl, context);
const list = sp.web.getList(target.listId); const list = sp.web.getList(target.listId);
const dataListInfo = await traced(`dataListTitle:${type.name}#${target.listId}`, () => list.select('Title')()); // target.title — из bulk-ответа /api/request-types/lists, уже загруженного
const dataTitle = (dataListInfo.Title as string) || ''; // при старте страницы (см. IListTarget.title): 11 партиций — это 11 лишних
// REST-запросов только за тем, что уже пришло бесплатно одним запросом.
// REST — только если реестр партиций был недоступен (see listTargetsForType),
// тогда Title взять больше неоткуда.
const dataTitle =
target.title ??
((await traced(`dataListTitle:${type.name}#${target.listId}`, () => list.select('Title')())).Title as string) ??
'';
if (!DATA_LIST_SUFFIX.test(dataTitle)) { if (!DATA_LIST_SUFFIX.test(dataTitle)) {
debug.approvalSkippedReason = `«${dataTitle}» не оканчивается на «— данные» — список согласования не ищем`; debug.approvalSkippedReason = `«${dataTitle}» не оканчивается на «— данные» — список согласования не ищем`;
return debug; return debug;
@@ -707,6 +718,14 @@ export interface IListTarget {
siteUrl: string; siteUrl: string;
/** Абсолютный префикс пути элементов этого списка, со слешем на конце. */ /** Абсолютный префикс пути элементов этого списка, со слешем на конце. */
prefix: string; prefix: string;
/**
* Title списка данных на момент регистрации партиции (см. IListPartition.title) —
* уже пришёл бесплатно вместе с bulk-ответом /api/request-types/lists при
* старте страницы. Заполнен только когда партиция известна реестру; для
* фолбэка по RequestType.listId (реестр недоступен) — undefined, тогда
* indexApprovalsForPartition сам дочитает Title через REST.
*/
title?: string;
} }
/** /**
@@ -731,7 +750,8 @@ export function listTargetsForType(type: IRequestType): IListTarget[] {
return partitions.map((p) => ({ return partitions.map((p) => ({
listId: p.listId, listId: p.listId,
siteUrl: p.siteUrl, siteUrl: p.siteUrl,
prefix: `${siteOrigin(p.siteUrl).replace(/\/+$/, '')}${p.listId.replace(/\/+$/, '')}/` prefix: `${siteOrigin(p.siteUrl).replace(/\/+$/, '')}${p.listId.replace(/\/+$/, '')}/`,
title: p.title
})); }));
} }
@@ -3,20 +3,15 @@
* (индекс согласований, свои заявки по типу, конкретная партиция, Search), * (индекс согласований, свои заявки по типу, конкретная партиция, Search),
* а не просто «страница долго грузится». * а не просто «страница долго грузится».
* *
* Включение/выключение БЕЗ пересборки — из консоли браузера: * ВРЕМЕННО: пишет всегда, без флага-переключателя — этот модуль и все traced()
* window.rpPerfTrace = true // писать тайминги * вокруг снимаются целиком, как только разбор текущей проблемы с
* window.rpPerfTrace = false // выключить (по умолчанию) * производительностью закончится (см. myRequests.service.ts, RequestsTableView.tsx,
* SearchView.tsx).
*/ */
function enabled(): boolean {
return !!(window as unknown as { rpPerfTrace?: boolean }).rpPerfTrace;
}
const PREFIX = '[Перф]'; const PREFIX = '[Перф]';
/** Оборачивает async-шаг замером времени. Без включённой трассировки — просто вызывает fn(). */ /** Оборачивает async-шаг замером времени. */
export async function traced<T>(label: string, fn: () => Promise<T>): Promise<T> { export async function traced<T>(label: string, fn: () => Promise<T>): Promise<T> {
if (!enabled()) return fn();
const start = performance.now(); const start = performance.now();
try { try {
return await fn(); return await fn();