diff --git a/src/Umbraco.Cms.Api.Management/Factories/WebhookPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/WebhookPresentationFactory.cs index 491b235664..95a69dfeb5 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/WebhookPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/WebhookPresentationFactory.cs @@ -29,6 +29,8 @@ internal class WebhookPresentationFactory : IWebhookPresentationFactory var target = new WebhookResponseModel { Events = webhook.Events.Select(Create).ToArray(), + Name = webhook.Name, + Description = webhook.Description, Url = webhook.Url, Enabled = webhook.Enabled, Id = webhook.Key, @@ -44,6 +46,8 @@ internal class WebhookPresentationFactory : IWebhookPresentationFactory var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers) { Key = webhookRequestModel.Id ?? Guid.NewGuid(), + Name = webhookRequestModel.Name, + Description = webhookRequestModel.Description, }; return target; } @@ -53,6 +57,8 @@ internal class WebhookPresentationFactory : IWebhookPresentationFactory var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers) { Key = existingWebhookkey, + Name = webhookRequestModel.Name, + Description = webhookRequestModel.Description, }; return target; } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs index 5b58862377..3430f6aaba 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs @@ -120,7 +120,7 @@ public class ItemTypeMapDefinition : IMapDefinition // Umbraco.Code.MapAll private static void Map(IWebhook source, WebhookItemResponseModel target, MapperContext context) { - target.Name = string.Empty; //source.Name; + target.Name = source.Name ?? source.Url; target.Url = source.Url; target.Enabled = source.Enabled; target.Events = string.Join(",", source.Events); diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 964d4dd594..e07a6eabce 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -35940,6 +35940,14 @@ "enabled": { "type": "boolean" }, + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, "url": { "minLength": 1, "type": "string" @@ -45448,6 +45456,14 @@ "enabled": { "type": "boolean" }, + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, "url": { "minLength": 1, "type": "string" @@ -46339,6 +46355,14 @@ "enabled": { "type": "boolean" }, + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, "url": { "minLength": 1, "type": "string" diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Webhook/WebhookModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Webhook/WebhookModelBase.cs index 10f497c543..e121f6f575 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Webhook/WebhookModelBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Webhook/WebhookModelBase.cs @@ -6,6 +6,10 @@ public class WebhookModelBase { public bool Enabled { get; set; } = true; + public string? Name { get; set; } + + public string? Description { get; set; } + [Required] public string Url { get; set; } = string.Empty; diff --git a/src/Umbraco.Core/Models/IWebhook.cs b/src/Umbraco.Core/Models/IWebhook.cs index ab8c6ed05d..7e64b56647 100644 --- a/src/Umbraco.Core/Models/IWebhook.cs +++ b/src/Umbraco.Core/Models/IWebhook.cs @@ -1,9 +1,22 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; public interface IWebhook : IEntity { + // TODO (V16): Remove the default implementations from this interface. + string? Name + { + get { return null; } + set { } + } + + string? Description + { + get { return null; } + set { } + } + string Url { get; set; } string[] Events { get; set; } diff --git a/src/Umbraco.Core/Models/Webhook.cs b/src/Umbraco.Core/Models/Webhook.cs index 499cee0186..0528b3a6ef 100644 --- a/src/Umbraco.Core/Models/Webhook.cs +++ b/src/Umbraco.Core/Models/Webhook.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models; @@ -24,6 +24,8 @@ public class Webhook : EntityBase, IWebhook (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), enumerable => enumerable.GetHashCode()); + private string? _name; + private string? _description; private string _url; private string[] _events; private Guid[] _contentTypeKeys; @@ -39,6 +41,18 @@ public class Webhook : EntityBase, IWebhook _enabled = enabled ?? false; } + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + } + + public string? Description + { + get => _description; + set => SetPropertyValueAndDetectChanges(value, ref _description!, nameof(Description)); + } + public string Url { get => _url; diff --git a/src/Umbraco.Core/Services/WebhookService.cs b/src/Umbraco.Core/Services/WebhookService.cs index ac3fabd973..f6eff0b315 100644 --- a/src/Umbraco.Core/Services/WebhookService.cs +++ b/src/Umbraco.Core/Services/WebhookService.cs @@ -88,6 +88,8 @@ public class WebhookService : IWebhookService currentWebhook.Enabled = webhook.Enabled; currentWebhook.ContentTypeKeys = webhook.ContentTypeKeys; currentWebhook.Events = webhook.Events; + currentWebhook.Name = webhook.Name; + currentWebhook.Description = webhook.Description; currentWebhook.Url = webhook.Url; currentWebhook.Headers = webhook.Headers; diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index c70616bfd4..599b2fb9d3 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -104,7 +104,12 @@ public class UmbracoPlan : MigrationPlan To("{9D3CE7D4-4884-41D4-98E8-302EB6CB0CF6}"); To("{37875E80-5CDD-42FF-A21A-7D4E3E23E0ED}"); To("{42E44F9E-7262-4269-922D-7310CB48E724}"); + + // To 15.1.0 To("{7B51B4DE-5574-4484-993E-05D12D9ED703}"); To("{F3D3EF46-1B1F-47DB-B437-7D573EEDEB98}"); + + // To 15.3.0 + To("{7B11F01E-EE33-4B0B-81A1-F78F834CA45B}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_3_0/AddNameAndDescriptionToWebhooks.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_3_0/AddNameAndDescriptionToWebhooks.cs new file mode 100644 index 0000000000..eee8d5e26c --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_3_0/AddNameAndDescriptionToWebhooks.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_3_0; + +public class AddNameAndDescriptionToWebhooks : MigrationBase +{ + private readonly ILogger _logger; + + public AddNameAndDescriptionToWebhooks(IMigrationContext context, ILogger logger) + : base(context) + { + _logger = logger; + } + + protected override void Migrate() + { + Logger.LogDebug("Adding name and description columns to webhooks."); + + if (TableExists(Constants.DatabaseSchema.Tables.Webhook)) + { + var columns = Context.SqlContext.SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumn(columns, "name"); + AddColumn(columns, "description"); + } + else + { + Logger.LogWarning($"Table {Constants.DatabaseSchema.Tables.Webhook} does not exist so the addition of the name and description by columnss in migration {nameof(AddNameAndDescriptionToWebhooks)} cannot be completed."); + } + } + + private void AddColumn(List columns, string column) + { + if (columns + .SingleOrDefault(x => x.TableName == Constants.DatabaseSchema.Tables.Webhook && x.ColumnName == column) is null) + { + AddColumn(Constants.DatabaseSchema.Tables.Webhook, column); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs index 2fb0d13555..dac753fb06 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs @@ -1,4 +1,4 @@ -using NPoco; +using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; @@ -18,6 +18,15 @@ internal class WebhookDto [NullSetting(NullSetting = NullSettings.NotNull)] public Guid Key { get; set; } + [Column(Name = "name")] + [NullSetting(NullSetting = NullSettings.Null)] + public string? Name { get; set; } + + [Column(Name = "description")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] + [NullSetting(NullSetting = NullSettings.Null)] + public string? Description { get; set; } + [Column(Name = "url")] [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] [NullSetting(NullSetting = NullSettings.NotNull)] diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookFactory.cs index ad081b5bda..3b559f5227 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Infrastructure.Persistence.Dtos; namespace Umbraco.Cms.Infrastructure.Persistence.Factories; @@ -16,6 +16,8 @@ internal static class WebhookFactory { Id = dto.Id, Key = dto.Key, + Name = dto.Name, + Description = dto.Description, }; return entity; @@ -25,10 +27,12 @@ internal static class WebhookFactory { var dto = new WebhookDto { - Url = webhook.Url, - Key = webhook.Key, - Enabled = webhook.Enabled, Id = webhook.Id, + Key = webhook.Key, + Name = webhook.Name, + Description = webhook.Description, + Url = webhook.Url, + Enabled = webhook.Enabled, }; return dto; diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index e9e6025b7f..af18f5873c 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -1783,6 +1783,7 @@ export default { selectEventFirst: 'Please select an event first.', selectEvents: 'Select events', statusCode: 'Status code', + unnamedWebhook: 'Unnamed webhook', }, languages: { addLanguage: 'Add language', diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts index 433feb3f95..41a57432ea 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts @@ -423,6 +423,8 @@ export type CreateUserRequestModel = { export type CreateWebhookRequestModel = { enabled: boolean; + name?: (string) | null; + description?: (string) | null; url: string; contentTypeKeys: Array<(string)>; headers: { @@ -2670,6 +2672,8 @@ export type UpdateUserRequestModel = { export type UpdateWebhookRequestModel = { enabled: boolean; + name?: (string) | null; + description?: (string) | null; url: string; contentTypeKeys: Array<(string)>; headers: { @@ -2904,6 +2908,8 @@ export type WebhookLogResponseModel = { export type WebhookResponseModel = { enabled: boolean; + name?: (string) | null; + description?: (string) | null; url: string; contentTypeKeys: Array<(string)>; headers: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook-root/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook-root/constants.ts index 2840e34df8..fef24d12da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook-root/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook-root/constants.ts @@ -1 +1,2 @@ export * from './workspace/constants.js'; +export * from './paths.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook-root/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook-root/paths.ts new file mode 100644 index 0000000000..c9a98ebc4f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook-root/paths.ts @@ -0,0 +1,8 @@ +import { UMB_WEBHOOK_ROOT_ENTITY_TYPE } from '../entity.js'; +import { UMB_SETTINGS_SECTION_PATHNAME } from '@umbraco-cms/backoffice/settings'; +import { UMB_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/workspace'; + +export const UMB_WEBHOOK_ROOT_WORKSPACE_PATH = UMB_WORKSPACE_PATH_PATTERN.generateAbsolute({ + sectionName: UMB_SETTINGS_SECTION_PATHNAME, + entityType: UMB_WEBHOOK_ROOT_ENTITY_TYPE, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/repository/webhook-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/repository/webhook-collection.server.data-source.ts index 59583f7778..d5fbf0ef73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/repository/webhook-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/repository/webhook-collection.server.data-source.ts @@ -41,6 +41,8 @@ export class UmbWebhookCollectionServerDataSource implements UmbWebhookCollectio entityType: UMB_WEBHOOK_ENTITY_TYPE, unique: item.id, url: item.url, + name: item.name, + description: item.description, enabled: item.enabled, headers: item.headers, events: item.events, diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/column-layouts/content-type/webhook-table-name-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/column-layouts/content-type/webhook-table-name-column-layout.element.ts index 116bd06841..f4f031c5f6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/column-layouts/content-type/webhook-table-name-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/column-layouts/content-type/webhook-table-name-column-layout.element.ts @@ -1,5 +1,5 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { html, nothing, customElement, property, css, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbMediaTypeItemRepository } from '@umbraco-cms/backoffice/media-type'; import { UmbDocumentTypeItemRepository } from '@umbraco-cms/backoffice/document-type'; @@ -42,14 +42,7 @@ export class UmbWebhookTableContentTypeColumnLayoutElement extends UmbLitElement return html`${this._contentTypes}`; } - static override styles = [ - UmbTextStyles, - css` - :host { - white-space: nowrap; - } - `, - ]; + static override styles = [UmbTextStyles]; } declare global { diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/webhook-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/webhook-table-collection-view.element.ts index 318acd492e..b65fc8d32b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/webhook-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/views/table/webhook-table-collection-view.element.ts @@ -7,6 +7,7 @@ import type { UmbTableColumn, UmbTableConfig, UmbTableItem } from '@umbraco-cms/ import './column-layouts/name/webhook-table-name-column-layout.element.js'; import './column-layouts/content-type/webhook-table-name-column-layout.element.js'; +import { UMB_EDIT_WEBHOOK_WORKSPACE_PATH_PATTERN } from '../../../paths.js'; @customElement('umb-webhook-table-collection-view') export class UmbWebhookTableCollectionViewElement extends UmbLitElement { @@ -17,6 +18,10 @@ export class UmbWebhookTableCollectionViewElement extends UmbLitElement { @state() private _tableColumns: Array = [ + { + name: this.localize.term('general_name'), + alias: 'name', + }, { name: this.localize.term('webhooks_url'), alias: 'url', @@ -47,12 +52,14 @@ export class UmbWebhookTableCollectionViewElement extends UmbLitElement { #collectionContext?: UmbDefaultCollectionContext; #enabledLabel: string; #disabledLabel: string; + #unnamedWebhookLabel: string; constructor() { super(); this.#enabledLabel = this.localize.term('webhooks_enabled'); this.#disabledLabel = this.localize.term('webhooks_disabled'); + this.#unnamedWebhookLabel = this.localize.term('webhooks_unnamedWebhook'); this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => { this.#collectionContext = instance; @@ -67,13 +74,20 @@ export class UmbWebhookTableCollectionViewElement extends UmbLitElement { #createTableItems(webhooks: Array) { this._tableItems = webhooks.map((webhook) => { + const name = webhook.name || `(${this.#unnamedWebhookLabel})`; + const path = UMB_EDIT_WEBHOOK_WORKSPACE_PATH_PATTERN.generateAbsolute({ unique: webhook.unique }); + return { id: webhook.unique, icon: 'icon-webhook', data: [ + { + columnAlias: 'name', + value: html`${name}`, + }, { columnAlias: 'url', - value: html`${webhook.url}`, + value: webhook.url, }, { columnAlias: 'events', diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/constants.ts index 19190bc8db..aea860b0c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/constants.ts @@ -1,3 +1,4 @@ export * from './collection/constants.js'; export * from './workspace/constants.js'; export * from './repository/constants.js'; +export * from './paths.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/paths.ts new file mode 100644 index 0000000000..c4d46b6061 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/paths.ts @@ -0,0 +1,16 @@ +import { UMB_WEBHOOK_ENTITY_TYPE } from '../entity.js'; +import { UMB_SETTINGS_SECTION_PATHNAME } from '@umbraco-cms/backoffice/settings'; +import { UmbPathPattern } from '@umbraco-cms/backoffice/router'; +import { UMB_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/workspace'; + +export const UMB_WEBHOOK_WORKSPACE_PATH = UMB_WORKSPACE_PATH_PATTERN.generateAbsolute({ + sectionName: UMB_SETTINGS_SECTION_PATHNAME, + entityType: UMB_WEBHOOK_ENTITY_TYPE, +}); + +export const UMB_CREATE_WEBHOOK_WORKSPACE_PATH_PATTERN = new UmbPathPattern('create', UMB_WEBHOOK_WORKSPACE_PATH); + +export const UMB_EDIT_WEBHOOK_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ unique: string }>( + 'edit/:unique', + UMB_WEBHOOK_WORKSPACE_PATH, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/detail/webhook-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/detail/webhook-detail.server.data-source.ts index d1dc7b4538..10eea38fde 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/detail/webhook-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/detail/webhook-detail.server.data-source.ts @@ -41,6 +41,8 @@ export class UmbWebhookDetailServerDataSource implements UmbDetailDataSource event.alias), enabled: model.enabled, url: model.url, + name: model.name, + description: model.description, contentTypeKeys: model.contentTypes, }; @@ -126,6 +132,8 @@ export class UmbWebhookDetailServerDataSource implements UmbDetailDataSource event.alias), enabled: model.enabled, url: model.url, + name: model.name, + description: model.description, contentTypeKeys: model.contentTypes, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/types.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/types.ts index e0ca6fec9f..44f6cc5cff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/types.ts @@ -9,6 +9,8 @@ export interface UmbWebhookDetailModel { unique: string; enabled: boolean; url: string; + name: string | null | undefined; + description: string | null | undefined; events: Array; contentTypes: Array; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/views/webhook-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/views/webhook-details-workspace-view.element.ts index 88f302cca3..36d559bf57 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/views/webhook-details-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/views/webhook-details-workspace-view.element.ts @@ -2,7 +2,7 @@ import { UMB_WEBHOOK_WORKSPACE_CONTEXT } from '../webhook-workspace.context-toke import type { UmbInputWebhookHeadersElement } from '../../../components/input-webhook-headers.element.js'; import type { UmbInputWebhookEventsElement } from '../../../webhook-event/input-webhook-events.element.js'; import { css, customElement, html, state, nothing } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputDocumentTypeElement } from '@umbraco-cms/backoffice/document-type'; @@ -19,9 +19,6 @@ export class UmbWebhookDetailsWorkspaceViewElement extends UmbLitElement impleme @state() private _webhook?: UmbWebhookDetailModel; - @state() - private _isNew?: boolean; - @state() private _contentType?: string; @@ -36,9 +33,6 @@ export class UmbWebhookDetailsWorkspaceViewElement extends UmbLitElement impleme this._webhook = webhook; this._contentType = this._webhook?.events[0]?.eventType ?? undefined; }); - this.observe(this.#webhookWorkspaceContext.isNew, (isNew) => { - this._isNew = isNew; - }); }); } @@ -113,12 +107,7 @@ export class UmbWebhookDetailsWorkspaceViewElement extends UmbLitElement impleme mandatory label=${this.localize.term('webhooks_url')} description=${this.localize.term('webhooks_urlDescription')}> - + { this.#workspaceContext = context; - this.observe(this.#workspaceContext.isNew, (isNew) => (this._isNew = isNew)); - this.observe(this.#workspaceContext.url, (url) => (this._url = url)); + this.observe(this.#workspaceContext.url, (url) => (this._url = url ?? '')); + this.observe(this.#workspaceContext.name, (name) => (this._name = name ?? '')); + this.observe(this.#workspaceContext.description, (description) => (this._description = description ?? '')); }); } + #onNameChange(event: InputEvent & { target: UUIInputElement }) { + const value = event.target.value.toString(); + this.#workspaceContext?.setName(value); + } + + #onDescriptionChange(event: InputEvent & { target: UUIInputElement }) { + const value = event.target.value.toString(); + this.#workspaceContext?.setDescription(value); + } + override render() { return html` - - ${this._isNew ? html`

Add Webhook

` : html`

${this._url}

`} + + `; } - static override styles = [UmbTextStyles]; + static override styles = [ + UmbTextStyles, + css` + #header { + width: 100%; + } + + #name { + width: 100%; + } + + #description { + width: 100%; + --uui-input-height: var(--uui-size-8); + --uui-input-border-color: transparent; + } + + #description:hover { + --uui-input-border-color: var(--uui-color-border); + } + `, + ]; } export default UmbWebhookWorkspaceEditorElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/webhook-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/webhook-workspace.context.ts index fbd96f253d..d2d2d46231 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/webhook-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/workspace/webhook-workspace.context.ts @@ -21,6 +21,8 @@ export class UmbWebhookWorkspaceContext readonly headers = this._data.createObservablePartOfCurrent((data) => data?.headers); readonly enabled = this._data.createObservablePartOfCurrent((data) => data?.enabled); readonly url = this._data.createObservablePartOfCurrent((data) => data?.url); + readonly name = this._data.createObservablePartOfCurrent((data) => data?.name); + readonly description = this._data.createObservablePartOfCurrent((data) => data?.description); readonly events = this._data.createObservablePartOfCurrent((data) => data?.events); readonly contentTypes = this._data.createObservablePartOfCurrent((data) => data?.contentTypes); @@ -119,6 +121,24 @@ export class UmbWebhookWorkspaceContext this._data.updateCurrent({ url }); } + /** + * Sets the name + * @param {string} name - The name + * @memberof UmbWebhookWorkspaceContext + */ + setName(name: string) { + this._data.updateCurrent({ name }); + } + + /** + * Sets the description + * @param {string} description - The description + * @memberof UmbWebhookWorkspaceContext + */ + setDescription(description: string) { + this._data.updateCurrent({ description }); + } + /** * Gets the URL * @returns {string} - The URL diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/WebhookServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/WebhookServiceTests.cs index 53368593c1..fd367a8c26 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/WebhookServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/WebhookServiceTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; @@ -7,13 +7,15 @@ using Umbraco.Cms.Tests.Integration.Testing; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; +// TODO (V16): Make this test class internal (along with any others in this project). We should just expose the base classes and helpers. +// Could also consider moving the base classes to a different assembly, and shipping that, but not our tests. + [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class WebhookServiceTests : UmbracoIntegrationTest { private IWebhookService WebhookService => GetRequiredService(); - [Test] [TestCase("https://example.com", Constants.WebhookEvents.Aliases.ContentPublish, "00000000-0000-0000-0000-010000000000")] [TestCase("https://example.com", Constants.WebhookEvents.Aliases.ContentDelete, "00000000-0000-0000-0000-000200000000")] [TestCase("https://example.com", Constants.WebhookEvents.Aliases.ContentUnpublish, "00000000-0000-0000-0000-300000000000")] @@ -21,8 +23,9 @@ public class WebhookServiceTests : UmbracoIntegrationTest [TestCase("https://example.com", Constants.WebhookEvents.Aliases.MediaSave, "00000000-0000-0000-0000-000000500000")] public async Task Can_Create_And_Get(string url, string webhookEvent, Guid key) { - var createdWebhook = await WebhookService.CreateAsync(new Webhook(url, true, new[] { key }, new[] { webhookEvent })); - var webhook = await WebhookService.GetAsync(createdWebhook.Result.Key); + IWebhook webhook = new Webhook(url, true, [key], [webhookEvent]); + var createdWebhook = await WebhookService.CreateAsync(webhook); + webhook = await WebhookService.GetAsync(createdWebhook.Result.Key); Assert.Multiple(() => { @@ -34,12 +37,57 @@ public class WebhookServiceTests : UmbracoIntegrationTest }); } + [Test] + public async Task Can_Create_And_Update_And_Get_With_Name_And_Description() + { + var contentTypeKey = Guid.NewGuid(); + const string Url = "https://example.com"; + const string Event = Constants.WebhookEvents.Aliases.ContentPublish; + const string Name = "Example name"; + const string Description = "Example description"; + IWebhook webhook = new Webhook(Url, true, [contentTypeKey], [Event]) + { + Name = Name, + Description = Description + }; + var createdWebhook = await WebhookService.CreateAsync(webhook); + webhook = await WebhookService.GetAsync(createdWebhook.Result.Key); + + Assert.Multiple(() => + { + Assert.IsNotNull(webhook); + Assert.AreEqual(1, webhook.Events.Length); + Assert.IsTrue(webhook.Events.Contains(Event)); + Assert.AreEqual(Url, webhook.Url); + Assert.AreEqual(Name, webhook.Name); + Assert.AreEqual(Description, webhook.Description); + Assert.IsTrue(webhook.ContentTypeKeys.Contains(contentTypeKey)); + }); + + const string UpdatedName = "Updated name"; + const string UpdatedDescription = "Updated description"; + + webhook.Name = UpdatedName; + webhook.Description = UpdatedDescription; + + await WebhookService.UpdateAsync(webhook); + + var updatedWebhook = await WebhookService.GetAsync(webhook.Key); + + Assert.Multiple(() => + { + Assert.IsNotNull(updatedWebhook); + Assert.AreEqual(UpdatedName, updatedWebhook.Name); + Assert.AreEqual(UpdatedDescription, updatedWebhook.Description); + }); + } + [Test] public async Task Can_Get_All() { - var createdWebhookOne = await WebhookService.CreateAsync(new Webhook("https://example.com", true, new[] { Guid.NewGuid() }, new[] { Constants.WebhookEvents.Aliases.ContentPublish })); - var createdWebhookTwo = await WebhookService.CreateAsync(new Webhook("https://example.com", true, new[] { Guid.NewGuid() }, new[] { Constants.WebhookEvents.Aliases.ContentDelete })); - var createdWebhookThree = await WebhookService.CreateAsync(new Webhook("https://example.com", true, new[] { Guid.NewGuid() }, new[] { Constants.WebhookEvents.Aliases.ContentUnpublish })); + var createdWebhookOne = await WebhookService.CreateAsync(new Webhook("https://example.com", true, [Guid.NewGuid()], [Constants.WebhookEvents.Aliases.ContentPublish])); + var createdWebhookTwo = await WebhookService.CreateAsync(new Webhook("https://example.com", true, [Guid.NewGuid()], [Constants.WebhookEvents.Aliases.ContentDelete])); + var createdWebhookThree = await WebhookService.CreateAsync(new Webhook("https://example.com", true, [Guid.NewGuid()], [Constants.WebhookEvents.Aliases.ContentUnpublish])); var webhooks = await WebhookService.GetAllAsync(0, int.MaxValue); Assert.Multiple(() => @@ -59,7 +107,7 @@ public class WebhookServiceTests : UmbracoIntegrationTest [TestCase("https://example.com", Constants.WebhookEvents.Aliases.MediaSave, "00000000-0000-0000-0000-000000500000")] public async Task Can_Delete(string url, string webhookEvent, Guid key) { - var createdWebhook = await WebhookService.CreateAsync(new Webhook(url, true, new[] { key }, new[] { webhookEvent })); + var createdWebhook = await WebhookService.CreateAsync(new Webhook(url, true, [key], [webhookEvent])); var webhook = await WebhookService.GetAsync(createdWebhook.Result.Key); Assert.IsNotNull(webhook); @@ -71,7 +119,7 @@ public class WebhookServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Create_With_No_EntityKeys() { - var createdWebhook = await WebhookService.CreateAsync(new Webhook("https://example.com", events: new[] { Constants.WebhookEvents.Aliases.ContentPublish })); + var createdWebhook = await WebhookService.CreateAsync(new Webhook("https://example.com", events: [Constants.WebhookEvents.Aliases.ContentPublish])); var webhook = await WebhookService.GetAsync(createdWebhook.Result.Key); Assert.IsNotNull(webhook); @@ -81,8 +129,8 @@ public class WebhookServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Update() { - var createdWebhook = await WebhookService.CreateAsync(new Webhook("https://example.com", events: new[] { Constants.WebhookEvents.Aliases.ContentPublish })); - createdWebhook.Result.Events = new[] { Constants.WebhookEvents.Aliases.ContentDelete }; + var createdWebhook = await WebhookService.CreateAsync(new Webhook("https://example.com", events: [Constants.WebhookEvents.Aliases.ContentPublish])); + createdWebhook.Result.Events = [Constants.WebhookEvents.Aliases.ContentDelete]; await WebhookService.UpdateAsync(createdWebhook.Result); var updatedWebhook = await WebhookService.GetAsync(createdWebhook.Result.Key); @@ -94,9 +142,9 @@ public class WebhookServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Get_By_EventName() { - var webhook1 = await WebhookService.CreateAsync(new Webhook("https://example.com", events: new[] { Constants.WebhookEvents.Aliases.ContentPublish })); - var webhook2 = await WebhookService.CreateAsync(new Webhook("https://example.com", events: new[] { Constants.WebhookEvents.Aliases.ContentUnpublish })); - var webhook3 = await WebhookService.CreateAsync(new Webhook("https://example.com", events: new[] { Constants.WebhookEvents.Aliases.ContentUnpublish })); + var webhook1 = await WebhookService.CreateAsync(new Webhook("https://example.com", events: [Constants.WebhookEvents.Aliases.ContentPublish])); + var webhook2 = await WebhookService.CreateAsync(new Webhook("https://example.com", events: [Constants.WebhookEvents.Aliases.ContentUnpublish])); + var webhook3 = await WebhookService.CreateAsync(new Webhook("https://example.com", events: [Constants.WebhookEvents.Aliases.ContentUnpublish])); var result = await WebhookService.GetByAliasAsync(Constants.WebhookEvents.Aliases.ContentUnpublish);