Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5b7f927
add option to interface
madsrasmussen Jan 20, 2025
4ffa2ff
pass config to picker
madsrasmussen Jan 20, 2025
babda45
add option to interface
madsrasmussen Jan 20, 2025
33f2c99
force type
madsrasmussen Jan 20, 2025
330eb54
add request args to type
madsrasmussen Jan 20, 2025
8cab8eb
pass allowed content types as request args
madsrasmussen Jan 20, 2025
d110d53
add comments
madsrasmussen Jan 20, 2025
e470350
allow for passing type
madsrasmussen Jan 20, 2025
7eb9f5d
more type safety
madsrasmussen Jan 20, 2025
3babe8e
use correct types
madsrasmussen Jan 20, 2025
884904d
use correct types
madsrasmussen Jan 20, 2025
96a391f
add js docs
madsrasmussen Jan 20, 2025
d833239
remove debugger + map to only pass id to server
madsrasmussen Jan 20, 2025
b7d4053
add js docs
madsrasmussen Jan 20, 2025
8851e29
align naming
madsrasmussen Jan 20, 2025
fd9e0dd
add null check
madsrasmussen Jan 20, 2025
210b82c
align types
madsrasmussen Jan 20, 2025
fa2c45c
implement allowedContentTypes for member search
madsrasmussen Jan 20, 2025
7b4c450
fix imports
madsrasmussen Jan 20, 2025
f987b3e
add types for media search
madsrasmussen Jan 20, 2025
8782e90
add and use const
madsrasmussen Jan 20, 2025
d4959cd
align picker interfaces
madsrasmussen Jan 20, 2025
e19645b
align models
madsrasmussen Jan 20, 2025
e8c11ab
Merge branch 'v15/dev' into v15/bugfix/content-picker-search-allowed-…
madsrasmussen Jan 21, 2025
8bb5681
add entity type
madsrasmussen Jan 21, 2025
31088c7
filter for null value
madsrasmussen Jan 21, 2025
758a356
explicit naming
madsrasmussen Jan 21, 2025
0ecfa22
rename field
madsrasmussen Jan 21, 2025
d236f15
use query params
madsrasmussen Jan 21, 2025
e5dac41
Implement content type scoped search in item search controllers
kjac Jan 22, 2025
b26ff04
Merge remote-tracking branch 'origin/v15/bugfix/content-picker-search…
kjac Jan 22, 2025
8aa8e90
Merge branch 'v15/dev' into v15/bugfix/content-picker-search-allowed-…
madsrasmussen Jan 23, 2025
fd9e646
Fix bad naming
kjac Jan 23, 2025
78f97f8
generate server models
madsrasmussen Jan 23, 2025
a0fb959
wire up backend
madsrasmussen Jan 23, 2025
34cfaac
Merge branch 'v15/bugfix/content-picker-search-allowed-content-types'…
madsrasmussen Jan 23, 2025
8ab7e20
generate server models
madsrasmussen Jan 23, 2025
73f70a1
add selectable filter to member picker
madsrasmussen Jan 23, 2025
76142ad
Update member-picker-modal.element.ts
madsrasmussen Jan 23, 2025
3a72198
Fix indexed search for specific member and media types
kjac Jan 24, 2025
23d24fe
Merge branch 'v15/dev' into v15/bugfix/content-picker-search-allowed-…
madsrasmussen Jan 27, 2025
2bd9ae3
export consts
madsrasmussen Jan 27, 2025
6787fe8
Merge branch 'v15/dev' into v15/bugfix/content-picker-search-allowed-…
madsrasmussen Jan 28, 2025
3f9e514
Fix ambiguous constructor
kjac Jan 29, 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Asp.Versioning;
using System.Text.Json.Serialization;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
Expand Down Expand Up @@ -26,12 +27,17 @@ public SearchDocumentItemController(IIndexedEntitySearchService indexedEntitySea
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
=> await SearchFromParent(cancellationToken, query, skip, take);

[NonAction]
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);

[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedDocumentTypes = null)
{
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, skip, take);
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, skip, take);
var result = new PagedModel<DocumentItemResponseModel>
{
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,18 @@ public SearchMediaItemController(IIndexedEntitySearchService indexedEntitySearch
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
=> await SearchFromParent(cancellationToken, query, skip, take, null);

[NonAction]
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);

[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
{
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, skip, take);
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, skip, take);
var result = new PagedModel<MediaItemResponseModel>
{
Items = searchResult.Items.OfType<IMediaEntitySlim>().Select(_mediaPresentationFactory.CreateItemResponseModel),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ public SearchMemberItemController(IIndexedEntitySearchService indexedEntitySearc
_memberPresentationFactory = memberPresentationFactory;
}

[NonAction]
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
=> await SearchWithAllowedTypes(cancellationToken, query, skip, take);

[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<MemberItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
public async Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null)
{
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Member, query, skip, take);
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Member, query, null, allowedMemberTypes, skip, take);
var result = new PagedModel<MemberItemResponseModel>
{
Items = searchResult.Items.OfType<IMemberEntitySlim>().Select(_memberPresentationFactory.CreateItemResponseModel),
Expand Down
33 changes: 33 additions & 0 deletions src/Umbraco.Cms.Api.Management/OpenApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10086,6 +10086,17 @@
"type": "string",
"format": "uuid"
}
},
{
"name": "allowedDocumentTypes",
"in": "query",
"schema": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
}
],
"responses": {
Expand Down Expand Up @@ -15764,6 +15775,17 @@
"type": "string",
"format": "uuid"
}
},
{
"name": "allowedMediaTypes",
"in": "query",
"schema": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
}
],
"responses": {
Expand Down Expand Up @@ -19638,6 +19660,17 @@
"format": "int32",
"default": 100
}
},
{
"name": "allowedMemberTypes",
"in": "query",
"schema": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
}
],
"responses": {
Expand Down
6 changes: 6 additions & 0 deletions src/Umbraco.Core/Services/IIndexedEntitySearchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ namespace Umbraco.Cms.Core.Services;
/// </remarks>
public interface IIndexedEntitySearchService
{
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100, bool ignoreUserStartNodes = false);

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

// default implementation to avoid breaking changes falls back to old behaviour
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, Guid? parentId, IEnumerable<Guid>? contentTypeIds, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
=> Search(objectType,query, skip, take, ignoreUserStartNodes);
}
17 changes: 17 additions & 0 deletions src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public BackOfficeExamineSearcher(
_publishedUrlProvider = publishedUrlProvider;
}

[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
public IEnumerable<ISearchResult> Search(
string query,
UmbracoEntityTypes entityType,
Expand All @@ -60,6 +61,17 @@ public IEnumerable<ISearchResult> Search(
out long totalFound,
string? searchFrom = null,
bool ignoreUserStartNodes = false)
=> Search(query, entityType, pageSize, pageIndex, out totalFound, null, searchFrom, ignoreUserStartNodes);

public IEnumerable<ISearchResult> Search(
string query,
UmbracoEntityTypes entityType,
int pageSize,
long pageIndex,
out long totalFound,
string[]? contentTypeAliases,
string? searchFrom = null,
bool ignoreUserStartNodes = false)
{
var sb = new StringBuilder();

Expand Down Expand Up @@ -141,6 +153,11 @@ public IEnumerable<ISearchResult> Search(
entityType);
}

if (contentTypeAliases?.Any() is true)
{
sb.Append($"+({string.Join(" ", contentTypeAliases.Select(alias => $"{ExamineFieldNames.ItemTypeFieldName}:{alias}"))}) ");
}

if (!_examineManager.TryGetIndex(indexName, out IIndex? index))
{
throw new InvalidOperationException("No index found by name " + indexName);
Expand Down
13 changes: 13 additions & 0 deletions src/Umbraco.Infrastructure/Examine/IBackOfficeExamineSearcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Umbraco.Cms.Infrastructure.Examine;
/// </summary>
public interface IBackOfficeExamineSearcher
{
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
IEnumerable<ISearchResult> Search(
string query,
UmbracoEntityTypes entityType,
Expand All @@ -16,4 +17,16 @@ IEnumerable<ISearchResult> Search(
out long totalFound,
string? searchFrom = null,
bool ignoreUserStartNodes = false);

// default implementation to avoid breaking changes falls back to old behaviour
IEnumerable<ISearchResult> Search(
string query,
UmbracoEntityTypes entityType,
int pageSize,
long pageIndex,
out long totalFound,
string[]? contentTypeAliases,
string? searchFrom = null,
bool ignoreUserStartNodes = false)
=> Search(query, entityType, pageSize, pageIndex, out totalFound, null, searchFrom, ignoreUserStartNodes);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Entities;
Expand All @@ -12,11 +14,32 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
{
private readonly IBackOfficeExamineSearcher _backOfficeExamineSearcher;
private readonly IEntityService _entityService;
private readonly IContentTypeService _contentTypeService;
private readonly IMediaTypeService _mediaTypeService;
private readonly IMemberTypeService _memberTypeService;

public IndexedEntitySearchService(IBackOfficeExamineSearcher backOfficeExamineSearcher, IEntityService entityService)
: this(
backOfficeExamineSearcher,
entityService,
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>(),
StaticServiceProvider.Instance.GetRequiredService<IMediaTypeService>(),
StaticServiceProvider.Instance.GetRequiredService<IMemberTypeService>())
{
}

public IndexedEntitySearchService(
IBackOfficeExamineSearcher backOfficeExamineSearcher,
IEntityService entityService,
IContentTypeService contentTypeService,
IMediaTypeService mediaTypeService,
IMemberTypeService memberTypeService)
{
_backOfficeExamineSearcher = backOfficeExamineSearcher;
_entityService = entityService;
_contentTypeService = contentTypeService;
_mediaTypeService = mediaTypeService;
_memberTypeService = memberTypeService;
}

public PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
Expand All @@ -29,6 +52,16 @@ public PagedModel<IEntitySlim> Search(
int skip = 0,
int take = 100,
bool ignoreUserStartNodes = false)
=> Search(objectType, query, parentId, null, skip, take, ignoreUserStartNodes);

public PagedModel<IEntitySlim> Search(
UmbracoObjectTypes objectType,
string query,
Guid? parentId,
IEnumerable<Guid>? contentTypeIds,
int skip = 0,
int take = 100,
bool ignoreUserStartNodes = false)
{
UmbracoEntityTypes entityType = objectType switch
{
Expand All @@ -40,12 +73,24 @@ public PagedModel<IEntitySlim> Search(

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

Guid[]? contentTypeIdsAsArray = contentTypeIds as Guid[] ?? contentTypeIds?.ToArray();
var contentTypeAliases = contentTypeIdsAsArray?.Length > 0
? (entityType switch
{
UmbracoEntityTypes.Document => _contentTypeService.GetMany(contentTypeIdsAsArray).Select(x => x.Alias),
UmbracoEntityTypes.Media => _mediaTypeService.GetMany(contentTypeIdsAsArray).Select(x => x.Alias),
UmbracoEntityTypes.Member => _memberTypeService.GetMany(contentTypeIdsAsArray).Select(x => x.Alias),
_ => throw new NotSupportedException("This service only supports searching for documents, media and members")
}).ToArray()
: null;

IEnumerable<ISearchResult> searchResults = _backOfficeExamineSearcher.Search(
query,
entityType,
pageSize,
pageNumber,
out var totalFound,
contentTypeAliases,
ignoreUserStartNodes: ignoreUserStartNodes,
searchFrom: parentId?.ToString());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,7 @@ export class DocumentService {
* @param data.skip
* @param data.take
* @param data.parentId
* @param data.allowedDocumentTypes
* @returns unknown OK
* @throws ApiError
*/
Expand All @@ -1505,7 +1506,8 @@ export class DocumentService {
query: data.query,
skip: data.skip,
take: data.take,
parentId: data.parentId
parentId: data.parentId,
allowedDocumentTypes: data.allowedDocumentTypes
},
errors: {
401: 'The resource is protected and requires an authentication token'
Expand Down Expand Up @@ -3497,6 +3499,7 @@ export class MediaService {
* @param data.skip
* @param data.take
* @param data.parentId
* @param data.allowedMediaTypes
* @returns unknown OK
* @throws ApiError
*/
Expand All @@ -3508,7 +3511,8 @@ export class MediaService {
query: data.query,
skip: data.skip,
take: data.take,
parentId: data.parentId
parentId: data.parentId,
allowedMediaTypes: data.allowedMediaTypes
},
errors: {
401: 'The resource is protected and requires an authentication token'
Expand Down Expand Up @@ -4703,6 +4707,7 @@ export class MemberService {
* @param data.query
* @param data.skip
* @param data.take
* @param data.allowedMemberTypes
* @returns unknown OK
* @throws ApiError
*/
Expand All @@ -4713,7 +4718,8 @@ export class MemberService {
query: {
query: data.query,
skip: data.skip,
take: data.take
take: data.take,
allowedMemberTypes: data.allowedMemberTypes
},
errors: {
401: 'The resource is protected and requires an authentication token'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3316,6 +3316,7 @@ export type GetItemDocumentData = {
export type GetItemDocumentResponse = (Array<(DocumentItemResponseModel)>);

export type GetItemDocumentSearchData = {
allowedDocumentTypes?: Array<(string)>;
parentId?: string;
query?: string;
skip?: number;
Expand Down Expand Up @@ -3884,6 +3885,7 @@ export type GetItemMediaData = {
export type GetItemMediaResponse = (Array<(MediaItemResponseModel)>);

export type GetItemMediaSearchData = {
allowedMediaTypes?: Array<(string)>;
parentId?: string;
query?: string;
skip?: number;
Expand Down Expand Up @@ -4235,6 +4237,7 @@ export type GetItemMemberData = {
export type GetItemMemberResponse = (Array<(MemberItemResponseModel)>);

export type GetItemMemberSearchData = {
allowedMemberTypes?: Array<(string)>;
query?: string;
skip?: number;
take?: number;
Expand Down
Loading