Merge pull request 'AURORA-1894: красная рамка у ошибки ячейки, тост без Server error:undefined' (#277) from feat/275-aurora-1894-error-border-toast into test
This commit is contained in:
commit
5fe1264064
@ -200,9 +200,9 @@ const CellComponent = ({ cell, row, column, onClick, globalFilter, columnFilter,
|
|||||||
textAlign: isNumeric ? 'right' : 'left',
|
textAlign: isNumeric ? 'right' : 'left',
|
||||||
backgroundColor: isLocked ? '#f5f5f5' : backgroundColor,
|
backgroundColor: isLocked ? '#f5f5f5' : backgroundColor,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
|
...(isEditable ? EDITABLE_STYLES : NOT_EDITABLE_STYLES),
|
||||||
...(isLocked ? LOCKED_STYLES : {}),
|
...(isLocked ? LOCKED_STYLES : {}),
|
||||||
...(isInvalid ? INVALID_STYLES : {}),
|
...(isInvalid ? INVALID_STYLES : {}),
|
||||||
...(isEditable ? EDITABLE_STYLES : NOT_EDITABLE_STYLES),
|
|
||||||
}),
|
}),
|
||||||
[backgroundColor, isInvalid, isLocked, textColor, isEditable, isNumeric, nameIndent],
|
[backgroundColor, isInvalid, isLocked, textColor, isEditable, isNumeric, nameIndent],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { toast } from 'react-toastify';
|
|||||||
import { useWebSocket } from '../hooks/useWebSocket';
|
import { useWebSocket } from '../hooks/useWebSocket';
|
||||||
import { buildCellUpdatedMessage } from '../utils/messageUtils';
|
import { buildCellUpdatedMessage } from '../utils/messageUtils';
|
||||||
import { getRowId } from '../utils/rowUtils';
|
import { getRowId } from '../utils/rowUtils';
|
||||||
|
import { getWsErrorMessage } from '../utils/wsErrorUtils';
|
||||||
|
|
||||||
const RealtimeActionsContext = createContext(null);
|
const RealtimeActionsContext = createContext(null);
|
||||||
const RealtimeConnectionContext = createContext(null);
|
const RealtimeConnectionContext = createContext(null);
|
||||||
@ -106,8 +107,8 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
const handleMessage = useCallback((data) => {
|
const handleMessage = useCallback((data) => {
|
||||||
// Обработка ошибок
|
// Обработка ошибок
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.log(data.error.message);
|
console.log(data.error);
|
||||||
toast.error(`Server error:${data.error.message}` || '');
|
toast.error(getWsErrorMessage(data.error));
|
||||||
onErrorRef.current?.(data.error);
|
onErrorRef.current?.(data.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
35
web/src/components/RealtimeTable/utils/wsErrorUtils.js
Normal file
35
web/src/components/RealtimeTable/utils/wsErrorUtils.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Человеческие тексты для технических префиксов ошибок, которые шлёт backend
|
||||||
|
// (api/src/core/CONSTANTS.py: VALIDATION_PREFIXES, PG_403_ERRORS, и org_not_assigned из прав доступа).
|
||||||
|
const KNOWN_PREFIX_MESSAGES = [
|
||||||
|
{ prefix: 'computed_field:', message: 'Это расчётная ячейка, её нельзя изменить' },
|
||||||
|
{ prefix: 'role_not_allowed:', message: 'Нет прав на изменение этой колонки' },
|
||||||
|
{ prefix: 'window_closed:', message: 'Окно ввода для этой колонки закрыто' },
|
||||||
|
{ prefix: 'org_not_assigned', message: 'Вы не назначены на эту форму' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_ERROR_MESSAGE = 'Неизвестная ошибка сервера';
|
||||||
|
|
||||||
|
// Если текст начинается с известного технического префикса — возвращает человеческую замену, иначе null.
|
||||||
|
const resolveKnownPrefix = (text) => {
|
||||||
|
const normalized = String(text ?? '').trim();
|
||||||
|
if (!normalized) return null;
|
||||||
|
const lower = normalized.toLowerCase();
|
||||||
|
const match = KNOWN_PREFIX_MESSAGES.find(({ prefix }) => lower.startsWith(prefix));
|
||||||
|
return match ? match.message : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Приводит ошибку WebSocket (строка или объект {code, field, message}/{detail}) к читаемому тексту для тоста.
|
||||||
|
// Никогда не возвращает undefined/null и не добавляет технический префикс "Server error:".
|
||||||
|
export const getWsErrorMessage = (error) => {
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return resolveKnownPrefix(error) ?? (error.trim() || DEFAULT_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && typeof error === 'object') {
|
||||||
|
const raw = error.message || error.detail;
|
||||||
|
if (!raw) return DEFAULT_ERROR_MESSAGE;
|
||||||
|
return resolveKnownPrefix(raw) ?? raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_ERROR_MESSAGE;
|
||||||
|
};
|
||||||
53
web/src/components/RealtimeTable/utils/wsErrorUtils.test.js
Normal file
53
web/src/components/RealtimeTable/utils/wsErrorUtils.test.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { test } from 'node:test';
|
||||||
|
import { getWsErrorMessage } from './wsErrorUtils.js';
|
||||||
|
|
||||||
|
test('строка возвращается как есть', () => {
|
||||||
|
assert.equal(getWsErrorMessage('Ячейка уже редактируется другим пользователем'), 'Ячейка уже редактируется другим пользователем');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('объект с message возвращает message', () => {
|
||||||
|
assert.equal(getWsErrorMessage({ code: 400, field: 'q1.transfer_econ', message: 'Нельзя указать отрицательное значение' }), 'Нельзя указать отрицательное значение');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('объект без message возвращает detail', () => {
|
||||||
|
assert.equal(getWsErrorMessage({ detail: 'Ошибка подключения к базе данных' }), 'Ошибка подключения к базе данных');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('объект без message и без detail возвращает дефолтный текст, а не undefined', () => {
|
||||||
|
const result = getWsErrorMessage({ code: 500 });
|
||||||
|
assert.notEqual(result, undefined);
|
||||||
|
assert.equal(typeof result, 'string');
|
||||||
|
assert.ok(!result.includes('undefined'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('префикс computed_field: заменяется человеческим текстом', () => {
|
||||||
|
assert.equal(getWsErrorMessage('computed_field: q1.corrected_plan'), 'Это расчётная ячейка, её нельзя изменить');
|
||||||
|
assert.equal(getWsErrorMessage({ message: 'computed_field: q1.corrected_plan' }), 'Это расчётная ячейка, её нельзя изменить');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('префикс role_not_allowed: заменяется человеческим текстом', () => {
|
||||||
|
assert.equal(getWsErrorMessage('role_not_allowed: data.q1.transfer_econ'), 'Нет прав на изменение этой колонки');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('префикс window_closed: заменяется человеческим текстом', () => {
|
||||||
|
assert.equal(getWsErrorMessage('window_closed: data.q1.transfer_econ'), 'Окно ввода для этой колонки закрыто');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('префикс org_not_assigned заменяется человеческим текстом', () => {
|
||||||
|
assert.equal(getWsErrorMessage('org_not_assigned'), 'Вы не назначены на эту форму');
|
||||||
|
assert.equal(getWsErrorMessage({ message: 'org_not_assigned: form_id=123' }), 'Вы не назначены на эту форму');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('неизвестный текст возвращается без технического префикса Server error:', () => {
|
||||||
|
const result = getWsErrorMessage('Database unavailable');
|
||||||
|
assert.equal(result, 'Database unavailable');
|
||||||
|
assert.ok(!result.startsWith('Server error:'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('пустая строка и отсутствующая ошибка не превращаются в undefined', () => {
|
||||||
|
assert.notEqual(getWsErrorMessage(''), undefined);
|
||||||
|
assert.notEqual(getWsErrorMessage(null), undefined);
|
||||||
|
assert.notEqual(getWsErrorMessage(undefined), undefined);
|
||||||
|
assert.ok(!String(getWsErrorMessage(undefined)).includes('undefined'));
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user