method + test + implementation
This commit is contained in:
@@ -1,14 +1,30 @@
|
||||
import type { UmbApi } from '../models/api.interface.js';
|
||||
import { createExtensionApi } from './create-extension-api.function.js';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {UmbControllerHost} host - The host to parse on as the host to the extension api.
|
||||
* @param {string} alias - The alias of the extension api to create.
|
||||
* @param {Array<unknown>} constructorArgs - The constructor arguments to pass to the extension api, host will always be appended as the first argument, meaning these arguments will be second and so forth.
|
||||
* @returns {ApiType} a class instance of the api provided via the manifest that matches the given alias. You have to type this via the generic `ApiType` parameter.
|
||||
*/
|
||||
export async function createExtensionApiByAlias<ApiType extends UmbApi = UmbApi>(
|
||||
host: UmbControllerHost,
|
||||
alias: string,
|
||||
constructorArgs?: Array<unknown>,
|
||||
): Promise<ApiType | undefined> {
|
||||
// Get Manifest:
|
||||
const manifest = {};
|
||||
): Promise<ApiType> {
|
||||
// Get manifest:
|
||||
const manifest = umbExtensionsRegistry.getByAlias(alias);
|
||||
if (!manifest) {
|
||||
throw new Error(`Failed to get manifest by alias: ${alias}`);
|
||||
}
|
||||
|
||||
const api = await createExtensionApi<ApiType>(host, manifest, constructorArgs);
|
||||
if (!api) {
|
||||
throw new Error(`Failed to create extension api from alias: ${alias}`);
|
||||
}
|
||||
// Create extension:
|
||||
return createExtensionApi(host, manifest, constructorArgs);
|
||||
return api;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import type { ManifestApi } from '../types/index.js';
|
||||
import type { UmbApi } from '../models/api.interface.js';
|
||||
import { createExtensionApiByAlias } from './create-extension-api-by-alias.function.js';
|
||||
import { customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-test-controller-host')
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
interface UmbExtensionApiBoolTestClass extends UmbApi {
|
||||
isValidClassInstance(): boolean;
|
||||
}
|
||||
class UmbExtensionApiTrueTestClass implements UmbExtensionApiBoolTestClass {
|
||||
isValidClassInstance() {
|
||||
return true;
|
||||
}
|
||||
destroy() {}
|
||||
}
|
||||
|
||||
class UmbExtensionApiFalseTestClass implements UmbExtensionApiBoolTestClass {
|
||||
isValidClassInstance() {
|
||||
return false;
|
||||
}
|
||||
destroy() {}
|
||||
}
|
||||
|
||||
const jsModuleWithDefaultExport = {
|
||||
default: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
|
||||
const jsModuleWithApiExport = {
|
||||
api: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
|
||||
const jsModuleWithDefaultAndApiExport = {
|
||||
default: UmbExtensionApiFalseTestClass,
|
||||
api: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
|
||||
describe('Create Extension Api By Alias Method', () => {
|
||||
let hostElement: UmbTestControllerHostElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
|
||||
});
|
||||
|
||||
it('Returns `undefined` when manifest does not have any correct properties', (done) => {
|
||||
const manifest: ManifestApi = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
};
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
|
||||
createExtensionApiByAlias<UmbExtensionApiBoolTestClass>(hostElement, manifest.alias, []).then(() => {
|
||||
umbExtensionsRegistry.unregister(manifest.alias);
|
||||
done(new Error('Should not resolve'));
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
umbExtensionsRegistry.unregister(manifest.alias);
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
it('Handles when `api` property contains a class constructor', async () => {
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
api: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
|
||||
const api = await createExtensionApiByAlias<UmbExtensionApiBoolTestClass>(hostElement, manifest.alias, []);
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
|
||||
umbExtensionsRegistry.unregister(manifest.alias);
|
||||
});
|
||||
|
||||
it('Handles when `loader` has a default export', async () => {
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithDefaultExport),
|
||||
};
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
|
||||
const api = await createExtensionApiByAlias<UmbExtensionApiBoolTestClass>(hostElement, manifest.alias, []);
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
|
||||
umbExtensionsRegistry.unregister(manifest.alias);
|
||||
});
|
||||
|
||||
it('Handles when `loader` has a api export', async () => {
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithApiExport),
|
||||
};
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
|
||||
const api = await createExtensionApiByAlias<UmbExtensionApiBoolTestClass>(hostElement, manifest.alias, []);
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
|
||||
umbExtensionsRegistry.unregister(manifest.alias);
|
||||
});
|
||||
|
||||
it('Prioritizes api export from loader property', async () => {
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithDefaultAndApiExport),
|
||||
};
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
|
||||
const api = await createExtensionApiByAlias<UmbExtensionApiBoolTestClass>(hostElement, manifest.alias, []);
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
|
||||
umbExtensionsRegistry.unregister(manifest.alias);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './create-extension-api-by-alias.function.js';
|
||||
export * from './create-extension-api.function.js';
|
||||
export * from './create-extension-element-with-api.function.js';
|
||||
export * from './create-extension-element.function.js';
|
||||
|
||||
@@ -248,8 +248,8 @@ export class UmbExtensionRegistry<
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an observable that provides extensions matching the given alias.
|
||||
* @param alias {string} - The alias of the extensions to get.
|
||||
* Get an observable that provides an extension matching the given alias.
|
||||
* @param alias {string} - The alias of the extension to get.
|
||||
* @returns {Observable<T | undefined>} - An observable of the extension that matches the alias.
|
||||
*/
|
||||
byAlias<T extends ManifestBase = ManifestBase>(alias: string): Observable<T | undefined> {
|
||||
@@ -267,6 +267,19 @@ export class UmbExtensionRegistry<
|
||||
) as Observable<T | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an extension that matches the given alias, this will not return an observable, it is a one of retrieval if it exists at the given point in time.
|
||||
* @param alias {string} - The alias of the extension to get.
|
||||
* @returns {<T | undefined>} - The extension manifest that matches the alias.
|
||||
*/
|
||||
getByAlias<T extends ManifestBase = ManifestBase>(alias: string): T | undefined {
|
||||
const ext = this._extensions.getValue().find((ext) => ext.alias === alias) as T | undefined;
|
||||
if (ext?.kind) {
|
||||
return this.#mergeExtensionWithKinds([ext, this._kinds.getValue()]);
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an observable that provides extensions matching the given type and alias.
|
||||
* @param type {string} - The type of the extensions to get.
|
||||
|
||||
@@ -13,8 +13,8 @@ export class UmbObserverController<T = unknown> extends UmbObserver<T> implement
|
||||
constructor(
|
||||
host: UmbControllerHost,
|
||||
source: Observable<T>,
|
||||
callback: ObserverCallback<T>,
|
||||
alias: UmbControllerAlias,
|
||||
callback?: ObserverCallback<T>,
|
||||
alias?: UmbControllerAlias,
|
||||
) {
|
||||
super(source, callback);
|
||||
this.#host = host;
|
||||
|
||||
@@ -15,9 +15,11 @@ export class UmbObserver<T> {
|
||||
#callback!: ObserverCallback<T>;
|
||||
#subscription!: Subscription;
|
||||
|
||||
constructor(source: Observable<T>, callback: ObserverCallback<T>) {
|
||||
constructor(source: Observable<T>, callback?: ObserverCallback<T>) {
|
||||
this.#source = source;
|
||||
this.#subscription = source.subscribe(callback);
|
||||
if (callback) {
|
||||
this.#subscription = source.subscribe(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,9 +65,9 @@ export class UmbObserver<T> {
|
||||
destroy(): void {
|
||||
if (this.#subscription) {
|
||||
this.#subscription.unsubscribe();
|
||||
(this.#source as any) = undefined;
|
||||
(this.#callback as any) = undefined;
|
||||
(this.#subscription as any) = undefined;
|
||||
}
|
||||
(this.#source as any) = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,23 @@
|
||||
import { UmbEntityActionBase } from '../../entity-action-base.js';
|
||||
import type { UmbEntityActionArgs } from '../../types.js';
|
||||
import type { MetaEntityActionDeleteKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbDetailRepository, UmbItemRepository } from '@umbraco-cms/backoffice/repository';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
export class UmbDeleteEntityAction extends UmbEntityActionBase<MetaEntityActionDeleteKind> {
|
||||
// TODO: make base type for item and detail models
|
||||
#itemRepository?: UmbItemRepository<any>;
|
||||
#detailRepository?: UmbDetailRepository<any>;
|
||||
#init: Promise<unknown>;
|
||||
|
||||
constructor(host: UmbControllerHost, args: UmbEntityActionArgs<MetaEntityActionDeleteKind>) {
|
||||
super(host, args);
|
||||
|
||||
// TODO: We should properly look into how we can simplify the one time usage of a extension api, as its a bit of overkill to take conditions/overwrites and observation of extensions into play here: [NL]
|
||||
// But since this happens when we execute an action, it does most likely not hurt any users, but it is a bit of a overkill to do this for every action: [NL]
|
||||
this.#init = Promise.all([
|
||||
new UmbExtensionApiInitializer(
|
||||
this._host,
|
||||
umbExtensionsRegistry,
|
||||
this.args.meta.itemRepositoryAlias,
|
||||
[this._host],
|
||||
(permitted, ctrl) => {
|
||||
this.#itemRepository = permitted ? (ctrl.api as UmbItemRepository<any>) : undefined;
|
||||
},
|
||||
).asPromise(),
|
||||
|
||||
new UmbExtensionApiInitializer(
|
||||
this._host,
|
||||
umbExtensionsRegistry,
|
||||
this.args.meta.detailRepositoryAlias,
|
||||
[this._host],
|
||||
(permitted, ctrl) => {
|
||||
this.#detailRepository = permitted ? (ctrl.api as UmbDetailRepository<any>) : undefined;
|
||||
},
|
||||
).asPromise(),
|
||||
]);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.args.unique) throw new Error('Cannot delete an item without a unique identifier.');
|
||||
await this.#init;
|
||||
|
||||
const { data } = await this.#itemRepository!.requestItems([this.args.unique]);
|
||||
const itemRepository = await createExtensionApiByAlias<UmbItemRepository<any>>(
|
||||
this,
|
||||
this.args.meta.itemRepositoryAlias,
|
||||
);
|
||||
|
||||
const { data } = await itemRepository.requestItems([this.args.unique]);
|
||||
const item = data?.[0];
|
||||
if (!item) throw new Error('Item not found.');
|
||||
|
||||
@@ -59,7 +29,11 @@ export class UmbDeleteEntityAction extends UmbEntityActionBase<MetaEntityActionD
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
await this.#detailRepository!.delete(this.args.unique);
|
||||
const detailRepository = await createExtensionApiByAlias<UmbDetailRepository<any>>(
|
||||
this,
|
||||
this.args.meta.detailRepositoryAlias,
|
||||
);
|
||||
detailRepository.delete(this.args.unique);
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
|
||||
@@ -3,9 +3,10 @@ import type {
|
||||
UmbRepositoryResponse,
|
||||
UmbRepositoryResponseWithAsObservable,
|
||||
} from '../types.js';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
|
||||
export interface UmbDetailRepository<DetailModelType> {
|
||||
export interface UmbDetailRepository<DetailModelType> extends UmbApi {
|
||||
createScaffold(preset?: Partial<DetailModelType>): Promise<UmbRepositoryResponse<DetailModelType>>;
|
||||
requestByUnique(unique: string): Promise<UmbRepositoryResponseWithAsObservable<DetailModelType>>;
|
||||
byUnique(unique: string): Promise<Observable<DetailModelType | undefined>>;
|
||||
|
||||
Reference in New Issue
Block a user