оптимизация

This commit is contained in:
gorbenko.pavel
2026-08-21 09:34:28 +05:00
parent a4e6e097f3
commit 9b2ccafc17
5 changed files with 8353 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
# `tools/sp` — запросы к SharePoint портала заявок
Инструмент для **разбора**, а не для правки данных: задать тенанту вопрос и получить
ответ текстом, который можно приложить к задаче.
Появился из-за производительности: раз за разом нужно было выяснить «а что реально
отдаёт поисковый индекс?», «сколько на самом деле партиций у типа заявки?» — и каждый
раз это была ручная работа со снипетом в консоли браузера и копипастой результата.
## Что важно понимать про доступ
**Секреты через инструмент не проходят.** Вход — device code: команда печатает код,
пользователь вводит его в браузере сам, дальше CLI for Microsoft 365 держит токен у
себя. Ни пароля, ни client secret в репозитории и в командах нет.
**Работаем от имени пользователя, а не приложения.** Это не удобство, а требование:
SharePoint Search REST (`/_api/search/query`) под app-only-токеном обычно не отдаёт
ничего, ему нужен пользовательский контекст. А Search — главное, что здесь измеряют.
Побочный плюс: видно ровно то же, что видит пользователь в портале.
## Разовая подготовка
CLI for Microsoft 365 начиная с 7-й версии **не возит собственное приложение** — нужен
свой Entra app. Регистрируется один раз на всю команду.
1. Entra admin center → **App registrations** → **New registration**
- Name: `Portal Tools CLI`
- Supported account types: **Single tenant**
- Redirect URI: **Public client/native** → `http://localhost`
2. **Authentication** → Advanced settings → **Allow public client flows** → **Yes**
(без этого device code не работает)
3. **API permissions** → Add a permission → **SharePoint** → **Delegated**:
- `AllSites.Read` — чтение списков
- `Sites.Search.All` — поисковый индекс
4. **Grant admin consent** (нужен админ тенанта)
5. Скопировать **Application (client) ID** и **Directory (tenant) ID**
Дальше — переменные окружения (PowerShell, на сессию):
```powershell
$env:SP_APP_ID = "<application-client-id>"
$env:SP_TENANT = "<directory-tenant-id>"
```
`SP_SITE` по умолчанию `https://tnsukz.sharepoint.com/sites/requests` — тот же адрес,
что в `src/webparts/requestPortal/shared/config.ts`. Задавать нужно, только если
списки типов заявок живут на другом сайте.
## Использование
```bash
cd tools/sp
npm install # один раз
node sp.mjs login # покажет код — ввести его в браузере
node sp.mjs status
```
| команда | зачем |
| --- | --- |
| `node sp.mjs props` | отдаёт ли индекс поля заявки (`Retrievable`) — от этого зависит, можно ли убрать REST-дочитывание в `loadByIds` |
| `node sp.mjs lists` | все портальные списки с размерами и адресами — сколько реально партиций |
| `node sp.mjs search "<KQL>" --select a,b --rows N` | тот же поисковый запрос, что делает портал |
| `node sp.mjs get "/_api/web/..."` | произвольный GET, когда отдельной команды нет |
Примеры:
```bash
# Свои заявки глазами индекса — ровно как их видит searchOwnRequests
node sp.mjs search 'AuthorOWSUSER:"i:0#.f|membership|user@tnsu.kz"' \
--select ListItemID,Title,RequestStatusOWSCHCS,Created --rows 5
# Сколько элементов в каждой партиции
node sp.mjs lists
```
## Грабли, на которые уже наступали
**`Queryable` ≠ `Retrievable`.** У managed property это разные флаги. То, по чему
получается фильтровать, не обязано приходить в ответе. Проверка от 2026-08-17
подтвердила, что по `RequestStatusOWSCHCS` можно фильтровать, и это НЕ было
доказательством, что его можно получить (проверено отдельно 2026-08-20 — можно).
Команда `props` печатает `<нет в ответе>` и `<null>` разными пометками именно
поэтому: схлопывать их в пустую строку — значит потерять ответ на вопрос.
**Имена managed properties нестандартные.** Суффикс — по типу исходной колонки:
`OWSTEXT` (Text), `OWSCHCS` (Choice), `OWSUSER` (Person/Group). У колонок из группы
колонок сайта добавляется префикс группы — поэтому «ожидаемого» `ApproverOWSUSER` в
схеме нет, а есть `TansuSiteApproverOWSUSER` (разбирались 2026-08-06). Актуальные
имена — в константах `MP_*` в `features/myRequests/myRequests.service.ts`.
**`AuthorOWSUSER` — это не почта.** Приходит строкой `почта | ФИО`, а при
claims-логине частей будет четыре. Разбирать сплитом по первому элементу нельзя.
**POST `postquery`, а не GET `query`.** KQL портала перечисляет через `OR` адреса всех
партиций и в query-строке упирается в предел длины URL. Форма тела взята один в один
из PnPjs (`@pnp/sp/search/query.js`): `{ request: { ...query } }`, массивы плоские,
`odata=nometadata`. Иначе инструмент мерил бы не то, что портал.