Feature/vacation edo signing #23

Merged
gorbenko.pavel merged 27 commits from feature/vacation-edo-signing into main 2026-10-05 08:53:19 +00:00
2 changed files with 65 additions and 1 deletions
Showing only changes of commit 2009535f1a - Show all commits
@@ -0,0 +1,27 @@
import { trimPickerQuery } from './pickerContext';
const body = (q: unknown): string => JSON.stringify({ queryParams: { QueryString: q, PrincipalType: 1 } });
const query = (b: unknown): unknown => (JSON.parse(b as string) as { queryParams: { QueryString: unknown } }).queryParams.QueryString;
describe('trimPickerQuery', () => {
it('убирает пробел в конце и в начале', () => {
expect(query(trimPickerQuery(body('Иван ')))).toBe('Иван');
expect(query(trimPickerQuery(body(' Иван Петров ')))).toBe('Иван Петров');
});
it('схлопывает двойные пробелы внутри', () => {
expect(query(trimPickerQuery(body('Иван Петров')))).toBe('Иван Петров');
});
it('не трогает остальные поля запроса', () => {
const parsed = JSON.parse(trimPickerQuery(body('a ')) as string) as { queryParams: { PrincipalType: number } };
expect(parsed.queryParams.PrincipalType).toBe(1);
});
it('чужие тела возвращает как есть', () => {
expect(trimPickerQuery('не json QueryString')).toBe('не json QueryString');
expect(trimPickerQuery(undefined)).toBeUndefined();
const other = JSON.stringify({ a: 1 });
expect(trimPickerQuery(other)).toBe(other);
});
});
@@ -18,6 +18,43 @@ export function pickedLogin(user: IPickedUser): string {
return user.secondaryText || user.loginName || user.id || '';
}
/**
* Убирает пробелы по краям (и схлопывает внутренние) у QueryString в теле запроса
* поиска сотрудника. PeoplePicker из @pnp/spfx-controls-react шлёт введённый
* текст как есть, а SharePoint с «Иван » (пробел в конце, обычное дело при вводе
* «имя фамилия») ничего не находит. Тело не JSON или без QueryString — возвращается как было.
*/
export function trimPickerQuery(body: unknown): unknown {
if (typeof body !== 'string' || body.indexOf('QueryString') < 0) return body;
try {
const parsed = JSON.parse(body) as { queryParams?: { QueryString?: unknown } };
const query = parsed.queryParams?.QueryString;
if (typeof query !== 'string') return body;
parsed.queryParams!.QueryString = query.trim().replace(/\s+/g, ' ');
return JSON.stringify(parsed);
} catch {
return body;
}
}
/**
* spHttpClient с подчисткой запроса поиска (см. trimPickerQuery): прокси, чтобы
* остальные методы клиента работали как прежде, подменяется только post.
*/
function withTrimmedSearch(client: WebPartContext['spHttpClient']): WebPartContext['spHttpClient'] {
return new Proxy(client, {
get(target, prop, receiver) {
if (prop !== 'post') return Reflect.get(target, prop, receiver);
return (url: string, config: unknown, options?: { body?: unknown }) =>
(target.post as (u: string, c: unknown, o?: unknown) => unknown)(
url,
config,
options ? { ...options, body: trimPickerQuery(options.body) } : options
);
}
});
}
/**
* Контекст для PeoplePicker из WebPartContext.
* as unknown: @pnp/spfx-controls-react тянет свою копию sp-http-msgraph,
@@ -27,6 +64,6 @@ export function buildPickerContext(context: WebPartContext): IPeoplePickerContex
return {
absoluteUrl: context.pageContext.web.absoluteUrl,
msGraphClientFactory: context.msGraphClientFactory,
spHttpClient: context.spHttpClient
spHttpClient: withTrimmedSearch(context.spHttpClient)
} as unknown as IPeoplePickerContext;
}