Skip to content

Commit 469fd14

Browse files
madsrasmussenkjac
andauthored
Bugfix: Content Picker Search - support allowed content types config (#18042)
* add option to interface * pass config to picker * add option to interface * force type * add request args to type * pass allowed content types as request args * add comments * allow for passing type * more type safety * use correct types * use correct types * add js docs * remove debugger + map to only pass id to server * add js docs * align naming * add null check * align types * implement allowedContentTypes for member search * fix imports * add types for media search * add and use const * align picker interfaces * align models * add entity type * filter for null value * explicit naming * rename field * use query params * Implement content type scoped search in item search controllers * Fix bad naming * generate server models * wire up backend * generate server models * add selectable filter to member picker * Update member-picker-modal.element.ts * Fix indexed search for specific member and media types * export consts --------- Co-authored-by: kjac <[email protected]>
1 parent ad45494 commit 469fd14

File tree

45 files changed

+553
-154
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+553
-154
lines changed

src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Asp.Versioning;
1+
using System.Text.Json.Serialization;
2+
using Asp.Versioning;
23
using Microsoft.AspNetCore.Http;
34
using Microsoft.AspNetCore.Mvc;
45
using Umbraco.Cms.Api.Management.Factories;
@@ -26,12 +27,17 @@ public SearchDocumentItemController(IIndexedEntitySearchService indexedEntitySea
2627
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
2728
=> await SearchFromParent(cancellationToken, query, skip, take);
2829

30+
[NonAction]
31+
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
32+
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
33+
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
34+
2935
[HttpGet("search")]
3036
[MapToApiVersion("1.0")]
3137
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
32-
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
38+
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedDocumentTypes = null)
3339
{
34-
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, skip, take);
40+
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, skip, take);
3541
var result = new PagedModel<DocumentItemResponseModel>
3642
{
3743
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),

src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,18 @@ public SearchMediaItemController(IIndexedEntitySearchService indexedEntitySearch
2626
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
2727
=> await SearchFromParent(cancellationToken, query, skip, take, null);
2828

29+
[NonAction]
30+
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
31+
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
32+
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
33+
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
34+
2935
[HttpGet("search")]
3036
[MapToApiVersion("1.0")]
3137
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
32-
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
38+
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
3339
{
34-
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, skip, take);
40+
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, skip, take);
3541
var result = new PagedModel<MediaItemResponseModel>
3642
{
3743
Items = searchResult.Items.OfType<IMediaEntitySlim>().Select(_mediaPresentationFactory.CreateItemResponseModel),

src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ public SearchMemberItemController(IIndexedEntitySearchService indexedEntitySearc
2121
_memberPresentationFactory = memberPresentationFactory;
2222
}
2323

24+
[NonAction]
25+
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
26+
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
27+
=> await SearchWithAllowedTypes(cancellationToken, query, skip, take);
28+
2429
[HttpGet("search")]
2530
[MapToApiVersion("1.0")]
2631
[ProducesResponseType(typeof(PagedModel<MemberItemResponseModel>), StatusCodes.Status200OK)]
27-
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
32+
public async Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null)
2833
{
29-
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Member, query, skip, take);
34+
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Member, query, null, allowedMemberTypes, skip, take);
3035
var result = new PagedModel<MemberItemResponseModel>
3136
{
3237
Items = searchResult.Items.OfType<IMemberEntitySlim>().Select(_memberPresentationFactory.CreateItemResponseModel),

src/Umbraco.Cms.Api.Management/OpenApi.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10086,6 +10086,17 @@
1008610086
"type": "string",
1008710087
"format": "uuid"
1008810088
}
10089+
},
10090+
{
10091+
"name": "allowedDocumentTypes",
10092+
"in": "query",
10093+
"schema": {
10094+
"type": "array",
10095+
"items": {
10096+
"type": "string",
10097+
"format": "uuid"
10098+
}
10099+
}
1008910100
}
1009010101
],
1009110102
"responses": {
@@ -15764,6 +15775,17 @@
1576415775
"type": "string",
1576515776
"format": "uuid"
1576615777
}
15778+
},
15779+
{
15780+
"name": "allowedMediaTypes",
15781+
"in": "query",
15782+
"schema": {
15783+
"type": "array",
15784+
"items": {
15785+
"type": "string",
15786+
"format": "uuid"
15787+
}
15788+
}
1576715789
}
1576815790
],
1576915791
"responses": {
@@ -19638,6 +19660,17 @@
1963819660
"format": "int32",
1963919661
"default": 100
1964019662
}
19663+
},
19664+
{
19665+
"name": "allowedMemberTypes",
19666+
"in": "query",
19667+
"schema": {
19668+
"type": "array",
19669+
"items": {
19670+
"type": "string",
19671+
"format": "uuid"
19672+
}
19673+
}
1964119674
}
1964219675
],
1964319676
"responses": {

src/Umbraco.Core/Services/IIndexedEntitySearchService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ namespace Umbraco.Cms.Core.Services;
1212
/// </remarks>
1313
public interface IIndexedEntitySearchService
1414
{
15+
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
1516
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100, bool ignoreUserStartNodes = false);
1617

1718
// default implementation to avoid breaking changes falls back to old behaviour
19+
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
1820
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, Guid? parentId, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
1921
=> Search(objectType,query, skip, take, ignoreUserStartNodes);
22+
23+
// default implementation to avoid breaking changes falls back to old behaviour
24+
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, Guid? parentId, IEnumerable<Guid>? contentTypeIds, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
25+
=> Search(objectType,query, skip, take, ignoreUserStartNodes);
2026
}

src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public BackOfficeExamineSearcher(
5252
_publishedUrlProvider = publishedUrlProvider;
5353
}
5454

55+
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
5556
public IEnumerable<ISearchResult> Search(
5657
string query,
5758
UmbracoEntityTypes entityType,
@@ -60,6 +61,17 @@ public IEnumerable<ISearchResult> Search(
6061
out long totalFound,
6162
string? searchFrom = null,
6263
bool ignoreUserStartNodes = false)
64+
=> Search(query, entityType, pageSize, pageIndex, out totalFound, null, searchFrom, ignoreUserStartNodes);
65+
66+
public IEnumerable<ISearchResult> Search(
67+
string query,
68+
UmbracoEntityTypes entityType,
69+
int pageSize,
70+
long pageIndex,
71+
out long totalFound,
72+
string[]? contentTypeAliases,
73+
string? searchFrom = null,
74+
bool ignoreUserStartNodes = false)
6375
{
6476
var sb = new StringBuilder();
6577

@@ -141,6 +153,11 @@ public IEnumerable<ISearchResult> Search(
141153
entityType);
142154
}
143155

156+
if (contentTypeAliases?.Any() is true)
157+
{
158+
sb.Append($"+({string.Join(" ", contentTypeAliases.Select(alias => $"{ExamineFieldNames.ItemTypeFieldName}:{alias}"))}) ");
159+
}
160+
144161
if (!_examineManager.TryGetIndex(indexName, out IIndex? index))
145162
{
146163
throw new InvalidOperationException("No index found by name " + indexName);

src/Umbraco.Infrastructure/Examine/IBackOfficeExamineSearcher.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Umbraco.Cms.Infrastructure.Examine;
88
/// </summary>
99
public interface IBackOfficeExamineSearcher
1010
{
11+
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
1112
IEnumerable<ISearchResult> Search(
1213
string query,
1314
UmbracoEntityTypes entityType,
@@ -16,4 +17,16 @@ IEnumerable<ISearchResult> Search(
1617
out long totalFound,
1718
string? searchFrom = null,
1819
bool ignoreUserStartNodes = false);
20+
21+
// default implementation to avoid breaking changes falls back to old behaviour
22+
IEnumerable<ISearchResult> Search(
23+
string query,
24+
UmbracoEntityTypes entityType,
25+
int pageSize,
26+
long pageIndex,
27+
out long totalFound,
28+
string[]? contentTypeAliases,
29+
string? searchFrom = null,
30+
bool ignoreUserStartNodes = false)
31+
=> Search(query, entityType, pageSize, pageIndex, out totalFound, null, searchFrom, ignoreUserStartNodes);
1932
}

src/Umbraco.Infrastructure/Services/Implement/IndexedEntitySearchService.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Examine;
2+
using Microsoft.Extensions.DependencyInjection;
23
using Umbraco.Cms.Core;
4+
using Umbraco.Cms.Core.DependencyInjection;
35
using Umbraco.Cms.Core.Models;
46
using Umbraco.Cms.Core.Models.ContentEditing;
57
using Umbraco.Cms.Core.Models.Entities;
@@ -12,11 +14,32 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
1214
{
1315
private readonly IBackOfficeExamineSearcher _backOfficeExamineSearcher;
1416
private readonly IEntityService _entityService;
17+
private readonly IContentTypeService _contentTypeService;
18+
private readonly IMediaTypeService _mediaTypeService;
19+
private readonly IMemberTypeService _memberTypeService;
1520

1621
public IndexedEntitySearchService(IBackOfficeExamineSearcher backOfficeExamineSearcher, IEntityService entityService)
22+
: this(
23+
backOfficeExamineSearcher,
24+
entityService,
25+
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>(),
26+
StaticServiceProvider.Instance.GetRequiredService<IMediaTypeService>(),
27+
StaticServiceProvider.Instance.GetRequiredService<IMemberTypeService>())
28+
{
29+
}
30+
31+
public IndexedEntitySearchService(
32+
IBackOfficeExamineSearcher backOfficeExamineSearcher,
33+
IEntityService entityService,
34+
IContentTypeService contentTypeService,
35+
IMediaTypeService mediaTypeService,
36+
IMemberTypeService memberTypeService)
1737
{
1838
_backOfficeExamineSearcher = backOfficeExamineSearcher;
1939
_entityService = entityService;
40+
_contentTypeService = contentTypeService;
41+
_mediaTypeService = mediaTypeService;
42+
_memberTypeService = memberTypeService;
2043
}
2144

2245
public PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
@@ -29,6 +52,16 @@ public PagedModel<IEntitySlim> Search(
2952
int skip = 0,
3053
int take = 100,
3154
bool ignoreUserStartNodes = false)
55+
=> Search(objectType, query, parentId, null, skip, take, ignoreUserStartNodes);
56+
57+
public PagedModel<IEntitySlim> Search(
58+
UmbracoObjectTypes objectType,
59+
string query,
60+
Guid? parentId,
61+
IEnumerable<Guid>? contentTypeIds,
62+
int skip = 0,
63+
int take = 100,
64+
bool ignoreUserStartNodes = false)
3265
{
3366
UmbracoEntityTypes entityType = objectType switch
3467
{
@@ -40,12 +73,24 @@ public PagedModel<IEntitySlim> Search(
4073

4174
PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize);
4275

76+
Guid[]? contentTypeIdsAsArray = contentTypeIds as Guid[] ?? contentTypeIds?.ToArray();
77+
var contentTypeAliases = contentTypeIdsAsArray?.Length > 0
78+
? (entityType switch
79+
{
80+
UmbracoEntityTypes.Document => _contentTypeService.GetMany(contentTypeIdsAsArray).Select(x => x.Alias),
81+
UmbracoEntityTypes.Media => _mediaTypeService.GetMany(contentTypeIdsAsArray).Select(x => x.Alias),
82+
UmbracoEntityTypes.Member => _memberTypeService.GetMany(contentTypeIdsAsArray).Select(x => x.Alias),
83+
_ => throw new NotSupportedException("This service only supports searching for documents, media and members")
84+
}).ToArray()
85+
: null;
86+
4387
IEnumerable<ISearchResult> searchResults = _backOfficeExamineSearcher.Search(
4488
query,
4589
entityType,
4690
pageSize,
4791
pageNumber,
4892
out var totalFound,
93+
contentTypeAliases,
4994
ignoreUserStartNodes: ignoreUserStartNodes,
5095
searchFrom: parentId?.ToString());
5196

src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,7 @@ export class DocumentService {
14941494
* @param data.skip
14951495
* @param data.take
14961496
* @param data.parentId
1497+
* @param data.allowedDocumentTypes
14971498
* @returns unknown OK
14981499
* @throws ApiError
14991500
*/
@@ -1505,7 +1506,8 @@ export class DocumentService {
15051506
query: data.query,
15061507
skip: data.skip,
15071508
take: data.take,
1508-
parentId: data.parentId
1509+
parentId: data.parentId,
1510+
allowedDocumentTypes: data.allowedDocumentTypes
15091511
},
15101512
errors: {
15111513
401: 'The resource is protected and requires an authentication token'
@@ -3497,6 +3499,7 @@ export class MediaService {
34973499
* @param data.skip
34983500
* @param data.take
34993501
* @param data.parentId
3502+
* @param data.allowedMediaTypes
35003503
* @returns unknown OK
35013504
* @throws ApiError
35023505
*/
@@ -3508,7 +3511,8 @@ export class MediaService {
35083511
query: data.query,
35093512
skip: data.skip,
35103513
take: data.take,
3511-
parentId: data.parentId
3514+
parentId: data.parentId,
3515+
allowedMediaTypes: data.allowedMediaTypes
35123516
},
35133517
errors: {
35143518
401: 'The resource is protected and requires an authentication token'
@@ -4703,6 +4707,7 @@ export class MemberService {
47034707
* @param data.query
47044708
* @param data.skip
47054709
* @param data.take
4710+
* @param data.allowedMemberTypes
47064711
* @returns unknown OK
47074712
* @throws ApiError
47084713
*/
@@ -4713,7 +4718,8 @@ export class MemberService {
47134718
query: {
47144719
query: data.query,
47154720
skip: data.skip,
4716-
take: data.take
4721+
take: data.take,
4722+
allowedMemberTypes: data.allowedMemberTypes
47174723
},
47184724
errors: {
47194725
401: 'The resource is protected and requires an authentication token'

src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3316,6 +3316,7 @@ export type GetItemDocumentData = {
33163316
export type GetItemDocumentResponse = (Array<(DocumentItemResponseModel)>);
33173317

33183318
export type GetItemDocumentSearchData = {
3319+
allowedDocumentTypes?: Array<(string)>;
33193320
parentId?: string;
33203321
query?: string;
33213322
skip?: number;
@@ -3884,6 +3885,7 @@ export type GetItemMediaData = {
38843885
export type GetItemMediaResponse = (Array<(MediaItemResponseModel)>);
38853886

38863887
export type GetItemMediaSearchData = {
3888+
allowedMediaTypes?: Array<(string)>;
38873889
parentId?: string;
38883890
query?: string;
38893891
skip?: number;
@@ -4235,6 +4237,7 @@ export type GetItemMemberData = {
42354237
export type GetItemMemberResponse = (Array<(MemberItemResponseModel)>);
42364238

42374239
export type GetItemMemberSearchData = {
4240+
allowedMemberTypes?: Array<(string)>;
42384241
query?: string;
42394242
skip?: number;
42404243
take?: number;

0 commit comments

Comments
 (0)