Merge pull request #318 from umbraco/feature/resource-api-mixin

This commit is contained in:
Niels Lyngsø
2023-01-02 11:14:55 +01:00
committed by GitHub
6 changed files with 145 additions and 26 deletions

View File

@@ -2,12 +2,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { ApiError, ProblemDetails, ProfilingResource } from '@umbraco-cms/backend-api';
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
import { UmbNotificationDefaultData, UmbNotificationService } from '@umbraco-cms/services';
import { UmbResourceController } from '@umbraco-cms/controllers';
import { ProfilingResource } from '@umbraco-cms/backend-api';
@customElement('umb-dashboard-performance-profiling')
export class UmbDashboardPerformanceProfilingElement extends UmbContextConsumerMixin(LitElement) {
export class UmbDashboardPerformanceProfilingElement extends LitElement {
static styles = [
UUITextStyles,
css`
@@ -31,27 +30,7 @@ export class UmbDashboardPerformanceProfilingElement extends UmbContextConsumerM
@state()
private _profilingPerfomance = false;
private _notificationService?: UmbNotificationService;
private async _getProfilingStatus() {
try {
const status = await ProfilingResource.getProfilingStatus();
this._profilingStatus = status.enabled;
} catch (e) {
if (e instanceof ApiError) {
const error = e as ProblemDetails;
const data: UmbNotificationDefaultData = { message: error.message ?? 'Something went wrong' };
this._notificationService?.peek('danger', { data });
}
}
}
constructor() {
super();
this.consumeAllContexts(['umbNotificationService'], (instances) => {
this._notificationService = instances['umbNotificationService'];
});
}
private _resourceController = new UmbResourceController(this);
connectedCallback(): void {
super.connectedCallback();
@@ -59,6 +38,15 @@ export class UmbDashboardPerformanceProfilingElement extends UmbContextConsumerM
this._profilingPerfomance = localStorage.getItem('profilingPerformance') === 'true';
}
private async _getProfilingStatus() {
const [profilingStatus] = await this._resourceController.tryExecuteAndNotify(
ProfilingResource.getProfilingStatus()
);
if (profilingStatus) {
this._profilingStatus = profilingStatus.enabled;
}
}
private _changeProfilingPerformance() {
this._profilingPerfomance = !this._profilingPerfomance;
localStorage.setItem('profilingPerformance', this._profilingPerfomance.toString());

View File

@@ -0,0 +1 @@
export * from './resource.controller';

View File

@@ -0,0 +1,126 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ReactiveController, ReactiveControllerHost } from 'lit';
import { ApiError, CancelablePromise, ProblemDetails } from '@umbraco-cms/backend-api';
import { UmbNotificationOptions, UmbNotificationDefaultData, UmbNotificationService } from '@umbraco-cms/services';
import { UmbContextConsumer } from '@umbraco-cms/context-api';
export class UmbResourceController implements ReactiveController {
host: ReactiveControllerHost;
#promises: Promise<any>[] = [];
#notificationConsumer: UmbContextConsumer;
#notificationService?: UmbNotificationService;
constructor(host: ReactiveControllerHost) {
(this.host = host).addController(this);
this.#notificationConsumer = new UmbContextConsumer(
host as unknown as EventTarget,
'umbNotificationService',
(_instance: UmbNotificationService) => {
this.#notificationService = _instance;
}
);
}
hostConnected() {
this.#promises.length = 0;
this.#notificationConsumer.attach();
}
hostDisconnected() {
this.cancelAllResources();
this.#notificationConsumer.detach();
}
addResource(promise: Promise<any>): void {
this.#promises.push(promise);
}
/**
* Execute a given function and get the result as a promise.
*/
execute<T>(func: Promise<T>): Promise<T> {
this.addResource(func);
return func;
}
/**
* Wrap the {execute} function in a try/catch block and return a tuple with the result and the error.
*/
async tryExecute<T>(func: Promise<T>): Promise<[T | undefined, ProblemDetails | undefined]> {
try {
return [await this.execute(func), undefined];
} catch (e) {
return [undefined, this.#toProblemDetails(e)];
}
}
/**
* Wrap the {execute} function in a try/catch block and return the result.
* If the executor function throws an error, then show the details in a notification.
*/
async tryExecuteAndNotify<T>(
func: Promise<T>,
options?: UmbNotificationOptions<any>
): Promise<[T | undefined, ProblemDetails | undefined]> {
const [result, error] = await this.tryExecute(func);
if (error) {
const data: UmbNotificationDefaultData = {
headline: error.title ?? 'Server Error',
message: error.detail ?? 'Something went wrong',
};
if (this.#notificationService) {
this.#notificationService?.peek('danger', { data, ...options });
} else {
console.group('UmbResourceController');
console.error(error);
console.groupEnd();
}
}
return [result, error];
}
/**
* Cancel all resources that are currently being executed by this controller if they are cancelable.
*
* This works by checking if the promise is a CancelablePromise and if so, it will call the cancel method.
*
* This is useful when the controller is being disconnected from the DOM.
*
* @see CancelablePromise
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortController
*/
cancelAllResources() {
this.#promises.forEach((promise) => {
if (promise instanceof CancelablePromise) {
promise.cancel();
}
});
}
/**
* Extract the ProblemDetails object from an ApiError.
*
* This assumes that all ApiErrors contain a ProblemDetails object in their body.
*/
#toProblemDetails(error: unknown): ProblemDetails | undefined {
if (error instanceof ApiError) {
const errorDetails = error.body as ProblemDetails;
return errorDetails;
} else if (error instanceof Error) {
return {
title: error.name,
detail: error.message,
};
}
return undefined;
}
}

View File

@@ -3,5 +3,5 @@
/* eslint-disable */
export * from './modal';
export { UmbNotificationService } from './notification';
export * from './notification';
export type { UmbNotificationDefaultData } from './notification/layouts/default';

View File

@@ -28,6 +28,7 @@
"@umbraco-cms/observable-api": ["src/core/observable-api"],
"@umbraco-cms/utils": ["src/core/utils"],
"@umbraco-cms/test-utils": ["src/core/test-utils"],
"@umbraco-cms/controllers": ["src/core/controllers"],
"@umbraco-cms/services": ["src/core/services"],
"@umbraco-cms/components/*": ["src/backoffice/components/*"],
"@umbraco-cms/stores/*": ["src/core/stores/*"],

View File

@@ -16,8 +16,11 @@ export default {
'@umbraco-cms/context-api': './src/core/context-api/index.ts',
'@umbraco-cms/extensions-api': './src/core/extensions-api/index.ts',
'@umbraco-cms/observable-api': './src/core/observable-api/index.ts',
'@umbraco-cms/resource-api': './src/core/resource-api',
'@umbraco-cms/utils': './src/core/utils/index.ts',
'@umbraco-cms/test-utils': './src/core/test-utils/index.ts',
'@umbraco-cms/controllers': './src/core/controllers',
'@umbraco-cms/services': './src/core/services',
'@umbraco-cms/extensions-registry': './src/core/extensions-registry/index.ts',
},
},