Skip to content

hotfix: Correct context tokens for menu structure based workspaces #19172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5e6954f
setup context tokens to support menu structure workspace
nielslyngsoe Apr 27, 2025
9643cf5
Merge branch 'release/16.0' into v16/hotfix/setup-context-tokens-for-…
nielslyngsoe Apr 29, 2025
1bf917b
remove self
madsrasmussen May 1, 2025
601366a
Update menu-variant-tree-structure-workspace-context-base.ts
madsrasmussen May 1, 2025
20ac20a
Update menu-variant-tree-structure-workspace-context-base.ts
madsrasmussen May 1, 2025
a2c21e7
Merge branch 'release/16.0' into v16/hotfix/remove-self-from-ancestor…
nielslyngsoe May 5, 2025
06c653b
add parent entity context
madsrasmussen May 6, 2025
08b3004
provide parent context from structure context
madsrasmussen May 6, 2025
9e6f5df
Merge branch 'release/16.0' into v16/hotfix/setup-context-tokens-for-…
madsrasmussen May 6, 2025
8a21601
Merge branch 'v16/hotfix/setup-context-tokens-for-menu-structure-base…
madsrasmussen May 6, 2025
fb4897e
provide parent context from tree item
madsrasmussen May 6, 2025
2f25d50
provide parent for collections
madsrasmussen May 6, 2025
cc61e06
provide parent for sidebar menu with entity actions
madsrasmussen May 6, 2025
9145b42
revert name change
madsrasmussen May 6, 2025
3cc68e7
revert name change
madsrasmussen May 6, 2025
8dc4080
Merge branch 'v16/hotfix/remove-self-from-ancestor-entity-context-whe…
madsrasmussen May 6, 2025
1743109
revert rename of file
madsrasmussen May 6, 2025
2b8eaff
align naming across context tokens
madsrasmussen May 6, 2025
74d8b58
align base classes
madsrasmussen May 6, 2025
debad4b
add todos
madsrasmussen May 6, 2025
d517dd4
add deprecation warnings
madsrasmussen May 6, 2025
8c3b773
update method names
madsrasmussen May 6, 2025
267d46a
deprecate parent from workspace and introduce "create under parent" i…
madsrasmussen May 6, 2025
d70f7be
add comment
madsrasmussen May 6, 2025
b5467dd
remove unused
madsrasmussen May 6, 2025
bcda060
set to warn
madsrasmussen May 6, 2025
82dfe0c
clean up
madsrasmussen May 6, 2025
7e5e1f8
Merge branch 'release/16.0' into v16/hotfix/setup-context-tokens-for-…
madsrasmussen May 7, 2025
4da4b87
Merge branch 'release/16.0' into v16/hotfix/setup-context-tokens-for-…
madsrasmussen May 7, 2025
a1c5f18
Merge branch 'release/16.0' into v16/hotfix/setup-context-tokens-for-…
madsrasmussen May 8, 2025
916e633
mark as public but internal
madsrasmussen May 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Umbraco.Web.UI.Client/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default [
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-import-type-side-effects': 'warn',
'@typescript-eslint/no-deprecated': 'warn',
},
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export abstract class UmbContentTypeWorkspaceContextBase<
): Promise<DetailModelType | undefined> {
this.resetState();
this.loading.addState({ unique: LOADING_STATE_UNIQUE, message: `Creating ${this.getEntityType()} scaffold` });
this.setParent(args.parent);
this._internal_setCreateUnderParent(args.parent);

const request = this.structure.createScaffold(args.preset);
this._getDataPromise = request;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<

// We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL]
if (this.getIsNew()) {
const parent = this.getParent();
const parent = this._internal_getCreateUnderParent();
if (!parent) throw new Error('Parent is not set');
await this.#serverValidation.askServerForValidation(
saveData,
Expand Down Expand Up @@ -885,7 +885,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
async #create(variantIds: Array<UmbVariantId>, saveData: DetailModelType) {
if (!this._detailRepository) throw new Error('Detail repository is not set');

const parent = this.getParent();
const parent = this._internal_getCreateUnderParent();
if (!parent) throw new Error('Parent is not set');

const { data, error } = await this._detailRepository.create(saveData, parent.unique);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from '@umbraco-cms/backoffice/entity-action';
import type { UmbActionEventContext } from '@umbraco-cms/backoffice/action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { UMB_ENTITY_CONTEXT, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
import { UmbModalRouteRegistrationController, type UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router';

Expand Down Expand Up @@ -83,6 +83,7 @@ export class UmbDefaultCollectionContext<
});

#actionEventContext: UmbActionEventContext | undefined;
#parentEntityContext = new UmbParentEntityContext(this);

constructor(host: UmbControllerHost, defaultViewAlias: string, defaultFilter: Partial<FilterModelType> = {}) {
super(host, UMB_COLLECTION_CONTEXT);
Expand All @@ -92,6 +93,23 @@ export class UmbDefaultCollectionContext<

this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
this.#listenToEntityEvents();

// The parent entity context is used to get the parent entity for the collection items
// All items in the collection are children of the current entity context
this.consumeContext(UMB_ENTITY_CONTEXT, (context) => {
const currentEntityUnique = context?.getUnique();
const currentEntityType = context?.getEntityType();

const parent: UmbEntityModel | undefined =
currentEntityUnique && currentEntityType
? {
unique: currentEntityUnique,
entityType: currentEntityType,
}
: undefined;

this.#parentEntityContext?.setParent(parent);
});
}

setupView(viewElement: UmbControllerHost) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './contexts/ancestors/constants.js';
export * from './contexts/parent/constants.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { UMB_PARENT_ENTITY_CONTEXT } from './parent.entity-context-token.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { UmbParentEntityContext } from './parent.entity-context.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { UmbParentEntityContext } from './parent.entity-context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

export const UMB_PARENT_ENTITY_CONTEXT = new UmbContextToken<UmbParentEntityContext>('UmbParentEntityContext');
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { UmbEntityModel } from '../../types.js';
import { UMB_PARENT_ENTITY_CONTEXT } from './parent.entity-context-token.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';

/**
* A entity context for the parent
* @class UmbParentEntityContext
* @augments {UmbContextBase}
* @implements {UmbParentEntityContext}
*/
export class UmbParentEntityContext extends UmbContextBase {
#parent = new UmbObjectState<UmbEntityModel | undefined>(undefined);
parent = this.#parent.asObservable();

constructor(host: UmbControllerHost) {
super(host, UMB_PARENT_ENTITY_CONTEXT);
}

/**
* Gets the parent state
* @returns {UmbEntityModel | undefined} - The parent state
* @memberof UmbParentEntityContext
*/
getParent(): UmbEntityModel | undefined {
return this.#parent.getValue();
}

/**
* Sets the parent state
* @param {UmbEntityModel | undefined} parent - The parent state
* @memberof UmbParentEntityContext
*/
setParent(parent: UmbEntityModel | undefined): void {
this.#parent.setValue(parent);
}
}
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ export { UMB_ENTITY_CONTEXT } from './entity.context-token.js';
export { UmbEntityContext } from './entity.context.js';
export * from './constants.js';
export * from './contexts/ancestors/index.js';
export * from './contexts/parent/index.js';

export type * from './types.js';
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './components/index.js';
export * from './menu-tree-structure-workspace-context-base.js';
export * from './menu-structure-workspace-context.context-token.js';
export * from './menu-variant-structure-workspace-context.context-token.js';
export * from './menu-variant-tree-structure-workspace-context-base.js';

export type * from './types.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { UmbMenuStructureWorkspaceContext } from './menu-structure-workspace-context.interface.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

export const UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT = new UmbContextToken<UmbMenuStructureWorkspaceContext>(
'UmbWorkspaceContext',
'UmbMenuStructure',
);
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
import type { UmbStructureItemModel } from './types.js';
import { UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT } from './menu-structure-workspace-context.context-token.js';
import type { UmbTreeRepository, UmbTreeItemModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity';

interface UmbMenuTreeStructureWorkspaceContextBaseArgs {
treeRepositoryAlias: string;
}

// TODO: introduce base class for all menu structure workspaces to handle ancestors and parent
export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContextBase {
#workspaceContext?: typeof UMB_SUBMITTABLE_WORKSPACE_CONTEXT.TYPE;
#workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE;
#args: UmbMenuTreeStructureWorkspaceContextBaseArgs;

#structure = new UmbArrayState<UmbStructureItemModel>([], (x) => x.unique);
public readonly structure = this.#structure.asObservable();

#parent = new UmbObjectState<UmbStructureItemModel | undefined>(undefined);
/**
* @deprecated Will be removed in v.18: Use UMB_PARENT_ENTITY_CONTEXT instead.
*/
public readonly parent = this.#parent.asObservable();

#parentContext = new UmbParentEntityContext(this);
#ancestorContext = new UmbAncestorsEntityContext(this);

constructor(host: UmbControllerHost, args: UmbMenuTreeStructureWorkspaceContextBaseArgs) {
// TODO: set up context token
super(host, 'UmbMenuStructureWorkspaceContext');
super(host, UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT);
// 'UmbMenuStructureWorkspaceContext' is Obsolete, will be removed in v.18
this.provideContext('UmbMenuStructureWorkspaceContext', this);
this.#args = args;

// TODO: set up context token that supports parentEntityType, parentUnique, entityType.
this.consumeContext(UMB_SUBMITTABLE_WORKSPACE_CONTEXT, (instance) => {
this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.observe(this.#workspaceContext?.unique, (value) => {
if (!value) return;
Expand Down Expand Up @@ -59,35 +68,75 @@
const isNew = this.#workspaceContext?.getIsNew();

const entityTypeObservable = isNew
? (this.#workspaceContext as any)?.parentEntityType
: (this.#workspaceContext as any).entityType;
? this.#workspaceContext?._internal_createUnderParentEntityType
: this.#workspaceContext?.entityType;
const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string;
if (!entityType) throw new Error('Entity type is not available');

// If the entity type is different from the root entity type, then we can request the ancestors.
if (entityType !== root?.entityType) {
const uniqueObservable = isNew ? (this.#workspaceContext as any)?.parentUnique : this.#workspaceContext?.unique;
const uniqueObservable = isNew
? this.#workspaceContext?._internal_createUnderParentEntityUnique
: this.#workspaceContext?.unique;
const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string;
if (!unique) throw new Error('Unique is not available');

const { data } = await treeRepository.requestTreeItemAncestors({ treeItem: { unique, entityType } });

if (data) {
const ancestorItems = data.map((treeItem) => {
return {
unique: treeItem.unique,
entityType: treeItem.entityType,
name: treeItem.name,
isFolder: treeItem.isFolder,
};
});

structureItems.push(...ancestorItems);

this.#structure.setValue(structureItems);
this.#setParentData(structureItems);
this.#setAncestorData(data);
}
}
}

Check warning on line 103 in src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (release/16.0)

❌ Getting worse: Complex Method

UmbMenuTreeStructureWorkspaceContextBase.requestStructure increases in cyclomatic complexity from 17 to 18, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

#setParentData(structureItems: Array<UmbStructureItemModel>) {
/* If the item is not new, the current item is the last item in the array.
We filter out the current item unique to handle any case where it could show up */
const parent = structureItems.filter((item) => item.unique !== this.#workspaceContext?.getUnique()).pop();

const parent = structureItems[structureItems.length - 2];
// TODO: remove this when the parent gets removed from the structure interface
this.#parent.setValue(parent);
this.#structure.setValue(structureItems);

const parentEntity = parent
? {
unique: parent.unique,
entityType: parent.entityType,
}
: undefined;

this.#parentContext.setParent(parentEntity);
}

/* Notice: ancestors are based on the server "data" ancestors and are not based on the full Menu (UI) structure.
This will mean that any item placed in the data root will not have any ancestors. But will have a parent based on the UI structure.
*/
#setAncestorData(ancestors: Array<UmbTreeItemModel>) {
const ancestorEntities = ancestors
.map((treeItem) => {
const entity: UmbEntityModel = {
unique: treeItem.unique,
entityType: treeItem.entityType,
};

return entity;
})
/* If the item is not new, the current item is the last item in the array.
We filter out the current item unique to handle any case where it could show up */
.filter((item) => item.unique !== this.#workspaceContext?.getUnique());

this.#ancestorContext.setAncestors(ancestorEntities);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { UmbMenuVariantStructureWorkspaceContext } from './menu-variant-structure-workspace-context.interface.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

export const UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT =
new UmbContextToken<UmbMenuVariantStructureWorkspaceContext>(
'UmbWorkspaceContext',
'UmbMenuStructure',
(context): context is UmbMenuVariantStructureWorkspaceContext =>
'IS_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT' in context,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { UmbVariantStructureItemModel } from './types.js';
import type { UmbContext } from '@umbraco-cms/backoffice/class-api';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';

export interface UmbMenuVariantStructureWorkspaceContext extends UmbContext {
structure: Observable<UmbVariantStructureItemModel[]>;
}
Loading
Loading