From 5a6dcf80ae833c190177a1a12dfc9beecc0d46ad Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 22 Aug 2024 16:50:16 +0000 Subject: [PATCH 01/14] Adds new method to extensionRegistry for appendCondition --- .../registry/extension.registry.test.ts | 83 ++++++++++++++++++- .../registry/extension.registry.ts | 29 ++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index 83ee09d37b..0ad82590b7 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -1,4 +1,5 @@ -import type { ManifestElementWithElementName, ManifestKind, ManifestBase } from '../types/index.js'; +import { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace'; +import type { ManifestElementWithElementName, ManifestKind, ManifestBase, ManifestWithDynamicConditions, UmbConditionConfigBase } from '../types/index.js'; import { UmbExtensionRegistry } from './extension.registry.js'; import { expect } from '@open-wc/testing'; @@ -453,3 +454,83 @@ describe('UmbExtensionRegistry with exclusions', () => { expect(extensionRegistry.isRegistered('Umb.Test.Section.Late')).to.be.false; }); }); + +describe('Append Conditions', () => { + let extensionRegistry: UmbExtensionRegistry; + let manifests: Array< + ManifestWithDynamicConditions + >; + + beforeEach(() => { + extensionRegistry = new UmbExtensionRegistry(); + manifests = [ + { + type: 'section', + name: 'test-section-1', + alias: 'Umb.Test.Section.1', + weight: 1, + conditions: [ + { + alias: "Umb.Test.Condition.Valid" + } + ] + }, + { + type: 'section', + name: 'test-section-2', + alias: 'Umb.Test.Section.2', + weight: 200 + }, + ]; + + manifests.forEach((manifest) => extensionRegistry.register(manifest)); + + extensionRegistry.register({ + type: 'condition', + name: 'test-condition-invalid', + alias: 'Umb.Test.Condition.Invalid' + }); + }); + + + it('allows an extension condition to be updated', () => { + expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false; + + const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(ext.conditions?.length).to.equal(1); + + // Register new condition as if I was in my own entrypoint + extensionRegistry.register({ + type: 'condition', + name: 'test-condition-valid', + alias: 'Umb.Test.Condition.Valid' + }); + + // Add the new condition to the extension + const conditionToAdd:UmbConditionConfigBase = { + alias: 'Umb.Test.Condition.Valid' + }; + extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd); + + // Check new condition is registered + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; + + // Verify the extension now has two conditions + const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(updatedExt.conditions?.length).to.equal(2); + + // Add a condition with a specific config to Section2 + const workspaceCondition:WorkspaceAliasConditionConfig = { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document' + + }; + extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition); + + const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; + expect(updatedWorkspaceExt.conditions?.length).to.equal(1); + }); +}); diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index 2680d599e9..a716615b16 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -1,4 +1,4 @@ -import type { ManifestBase, ManifestKind } from '../types/index.js'; +import type { ManifestBase, ManifestKind, ManifestWithDynamicConditions, UmbConditionConfigBase } from '../types/index.js'; import type { SpecificManifestTypeOrManifestBase } from '../types/map.types.js'; import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; @@ -430,4 +430,31 @@ export class UmbExtensionRegistry< distinctUntilChanged(extensionAndKindMatchArrayMemoization), ) as Observable>; } + + /** + * Appends a new condition to an existing extension + * Useful to add a condition for example the Save And Publish workspace action shipped by core + * As overwriting the whole extension to simply add an extra condition is not ideal as it causes a lot of duplicated code + * and could easily get out of sync from the CMS core implementation if a 3rd party dev was to try and overwrite it + * @param alias {string} - The alias of the extension to get. + * @param newCondition {UmbConditionConfigBase} - The condition to append to the extension. + */ + appendCondition(alias: string, newCondition: UmbConditionConfigBase) { + const allExtensions = this._extensions.getValue(); + const extensionToUpdate = allExtensions.find((ext) => ext.alias === alias) as ManifestWithDynamicConditions; + + if(extensionToUpdate === undefined) { + console.error(`Extension with alias ${alias} not found`); + } + + // Append the condition to the extensions conditions array + if (extensionToUpdate.conditions){ + extensionToUpdate.conditions.push(newCondition); + } else { + extensionToUpdate.conditions = [newCondition]; + } + + // Update the extensions observable + this._extensions.setValue(allExtensions); + } } From c6c80ddf290e35ecdb44c3640fc2ce274f0c50a0 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 23 Aug 2024 09:57:11 +0000 Subject: [PATCH 02/14] Adds appendConditions to allow adding multiple in one go --- .../registry/extension.registry.test.ts | 20 +++++++++++++++++++ .../registry/extension.registry.ts | 11 +++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index 0ad82590b7..f6add8c5c2 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -533,4 +533,24 @@ describe('Append Conditions', () => { const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(updatedWorkspaceExt.conditions?.length).to.equal(1); }); + + it('allows an extension to update with multiple conditions', () => { + const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(ext.conditions?.length).to.equal(1); + + const conditions:Array = [ + { + alias: 'Umb.Test.Condition.Valid' + }, + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document' + } as WorkspaceAliasConditionConfig + ] + + extensionRegistry.appendConditions('Umb.Test.Section.1', conditions); + + const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(ext.conditions?.length).to.equal(3); + }); }); diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index a716615b16..4311109022 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -436,7 +436,7 @@ export class UmbExtensionRegistry< * Useful to add a condition for example the Save And Publish workspace action shipped by core * As overwriting the whole extension to simply add an extra condition is not ideal as it causes a lot of duplicated code * and could easily get out of sync from the CMS core implementation if a 3rd party dev was to try and overwrite it - * @param alias {string} - The alias of the extension to get. + * @param alias {string} - The alias of the extension to append the condition to * @param newCondition {UmbConditionConfigBase} - The condition to append to the extension. */ appendCondition(alias: string, newCondition: UmbConditionConfigBase) { @@ -457,4 +457,13 @@ export class UmbExtensionRegistry< // Update the extensions observable this._extensions.setValue(allExtensions); } + + /** + * Appends a collection of conditions to an exsiting extension + * @param alias {string} - The alias of the extension to append the condition to + * @param newConditions {Array} - A collection of conditions to append to an extension. + */ + appendConditions(alias: string, newConditions: Array){ + newConditions.forEach((condition) => this.appendCondition(alias, condition)); + } } From be2f95a854439009ba8e141658603871d0be5917 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 29 Aug 2024 11:10:48 +0000 Subject: [PATCH 03/14] Adds prependCondition and prependConditions so you can make sure your condition loads first, as order of conditions are important --- .../registry/extension.registry.test.ts | 89 +++++++++++++++++++ .../registry/extension.registry.ts | 48 ++++++++-- 2 files changed, 132 insertions(+), 5 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index f6add8c5c2..9add871f65 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -554,3 +554,92 @@ describe('Append Conditions', () => { expect(ext.conditions?.length).to.equal(3); }); }); + +describe('Prepend Conditions', () => { + let extensionRegistry: UmbExtensionRegistry; + let manifests: Array; + + beforeEach(() => { + extensionRegistry = new UmbExtensionRegistry(); + manifests = [ + { + type: 'section', + name: 'test-section-1', + alias: 'Umb.Test.Section.1', + weight: 1, + conditions: [ + { + alias: "Umb.Test.Condition.Valid" + } + ] + }, + { + type: 'section', + name: 'test-section-2', + alias: 'Umb.Test.Section.2', + weight: 200 + }, + ]; + + manifests.forEach((manifest) => extensionRegistry.register(manifest)); + + extensionRegistry.register({ + type: 'condition', + name: 'test-condition-invalid', + alias: 'Umb.Test.Condition.Invalid' + }); + }); + + it('allows an extension condition to be prepended', () => { + expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false; + + const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(ext.conditions?.length).to.equal(1); + + // Register new condition as if I was in my own entrypoint + extensionRegistry.register({ + type: 'condition', + name: 'test-condition-valid', + alias: 'Umb.Test.Condition.Valid' + }); + + // Prepend the new condition to the extension + const conditionToPrepend: UmbConditionConfigBase = { + alias: 'Umb.Test.Condition.Valid' + }; + extensionRegistry.prependCondition('Umb.Test.Section.1', conditionToPrepend); + + // Check new condition is registered + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; + + // Verify the extension now has two conditions and the new condition is prepended + const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(updatedExt.conditions?.length).to.equal(2); + expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); + }); + + it('allows an extension to update with multiple prepended conditions', () => { + const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(ext.conditions?.length).to.equal(1); + + const conditions: Array = [ + { + alias: 'Umb.Test.Condition.Valid' + }, + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document' + } as WorkspaceAliasConditionConfig + ]; + + extensionRegistry.prependConditions('Umb.Test.Section.1', conditions); + + const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(extUpdated.conditions?.length).to.equal(3); + expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); + }); +}); diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index 4311109022..f6e9f9d934 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -1,4 +1,9 @@ -import type { ManifestBase, ManifestKind, ManifestWithDynamicConditions, UmbConditionConfigBase } from '../types/index.js'; +import type { + ManifestBase, + ManifestKind, + ManifestWithDynamicConditions, + UmbConditionConfigBase, +} from '../types/index.js'; import type { SpecificManifestTypeOrManifestBase } from '../types/map.types.js'; import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; @@ -430,7 +435,7 @@ export class UmbExtensionRegistry< distinctUntilChanged(extensionAndKindMatchArrayMemoization), ) as Observable>; } - + /** * Appends a new condition to an existing extension * Useful to add a condition for example the Save And Publish workspace action shipped by core @@ -443,12 +448,12 @@ export class UmbExtensionRegistry< const allExtensions = this._extensions.getValue(); const extensionToUpdate = allExtensions.find((ext) => ext.alias === alias) as ManifestWithDynamicConditions; - if(extensionToUpdate === undefined) { + if (extensionToUpdate === undefined) { console.error(`Extension with alias ${alias} not found`); } // Append the condition to the extensions conditions array - if (extensionToUpdate.conditions){ + if (extensionToUpdate.conditions) { extensionToUpdate.conditions.push(newCondition); } else { extensionToUpdate.conditions = [newCondition]; @@ -463,7 +468,40 @@ export class UmbExtensionRegistry< * @param alias {string} - The alias of the extension to append the condition to * @param newConditions {Array} - A collection of conditions to append to an extension. */ - appendConditions(alias: string, newConditions: Array){ + appendConditions(alias: string, newConditions: Array) { newConditions.forEach((condition) => this.appendCondition(alias, condition)); } + + /** + * Prepends a new condition to an existing extension + * @param alias {string} - The alias of the extension to prepend the condition to + * @param newCondition {UmbConditionConfigBase} - The condition to prepend to the extension. + */ + prependCondition(alias: string, newCondition: UmbConditionConfigBase) { + const allExtensions = this._extensions.getValue(); + const extensionToUpdate = allExtensions.find((ext) => ext.alias === alias) as ManifestWithDynamicConditions; + + if (extensionToUpdate === undefined) { + console.error(`Extension with alias ${alias} not found`); + } + + // Prepend the condition to the extensions conditions array + if (extensionToUpdate.conditions) { + extensionToUpdate.conditions.unshift(newCondition); + } else { + extensionToUpdate.conditions = [newCondition]; + } + + // Update the extensions observable + this._extensions.setValue(allExtensions); + } + + /** + * Prepends a collection of conditions to an existing extension + * @param alias {string} - The alias of the extension to prepend the conditions to + * @param newConditions {Array} - A collection of conditions to prepend to an extension. + */ + prependConditions(alias: string, newConditions: Array) { + newConditions.forEach((condition) => this.prependCondition(alias, condition)); + } } From 6e3a3e6112d579cfc69bca4011b2caa884f71d76 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 29 Aug 2024 16:14:06 +0000 Subject: [PATCH 04/14] Adds _whenExtensionAliasIsRegistered as a promise so that we basically wait until the manifest is registered so late extensions will work as well Seems to work with my hack in an entrypoint and the console logs the extension with the updated condition but its the tests I am struggling to get to work. As this code is heavily worked from me trying to Google, read RXJS docs and talking with Copilot I am not fully in confident in this approach --- .../registry/extension.registry.test.ts | 19 +-- .../registry/extension.registry.ts | 114 +++++++++++++----- src/packages/property-editors/entry-point.ts | 31 +++++ 3 files changed, 123 insertions(+), 41 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index 9add871f65..94c5fccbf5 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -493,7 +493,7 @@ describe('Append Conditions', () => { }); - it('allows an extension condition to be updated', () => { + it('allows an extension condition to be updated', async () => { expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; @@ -513,7 +513,7 @@ describe('Append Conditions', () => { const conditionToAdd:UmbConditionConfigBase = { alias: 'Umb.Test.Condition.Valid' }; - extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd); + await extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd); // Check new condition is registered expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; @@ -528,13 +528,13 @@ describe('Append Conditions', () => { match: 'Umb.Workspace.Document' }; - extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition); + await extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition); const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(updatedWorkspaceExt.conditions?.length).to.equal(1); }); - it('allows an extension to update with multiple conditions', () => { + it('allows an extension to update with multiple conditions', async () => { const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(ext.conditions?.length).to.equal(1); @@ -548,7 +548,7 @@ describe('Append Conditions', () => { } as WorkspaceAliasConditionConfig ] - extensionRegistry.appendConditions('Umb.Test.Section.1', conditions); + await extensionRegistry.appendConditions('Umb.Test.Section.1', conditions); const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(ext.conditions?.length).to.equal(3); @@ -590,7 +590,7 @@ describe('Prepend Conditions', () => { }); }); - it('allows an extension condition to be prepended', () => { + it('allows an extension condition to be prepended', async () => { expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; @@ -610,7 +610,8 @@ describe('Prepend Conditions', () => { const conditionToPrepend: UmbConditionConfigBase = { alias: 'Umb.Test.Condition.Valid' }; - extensionRegistry.prependCondition('Umb.Test.Section.1', conditionToPrepend); + + await extensionRegistry.prependCondition('Umb.Test.Section.1', conditionToPrepend); // Check new condition is registered expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; @@ -621,7 +622,7 @@ describe('Prepend Conditions', () => { expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); }); - it('allows an extension to update with multiple prepended conditions', () => { + it('allows an extension to update with multiple prepended conditions', async () => { const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(ext.conditions?.length).to.equal(1); @@ -635,7 +636,7 @@ describe('Prepend Conditions', () => { } as WorkspaceAliasConditionConfig ]; - extensionRegistry.prependConditions('Umb.Test.Section.1', conditions); + await extensionRegistry.prependConditions('Umb.Test.Section.1', conditions); const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(extUpdated.conditions?.length).to.equal(3); diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index f6e9f9d934..7d2837de3e 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -6,8 +6,8 @@ import type { } from '../types/index.js'; import type { SpecificManifestTypeOrManifestBase } from '../types/map.types.js'; import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { map, distinctUntilChanged, combineLatest, of, switchMap } from '@umbraco-cms/backoffice/external/rxjs'; +import type { Observable, Subscription } from '@umbraco-cms/backoffice/external/rxjs'; +import { map, distinctUntilChanged, combineLatest, of, switchMap, filter } from '@umbraco-cms/backoffice/external/rxjs'; /** * @@ -436,6 +436,37 @@ export class UmbExtensionRegistry< ) as Observable>; } + + /** + * Returns a promise that resolves when the extension with the specified alias is found. + * @param alias {string} - The alias of the extension to wait for. + * @returns {Promise} - A promise that resolves with the extension. + */ + private _whenExtensionAliasIsRegistered(alias: string): Promise { + return new Promise((resolve, reject) => { + const subscription: Subscription = this.extensions + .pipe(filter((allExtensions) => allExtensions.some((ext) => ext.alias === alias))) + .subscribe({ + next: (allExtensions) => { + console.log('I AM IN NEXT', allExtensions); + const extension = allExtensions.find((ext) => ext.alias === alias); + if (extension) { + subscription.unsubscribe(); + resolve(extension as ManifestBase); + } + }, + error: (error) => { + console.error('I AM IN ERROR', error); + reject(error); + }, + complete: () => { + console.log('I AM IN COMPLETE'); + reject(new Error(`Extension with alias ${alias} not found`)); + }, + }); + }); + } + /** * Appends a new condition to an existing extension * Useful to add a condition for example the Save And Publish workspace action shipped by core @@ -444,23 +475,39 @@ export class UmbExtensionRegistry< * @param alias {string} - The alias of the extension to append the condition to * @param newCondition {UmbConditionConfigBase} - The condition to append to the extension. */ - appendCondition(alias: string, newCondition: UmbConditionConfigBase) { - const allExtensions = this._extensions.getValue(); - const extensionToUpdate = allExtensions.find((ext) => ext.alias === alias) as ManifestWithDynamicConditions; + async appendCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { + try { - if (extensionToUpdate === undefined) { - console.error(`Extension with alias ${alias} not found`); - } + // Wait for the extension to be registered (as it could be registered late) + const extensionToWaitFor = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; - // Append the condition to the extensions conditions array - if (extensionToUpdate.conditions) { - extensionToUpdate.conditions.push(newCondition); - } else { - extensionToUpdate.conditions = [newCondition]; - } + // Got it... now carry on & mutate it + console.log('got the extension to update/mutate', extensionToWaitFor); + + // Append the condition to the extensions conditions array + if (extensionToWaitFor.conditions) { + extensionToWaitFor.conditions.push(newCondition); + } else { + extensionToWaitFor.conditions = [newCondition]; + } - // Update the extensions observable - this._extensions.setValue(allExtensions); + const allExtensions = this._extensions.getValue(); + const extensionToUpdateIndex = allExtensions.findIndex((ext) => ext.alias === alias); + if (extensionToUpdateIndex !== -1) { + // Replace the existing extension with the updated one + allExtensions[extensionToUpdateIndex] = extensionToWaitFor as ManifestTypes; + + // Update the main extensions collection/observable + this._extensions.setValue(allExtensions); + + // Log the updated extensions for debugging + console.log('UPDATED extensions:', this._extensions.getValue()); + console.table(this._extensions.getValue()); + } + } catch (error) { + // TODO: [WB] Will this ever catch an error? + console.error(`Extension with alias ${alias} was never found and threw ${error}`); + } } /** @@ -468,7 +515,7 @@ export class UmbExtensionRegistry< * @param alias {string} - The alias of the extension to append the condition to * @param newConditions {Array} - A collection of conditions to append to an extension. */ - appendConditions(alias: string, newConditions: Array) { + async appendConditions(alias: string, newConditions: Array): Promise { newConditions.forEach((condition) => this.appendCondition(alias, condition)); } @@ -477,23 +524,26 @@ export class UmbExtensionRegistry< * @param alias {string} - The alias of the extension to prepend the condition to * @param newCondition {UmbConditionConfigBase} - The condition to prepend to the extension. */ - prependCondition(alias: string, newCondition: UmbConditionConfigBase) { - const allExtensions = this._extensions.getValue(); - const extensionToUpdate = allExtensions.find((ext) => ext.alias === alias) as ManifestWithDynamicConditions; + async prependCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { + try { - if (extensionToUpdate === undefined) { - console.error(`Extension with alias ${alias} not found`); - } + // Wait for the extension to be registered (as it could be registered late) + const extensionToUpdate = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; - // Prepend the condition to the extensions conditions array - if (extensionToUpdate.conditions) { - extensionToUpdate.conditions.unshift(newCondition); - } else { - extensionToUpdate.conditions = [newCondition]; - } + // Got it... now carry on & mutate it + console.log('got the extension to update/mutate', extensionToUpdate); + + // Append the condition to the extensions conditions array + if (extensionToUpdate.conditions) { + extensionToUpdate.conditions.unshift(newCondition); + } else { + extensionToUpdate.conditions = [newCondition]; + } - // Update the extensions observable - this._extensions.setValue(allExtensions); + } catch (error) { + // TODO: [WB] Will this ever catch an error? + console.error(`Extension with alias ${alias} was never found and threw ${error}`); + } } /** @@ -501,7 +551,7 @@ export class UmbExtensionRegistry< * @param alias {string} - The alias of the extension to prepend the conditions to * @param newConditions {Array} - A collection of conditions to prepend to an extension. */ - prependConditions(alias: string, newConditions: Array) { + async prependConditions(alias: string, newConditions: Array): Promise { newConditions.forEach((condition) => this.prependCondition(alias, condition)); } } diff --git a/src/packages/property-editors/entry-point.ts b/src/packages/property-editors/entry-point.ts index 9745b0d139..baafe2af43 100644 --- a/src/packages/property-editors/entry-point.ts +++ b/src/packages/property-editors/entry-point.ts @@ -1,2 +1,33 @@ +import type { ManifestWithDynamicConditions, UmbConditionConfigBase, UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; +import type { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace'; + import './checkbox-list/components/index.js'; import './content-picker/components/index.js'; + +export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { + + console.log('HELLO AGAIN'); + + const condition: UmbConditionConfigBase = { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.WARRENYO', + } as WorkspaceAliasConditionConfig; + + _extensionRegistry.appendCondition('Umb.Dashboard.UmbracoNewsLATE', condition); + + const ext:ManifestWithDynamicConditions = { + alias: 'Umb.Dashboard.UmbracoNewsLATE', + type: 'dashboard', + name: 'WARREN Package', + weight: 100, + conditions: [ + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.LATE-COMER-EXISTING', + } as WorkspaceAliasConditionConfig, + ], + }; + + _extensionRegistry.register(ext); + +}; From 3ba83153b15106f667030b08e066fc0051f40431 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 30 Aug 2024 10:19:27 +0200 Subject: [PATCH 05/14] simplify _whenExtensionAliasIsRegistered --- .../registry/extension.registry.ts | 43 ++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index 7d2837de3e..e21077c4ab 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -6,8 +6,16 @@ import type { } from '../types/index.js'; import type { SpecificManifestTypeOrManifestBase } from '../types/map.types.js'; import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api'; -import type { Observable, Subscription } from '@umbraco-cms/backoffice/external/rxjs'; -import { map, distinctUntilChanged, combineLatest, of, switchMap, filter } from '@umbraco-cms/backoffice/external/rxjs'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { + map, + distinctUntilChanged, + combineLatest, + of, + switchMap, + filter, + firstValueFrom, +} from '@umbraco-cms/backoffice/external/rxjs'; /** * @@ -436,35 +444,15 @@ export class UmbExtensionRegistry< ) as Observable>; } - /** * Returns a promise that resolves when the extension with the specified alias is found. * @param alias {string} - The alias of the extension to wait for. * @returns {Promise} - A promise that resolves with the extension. */ - private _whenExtensionAliasIsRegistered(alias: string): Promise { - return new Promise((resolve, reject) => { - const subscription: Subscription = this.extensions - .pipe(filter((allExtensions) => allExtensions.some((ext) => ext.alias === alias))) - .subscribe({ - next: (allExtensions) => { - console.log('I AM IN NEXT', allExtensions); - const extension = allExtensions.find((ext) => ext.alias === alias); - if (extension) { - subscription.unsubscribe(); - resolve(extension as ManifestBase); - } - }, - error: (error) => { - console.error('I AM IN ERROR', error); - reject(error); - }, - complete: () => { - console.log('I AM IN COMPLETE'); - reject(new Error(`Extension with alias ${alias} not found`)); - }, - }); - }); + private async _whenExtensionAliasIsRegistered(alias: string): Promise { + const source = this.extensions.pipe(filter((allExtensions) => allExtensions.some((ext) => ext.alias === alias))); + const value = await firstValueFrom(source); + return value[0]; } /** @@ -477,7 +465,6 @@ export class UmbExtensionRegistry< */ async appendCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { try { - // Wait for the extension to be registered (as it could be registered late) const extensionToWaitFor = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; @@ -526,7 +513,6 @@ export class UmbExtensionRegistry< */ async prependCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { try { - // Wait for the extension to be registered (as it could be registered late) const extensionToUpdate = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; @@ -539,7 +525,6 @@ export class UmbExtensionRegistry< } else { extensionToUpdate.conditions = [newCondition]; } - } catch (error) { // TODO: [WB] Will this ever catch an error? console.error(`Extension with alias ${alias} was never found and threw ${error}`); From 130646d9f8bc4460f17bab1acf4238b1b71af6be Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 30 Aug 2024 10:22:24 +0200 Subject: [PATCH 06/14] split some tests --- .../registry/extension.registry.test.ts | 188 +++++++++--------- 1 file changed, 97 insertions(+), 91 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index 94c5fccbf5..dd1e8612f9 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -1,5 +1,11 @@ import { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace'; -import type { ManifestElementWithElementName, ManifestKind, ManifestBase, ManifestWithDynamicConditions, UmbConditionConfigBase } from '../types/index.js'; +import type { + ManifestElementWithElementName, + ManifestKind, + ManifestBase, + ManifestWithDynamicConditions, + UmbConditionConfigBase, +} from '../types/index.js'; import { UmbExtensionRegistry } from './extension.registry.js'; import { expect } from '@open-wc/testing'; @@ -457,9 +463,7 @@ describe('UmbExtensionRegistry with exclusions', () => { describe('Append Conditions', () => { let extensionRegistry: UmbExtensionRegistry; - let manifests: Array< - ManifestWithDynamicConditions - >; + let manifests: Array; beforeEach(() => { extensionRegistry = new UmbExtensionRegistry(); @@ -471,15 +475,15 @@ describe('Append Conditions', () => { weight: 1, conditions: [ { - alias: "Umb.Test.Condition.Valid" - } - ] + alias: 'Umb.Test.Condition.Valid', + }, + ], }, { type: 'section', name: 'test-section-2', alias: 'Umb.Test.Section.2', - weight: 200 + weight: 200, }, ]; @@ -488,17 +492,18 @@ describe('Append Conditions', () => { extensionRegistry.register({ type: 'condition', name: 'test-condition-invalid', - alias: 'Umb.Test.Condition.Invalid' + alias: 'Umb.Test.Condition.Invalid', }); }); - - it('allows an extension condition to be updated', async () => { + it('should have the extensions registered', () => { expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false; + }); + it('allows an extension condition to be updated', async () => { const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(ext.conditions?.length).to.equal(1); @@ -506,12 +511,12 @@ describe('Append Conditions', () => { extensionRegistry.register({ type: 'condition', name: 'test-condition-valid', - alias: 'Umb.Test.Condition.Valid' + alias: 'Umb.Test.Condition.Valid', }); // Add the new condition to the extension - const conditionToAdd:UmbConditionConfigBase = { - alias: 'Umb.Test.Condition.Valid' + const conditionToAdd: UmbConditionConfigBase = { + alias: 'Umb.Test.Condition.Valid', }; await extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd); @@ -523,10 +528,9 @@ describe('Append Conditions', () => { expect(updatedExt.conditions?.length).to.equal(2); // Add a condition with a specific config to Section2 - const workspaceCondition:WorkspaceAliasConditionConfig = { + const workspaceCondition: WorkspaceAliasConditionConfig = { alias: 'Umb.Condition.WorkspaceAlias', - match: 'Umb.Workspace.Document' - + match: 'Umb.Workspace.Document', }; await extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition); @@ -538,15 +542,15 @@ describe('Append Conditions', () => { const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(ext.conditions?.length).to.equal(1); - const conditions:Array = [ + const conditions: Array = [ { - alias: 'Umb.Test.Condition.Valid' + alias: 'Umb.Test.Condition.Valid', }, { alias: 'Umb.Condition.WorkspaceAlias', - match: 'Umb.Workspace.Document' - } as WorkspaceAliasConditionConfig - ] + match: 'Umb.Workspace.Document', + } as WorkspaceAliasConditionConfig, + ]; await extensionRegistry.appendConditions('Umb.Test.Section.1', conditions); @@ -560,87 +564,89 @@ describe('Prepend Conditions', () => { let manifests: Array; beforeEach(() => { - extensionRegistry = new UmbExtensionRegistry(); - manifests = [ - { - type: 'section', - name: 'test-section-1', - alias: 'Umb.Test.Section.1', - weight: 1, - conditions: [ - { - alias: "Umb.Test.Condition.Valid" - } - ] - }, + extensionRegistry = new UmbExtensionRegistry(); + manifests = [ + { + type: 'section', + name: 'test-section-1', + alias: 'Umb.Test.Section.1', + weight: 1, + conditions: [ { - type: 'section', - name: 'test-section-2', - alias: 'Umb.Test.Section.2', - weight: 200 + alias: 'Umb.Test.Condition.Valid', }, - ]; + ], + }, + { + type: 'section', + name: 'test-section-2', + alias: 'Umb.Test.Section.2', + weight: 200, + }, + ]; - manifests.forEach((manifest) => extensionRegistry.register(manifest)); + manifests.forEach((manifest) => extensionRegistry.register(manifest)); - extensionRegistry.register({ - type: 'condition', - name: 'test-condition-invalid', - alias: 'Umb.Test.Condition.Invalid' - }); + extensionRegistry.register({ + type: 'condition', + name: 'test-condition-invalid', + alias: 'Umb.Test.Condition.Invalid', + }); + }); + + it('should have the extensions registered', () => { + expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false; }); it('allows an extension condition to be prepended', async () => { - expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; - expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false; - - const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; - expect(ext.conditions?.length).to.equal(1); - - // Register new condition as if I was in my own entrypoint - extensionRegistry.register({ - type: 'condition', - name: 'test-condition-valid', - alias: 'Umb.Test.Condition.Valid' - }); - - // Prepend the new condition to the extension - const conditionToPrepend: UmbConditionConfigBase = { - alias: 'Umb.Test.Condition.Valid' - }; - - await extensionRegistry.prependCondition('Umb.Test.Section.1', conditionToPrepend); - - // Check new condition is registered - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; - - // Verify the extension now has two conditions and the new condition is prepended - const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; - expect(updatedExt.conditions?.length).to.equal(2); - expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); + const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(ext.conditions?.length).to.equal(1); + + // Register new condition as if I was in my own entrypoint + extensionRegistry.register({ + type: 'condition', + name: 'test-condition-valid', + alias: 'Umb.Test.Condition.Valid', + }); + + // Prepend the new condition to the extension + const conditionToPrepend: UmbConditionConfigBase = { + alias: 'Umb.Test.Condition.Valid', + }; + + await extensionRegistry.prependCondition('Umb.Test.Section.1', conditionToPrepend); + + // Check new condition is registered + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; + + // Verify the extension now has two conditions and the new condition is prepended + const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(updatedExt.conditions?.length).to.equal(2); + expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); }); it('allows an extension to update with multiple prepended conditions', async () => { - const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; - expect(ext.conditions?.length).to.equal(1); + const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(ext.conditions?.length).to.equal(1); - const conditions: Array = [ - { - alias: 'Umb.Test.Condition.Valid' - }, - { - alias: 'Umb.Condition.WorkspaceAlias', - match: 'Umb.Workspace.Document' - } as WorkspaceAliasConditionConfig - ]; + const conditions: Array = [ + { + alias: 'Umb.Test.Condition.Valid', + }, + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document', + } as WorkspaceAliasConditionConfig, + ]; - await extensionRegistry.prependConditions('Umb.Test.Section.1', conditions); + await extensionRegistry.prependConditions('Umb.Test.Section.1', conditions); - const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; - expect(extUpdated.conditions?.length).to.equal(3); - expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); - expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); + const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + expect(extUpdated.conditions?.length).to.equal(3); + expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); }); }); From 900e98b71446ad151e13d8d91cdd5efa070d4cee Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 30 Aug 2024 11:43:31 +0100 Subject: [PATCH 07/14] Rework of tests to pass and minor fix to _whenExtensionAliasIsRegistered to get the ext alias that matches --- .../registry/extension.registry.test.ts | 56 +++++++++++-------- .../registry/extension.registry.ts | 21 +++---- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index dd1e8612f9..e089f1bd17 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -475,7 +475,7 @@ describe('Append Conditions', () => { weight: 1, conditions: [ { - alias: 'Umb.Test.Condition.Valid', + alias: 'Umb.Test.Condition.Invalid', }, ], }, @@ -483,7 +483,7 @@ describe('Append Conditions', () => { type: 'section', name: 'test-section-2', alias: 'Umb.Test.Section.2', - weight: 200, + weight: 200 }, ]; @@ -523,19 +523,23 @@ describe('Append Conditions', () => { // Check new condition is registered expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; - // Verify the extension now has two conditions + // Verify the extension now has two conditions and in correct order with aliases const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(updatedExt.conditions?.length).to.equal(2); + expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid'); + expect(updatedExt.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); // Add a condition with a specific config to Section2 const workspaceCondition: WorkspaceAliasConditionConfig = { alias: 'Umb.Condition.WorkspaceAlias', match: 'Umb.Workspace.Document', }; + await extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition); const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(updatedWorkspaceExt.conditions?.length).to.equal(1); + expect(updatedWorkspaceExt.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); }); it('allows an extension to update with multiple conditions', async () => { @@ -555,7 +559,10 @@ describe('Append Conditions', () => { await extensionRegistry.appendConditions('Umb.Test.Section.1', conditions); const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; - expect(ext.conditions?.length).to.equal(3); + expect(extUpdated.conditions?.length).to.equal(3); + expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid'); + expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); + expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); }); }); @@ -582,59 +589,60 @@ describe('Prepend Conditions', () => { name: 'test-section-2', alias: 'Umb.Test.Section.2', weight: 200, + conditions: [ + { + alias: 'Umb.Test.Condition.Valid', + }, + ], }, ]; manifests.forEach((manifest) => extensionRegistry.register(manifest)); - - extensionRegistry.register({ - type: 'condition', - name: 'test-condition-invalid', - alias: 'Umb.Test.Condition.Invalid', - }); }); it('should have the extensions registered', () => { expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.false; expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false; }); it('allows an extension condition to be prepended', async () => { const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(ext.conditions?.length).to.equal(1); + expect(ext.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); - // Register new condition as if I was in my own entrypoint extensionRegistry.register({ type: 'condition', - name: 'test-condition-valid', - alias: 'Umb.Test.Condition.Valid', + name: 'test-condition-invalid', + alias: 'Umb.Test.Condition.Invalid', }); // Prepend the new condition to the extension const conditionToPrepend: UmbConditionConfigBase = { - alias: 'Umb.Test.Condition.Valid', + alias: 'Umb.Test.Condition.Invalid', }; await extensionRegistry.prependCondition('Umb.Test.Section.1', conditionToPrepend); // Check new condition is registered - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; + expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; // Verify the extension now has two conditions and the new condition is prepended const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(updatedExt.conditions?.length).to.equal(2); - expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); + expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid'); // Our new one prepended + expect(updatedExt.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); }); it('allows an extension to update with multiple prepended conditions', async () => { - const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + const ext = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(ext.conditions?.length).to.equal(1); + expect(ext.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); const conditions: Array = [ { - alias: 'Umb.Test.Condition.Valid', + alias: 'Umb.Test.Condition.Invalid', }, { alias: 'Umb.Condition.WorkspaceAlias', @@ -642,11 +650,15 @@ describe('Prepend Conditions', () => { } as WorkspaceAliasConditionConfig, ]; - await extensionRegistry.prependConditions('Umb.Test.Section.1', conditions); + await extensionRegistry.prependConditions('Umb.Test.Section.2', conditions); - const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; + const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(extUpdated.conditions?.length).to.equal(3); + + // The thing to note here our two new conditions is that are prepended in reverse order they are passed in + // as each one is prepended to the front of the array expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); - expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); + expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); + expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Test.Condition.Valid'); }); }); diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index e21077c4ab..5cd0dbcbe8 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -452,7 +452,8 @@ export class UmbExtensionRegistry< private async _whenExtensionAliasIsRegistered(alias: string): Promise { const source = this.extensions.pipe(filter((allExtensions) => allExtensions.some((ext) => ext.alias === alias))); const value = await firstValueFrom(source); - return value[0]; + const ext = value.find((ext) => ext.alias === alias) as ManifestBase; + return ext; } /** @@ -468,9 +469,6 @@ export class UmbExtensionRegistry< // Wait for the extension to be registered (as it could be registered late) const extensionToWaitFor = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; - // Got it... now carry on & mutate it - console.log('got the extension to update/mutate', extensionToWaitFor); - // Append the condition to the extensions conditions array if (extensionToWaitFor.conditions) { extensionToWaitFor.conditions.push(newCondition); @@ -486,10 +484,6 @@ export class UmbExtensionRegistry< // Update the main extensions collection/observable this._extensions.setValue(allExtensions); - - // Log the updated extensions for debugging - console.log('UPDATED extensions:', this._extensions.getValue()); - console.table(this._extensions.getValue()); } } catch (error) { // TODO: [WB] Will this ever catch an error? @@ -503,7 +497,9 @@ export class UmbExtensionRegistry< * @param newConditions {Array} - A collection of conditions to append to an extension. */ async appendConditions(alias: string, newConditions: Array): Promise { - newConditions.forEach((condition) => this.appendCondition(alias, condition)); + for (const condition of newConditions) { + await this.appendCondition(alias, condition); + } } /** @@ -516,9 +512,6 @@ export class UmbExtensionRegistry< // Wait for the extension to be registered (as it could be registered late) const extensionToUpdate = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; - // Got it... now carry on & mutate it - console.log('got the extension to update/mutate', extensionToUpdate); - // Append the condition to the extensions conditions array if (extensionToUpdate.conditions) { extensionToUpdate.conditions.unshift(newCondition); @@ -537,6 +530,8 @@ export class UmbExtensionRegistry< * @param newConditions {Array} - A collection of conditions to prepend to an extension. */ async prependConditions(alias: string, newConditions: Array): Promise { - newConditions.forEach((condition) => this.prependCondition(alias, condition)); + for (const condition of newConditions) { + await this.prependCondition(alias, condition); + } } } From 7a0a86e32b6da54523a8f02cac909c5d16c68f07 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 30 Aug 2024 13:27:48 +0100 Subject: [PATCH 08/14] Trying to add a test to show that a late registered extension works - but test says otherwise compared to using it in entrypoint --- .../registry/extension.registry.test.ts | 46 ++++++++++++++++++- src/packages/property-editors/entry-point.ts | 20 +++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index e089f1bd17..b38fdac8b2 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -483,7 +483,7 @@ describe('Append Conditions', () => { type: 'section', name: 'test-section-2', alias: 'Umb.Test.Section.2', - weight: 200 + weight: 200, }, ]; @@ -661,4 +661,48 @@ describe('Prepend Conditions', () => { expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Test.Condition.Valid'); }); + + it('allows conditions to be prepended when an extension is loaded later on', async () => { + const conditions: Array = [ + { + alias: 'Umb.Test.Condition.Invalid', + }, + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document', + } as WorkspaceAliasConditionConfig, + ]; + + console.log('About to go KABOOM..'); + + // Prepend the conditions + // [WB] HELP: Why is this fine when using in in an entrypoint + // /src/packages/property-editors/entry-point.ts + // But this TEST implodes if it can't find the extension that is not yet registered + await extensionRegistry.prependConditions('Late.Extension.To.Be.Loaded', conditions); + + // Make sure the extension is not registered YET + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false; + + // Register the extension LATE/after the conditions have been added + extensionRegistry.register({ + type: 'section', + name: 'Late Section Extension with one condition', + alias: 'Late.Extension.To.Be.Loaded', + weight: 200, + conditions: [ + { + alias: 'Umb.Test.Condition.Valid', + }, + ], + }); + + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true; + + const extUpdated = extensionRegistry.getByAlias('Late.Extension.To.Be.Loaded') as ManifestWithDynamicConditions; + expect(extUpdated.conditions?.length).to.equal(3); + expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); + expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Test.Condition.Valid'); + }); }); diff --git a/src/packages/property-editors/entry-point.ts b/src/packages/property-editors/entry-point.ts index baafe2af43..64d64bc9c4 100644 --- a/src/packages/property-editors/entry-point.ts +++ b/src/packages/property-editors/entry-point.ts @@ -1,4 +1,8 @@ -import type { ManifestWithDynamicConditions, UmbConditionConfigBase, UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; +import type { + ManifestWithDynamicConditions, + UmbConditionConfigBase, + UmbEntryPointOnInit, +} from '@umbraco-cms/backoffice/extension-api'; import type { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace'; import './checkbox-list/components/index.js'; @@ -13,9 +17,16 @@ export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { match: 'Umb.Workspace.WARRENYO', } as WorkspaceAliasConditionConfig; + console.log( + 'Should not be false and not registered', + _extensionRegistry.isRegistered('Umb.Dashboard.UmbracoNewsLATE'), + ); + _extensionRegistry.appendCondition('Umb.Dashboard.UmbracoNewsLATE', condition); - const ext:ManifestWithDynamicConditions = { + console.log('I HAZ APPENED CONDITIONS'); + + const ext: ManifestWithDynamicConditions = { alias: 'Umb.Dashboard.UmbracoNewsLATE', type: 'dashboard', name: 'WARREN Package', @@ -30,4 +41,9 @@ export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { _extensionRegistry.register(ext); + const amIRegistered = _extensionRegistry.isRegistered('Umb.Dashboard.UmbracoNewsLATE'); + console.log('Should be true and registered', amIRegistered); + + const getTheThing = _extensionRegistry.getByAlias('Umb.Dashboard.UmbracoNewsLATE'); + console.log('Should be the extension', getTheThing); }; From d902e2c8b7a342bae9b901d02cdc05515ca31709 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 2 Sep 2024 17:18:21 +0100 Subject: [PATCH 09/14] Remove the prepend conditions and rename append to addCondition and addCondtions As the order of conditions is not important, as all conditions set on an extensions must be true/valid --- .../registry/extension.registry.ts | 43 +++---------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index 5cd0dbcbe8..26b483b032 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -457,14 +457,14 @@ export class UmbExtensionRegistry< } /** - * Appends a new condition to an existing extension + * Add a new condition to an existing extension * Useful to add a condition for example the Save And Publish workspace action shipped by core * As overwriting the whole extension to simply add an extra condition is not ideal as it causes a lot of duplicated code * and could easily get out of sync from the CMS core implementation if a 3rd party dev was to try and overwrite it * @param alias {string} - The alias of the extension to append the condition to * @param newCondition {UmbConditionConfigBase} - The condition to append to the extension. */ - async appendCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { + async addCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { try { // Wait for the extension to be registered (as it could be registered late) const extensionToWaitFor = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; @@ -492,46 +492,13 @@ export class UmbExtensionRegistry< } /** - * Appends a collection of conditions to an exsiting extension + * Adds a collection of conditions to an exsiting extension * @param alias {string} - The alias of the extension to append the condition to * @param newConditions {Array} - A collection of conditions to append to an extension. */ - async appendConditions(alias: string, newConditions: Array): Promise { + async addConditions(alias: string, newConditions: Array): Promise { for (const condition of newConditions) { - await this.appendCondition(alias, condition); - } - } - - /** - * Prepends a new condition to an existing extension - * @param alias {string} - The alias of the extension to prepend the condition to - * @param newCondition {UmbConditionConfigBase} - The condition to prepend to the extension. - */ - async prependCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { - try { - // Wait for the extension to be registered (as it could be registered late) - const extensionToUpdate = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; - - // Append the condition to the extensions conditions array - if (extensionToUpdate.conditions) { - extensionToUpdate.conditions.unshift(newCondition); - } else { - extensionToUpdate.conditions = [newCondition]; - } - } catch (error) { - // TODO: [WB] Will this ever catch an error? - console.error(`Extension with alias ${alias} was never found and threw ${error}`); - } - } - - /** - * Prepends a collection of conditions to an existing extension - * @param alias {string} - The alias of the extension to prepend the conditions to - * @param newConditions {Array} - A collection of conditions to prepend to an extension. - */ - async prependConditions(alias: string, newConditions: Array): Promise { - for (const condition of newConditions) { - await this.prependCondition(alias, condition); + await this.addCondition(alias, condition); } } } From 17b9ee517da2ba5ab1ec3f6f6951c814fd8d470e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 2 Sep 2024 20:03:34 +0100 Subject: [PATCH 10/14] Update tests Still need to uncomment & reinvestigate the late loading test --- .../registry/extension.registry.test.ts | 191 +++++------------- 1 file changed, 47 insertions(+), 144 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index b38fdac8b2..feb27191c8 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -461,7 +461,7 @@ describe('UmbExtensionRegistry with exclusions', () => { }); }); -describe('Append Conditions', () => { +describe('Add Conditions', () => { let extensionRegistry: UmbExtensionRegistry; let manifests: Array; @@ -518,7 +518,7 @@ describe('Append Conditions', () => { const conditionToAdd: UmbConditionConfigBase = { alias: 'Umb.Test.Condition.Valid', }; - await extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd); + await extensionRegistry.addCondition('Umb.Test.Section.1', conditionToAdd); // Check new condition is registered expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; @@ -535,7 +535,7 @@ describe('Append Conditions', () => { match: 'Umb.Workspace.Document', }; - await extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition); + await extensionRegistry.addCondition('Umb.Test.Section.2', workspaceCondition); const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(updatedWorkspaceExt.conditions?.length).to.equal(1); @@ -556,7 +556,7 @@ describe('Append Conditions', () => { } as WorkspaceAliasConditionConfig, ]; - await extensionRegistry.appendConditions('Umb.Test.Section.1', conditions); + await extensionRegistry.addConditions('Umb.Test.Section.1', conditions); const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(extUpdated.conditions?.length).to.equal(3); @@ -564,145 +564,48 @@ describe('Append Conditions', () => { expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); }); -}); - -describe('Prepend Conditions', () => { - let extensionRegistry: UmbExtensionRegistry; - let manifests: Array; - - beforeEach(() => { - extensionRegistry = new UmbExtensionRegistry(); - manifests = [ - { - type: 'section', - name: 'test-section-1', - alias: 'Umb.Test.Section.1', - weight: 1, - conditions: [ - { - alias: 'Umb.Test.Condition.Valid', - }, - ], - }, - { - type: 'section', - name: 'test-section-2', - alias: 'Umb.Test.Section.2', - weight: 200, - conditions: [ - { - alias: 'Umb.Test.Condition.Valid', - }, - ], - }, - ]; - - manifests.forEach((manifest) => extensionRegistry.register(manifest)); - }); - - it('should have the extensions registered', () => { - expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; - expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true; - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.false; - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false; - }); - - it('allows an extension condition to be prepended', async () => { - const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; - expect(ext.conditions?.length).to.equal(1); - expect(ext.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); - - extensionRegistry.register({ - type: 'condition', - name: 'test-condition-invalid', - alias: 'Umb.Test.Condition.Invalid', - }); - - // Prepend the new condition to the extension - const conditionToPrepend: UmbConditionConfigBase = { - alias: 'Umb.Test.Condition.Invalid', - }; - - await extensionRegistry.prependCondition('Umb.Test.Section.1', conditionToPrepend); - - // Check new condition is registered - expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true; - - // Verify the extension now has two conditions and the new condition is prepended - const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; - expect(updatedExt.conditions?.length).to.equal(2); - expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid'); // Our new one prepended - expect(updatedExt.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); - }); - - it('allows an extension to update with multiple prepended conditions', async () => { - const ext = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; - expect(ext.conditions?.length).to.equal(1); - expect(ext.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); - - const conditions: Array = [ - { - alias: 'Umb.Test.Condition.Invalid', - }, - { - alias: 'Umb.Condition.WorkspaceAlias', - match: 'Umb.Workspace.Document', - } as WorkspaceAliasConditionConfig, - ]; - - await extensionRegistry.prependConditions('Umb.Test.Section.2', conditions); - - const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; - expect(extUpdated.conditions?.length).to.equal(3); - - // The thing to note here our two new conditions is that are prepended in reverse order they are passed in - // as each one is prepended to the front of the array - expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); - expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); - expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Test.Condition.Valid'); - }); - - it('allows conditions to be prepended when an extension is loaded later on', async () => { - const conditions: Array = [ - { - alias: 'Umb.Test.Condition.Invalid', - }, - { - alias: 'Umb.Condition.WorkspaceAlias', - match: 'Umb.Workspace.Document', - } as WorkspaceAliasConditionConfig, - ]; - console.log('About to go KABOOM..'); - - // Prepend the conditions - // [WB] HELP: Why is this fine when using in in an entrypoint - // /src/packages/property-editors/entry-point.ts - // But this TEST implodes if it can't find the extension that is not yet registered - await extensionRegistry.prependConditions('Late.Extension.To.Be.Loaded', conditions); - - // Make sure the extension is not registered YET - expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false; - - // Register the extension LATE/after the conditions have been added - extensionRegistry.register({ - type: 'section', - name: 'Late Section Extension with one condition', - alias: 'Late.Extension.To.Be.Loaded', - weight: 200, - conditions: [ - { - alias: 'Umb.Test.Condition.Valid', - }, - ], - }); - - expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true; - - const extUpdated = extensionRegistry.getByAlias('Late.Extension.To.Be.Loaded') as ManifestWithDynamicConditions; - expect(extUpdated.conditions?.length).to.equal(3); - expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); - expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); - expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Test.Condition.Valid'); - }); + // it('allows conditions to be prepended when an extension is loaded later on', async () => { + // const conditions: Array = [ + // { + // alias: 'Umb.Test.Condition.Invalid', + // }, + // { + // alias: 'Umb.Condition.WorkspaceAlias', + // match: 'Umb.Workspace.Document', + // } as WorkspaceAliasConditionConfig, + // ]; + + // console.log('About to go KABOOM..'); + + // // Prepend the conditions + // // [WB] HELP: Why is this fine when using in in an entrypoint + // // /src/packages/property-editors/entry-point.ts + // // But this TEST implodes if it can't find the extension that is not yet registered + // await extensionRegistry.addConditions('Late.Extension.To.Be.Loaded', conditions); + + // // Make sure the extension is not registered YET + // expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false; + + // // Register the extension LATE/after the conditions have been added + // extensionRegistry.register({ + // type: 'section', + // name: 'Late Section Extension with one condition', + // alias: 'Late.Extension.To.Be.Loaded', + // weight: 200, + // conditions: [ + // { + // alias: 'Umb.Test.Condition.Valid', + // }, + // ], + // }); + + // expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true; + + // const extUpdated = extensionRegistry.getByAlias('Late.Extension.To.Be.Loaded') as ManifestWithDynamicConditions; + // expect(extUpdated.conditions?.length).to.equal(3); + // expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + // expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); + // expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Test.Condition.Valid'); + // }); }); From ccd4dd44943a2a16452b90b70551da95d1da27b4 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 3 Sep 2024 19:38:45 +0100 Subject: [PATCH 11/14] Fix up code so test passes & my rough entrypoint demo of usage works --- src/packages/property-editors/entry-point.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/property-editors/entry-point.ts b/src/packages/property-editors/entry-point.ts index 64d64bc9c4..f83895ac9b 100644 --- a/src/packages/property-editors/entry-point.ts +++ b/src/packages/property-editors/entry-point.ts @@ -22,9 +22,9 @@ export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { _extensionRegistry.isRegistered('Umb.Dashboard.UmbracoNewsLATE'), ); - _extensionRegistry.appendCondition('Umb.Dashboard.UmbracoNewsLATE', condition); + _extensionRegistry.addCondition('Umb.Dashboard.UmbracoNewsLATE', condition); - console.log('I HAZ APPENED CONDITIONS'); + console.log('I HAZ ADDED CONDITION'); const ext: ManifestWithDynamicConditions = { alias: 'Umb.Dashboard.UmbracoNewsLATE', From 6f0df55f3cdffb65412db644dc28eb319359a114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 12 Sep 2024 20:38:05 +0200 Subject: [PATCH 12/14] import type --- src/libs/extension-api/registry/extension.registry.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index feb27191c8..7f1d48752a 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -1,4 +1,4 @@ -import { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace'; +import type { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace'; import type { ManifestElementWithElementName, ManifestKind, From 46ec82674204832db9984eeee5b2c4d66f0b8ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 12 Sep 2024 22:01:34 +0200 Subject: [PATCH 13/14] remove test code --- src/packages/property-editors/entry-point.ts | 47 -------------------- 1 file changed, 47 deletions(-) diff --git a/src/packages/property-editors/entry-point.ts b/src/packages/property-editors/entry-point.ts index f83895ac9b..9745b0d139 100644 --- a/src/packages/property-editors/entry-point.ts +++ b/src/packages/property-editors/entry-point.ts @@ -1,49 +1,2 @@ -import type { - ManifestWithDynamicConditions, - UmbConditionConfigBase, - UmbEntryPointOnInit, -} from '@umbraco-cms/backoffice/extension-api'; -import type { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace'; - import './checkbox-list/components/index.js'; import './content-picker/components/index.js'; - -export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { - - console.log('HELLO AGAIN'); - - const condition: UmbConditionConfigBase = { - alias: 'Umb.Condition.WorkspaceAlias', - match: 'Umb.Workspace.WARRENYO', - } as WorkspaceAliasConditionConfig; - - console.log( - 'Should not be false and not registered', - _extensionRegistry.isRegistered('Umb.Dashboard.UmbracoNewsLATE'), - ); - - _extensionRegistry.addCondition('Umb.Dashboard.UmbracoNewsLATE', condition); - - console.log('I HAZ ADDED CONDITION'); - - const ext: ManifestWithDynamicConditions = { - alias: 'Umb.Dashboard.UmbracoNewsLATE', - type: 'dashboard', - name: 'WARREN Package', - weight: 100, - conditions: [ - { - alias: 'Umb.Condition.WorkspaceAlias', - match: 'Umb.Workspace.LATE-COMER-EXISTING', - } as WorkspaceAliasConditionConfig, - ], - }; - - _extensionRegistry.register(ext); - - const amIRegistered = _extensionRegistry.isRegistered('Umb.Dashboard.UmbracoNewsLATE'); - console.log('Should be true and registered', amIRegistered); - - const getTheThing = _extensionRegistry.getByAlias('Umb.Dashboard.UmbracoNewsLATE'); - console.log('Should be the extension', getTheThing); -}; From d8c59cbc466ae48587c7b40e02037039db6ee07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 12 Sep 2024 22:01:45 +0200 Subject: [PATCH 14/14] refactor for a more direct appending implementation --- .../registry/extension.registry.test.ts | 164 +++++++++++++----- .../registry/extension.registry.ts | 120 ++++++------- 2 files changed, 178 insertions(+), 106 deletions(-) diff --git a/src/libs/extension-api/registry/extension.registry.test.ts b/src/libs/extension-api/registry/extension.registry.test.ts index 7f1d48752a..caed039f60 100644 --- a/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/libs/extension-api/registry/extension.registry.test.ts @@ -518,7 +518,7 @@ describe('Add Conditions', () => { const conditionToAdd: UmbConditionConfigBase = { alias: 'Umb.Test.Condition.Valid', }; - await extensionRegistry.addCondition('Umb.Test.Section.1', conditionToAdd); + await extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd); // Check new condition is registered expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true; @@ -529,13 +529,17 @@ describe('Add Conditions', () => { expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid'); expect(updatedExt.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); + // Verify the other extension was not updated: + const otherExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; + expect(otherExt.conditions).to.be.undefined; + // Add a condition with a specific config to Section2 const workspaceCondition: WorkspaceAliasConditionConfig = { alias: 'Umb.Condition.WorkspaceAlias', match: 'Umb.Workspace.Document', }; - await extensionRegistry.addCondition('Umb.Test.Section.2', workspaceCondition); + await extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition); const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(updatedWorkspaceExt.conditions?.length).to.equal(1); @@ -556,7 +560,7 @@ describe('Add Conditions', () => { } as WorkspaceAliasConditionConfig, ]; - await extensionRegistry.addConditions('Umb.Test.Section.1', conditions); + await extensionRegistry.appendConditions('Umb.Test.Section.1', conditions); const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions; expect(extUpdated.conditions?.length).to.equal(3); @@ -565,47 +569,115 @@ describe('Add Conditions', () => { expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); }); - // it('allows conditions to be prepended when an extension is loaded later on', async () => { - // const conditions: Array = [ - // { - // alias: 'Umb.Test.Condition.Invalid', - // }, - // { - // alias: 'Umb.Condition.WorkspaceAlias', - // match: 'Umb.Workspace.Document', - // } as WorkspaceAliasConditionConfig, - // ]; - - // console.log('About to go KABOOM..'); - - // // Prepend the conditions - // // [WB] HELP: Why is this fine when using in in an entrypoint - // // /src/packages/property-editors/entry-point.ts - // // But this TEST implodes if it can't find the extension that is not yet registered - // await extensionRegistry.addConditions('Late.Extension.To.Be.Loaded', conditions); - - // // Make sure the extension is not registered YET - // expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false; - - // // Register the extension LATE/after the conditions have been added - // extensionRegistry.register({ - // type: 'section', - // name: 'Late Section Extension with one condition', - // alias: 'Late.Extension.To.Be.Loaded', - // weight: 200, - // conditions: [ - // { - // alias: 'Umb.Test.Condition.Valid', - // }, - // ], - // }); - - // expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true; - - // const extUpdated = extensionRegistry.getByAlias('Late.Extension.To.Be.Loaded') as ManifestWithDynamicConditions; - // expect(extUpdated.conditions?.length).to.equal(3); - // expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); - // expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); - // expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Test.Condition.Valid'); - // }); + it('allows conditions to be prepended when an extension is loaded later on', async () => { + const conditions: Array = [ + { + alias: 'Umb.Test.Condition.Invalid', + }, + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document', + } as WorkspaceAliasConditionConfig, + ]; + + // Prepend the conditions, but do not await this. + extensionRegistry.appendConditions('Late.Extension.To.Be.Loaded', conditions); + + // Make sure the extension is not registered YET + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false; + + // Register the extension LATE/after the conditions have been added + extensionRegistry.register({ + type: 'section', + name: 'Late Section Extension with one condition', + alias: 'Late.Extension.To.Be.Loaded', + weight: 200, + conditions: [ + { + alias: 'Umb.Test.Condition.Valid', + }, + ], + }); + + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true; + + const extUpdated = extensionRegistry.getByAlias('Late.Extension.To.Be.Loaded') as ManifestWithDynamicConditions; + + expect(extUpdated.conditions?.length).to.equal(3); + expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); + expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); + expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + }); + + /** + * As of current state, it is by design without further reasons to why, but it is made so additional conditions are only added to a current or next time registered manifest. + * Meaning if it happens to be unregistered and re-registered it does not happen again. + * Unless the exact same appending of conditions happens again. [NL] + * + * This makes sense if extensions gets offloaded and re-registered, but the extension that registered additional conditions didn't get loaded/registered second time. Therefor they need to be re-registered for such to work. [NL] + */ + it('only append conditions to the next time the extension is registered', async () => { + const conditions: Array = [ + { + alias: 'Umb.Test.Condition.Invalid', + }, + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document', + } as WorkspaceAliasConditionConfig, + ]; + + // Prepend the conditions, but do not await this. + extensionRegistry.appendConditions('Late.Extension.To.Be.Loaded', conditions); + + // Make sure the extension is not registered YET + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false; + + // Register the extension LATE/after the conditions have been added + extensionRegistry.register({ + type: 'section', + name: 'Late Section Extension with one condition', + alias: 'Late.Extension.To.Be.Loaded', + weight: 200, + conditions: [ + { + alias: 'Umb.Test.Condition.Valid', + }, + ], + }); + + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true; + + const extUpdateFirstTime = extensionRegistry.getByAlias( + 'Late.Extension.To.Be.Loaded', + ) as ManifestWithDynamicConditions; + expect(extUpdateFirstTime.conditions?.length).to.equal(3); + + extensionRegistry.unregister('Late.Extension.To.Be.Loaded'); + + // Make sure the extension is not registered YET + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false; + + // Register the extension LATE/after the conditions have been added + extensionRegistry.register({ + type: 'section', + name: 'Late Section Extension with one condition', + alias: 'Late.Extension.To.Be.Loaded', + weight: 200, + conditions: [ + { + alias: 'Umb.Test.Condition.Valid', + }, + ], + }); + + expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true; + + const extUpdateSecondTime = extensionRegistry.getByAlias( + 'Late.Extension.To.Be.Loaded', + ) as ManifestWithDynamicConditions; + + expect(extUpdateSecondTime.conditions?.length).to.equal(1); + expect(extUpdateSecondTime.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); + }); }); diff --git a/src/libs/extension-api/registry/extension.registry.ts b/src/libs/extension-api/registry/extension.registry.ts index 26b483b032..e1535825a4 100644 --- a/src/libs/extension-api/registry/extension.registry.ts +++ b/src/libs/extension-api/registry/extension.registry.ts @@ -7,15 +7,7 @@ import type { import type { SpecificManifestTypeOrManifestBase } from '../types/map.types.js'; import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { - map, - distinctUntilChanged, - combineLatest, - of, - switchMap, - filter, - firstValueFrom, -} from '@umbraco-cms/backoffice/external/rxjs'; +import { map, distinctUntilChanged, combineLatest, of, switchMap } from '@umbraco-cms/backoffice/external/rxjs'; /** * @@ -113,8 +105,26 @@ export class UmbExtensionRegistry< private _kinds = new UmbBasicState>>([]); public readonly kinds = this._kinds.asObservable(); + #exclusions: Array = []; + #additionalConditions: Map> = new Map(); + #appendAdditionalConditions(manifest: ManifestTypes) { + const newConditions = this.#additionalConditions.get(manifest.alias); + if (newConditions) { + // Append the condition to the extensions conditions array + if ((manifest as ManifestWithDynamicConditions).conditions) { + for (const condition of newConditions) { + (manifest as ManifestWithDynamicConditions).conditions!.push(condition); + } + } else { + (manifest as ManifestWithDynamicConditions).conditions = newConditions; + } + this.#additionalConditions.delete(manifest.alias); + } + return manifest; + } + defineKind(kind: ManifestKind): void { const extensionsValues = this._extensions.getValue(); const extension = extensionsValues.find( @@ -149,12 +159,25 @@ export class UmbExtensionRegistry< }; register(manifest: ManifestTypes | ManifestKind): void { - const isValid = this.#checkExtension(manifest); + const isValid = this.#validateExtension(manifest); if (!isValid) { return; } - this._extensions.setValue([...this._extensions.getValue(), manifest as ManifestTypes]); + if (manifest.type === 'kind') { + this.defineKind(manifest as ManifestKind); + return; + } + + const isApproved = this.#isExtensionApproved(manifest); + if (!isApproved) { + return; + } + + this._extensions.setValue([ + ...this._extensions.getValue(), + this.#appendAdditionalConditions(manifest as ManifestTypes), + ]); } getAllExtensions(): Array { @@ -190,7 +213,7 @@ export class UmbExtensionRegistry< return false; } - #checkExtension(manifest: ManifestTypes | ManifestKind): boolean { + #validateExtension(manifest: ManifestTypes | ManifestKind): boolean { if (!manifest.type) { console.error(`Extension is missing type`, manifest); return false; @@ -201,11 +224,9 @@ export class UmbExtensionRegistry< return false; } - if (manifest.type === 'kind') { - this.defineKind(manifest as ManifestKind); - return false; - } - + return true; + } + #isExtensionApproved(manifest: ManifestTypes | ManifestKind): boolean { if (!this.#acceptExtension(manifest as ManifestTypes)) { return false; } @@ -445,60 +466,39 @@ export class UmbExtensionRegistry< } /** - * Returns a promise that resolves when the extension with the specified alias is found. - * @param alias {string} - The alias of the extension to wait for. - * @returns {Promise} - A promise that resolves with the extension. + * Append a new condition to an existing extension + * Useful to add a condition for example the Save And Publish workspace action shipped by core. + * @param {string} alias - The alias of the extension to append the condition to. + * @param {UmbConditionConfigBase} newCondition - The condition to append to the extension. */ - private async _whenExtensionAliasIsRegistered(alias: string): Promise { - const source = this.extensions.pipe(filter((allExtensions) => allExtensions.some((ext) => ext.alias === alias))); - const value = await firstValueFrom(source); - const ext = value.find((ext) => ext.alias === alias) as ManifestBase; - return ext; + appendCondition(alias: string, newCondition: UmbConditionConfigBase) { + this.appendConditions(alias, [newCondition]); } /** - * Add a new condition to an existing extension - * Useful to add a condition for example the Save And Publish workspace action shipped by core - * As overwriting the whole extension to simply add an extra condition is not ideal as it causes a lot of duplicated code - * and could easily get out of sync from the CMS core implementation if a 3rd party dev was to try and overwrite it - * @param alias {string} - The alias of the extension to append the condition to - * @param newCondition {UmbConditionConfigBase} - The condition to append to the extension. + * Appends an array of conditions to an existing extension + * @param {string} alias - The alias of the extension to append the condition to + * @param {Array} newConditions - An array of conditions to be appended to an extension manifest. */ - async addCondition(alias: string, newCondition: UmbConditionConfigBase): Promise { - try { - // Wait for the extension to be registered (as it could be registered late) - const extensionToWaitFor = (await this._whenExtensionAliasIsRegistered(alias)) as ManifestWithDynamicConditions; - - // Append the condition to the extensions conditions array - if (extensionToWaitFor.conditions) { - extensionToWaitFor.conditions.push(newCondition); - } else { - extensionToWaitFor.conditions = [newCondition]; - } + appendConditions(alias: string, newConditions: Array) { + const existingConditionsToBeAdded = this.#additionalConditions.get(alias); + this.#additionalConditions.set( + alias, + existingConditionsToBeAdded ? [...existingConditionsToBeAdded, ...newConditions] : newConditions, + ); - const allExtensions = this._extensions.getValue(); - const extensionToUpdateIndex = allExtensions.findIndex((ext) => ext.alias === alias); - if (extensionToUpdateIndex !== -1) { + const allExtensions = this._extensions.getValue(); + for (const extension of allExtensions) { + if (extension.alias === alias) { // Replace the existing extension with the updated one - allExtensions[extensionToUpdateIndex] = extensionToWaitFor as ManifestTypes; + allExtensions[allExtensions.indexOf(extension)] = this.#appendAdditionalConditions(extension as ManifestTypes); // Update the main extensions collection/observable this._extensions.setValue(allExtensions); - } - } catch (error) { - // TODO: [WB] Will this ever catch an error? - console.error(`Extension with alias ${alias} was never found and threw ${error}`); - } - } - /** - * Adds a collection of conditions to an exsiting extension - * @param alias {string} - The alias of the extension to append the condition to - * @param newConditions {Array} - A collection of conditions to append to an extension. - */ - async addConditions(alias: string, newConditions: Array): Promise { - for (const condition of newConditions) { - await this.addCondition(alias, condition); + //Stop the search: + break; + } } } }