Merge remote-tracking branch 'origin/main' into v14/bugfix/mntp-min-max-validation
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { OpenAPI } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import {
|
||||
extractUmbNotificationColor,
|
||||
isUmbNotifications,
|
||||
UMB_NOTIFICATION_CONTEXT,
|
||||
UMB_NOTIFICATION_HEADER,
|
||||
} from '@umbraco-cms/backoffice/notification';
|
||||
|
||||
/**
|
||||
* Controller that adds interceptors to the OpenAPI client
|
||||
*/
|
||||
export class UmbApiInterceptorController extends UmbControllerBase {
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
this.#addUmbNotificationsInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interceptor which checks responses for the umb-notifications header and displays them as a notification if any. Removes the umb-notifications from the headers.
|
||||
*/
|
||||
#addUmbNotificationsInterceptor() {
|
||||
OpenAPI.interceptors.response.use((response) => {
|
||||
const umbNotifications = response.headers.get(UMB_NOTIFICATION_HEADER);
|
||||
if (!umbNotifications) return response;
|
||||
|
||||
const notifications = JSON.parse(umbNotifications);
|
||||
if (!isUmbNotifications(notifications)) return response;
|
||||
|
||||
this.getContext(UMB_NOTIFICATION_CONTEXT).then((notificationContext) => {
|
||||
for (const notification of notifications) {
|
||||
notificationContext.peek(extractUmbNotificationColor(notification.type), {
|
||||
data: { headline: notification.category, message: notification.message },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const newHeader = new Headers();
|
||||
for (const header of response.headers.entries()) {
|
||||
const [key, value] = header;
|
||||
if (key !== UMB_NOTIFICATION_HEADER) newHeader.set(key, value);
|
||||
}
|
||||
|
||||
const newResponse = new Response(response.body, {
|
||||
headers: newHeader,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
});
|
||||
|
||||
return newResponse;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { UmbAppErrorElement } from './app-error.element.js';
|
||||
import { UmbAppContext } from './app.context.js';
|
||||
import { UmbServerConnection } from './server-connection.js';
|
||||
import { UmbAppAuthController } from './app-auth.controller.js';
|
||||
import { UmbApiInterceptorController } from './api-interceptor.controller.js';
|
||||
import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
|
||||
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
@@ -147,6 +148,8 @@ export class UmbAppElement extends UmbLitElement {
|
||||
|
||||
OpenAPI.BASE = window.location.origin;
|
||||
|
||||
new UmbApiInterceptorController(this);
|
||||
|
||||
new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
|
||||
|
||||
new UUIIconRegistryEssential().attach(this);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { UmbNotificationColor } from './notification.context.js';
|
||||
import { EventMessageTypeModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
|
||||
export function extractUmbNotificationColor(type: EventMessageTypeModel): UmbNotificationColor {
|
||||
switch (type) {
|
||||
case EventMessageTypeModel.ERROR:
|
||||
return 'danger';
|
||||
case EventMessageTypeModel.WARNING:
|
||||
return 'warning';
|
||||
case EventMessageTypeModel.INFO:
|
||||
case EventMessageTypeModel.DEFAULT:
|
||||
return 'default';
|
||||
case EventMessageTypeModel.SUCCESS:
|
||||
return 'positive';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,6 @@ import './layouts/default/index.js';
|
||||
|
||||
export * from './notification.context.js';
|
||||
export * from './notification-handler.js';
|
||||
|
||||
export * from './isUmbNotifications.function.js';
|
||||
export * from './extractUmbNotificationColor.function.js';
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { EventMessageTypeModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
|
||||
function objectIsUmbNotification(notification: unknown): notification is UmbNotificationsEventModel {
|
||||
if (typeof notification !== 'object' || notification === null) {
|
||||
return false;
|
||||
}
|
||||
const object = notification as UmbNotificationsEventModel;
|
||||
return (
|
||||
typeof object.category === 'string' &&
|
||||
typeof object.message === 'string' &&
|
||||
typeof object.type === 'string' &&
|
||||
Object.values(EventMessageTypeModel).includes(object.type)
|
||||
);
|
||||
}
|
||||
|
||||
export interface UmbNotificationsEventModel {
|
||||
category: string;
|
||||
message: string;
|
||||
type: EventMessageTypeModel;
|
||||
}
|
||||
|
||||
export function isUmbNotifications(notifications: Array<unknown>): notifications is Array<UmbNotificationsEventModel> {
|
||||
return notifications.every(objectIsUmbNotification);
|
||||
}
|
||||
|
||||
export const UMB_NOTIFICATION_HEADER = 'umb-notifications';
|
||||
@@ -6,6 +6,7 @@ import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UMB_NOTIFICATION_CONTEXT, type UmbNotificationOptions } from '@umbraco-cms/backoffice/notification';
|
||||
import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository';
|
||||
import type { ProblemDetails } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
|
||||
export class UmbResourceController extends UmbControllerBase {
|
||||
#promise: Promise<any>;
|
||||
@@ -57,8 +58,8 @@ export class UmbResourceController extends UmbControllerBase {
|
||||
* If the executor function throws an error, then show the details in a notification.
|
||||
*/
|
||||
async tryExecuteAndNotify<T>(options?: UmbNotificationOptions): Promise<UmbDataSourceResponse<T>> {
|
||||
const { data, error: _error } = await UmbResourceController.tryExecute<T>(this.#promise);
|
||||
const error: any = _error;
|
||||
const { data, error } = await UmbResourceController.tryExecute<T>(this.#promise);
|
||||
|
||||
if (error) {
|
||||
/**
|
||||
* Determine if we want to show a notification or just log the error to the console.
|
||||
@@ -71,18 +72,33 @@ export class UmbResourceController extends UmbControllerBase {
|
||||
} else {
|
||||
console.group('ApiError caught in UmbResourceController');
|
||||
console.error('Request failed', error.request);
|
||||
console.error('ProblemDetails', error.body);
|
||||
console.error('Request body', error.body);
|
||||
console.error('Error', error);
|
||||
|
||||
let problemDetails: ProblemDetails | null = null;
|
||||
|
||||
// ApiError - body could hold a ProblemDetails from the server
|
||||
if (typeof error.body !== 'undefined' && !!error.body) {
|
||||
try {
|
||||
(error as any).body = typeof error.body === 'string' ? JSON.parse(error.body) : error.body;
|
||||
(error as any).body = problemDetails = typeof error.body === 'string' ? JSON.parse(error.body) : error.body;
|
||||
} catch (e) {
|
||||
console.error('Error parsing error body (expected JSON)', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the operation status ends with `ByNotification` and if so, don't show a notification
|
||||
* This is a special case where the operation was cancelled by the server and the client gets a notification header instead.
|
||||
*/
|
||||
let isCancelledByNotification = false;
|
||||
if (
|
||||
problemDetails?.operationStatus &&
|
||||
typeof problemDetails.operationStatus === 'string' &&
|
||||
problemDetails.operationStatus.endsWith('ByNotification')
|
||||
) {
|
||||
isCancelledByNotification = true;
|
||||
}
|
||||
|
||||
// Go through the error status codes and act accordingly
|
||||
switch (error.status ?? 0) {
|
||||
case 401: {
|
||||
@@ -103,14 +119,14 @@ export class UmbResourceController extends UmbControllerBase {
|
||||
case 500:
|
||||
// Server Error
|
||||
|
||||
if (this.#notificationContext) {
|
||||
let headline = error.body?.title ?? error.name ?? 'Server Error';
|
||||
if (!isCancelledByNotification && this.#notificationContext) {
|
||||
let headline = problemDetails?.title ?? error.name ?? 'Server Error';
|
||||
let message = 'A fatal server error occurred. If this continues, please reach out to your administrator.';
|
||||
|
||||
// Special handling for ObjectCacheAppCache corruption errors, which we are investigating
|
||||
if (
|
||||
error.body?.detail?.includes('ObjectCacheAppCache') ||
|
||||
error.body?.detail?.includes('Umbraco.Cms.Infrastructure.Scoping.Scope.DisposeLastScope()')
|
||||
problemDetails?.detail?.includes('ObjectCacheAppCache') ||
|
||||
problemDetails?.detail?.includes('Umbraco.Cms.Infrastructure.Scoping.Scope.DisposeLastScope()')
|
||||
) {
|
||||
headline = 'Please restart the server';
|
||||
message =
|
||||
@@ -128,12 +144,14 @@ export class UmbResourceController extends UmbControllerBase {
|
||||
break;
|
||||
default:
|
||||
// Other errors
|
||||
if (this.#notificationContext) {
|
||||
if (!isCancelledByNotification && this.#notificationContext) {
|
||||
this.#notificationContext.peek('danger', {
|
||||
data: {
|
||||
headline: error.body?.title ?? error.name ?? 'Server Error',
|
||||
message: error.body?.detail ?? error.message ?? 'Something went wrong',
|
||||
structuredList: error.body.errors,
|
||||
headline: problemDetails?.title ?? error.name ?? 'Server Error',
|
||||
message: problemDetails?.detail ?? error.message ?? 'Something went wrong',
|
||||
structuredList: problemDetails?.errors
|
||||
? (problemDetails.errors as Record<string, Array<unknown>>)
|
||||
: undefined,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user