Мониторинг: в логах заявок — вид, код и название заявки
Tests / frontend-test (pull_request) Successful in 1m23s

«Создана заявка: Заявка на прием · TNS-… · Инженер — Проект X», «Заявка отправлена на согласование: …»,
«Заявка согласована: TNS-… · …» — заявку видно прямо в списке логов; те же данные — в полях request.*.
Отправка на согласование пишется при сохранении заявки с отправленным статусом (там известно, что за
заявка), из writeRoute лог убран. Код и название для решения дочитываются, сбой чтения решение не ломает.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-30 18:04:33 +05:00
parent c1c63b33e1
commit 168c242a84
3 changed files with 69 additions and 23 deletions
@@ -1,5 +1,4 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { log } from '@tnsu/monitoring-web';
import { resolveRoute } from './approvalRoute.service';
import { ApprovalListService, IApproverToWrite } from './approvalList.service';
import { IMatrixRow } from './models/IMatrixRow';
@@ -69,11 +68,6 @@ export async function writeApprovers(
await approval.createSteps(requestId, approvers, rowStatus, APPROVAL_PHASE.approval, 1);
if (submitted) {
await approval.activateNextQueued(requestId);
log.info('Заявка отправлена на согласование', {
'request.id': requestId,
'request.status': status,
'route.approvers': approvers.length,
});
}
}
@@ -144,11 +138,5 @@ export async function writeRoute(
if (submitted) {
await approval.activateNextQueued(requestId);
log.info('Заявка отправлена на согласование', {
'request.id': requestId,
'request.status': status,
'route.approvers': approvers.length,
'route.executors': executors.length,
});
}
}
@@ -345,14 +345,7 @@ export class ApproverService {
const rows = await this.approval.getForRequest(requestId);
const requestStatus = this.computeRequestStatus(rows);
await this.updateRequestStatus(requestId, requestStatus);
log.info(DECISION_MESSAGE[decision], {
'request.id': requestId,
'request.list': this.dataListRef,
'request.status': requestStatus,
'approval.decision': decision,
'approval.rowId': rowId,
'approval.comment': comment.slice(0, 500),
});
await this.logDecision(requestId, rowId, decision, comment, requestStatus);
emitRequestsChanged();
return requestStatus;
}
@@ -381,6 +374,42 @@ export class ApproverService {
return REQUEST_STATUS.execution;
}
/**
* Решение — в логи мониторинга, с кодом и названием заявки, чтобы её было видно прямо в списке логов.
* Код и название дочитываются отдельно; не получилось — пишем без них: решение уже применено.
*/
private async logDecision(
requestId: number,
rowId: number,
decision: ApproverDecision,
comment: string,
requestStatus: string
): Promise<void> {
let code = '';
let title = '';
try {
const item = (await this.dataList().items.getById(requestId).select('Title', 'RequestUniqueCode')()) as {
Title?: string;
RequestUniqueCode?: string;
};
code = item.RequestUniqueCode || '';
title = item.Title || '';
} catch {
// без кода и названия — лог всё равно нужен
}
const what = [code, title].filter(Boolean).join(' · ') || `№${requestId}`;
log.info(`${DECISION_MESSAGE[decision]}: ${what}`, {
'request.id': requestId,
'request.code': code,
'request.title': title,
'request.list': this.dataListRef,
'request.status': requestStatus,
'approval.decision': decision,
'approval.rowId': rowId,
'approval.comment': comment.slice(0, 500),
});
}
private dataList(): IList {
return this.dataListRef.startsWith('/')
? getSP().web.getList(this.dataListRef)
+32 -3
View File
@@ -7,6 +7,8 @@ import { getSP } from './pnp/pnpjs';
import { buildRequestUniqueCode } from './requestCode';
import { dataListTitle } from './listTitles';
import { RequestTypeId } from '../models/types';
import { REQUEST_TYPES } from '../data/requests';
import { isSubmittedStatus } from './approval/models/statuses';
/**
* Список данных, в который фактически пишем: listId (конкретная партиция —
@@ -51,6 +53,7 @@ export async function saveRequestItem(
if (existingId) {
await list.items.getById(existingId).update(item);
logSaved(formId, existingId, undefined, item, false);
return { id: existingId };
}
@@ -59,8 +62,34 @@ export async function saveRequestItem(
// Код известен только после создания элемента (нужен его Id).
const code = buildRequestUniqueCode(codePrefix, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
// Событие для мониторинга: кто (пользователь — из токена) и какую заявку создал. Отправка на согласование
// и решения пишутся отдельно — approvalSubmission.service.ts и approverDecision.service.ts.
log.info('Создана заявка', { 'request.type': formId, 'request.id': id, 'request.code': code });
logSaved(formId, id, code, item, true);
return { id, code };
}
/**
* События для мониторинга: какая заявка создана и отправлена на согласование (пользователь — из токена).
* В тексте — вид, код и название заявки, чтобы её было видно прямо в списке логов. Решения согласующих
* пишет approverDecision.service.ts.
*/
function logSaved(
formId: RequestTypeId,
id: number,
code: string | undefined,
item: Record<string, unknown>,
created: boolean
): void {
const typeTitle = REQUEST_TYPES.find((t) => t.id === formId)?.title ?? formId;
const title = typeof item.Title === 'string' ? item.Title : '';
const status = typeof item.RequestStatus === 'string' ? item.RequestStatus : '';
const what = [typeTitle, code, title].filter(Boolean).join(' · ');
const attributes = {
'request.type': formId,
'request.typeTitle': typeTitle,
'request.id': id,
'request.code': code ?? '',
'request.title': title,
'request.status': status,
};
if (created) log.info(`Создана заявка: ${what}`, attributes);
if (isSubmittedStatus(status)) log.info(`Заявка отправлена на согласование: ${what}`, attributes);
}