From f7979ac5342b8e454294d6cba4a2b7047e7b5600 Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Fri, 28 Aug 2026 14:10:36 +0500 Subject: [PATCH] =?UTF-8?q?docs(tools/sp):=20=D0=B3=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=B0=20?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=B0=D0=B2=20=D0=BD=D0=B0=20=D1=8D=D0=BB?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверено на тенанте 2026-08-28, при разборе «перевести отчёт по вакансиям на Search или нет»: 1. Часть полей индекс не отдаёт ВООБЩЕ, хотя значения в списках есть (OpenReason/OpenReasonDetails, CandidateName/HireDate/ReplacedEmployee, Ticket). Рядом в тех же списках PositionOWSTEXT, RequestStatusOWSCHCS, SelectionRequestIdOWSNMBR и др. отдаются нормально — то есть дело не в типе колонки и не в задержке crawl. Добавлена таблица с тем, какие имена уже перепробованы, чтобы не ходить по кругу. 2. Отличить «managed property нет» от «есть, но пусто» через SelectProperties нельзя — на несуществующее имя молча приходит null. Отличается сортировкой: SortList по несуществующему свойству падает 400/500. 3. Права у заявок портала разорваны на КАЖДОМ элементе (HasUniqueRoleAssignments = true), поэтому под своей учёткой в списке видно 2 элемента из 19 — не баг и не кэш браузера. App-only это не трогает. Отсюда же: ItemCount списка считает всё, /items — только доступное вам. Заодно исправлено утверждение «Search под app-only обычно ничего не отдаёт» — на сертификате CN=PnP-SharePoint-Sync отдаёт, и postquery, и кастомные managed properties. Co-Authored-By: Claude Opus 5 --- tools/sp/README.md | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tools/sp/README.md b/tools/sp/README.md index 6e24608..a9148c1 100644 --- a/tools/sp/README.md +++ b/tools/sp/README.md @@ -13,10 +13,14 @@ пользователь вводит его в браузере сам, дальше CLI for Microsoft 365 держит токен у себя. Ни пароля, ни client secret в репозитории и в командах нет. -**Работаем от имени пользователя, а не приложения.** Это не удобство, а требование: -SharePoint Search REST (`/_api/search/query`) под app-only-токеном обычно не отдаёт -ничего, ему нужен пользовательский контекст. А Search — главное, что здесь измеряют. -Побочный плюс: видно ровно то же, что видит пользователь в портале. +**Работаем от имени пользователя, а не приложения.** Главное, что это даёт: видно +ровно то же, что видит пользователь в портале, — а у заявок портала это принципиально, +потому что права разорваны НА КАЖДОМ элементе (см. «Грабли» ниже). + +Раньше здесь было написано, что Search под app-only-токеном «обычно не отдаёт ничего». +Проверено 2026-08-28 на сертификате `CN=PnP-SharePoint-Sync` (тот же, что у бэкенда +`tnsu-approval-route`): **отдаёт** — и `postquery`, и кастомные managed properties. +Так что app-only Search как таковой рабочий; ограничения у него другие, см. «Грабли». ## Разовая подготовка @@ -96,3 +100,32 @@ claims-логине частей будет четыре. Разбирать с партиций и в query-строке упирается в предел длины URL. Форма тела взята один в один из PnPjs (`@pnp/sp/search/query.js`): `{ request: { ...query } }`, массивы плоские, `odata=nometadata`. Иначе инструмент мерил бы не то, что портал. + +**Часть полей индекс не отдаёт ВООБЩЕ, хотя значения в списках есть** (проверено +2026-08-28 поштучным сравнением Search и REST на одних и тех же элементах): + +| Список | Поле | Пробовали имена | +| --- | --- | --- | +| Заявка на подбор | `OpenReason`, `OpenReasonDetails` | `…OWSCHCS`, `…OWSMTXT`, `…OWSTEXT`, с префиксом `TansuSite` | +| Прием на работу | `CandidateName`, `HireDate`, `ReplacedEmployee` | `…OWSTEXT`, `…OWSDATE`, с префиксом `TansuSite` | +| — согласование | `Ticket` (Lookup) | `TicketOWSTEXT`, `TicketOWSLKID`, `TicketId`, `Ticket` | + +Отличить «managed property нет вовсе» от «есть, но у строк пусто» через +`SelectProperties` НЕЛЬЗЯ — на несуществующее имя ответ молча приходит с `null`. +Отличается сортировкой: `SortList` по несуществующему свойству падает ошибкой +400/500, по существующему — работает. + +При этом рядом в тех же списках нормально отдаются `PositionOWSTEXT`, +`RequestStatusOWSCHCS`, `SelectionRequestIdOWSNMBR`, `SelectionRequestListIdOWSTEXT`, +`SubmitDateOWSDATE`, `TansuSiteApproverOWSUSER` — то есть это не про тип колонки и не +про задержку crawl (у свежесозданных элементов рабочие поля появляются, а эти нет). +Из-за этого отчёт по вакансиям в `tnsu-approval-route` остался на items REST по +партициям, а не на Search — подробный расчёт в doc-комментарии `VacancyReportService`. + +**Права разорваны на КАЖДОМ элементе.** У заявок портала +`HasUniqueRoleAssignments = true` поштучно: доступ выдан только участникам заявки плюс +общему админу. Поэтому под своей учёткой в списке видно 2 элемента из 19 — и это не +баг и не кэш браузера, а модель доступа. App-only токен (сертификат) это не трогает и +читает всё. Если сравниваете «сколько элементов в списке», обязательно фиксируйте, под +кем смотрите: `ItemCount` в метаданных списка считает ВСЁ, а `/items` — только +доступное вам.