Feature/request type access all requests (#22)
Tests / frontend-test (push) Successful in 1m24s
Tests / frontend-test (push) Successful in 1m24s
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/22
This commit was merged in pull request #22.
This commit is contained in:
@@ -147,6 +147,28 @@ export async function getRequestTypeIdByPageUrl(context: WebPartContext, pageUrl
|
||||
return found.id;
|
||||
}
|
||||
|
||||
// GET /api/request-type-access/{requestTypeId}/emails — email'ы, кому в
|
||||
// админке approval-route выдан доступ видеть ВСЕ заявки этого типа (не только
|
||||
// свои). Тот же эндпоинт дёргает и Power Automate — здесь просто ещё один
|
||||
// потребитель.
|
||||
export async function getRequestTypeAccessEmails(
|
||||
context: WebPartContext,
|
||||
requestTypeId: number
|
||||
): Promise<string[]> {
|
||||
return withNetworkRetry(async () => {
|
||||
const c = await client(context);
|
||||
const response: HttpClientResponse = await c.get(
|
||||
`${getApprovalApiBaseUrl()}/api/request-type-access/${requestTypeId}/emails`,
|
||||
AadHttpClient.configurations.v1
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`request-type-access/${requestTypeId}/emails: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const body: IApiResponse<string[]> = await response.json();
|
||||
return body.data ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
// POST /api/template-routes/resolve — планируемый маршрут для типа заявки и
|
||||
// пользователя. Бэкенд сам подставляет исполнителей шага (executorEmail/position)
|
||||
// по ролям исходя из userEmail. Параметр status выбирает ФАЗУ маршрута:
|
||||
|
||||
@@ -31,8 +31,11 @@ export {
|
||||
getRequestTypeIdByPageUrl,
|
||||
getListPartitions,
|
||||
getCurrentListPartition,
|
||||
getRequestTypeAccessEmails,
|
||||
resolveRoute
|
||||
} from './approvalRoute.service';
|
||||
export { hasRequestTypeAccess, accessibleAllRequestsTypes } from './requestTypeAccess';
|
||||
export { useAccessibleAllRequestsFormKeys } from './useAccessibleAllRequestsFormKeys';
|
||||
export {
|
||||
initListPartitions,
|
||||
listPartitionsLoaded,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { getRequestTypeAccessEmails } from './approvalRoute.service';
|
||||
import { IRequestType } from './models/IApprovalRouteApi';
|
||||
import { effectiveUserEmail } from '../impersonation';
|
||||
|
||||
// Кеш на уровне МОДУЛЯ, по requestTypeId — тот же приём, что и у
|
||||
// requestTypesPromise в approvalRoute.service.ts: реестр доступа меняется
|
||||
// только из админки approval-route, а не в течение сеанса портала. Ошибка НЕ
|
||||
// кешируется, чтобы временный сбой сети не «залип» на весь сеанс.
|
||||
const cache = new Map<number, Promise<string[]>>();
|
||||
|
||||
function emailsForType(context: WebPartContext, requestTypeId: number): Promise<string[]> {
|
||||
let pending = cache.get(requestTypeId);
|
||||
if (!pending) {
|
||||
pending = getRequestTypeAccessEmails(context, requestTypeId).catch((e) => {
|
||||
cache.delete(requestTypeId);
|
||||
throw e;
|
||||
});
|
||||
cache.set(requestTypeId, pending);
|
||||
}
|
||||
return pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Есть ли у ТЕКУЩЕГО пользователя (effectiveUserEmail — учитывает отладочную
|
||||
* подмену личности) доступ видеть все заявки этого типа. Ошибка запроса — не
|
||||
* доступ, а не исключение наверх: как и hasReportsAccess, тихий сбой сети не
|
||||
* должен ронять раздел, который его вызывает.
|
||||
*/
|
||||
export async function hasRequestTypeAccess(context: WebPartContext, requestTypeId: number): Promise<boolean> {
|
||||
const email = effectiveUserEmail(context).trim().toLowerCase();
|
||||
if (!email) return false;
|
||||
try {
|
||||
const emails = await emailsForType(context, requestTypeId);
|
||||
return emails.some((e) => e.trim().toLowerCase() === email);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Из переданных типов — те, чьи заявки текущему пользователю разрешено видеть целиком. */
|
||||
export async function accessibleAllRequestsTypes(
|
||||
context: WebPartContext,
|
||||
types: IRequestType[]
|
||||
): Promise<IRequestType[]> {
|
||||
const flags = await Promise.all(types.map((t) => hasRequestTypeAccess(context, t.id)));
|
||||
return types.filter((_, i) => flags[i]);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { getRequestTypesCached } from './approvalRoute.service';
|
||||
import { accessibleAllRequestsTypes } from './requestTypeAccess';
|
||||
|
||||
/**
|
||||
* pageUrl (formKey формы) → числовой RequestTypeId approval-route, только для
|
||||
* типов, чьи заявки текущему пользователю разрешено видеть целиком (см.
|
||||
* requestTypeAccess.ts). Один поход по всем типам разом — плитки и кнопка на
|
||||
* открытой форме (SubmitFeature) решают по нему, показывать ли переход в
|
||||
* «Все заявки», не дожидаясь резолва конкретного типа.
|
||||
*
|
||||
* undefined — ответ ещё не пришёл (плитки без кнопки, а не с ошибочно пустой).
|
||||
*/
|
||||
export function useAccessibleAllRequestsFormKeys(context: WebPartContext): Map<string, number> | undefined {
|
||||
const [map, setMap] = useState<Map<string, number> | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
getRequestTypesCached(context)
|
||||
.then((types) => accessibleAllRequestsTypes(context, types))
|
||||
.then((accessible) => {
|
||||
if (cancelled) return;
|
||||
setMap(new Map(accessible.map((t) => [t.pageUrl, t.id])));
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setMap(new Map());
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [context]);
|
||||
|
||||
return map;
|
||||
}
|
||||
Reference in New Issue
Block a user