1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

feat: make maintenance-related 503s more intuitive (#5018)

This makes maintenance-related 503s more intuitive on our UI by
mentioning that maintenance banner is currently enabled.


![image](https://github.com/Unleash/unleash/assets/14320932/43142c58-6b87-4b2d-9239-50f2bb1409e6)
This commit is contained in:
Nuno Góis 2023-10-16 09:27:29 +01:00 committed by GitHub
parent c41f23ae54
commit 6c21ed5f74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 7 deletions

View File

@ -4,3 +4,4 @@ export const CREATED = 201;
export const NOT_FOUND = 404; export const NOT_FOUND = 404;
export const FORBIDDEN = 403; export const FORBIDDEN = 403;
export const UNAUTHORIZED = 401; export const UNAUTHORIZED = 401;
export const UNAVAILABLE = 503;

View File

@ -5,6 +5,7 @@ import {
NOT_FOUND, NOT_FOUND,
OK, OK,
UNAUTHORIZED, UNAUTHORIZED,
UNAVAILABLE,
} from 'constants/statusCodes'; } from 'constants/statusCodes';
import { import {
AuthenticationError, AuthenticationError,
@ -12,6 +13,7 @@ import {
ForbiddenError, ForbiddenError,
headers, headers,
NotFoundError, NotFoundError,
UnavailableError,
} from 'utils/apiUtils'; } from 'utils/apiUtils';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import { ACCESS_DENIED_TEXT } from 'utils/formatAccessText'; import { ACCESS_DENIED_TEXT } from 'utils/formatAccessText';
@ -27,6 +29,7 @@ interface IUseAPI {
handleNotFound?: ApiErrorHandler; handleNotFound?: ApiErrorHandler;
handleUnauthorized?: ApiErrorHandler; handleUnauthorized?: ApiErrorHandler;
handleForbidden?: ApiErrorHandler; handleForbidden?: ApiErrorHandler;
handleUnavailable?: ApiErrorHandler;
propagateErrors?: boolean; propagateErrors?: boolean;
} }
@ -35,6 +38,7 @@ const useAPI = ({
handleNotFound, handleNotFound,
handleForbidden, handleForbidden,
handleUnauthorized, handleUnauthorized,
handleUnavailable,
propagateErrors = false, propagateErrors = false,
}: IUseAPI) => { }: IUseAPI) => {
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({});
@ -104,6 +108,22 @@ const useAPI = ({
} }
} }
if (res.status === UNAVAILABLE) {
if (handleUnavailable) {
return handleUnavailable(setErrors, res, requestId);
} else {
setErrors((prev) => ({
...prev,
unavailable: 'This operation is unavailable',
}));
}
if (propagateErrors) {
const response = await res.json();
throw new UnavailableError(res.status, response);
}
}
if (res.status > 399) { if (res.status > 399) {
const response = await res.json(); const response = await res.json();
if (response?.details?.length > 0 && propagateErrors) { if (response?.details?.length > 0 && propagateErrors) {

View File

@ -3,12 +3,17 @@ import {
FORBIDDEN, FORBIDDEN,
NOT_FOUND, NOT_FOUND,
UNAUTHORIZED, UNAUTHORIZED,
UNAVAILABLE,
} from 'constants/statusCodes'; } from 'constants/statusCodes';
export interface IErrorBody { export interface IErrorBody {
message?: string;
details?: { message: string }[]; details?: { message: string }[];
} }
const getErrorMessage = (body: IErrorBody) =>
body.details?.[0]?.message || body.message;
export class AuthenticationError extends Error { export class AuthenticationError extends Error {
statusCode: number; statusCode: number;
@ -24,23 +29,31 @@ export class ForbiddenError extends Error {
body: IErrorBody; body: IErrorBody;
constructor(statusCode: number = FORBIDDEN, body: IErrorBody = {}) { constructor(statusCode: number = FORBIDDEN, body: IErrorBody = {}) {
super( super(getErrorMessage(body) || 'You cannot perform this action');
body.details?.length
? body.details[0].message
: 'You cannot perform this action',
);
this.name = 'ForbiddenError'; this.name = 'ForbiddenError';
this.statusCode = statusCode; this.statusCode = statusCode;
this.body = body; this.body = body;
} }
} }
export class UnavailableError extends Error {
statusCode: number;
body: IErrorBody;
constructor(statusCode: number = UNAVAILABLE, body: IErrorBody = {}) {
super(getErrorMessage(body) || 'This operation is unavailable');
this.name = 'UnavailableError';
this.statusCode = statusCode;
this.body = body;
}
}
export class BadRequestError extends Error { export class BadRequestError extends Error {
statusCode: number; statusCode: number;
body: IErrorBody; body: IErrorBody;
constructor(statusCode: number = BAD_REQUEST, body: IErrorBody = {}) { constructor(statusCode: number = BAD_REQUEST, body: IErrorBody = {}) {
super(body.details?.length ? body.details[0].message : 'Bad request'); super(getErrorMessage(body) || 'Bad request');
this.name = 'BadRequestError'; this.name = 'BadRequestError';
this.statusCode = statusCode; this.statusCode = statusCode;
this.body = body; this.body = body;

View File

@ -2,6 +2,9 @@ import { IUnleashConfig } from '../types';
import MaintenanceService from '../services/maintenance-service'; import MaintenanceService from '../services/maintenance-service';
import { IAuthRequest } from '../routes/unleash-types'; import { IAuthRequest } from '../routes/unleash-types';
export const MAINTENANCE_MODE_ENABLED =
'Unleash is currently in maintenance mode.';
const maintenanceMiddleware = ( const maintenanceMiddleware = (
{ getLogger }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>, { getLogger }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
maintenanceService: MaintenanceService, maintenanceService: MaintenanceService,
@ -17,7 +20,9 @@ const maintenanceMiddleware = (
writeMethod && writeMethod &&
(await maintenanceService.isMaintenanceMode()) (await maintenanceService.isMaintenanceMode())
) { ) {
res.status(503).send({}); res.status(503).send({
message: MAINTENANCE_MODE_ENABLED,
});
} else { } else {
next(); next();
} }