Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public async Task<UserGroupResponseModel> CreateAsync(IUserGroup userGroup)
{
Id = userGroup.Key,
Name = userGroup.Name ?? string.Empty,
Description = userGroup.Description ?? string.Empty,
Alias = userGroup.Alias,
DocumentStartNode = ReferenceByIdModel.ReferenceOrNull(contentStartNodeKey),
DocumentRootAccess = contentRootAccess,
Expand Down Expand Up @@ -87,6 +88,7 @@ public async Task<UserGroupResponseModel> CreateAsync(IReadOnlyUserGroup userGro
{
Id = userGroup.Key,
Name = userGroup.Name ?? string.Empty,
Description = userGroup.Description ?? string.Empty,
Alias = userGroup.Alias,
DocumentStartNode = ReferenceByIdModel.ReferenceOrNull(contentStartNodeKey),
MediaStartNode = ReferenceByIdModel.ReferenceOrNull(mediaStartNodeKey),
Expand Down Expand Up @@ -132,6 +134,7 @@ public async Task<Attempt<IUserGroup, UserGroupOperationStatus>> CreateAsync(Cre
{
Name = CleanUserGroupNameOrAliasForXss(requestModel.Name),
Alias = CleanUserGroupNameOrAliasForXss(requestModel.Alias),
Description = requestModel.Description,
Icon = requestModel.Icon,
HasAccessToAllLanguages = requestModel.HasAccessToAllLanguages,
Permissions = requestModel.FallbackPermissions,
Expand Down Expand Up @@ -197,9 +200,10 @@ public async Task<Attempt<IUserGroup, UserGroupOperationStatus>> UpdateAsync(IUs

current.Name = CleanUserGroupNameOrAliasForXss(request.Name);
current.Alias = CleanUserGroupNameOrAliasForXss(request.Alias);
current.Description = request.Description;
current.Icon = request.Icon;
current.HasAccessToAllLanguages = request.HasAccessToAllLanguages;

current.Permissions = request.FallbackPermissions;
current.GranularPermissions = await _permissionPresentationFactory.CreatePermissionSetsAsync(request.Permissions);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;

namespace Umbraco.Cms.Api.Management.ViewModels.UserGroup;

Expand All @@ -22,6 +22,11 @@ public class UserGroupBase
/// </summary>
public required string Alias { get; init; }

/// <summary>
/// The description of the user group
/// </summary>
public string? Description { get; set; }

/// <summary>
/// The Icon for the user group
/// </summary>
Expand Down
9 changes: 4 additions & 5 deletions src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ public interface IReadOnlyUserGroup
{
string? Name { get; }

string Alias { get; }

string? Description { get { return null; } }

string? Icon { get; }

int Id { get; }
Expand All @@ -19,11 +23,6 @@ public interface IReadOnlyUserGroup

int? StartMediaId { get; }

/// <summary>
/// The alias
/// </summary>
string Alias { get; }

// This is set to return true as default to avoid breaking changes.
bool HasAccessToAllLanguages => true;

Expand Down
8 changes: 8 additions & 0 deletions src/Umbraco.Core/Models/Membership/IUserGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ public interface IUserGroup : IEntity, IRememberBeingDirty
/// </summary>
string? Name { get; set; }

/// <summary>
/// The description
/// </summary>
string? Description {
get => null;
set { }
}

/// <summary>
/// If this property is true it will give the group access to all languages
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public bool Equals(ReadOnlyUserGroup? other)

public string Name { get; }

public string? Description { get; }

public string? Icon { get; }

public int? StartContentId { get; }
Expand Down
8 changes: 8 additions & 0 deletions src/Umbraco.Core/Models/Membership/UserGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class UserGroup : EntityBase, IUserGroup, IReadOnlyUserGroup
private string _alias;
private string? _icon;
private string _name;
private string? _description;
private bool _hasAccessToAllLanguages;
private ISet<string> _permissions;
private ISet<IGranularPermission> _granularPermissions;
Expand Down Expand Up @@ -111,6 +112,13 @@ public string? Name
set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name));
}

[DataMember]
public string? Description
{
get => _description;
set => SetPropertyValueAndDetectChanges(value, ref _description!, nameof(Description));
}

[DataMember]
public bool HasAccessToAllLanguages
{
Expand Down
3 changes: 3 additions & 0 deletions src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@
// To 17.0.1
To<V_17_0_1.EnsureUmbracoPropertyDataColumnCasing>("{BE5CA411-E12D-4455-A59E-F12A669E5363}");

// To 17.1.0
To<V_17_1_0.AddDescriptionToUserGroup>("{F1A2B3C4-D5E6-4789-ABCD-1234567890AB}");

Check warning on line 149 in src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Large Method

UmbracoPlan has 70 lines, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
// To 18.0.0
// TODO (V18): Enable on 18 branch
//// To<V_18_0_0.MigrateSingleBlockList>("{74332C49-B279-4945-8943-F8F00B1F5949}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;

namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_17_1_0
{
/// <summary>
/// Migration to add a description column to the user group table.
/// </summary>
public class AddDescriptionToUserGroup : AsyncMigrationBase
{
/// <summary>
/// Initializes a new instance of the <see cref="AddDescriptionToUserGroup"/> class.
/// </summary>
/// <param name="context">The migration context.</param>
public AddDescriptionToUserGroup(
IMigrationContext context)
: base(context)
{
}

/// <inheritdoc/>
protected override async Task MigrateAsync()
{
if (TableExists(Constants.DatabaseSchema.Tables.UserGroup) is false)
{
return;
}

const string ColumnName = "description";
var hasColumn = Context.SqlContext.SqlSyntax.GetColumnsInSchema(Context.Database)
.Any(c =>
c.TableName == Constants.DatabaseSchema.Tables.UserGroup &&
c.ColumnName == ColumnName);

if (hasColumn)
{
return;
}

AddColumn<UserGroupDto>(Constants.DatabaseSchema.Tables.UserGroup, ColumnName);
}
}
}
5 changes: 5 additions & 0 deletions src/Umbraco.Infrastructure/Persistence/Dtos/UserGroupDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public UserGroupDto()
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoUserGroup_userGroupName")]
public string? Name { get; set; }

[Column(Name = "description")]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.Null)]
public string? Description { get; set; }

[Column("userGroupDefaultPermissions")]
[Length(50)]
[NullSetting(NullSetting = NullSettings.Null)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static IUserGroup BuildEntity(IShortStringHelper shortStringHelper, UserG
userGroup.StartMediaId = dto.StartMediaId;
userGroup.Permissions = dto.UserGroup2PermissionDtos.Select(x => x.Permission).ToHashSet();
userGroup.HasAccessToAllLanguages = dto.HasAccessToAllLanguages;
userGroup.Description = dto.Description;
if (dto.UserGroup2AppDtos != null)
{
foreach (UserGroup2AppDto app in dto.UserGroup2AppDtos)
Expand Down Expand Up @@ -84,6 +85,7 @@ public static UserGroupDto BuildDto(IUserGroup entity)
Key = entity.Key,
Alias = entity.Alias,
Name = entity.Name,
Description = entity.Description,
UserGroup2AppDtos = new List<UserGroup2AppDto>(),
CreateDate = entity.CreateDate,
UpdateDate = entity.UpdateDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ protected override void DefineMaps()
DefineMap<UserGroup, UserGroupDto>(nameof(UserGroup.Icon), nameof(UserGroupDto.Icon));
DefineMap<UserGroup, UserGroupDto>(nameof(UserGroup.StartContentId), nameof(UserGroupDto.StartContentId));
DefineMap<UserGroup, UserGroupDto>(nameof(UserGroup.StartMediaId), nameof(UserGroupDto.StartMediaId));
DefineMap<UserGroup, UserGroupDto>(nameof(UserGroup.Description), nameof(UserGroupDto.Description));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ private static void AppendGroupBy(Sql<ISqlContext> sql) =>
x => x.UpdateDate,
x => x.Alias,
x => x.Name,
x => x.Description,
x => x.HasAccessToAllLanguages,
x => x.Key,
x => x.DefaultPermissions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const data: Array<UmbMockUserGroupModel> = [
id: 'user-group-administrators-id',
name: 'Administrators',
alias: 'admin',
description: 'Administrators have full access to all settings and features within the CMS.',
icon: 'icon-medal',
fallbackPermissions: [
UMB_USER_PERMISSION_DOCUMENT_READ,
Expand Down Expand Up @@ -91,6 +92,7 @@ export const data: Array<UmbMockUserGroupModel> = [
id: 'user-group-editors-id',
name: 'Editors',
alias: 'editors',
description: 'The Editors group is responsible for creating, updating, and managing Content and Media within the platform. While they do not have access to system-level areas such as Settings, Users, or Packages, they play a crucial role in maintaining the website’s daily content operations. Editors start from the Media root node when handling media files, ensuring they can upload, modify, and organize assets relevant to their work.',
icon: 'icon-tools',
documentStartNode: { id: 'all-property-editors-document-id' },
fallbackPermissions: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ export type CreateUserGroupRequestModel = {
fallbackPermissions: Array<string>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
id?: string | null;
description?: string | null;
};

export type CreateUserRequestModel = {
Expand Down Expand Up @@ -2810,6 +2811,7 @@ export type UpdateUserDataRequestModel = {

export type UpdateUserGroupRequestModel = {
name: string;
description?: string | null;
alias: string;
icon?: string | null;
sections: Array<string>;
Expand Down Expand Up @@ -2925,6 +2927,7 @@ export type UserGroupResponseModel = {
id: string;
isDeletable: boolean;
aliasCanBeChanged: boolean;
description?: string | null;
};

export type UserInstallRequestModel = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { css, html, LitElement, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import type { UmbTableItem } from '@umbraco-cms/backoffice/components';

@customElement('umb-user-group-table-description-column-layout')
export class UmbUserGroupTableDescriptionColumnLayoutElement extends LitElement {
@property({ type: Object, attribute: false })
item!: UmbTableItem;

@property({ attribute: false })
value!: string;

override render() {
if (!this.value) {
return html``;
}
return html`<span title=${this.value}>${this.value}</span>`;
}

static override styles = [
css`
:host {
display: block;
max-width: 300px;
}

span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`,
];
}

export default UmbUserGroupTableDescriptionColumnLayoutElement;

declare global {
interface HTMLElementTagNameMap {
'umb-user-group-table-description-column-layout': UmbUserGroupTableDescriptionColumnLayoutElement;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class UmbUserGroupCollectionServerDataSource implements UmbCollectionData
mediaRootAccess: item.mediaRootAccess,
mediaStartNode: item.mediaStartNode ? { unique: item.mediaStartNode.id } : null,
name: item.name,
description: item.description || null,
permissions: item.permissions,
sections: item.sections,
unique: item.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbUniqueItemModel } from '@umbraco-cms/backoffice/models';

import '../components/user-group-table-name-column-layout.element.js';
import '../components/user-group-table-description-column-layout.element.js';
import '../components/user-group-table-sections-column-layout.element.js';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';

Expand All @@ -33,6 +34,11 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement {
alias: 'userGroupName',
elementName: 'umb-user-group-table-name-column-layout',
},
{
name: this.localize.term('general_description'),
alias: 'description',
elementName: 'umb-user-group-table-description-column-layout',
},
{
name: this.localize.term('main_sections'),
alias: 'userGroupSections',
Expand Down Expand Up @@ -133,6 +139,10 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement {
name: userGroup.name,
},
},
{
columnAlias: 'description',
value: userGroup.description ?? '',
},
{
columnAlias: 'userGroupSections',
value: userGroup.sections,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
this.#observeMediaStartNode(value);
}

@property({ type: String })
description: string | null = null;

@property({ type: Array })
public get sections(): Array<string> {
return [];
Expand Down Expand Up @@ -150,18 +153,20 @@
`;
}

#renderDetails() {

Check notice on line 156 in src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ No longer an issue: Complex Conditional

UmbUserGroupRefElement.renderDetails no longer has a complex conditional
const hasSections = this._sectionLabels.length;
const hasDocument = !!this._documentLabel || this.documentRootAccess;
const hasMedia = !!this._mediaLabel || this.mediaRootAccess;
const hasUserPermissions = this._userPermissionLabels.length;

if (!hasSections && !hasDocument && !hasMedia && !hasUserPermissions) return;

return html`
<div id="details">
${this.#renderSections()} ${this.#renderDocumentStartNode()} ${this.#renderMediaStartNode()}
${this.#renderUserPermissions()}
${this.#renderDescription()} ${this.#renderSections()} ${this.#renderDocumentStartNode()}
${this.#renderMediaStartNode()} ${this.#renderUserPermissions()}
</div>
`;
}

#renderDescription() {
if (!this.description) return;
return html`
<div>
<small>${this.description}</small>
</div>
`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export class UmbUserGroupPickerModalElement extends UmbModalBaseElement<
?mediaRootAccess=${userGroup.mediaRootAccess}
.mediaStartNode=${!userGroup.mediaRootAccess ? userGroup.mediaStartNode?.unique : null}
.sections=${userGroup.sections}
.description=${userGroup.description}
@selected=${(event: UUIMenuItemEvent) => this.#onSelected(event, userGroup)}
@deselected=${(event: UUIMenuItemEvent) => this.#onDeselected(event, userGroup)}>
${when(userGroup.icon, () => html`<umb-icon name=${userGroup.icon!} slot="icon"></umb-icon>`)}
Expand Down
Loading
Loading