Retry state conflicts and always render server picker
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
export const HARBOR_VERSIONS = Object.freeze({
|
||||
macClient: '0.26.5',
|
||||
gatewayClient: '0.27.5',
|
||||
macClient: '0.26.6',
|
||||
gatewayClient: '0.27.6',
|
||||
gatewayBackend: '0.27.0',
|
||||
});
|
||||
|
||||
|
||||
+19
-5
@@ -53,6 +53,13 @@ const operationErrorContext: Record<string, string> = {
|
||||
'route-rules': 'routing',
|
||||
};
|
||||
|
||||
function asHarborApiError(error: unknown) {
|
||||
const candidate = error && typeof error === 'object' ? error as Record<string, unknown> : {};
|
||||
return error instanceof HarborApiError
|
||||
? error
|
||||
: new HarborApiError({ code: candidate.code }, Number(candidate.status));
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const previewReady = new URLSearchParams(window.location.search).has('preview-ready');
|
||||
const [{ snapshot: state, transport }, dispatch] = useReducer(
|
||||
@@ -135,19 +142,26 @@ export function App() {
|
||||
try {
|
||||
return await applyMutation(action);
|
||||
} catch (err) {
|
||||
let safeError = asHarborApiError(err);
|
||||
if (safeError.code === 'STATE_CONFLICT' && context !== 'routing') {
|
||||
await loadState();
|
||||
try {
|
||||
return await applyMutation(action);
|
||||
} catch (retryError) {
|
||||
safeError = asHarborApiError(retryError);
|
||||
}
|
||||
}
|
||||
await loadState();
|
||||
const candidate = err && typeof err === 'object' ? err as Record<string, unknown> : {};
|
||||
const safeError = err instanceof HarborApiError
|
||||
? err
|
||||
: new HarborApiError({ code: candidate.code }, Number(candidate.status));
|
||||
setError({
|
||||
context,
|
||||
profileId,
|
||||
message: context === 'routing' && safeError.code === 'STATE_CONFLICT'
|
||||
? 'Правила уже изменились в другом окне. Проверьте статусы строк и сохраните ещё раз.'
|
||||
: safeError.code === 'STATE_CONFLICT'
|
||||
? 'Harbor снова получил новые данные во время действия. Повторите ещё раз.'
|
||||
: safeError.message,
|
||||
code: safeError.code,
|
||||
correlationId: safeError.correlationId,
|
||||
correlationId: safeError.code === 'STATE_CONFLICT' ? '' : safeError.correlationId,
|
||||
retry: safeError.retryable && safeError.code !== 'STATE_CONFLICT'
|
||||
? () => run(key, action, context, target, profileId)
|
||||
: null,
|
||||
|
||||
@@ -487,7 +487,7 @@ function ProfileGroup({
|
||||
inert={!expanded ? true : undefined}
|
||||
>
|
||||
<div className="client-profile-body-inner">
|
||||
{!localStatus && renderServerPicker(profile, {
|
||||
{renderServerPicker(profile, {
|
||||
disabled: controlsBlocked,
|
||||
leaving: false,
|
||||
revealVersion: feature.revealVersions[profile.id] || 0,
|
||||
|
||||
@@ -35,6 +35,8 @@ test('App owns profile mutations and always uses the latest canonical revision',
|
||||
assert.match(app, /api\.profiles\.add\(label, url, revisionRef\.current\)/);
|
||||
assert.match(app, /api\.profiles\.refresh\(profileId, revisionRef\.current\)/);
|
||||
assert.match(app, /api\.profiles\.forget\(profileId, mode, revisionRef\.current\)/);
|
||||
assert.match(app, /safeError\.code === 'STATE_CONFLICT' && context !== 'routing'[\s\S]*await loadState\(\)[\s\S]*return await applyMutation\(action\)/);
|
||||
assert.match(app, /safeError\.code === 'STATE_CONFLICT' \? '' : safeError\.correlationId/);
|
||||
assert.match(page, /profiles,[\s\S]*onAdd: onAddProfile,[\s\S]*onRefresh: onRefreshProfile,[\s\S]*onForget: onForgetProfile/);
|
||||
assert.doesNotMatch(page, /onRenameProfile/);
|
||||
assert.doesNotMatch(feature, /from ['"][^'"]*\/api\/|\bapi\./);
|
||||
@@ -113,7 +115,8 @@ test('profiles render as flat accordion groups with scoped controls', () => {
|
||||
assert.match(feature, /\{!expired && visited && <div/);
|
||||
assert.match(page, /const subscriptionError = error\?\.context === 'subscription'[\s\S]*profile\.id === error\.profileId[\s\S]*profile\.subscription\.errorCode === 'SUBSCRIPTION_EXPIRED'[\s\S]*error: subscriptionError/);
|
||||
assert.match(feature, /profile\.subscription\.status === 'stale'[\s\S]*profile\.subscription\.fetchedAt/);
|
||||
assert.match(feature, /\{!localStatus && renderServerPicker\(profile/);
|
||||
assert.match(feature, /\{renderServerPicker\(profile/);
|
||||
assert.doesNotMatch(feature, /!localStatus && renderServerPicker/);
|
||||
assert.doesNotMatch(feature, /Выберите сервер этой подписки|client-profile-server-hint/);
|
||||
assert.match(feature, /feature\.operations\.profileRefresh\?\.target === profile\.id/);
|
||||
assert.match(feature, /\{!expired && visibleServer && <button[\s\S]*client-profile-selected-server[\s\S]*aria-expanded=\{expanded\}/);
|
||||
|
||||
Reference in New Issue
Block a user