From eb7f22025ffdd555894b92ff0793232caad45807 Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu Date: Wed, 16 Nov 2022 17:23:37 +0200 Subject: [PATCH 01/11] Algolia Search Engine integration --- .../AlgoliaComposer.cs | 38 ++++ .../AlgoliaDashboard.cs | 17 ++ .../Search/Algolia/config/lang/en-US.xml | 9 + .../Search/Algolia/js/algolia.resource.js | 29 +++ .../Search/Algolia/js/algolia.service.js | 30 +++ .../Search/Algolia/js/dashboard.controller.js | 176 +++++++++++++++ .../Search/Algolia/package.manifest | 11 + .../Search/Algolia/views/dashboard.html | 207 ++++++++++++++++++ .../Builders/RecordBuilder.cs | 39 ++++ .../Configuration/AlgoliaSettings.cs | 12 + .../Constants.cs | 10 + .../Controllers/SearchController.cs | 124 +++++++++++ .../Migrations/AddAlgoliaIndicesTable.cs | 23 ++ .../Migrations/AlgoliaIndex.cs | 26 +++ .../Migrations/RunAlgoliaIndicesMigration.cs | 52 +++++ .../Models/ContentData.cs | 10 + .../Models/IndexConfiguration.cs | 11 + .../Models/Record.cs | 16 ++ .../Models/Response.cs | 14 ++ .../NewContentPublishedHandler.cs | 64 ++++++ .../Services/AlgoliaIndexService.cs | 56 +++++ .../Services/AlgoliaSearchService.cs | 31 +++ .../Services/IAlgoliaIndexService.cs | 12 + .../Services/IAlgoliaSearchService.cs | 8 + .../Services/IScopeService.cs | 17 ++ .../Services/ScopeService.cs | 62 ++++++ ...aco.Cms.Integrations.Search.Algolia.csproj | 51 +++++ src/Umbraco.Cms.Integrations.sln | 14 +- 28 files changed, 1168 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaDashboard.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/config/lang/en-US.xml create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Builders/RecordBuilder.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Configuration/AlgoliaSettings.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Constants.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AddAlgoliaIndicesTable.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AlgoliaIndex.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/RunAlgoliaIndicesMigration.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Models/IndexConfiguration.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Models/Record.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Models/Response.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Notifications/NewContentPublishedHandler.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaSearchService.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Services/IScopeService.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Services/ScopeService.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs new file mode 100644 index 00000000..86e7283c --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs @@ -0,0 +1,38 @@ +using Algolia.Search.Models.Search; + +using Microsoft.Extensions.DependencyInjection; + +using System.Dynamic; + +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Integrations.Search.Algolia; +using Umbraco.Cms.Integrations.Search.Algolia.Configuration; +using Umbraco.Cms.Integrations.Search.Algolia.Migrations; +using Umbraco.Cms.Integrations.Search.Algolia.Models; +using Umbraco.Cms.Integrations.Search.Algolia.Notifications; +using Umbraco.Cms.Integrations.Search.Algolia.Services; + +namespace Umbraco.Cms.Integrations.Crm.ActiveCampaign +{ + public class AlgoliaComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + + var options = builder.Services.AddOptions() + .Bind(builder.Config.GetSection(Constants.SettingsPath)); + + builder.Services.AddSingleton(); + + builder.Services.AddSingleton>, AlgoliaSearchService>(); + + builder.Services.AddScoped, ScopeService>(); + + builder.AddNotificationHandler(); + } + + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaDashboard.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaDashboard.cs new file mode 100644 index 00000000..40a69c79 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaDashboard.cs @@ -0,0 +1,17 @@ +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Dashboards; + +namespace Umbraco.Cms.Integrations.Search.Algolia +{ + [Weight(100)] + public class AlgoliaDashboard : IDashboard + { + public string[] Sections => new[] { Umbraco.Cms.Core.Constants.Applications.Settings }; + + public IAccessRule[] AccessRules => Array.Empty(); + + public string Alias => "algoliaSearchManagement"; + + public string View => "/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html"; + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/config/lang/en-US.xml b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/config/lang/en-US.xml new file mode 100644 index 00000000..bb705001 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/config/lang/en-US.xml @@ -0,0 +1,9 @@ + + + + Algolia Search Management + + + Push Data + + diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js new file mode 100644 index 00000000..aa2f8b7e --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js @@ -0,0 +1,29 @@ +angular.module('umbraco.resources').factory('umbracoCmsIntegrationsSearchAlgoliaResource', + function ($http, umbRequestHelper) { + + const apiEndpoint = "backoffice/UmbracoCmsIntegrationsSearchAlgolia/Search"; + + return { + getIndices: function () { + return umbRequestHelper.resourcePromise( + $http.get(`${apiEndpoint}/GetIndices`), + "Failed"); + }, + saveIndex: function (name, contentData) { + return umbRequestHelper.resourcePromise( + $http.post(`${apiEndpoint}/SaveIndex`, { name: name, contentData: contentData }), + "Failed"); + }, + deleteIndex: function (id) { + return umbRequestHelper.resourcePromise( + $http.delete(`${apiEndpoint}/DeleteIndex?id=${id}`), + "Failed"); + }, + search: function (indexId, query) { + return umbRequestHelper.resourcePromise( + $http.get(`${apiEndpoint}/Search?indexId=${indexId}&query=${query}`), + "Failed"); + } + }; + } +); \ No newline at end of file diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js new file mode 100644 index 00000000..9f14dd66 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js @@ -0,0 +1,30 @@ +function algoliaService(contentTypeResource) { + return { + getContentTypes: function (callback) { + contentTypeResource.getAll().then(function (data) { + callback(data.map((item) => { return { id: item.id, alias: item.alias, name: item.name, checked: false } })); + }); + }, + getPropertiesByContentTypeId: function (contentTypeId, callback) { + contentTypeResource.getById(contentTypeId).then(function (data) { + var properties = []; + + for (var i = 0; i < data.groups.length; i++) { + for (var j = 0; j < data.groups[i].properties.length; j++) { + properties.push({ + id: data.groups[i].properties[j].id, + alias: data.groups[i].properties[j].alias, + name: data.groups[i].properties[j].label, + checked: false + }); + } + } + + callback(properties); + }); + } + } +} + +angular.module("umbraco.services") + .factory("algoliaService", algoliaService); \ No newline at end of file diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js new file mode 100644 index 00000000..2424f221 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js @@ -0,0 +1,176 @@ +function dashboardController(notificationsService, algoliaService, umbracoCmsIntegrationsSearchAlgoliaResource) { + var vm = this; + + vm.searchQuery = ""; + vm.searchIndex = {}; + vm.searchResults = {}; + + vm.viewState = "list"; + + init(); + + vm.addIndex = addIndex; + vm.saveIndex = saveIndex; + vm.viewIndex = viewIndex; + vm.deleteIndex = deleteIndex; + vm.search = search; + + function init() { + + // contentData property: + // array of objects: + // contentType -> string value + // properties -> string array + vm.manageIndex = { + id: 0, + name: "", + selectedContentType: "", + contentTypes: [], + properties: [], + contentData: [], + selectContentType: function (contentType) { + + this.properties = []; + + var checked = !contentType.checked; + + if (checked) { + + this.selectedContentType = contentType.alias; + this.contentTypes.find(p => p.alias == contentType.alias).checked = true; + + var contentItem = { + contentType: contentType.alias, + properties: [] + }; + + this.contentData.push(contentItem); + } + else { + this.contentTypes.find(p => p.alias == contentType.alias).checked = false; + + const contentTypeIndex = this.contentData.findIndex((obj) => obj.contentType === contentType.alias); + + if (contentTypeIndex > -1) this.contentData.splice(contentTypeIndex, 1); + + this.selectedContentType = ""; + + this.properties = []; + } + }, + showProperties: function (contentType) { + + algoliaService.getPropertiesByContentTypeId(contentType.id, (response) => { + vm.manageIndex.properties = response; + + var contentTypeData = this.contentData.find(p => p.contentType == contentType.alias); + if (contentTypeData && contentTypeData.properties.length > 0) { + vm.manageIndex.properties = vm.manageIndex.properties.map((obj) => { + if (contentTypeData.properties.find(p => p == obj.alias)) { + obj.checked = true; + } + + return obj; + }); + } + }); + }, + selectProperty: function (property) { + var checked = !property.checked; + + if (this.contentData.length == 0 || this.contentData.find(p => p.contentType === this.selectedContentType) === undefined) { + notificationsService.warning("Please select the property matching content type."); + return false; + } + + if (checked) { + this.properties.find(p => p.alias == property.alias).checked = true; + this.contentData.find(p => p.contentType === this.selectedContentType).properties.push(property.alias); + } + else { + const propertyIndex = this.contentData.find(p => p.contentType === this.selectedContentType).properties.indexOf(property.alias); + if (propertyIndex > -1) this.contentData.find(p => p.contentType === this.selectedContentType).properties.splice(propertyIndex, 1); + } + }, + reset: function () { + this.visible = false; + this.name = ""; + this.selectedContentType = ""; + this.contentTypes = []; + this.properties = []; + this.contentData = []; + } + }; + + algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); + + getIndices(); + } + + function addIndex() { + vm.viewState = "manage"; + } + + function saveIndex() { + + if (vm.manageIndex.name.length == 0 || vm.manageIndex.contentData.length == 0) { + notificationsService.error("Index name and content schema are required"); + return false; + } + umbracoCmsIntegrationsSearchAlgoliaResource + .saveIndex(vm.manageIndex.name, vm.manageIndex.contentData) + .then(function (response) { + if (response.length == 0) { + vm.manageIndex.reset(); + algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); + } else { + notificationsService.error(response); + } + + vm.viewState = "list"; + + getIndices(); + }); + } + + function viewIndex(index) { + + vm.viewState = "manage"; + + vm.manageIndex.id = index.id; + vm.manageIndex.name = index.name; + vm.manageIndex.contentData = index.contentData; + + algoliaService.getContentTypes((response) => { + + vm.manageIndex.contentTypes = response; + + for (var i = 0; i < vm.manageIndex.contentData.length; i++) { + vm.manageIndex.contentTypes.find(p => p.alias === vm.manageIndex.contentData[i].contentType).checked = true; + } + }); + } + + function deleteIndex(index) { + umbracoCmsIntegrationsSearchAlgoliaResource.deleteIndex(index.id).then(function (response) { + getIndices(); + }); + } + + function search() { + umbracoCmsIntegrationsSearchAlgoliaResource.search(vm.searchIndex.id, vm.searchQuery).then(function (response) { + vm.searchResults = response; + }); + } + + function getIndices() { + vm.indices = []; + + umbracoCmsIntegrationsSearchAlgoliaResource.getIndices().then(function (data) { + vm.indices = data; + }); + } +} + +angular.module("umbraco") + .controller("Umbraco.Cms.Integrations.Search.Algolia.DashboardController", dashboardController); \ No newline at end of file diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest new file mode 100644 index 00000000..606ae75c --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest @@ -0,0 +1,11 @@ +{ + "name": "Umbraco.Cms.Integrations.Search.Algolia", + "version": "1.0.0", + "allowPackageTelemetry": true, + "javascript": [ + "~/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js", + "~/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js", + "~/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js" + ], + "css": [] +} \ No newline at end of file diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html new file mode 100644 index 00000000..023c7225 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html @@ -0,0 +1,207 @@ +
+ +
+ +
+
+
+ Algolia Indices +
+
+
+
+
Manage Algolia Indices
+
Search the index and view the results
+
+ + +
+
+
+
+
+
+
+
+
+ +
+
+

{{ item.contentType }} - {{ item.properties.join() }}

+
+
+ + + +
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+ Manage Index +
+
+
+ +
+
+
Content Types
+
Please select the content types you want to index
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
Content Types
+
+
+
+ + +
+
+ +
+
+
+
+
+
Properties
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+ + + + + +
+
+
+ Search +
+
+
+ +
+
+
Search over index
+
Please select the content types you want to index
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+

Items Count: {{ vm.searchResults.itemsCount }}

+

Pages Count: {{ vm.searchResults.pagesCount }}

+

Items per Page: {{ vm.searchResults.itemsPerPage }}

+
+

+ {{ key }} : {{ value }} +

+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Builders/RecordBuilder.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Builders/RecordBuilder.cs new file mode 100644 index 00000000..ae5898f8 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Builders/RecordBuilder.cs @@ -0,0 +1,39 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Integrations.Search.Algolia.Models; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Builders +{ + public class RecordBuilder + { + private readonly Record _record = new(); + + public RecordBuilder BuildFromContent(IContent content, Func filter = null) + { + _record.ObjectID = content.Key.ToString(); + + foreach (var property in content.Properties.Where(filter ?? (p => true))) + { + if (!_record.Data.ContainsKey(property.Alias)) + _record.Data.Add(property.Alias, property.GetValue().ToString()); + } + + return this; + } + + public RecordBuilder BuildFromContent(IPublishedContent publishedContent, Func filter = null) + { + _record.ObjectID = publishedContent.Key.ToString(); + + foreach (var property in publishedContent.Properties.Where(filter ?? (p => true))) + { + if (!_record.Data.ContainsKey(property.Alias) && property.HasValue()) + _record.Data.Add(property.Alias, property.GetValue().ToString()); + } + + return this; + } + + public Record Build() => _record; + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Configuration/AlgoliaSettings.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Configuration/AlgoliaSettings.cs new file mode 100644 index 00000000..ff32739b --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Configuration/AlgoliaSettings.cs @@ -0,0 +1,12 @@ + +namespace Umbraco.Cms.Integrations.Search.Algolia.Configuration +{ + public class AlgoliaSettings + { + public string ApplicationId { get; set; } + + public string AdminApiKey { get; set; } + + public string ApiKey { get; set; } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Constants.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Constants.cs new file mode 100644 index 00000000..7044e2a2 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Constants.cs @@ -0,0 +1,10 @@ + +namespace Umbraco.Cms.Integrations.Search.Algolia +{ + public class Constants + { + public const string SettingsPath = "Umbraco:Cms:Integrations:Search:Algolia:Settings"; + + public const string AlgoliaIndicesTableName = "algoliaIndices"; + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs new file mode 100644 index 00000000..0d57a9df --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs @@ -0,0 +1,124 @@ +using Algolia.Search.Models.Search; + +using Microsoft.AspNetCore.Mvc; + +using System.Text.Json; +using Umbraco.Cms.Integrations.Search.Algolia.Builders; +using Umbraco.Cms.Integrations.Search.Algolia.Migrations; +using Umbraco.Cms.Integrations.Search.Algolia.Models; +using Umbraco.Cms.Integrations.Search.Algolia.Services; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Controllers +{ + [PluginController("UmbracoCmsIntegrationsSearchAlgolia")] + public class SearchController : UmbracoAuthorizedApiController + { + private readonly IAlgoliaIndexService _indexService; + + private readonly IAlgoliaSearchService> _searchService; + + private readonly IScopeService _scopeService; + + private readonly UmbracoHelper _umbracoHelper; + + public SearchController(IAlgoliaIndexService indexService, IAlgoliaSearchService> searchService, + IScopeService scopeService, + UmbracoHelper umbracoHelper) + { + _indexService = indexService; + + _searchService = searchService; + + _scopeService = scopeService; + + _umbracoHelper = umbracoHelper; + } + + [HttpGet] + public IActionResult GetIndices() + { + var results = _scopeService.Get().Select(p => new IndexConfiguration + { + Id = p.Id, + Name = p.Name, + ContentData = JsonSerializer.Deserialize>(p.SerializedData) + }); + + return new JsonResult(results); + } + + [HttpPost] + public string SaveIndex([FromBody] IndexConfiguration index) + { + try + { + _scopeService.AddOrUpdate(new AlgoliaIndex + { + Id = index.Id, + Name = index.Name, + SerializedData = JsonSerializer.Serialize(index.ContentData), + Date = DateTime.Now + }); + + var payload = new List(); + + foreach (var item in index.ContentData) + { + var contentItems = _umbracoHelper.ContentAtXPath($"//{item.ContentType}"); + + foreach (var contentItem in contentItems) + { + var record = new RecordBuilder() + .BuildFromContent(contentItem, (p) => item.Properties.Any(q => q == p.Alias)) + .Build(); + + payload.Add(record); + } + } + + return _indexService.PushData(index.Name, payload); + } + catch(Exception ex) + { + return ex.Message; + } + } + + [HttpDelete] + public string DeleteIndex(int id) + { + try + { + _scopeService.Delete(id); + + return string.Empty; + } + catch(Exception ex) + { + return ex.Message; + } + } + + [HttpGet] + public IActionResult Search(int indexId, string query) + { + var index = _scopeService.GetById(indexId); + + var searchResults = _searchService.Search(index.Name, query); + + var response = new Response + { + ItemsCount = searchResults.NbHits, + PagesCount = searchResults.NbPages, + ItemsPerPage = searchResults.HitsPerPage, + Hits = searchResults.Hits.Select(p => p.Data.ToDictionary(x => x.Key, y => y.Value.ToString())).ToList() + }; + + return new JsonResult(response); + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AddAlgoliaIndicesTable.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AddAlgoliaIndicesTable.cs new file mode 100644 index 00000000..6ae6c389 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AddAlgoliaIndicesTable.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Logging; + +using Umbraco.Cms.Infrastructure.Migrations; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Migrations +{ + public class AddAlgoliaIndicesTable : MigrationBase + { + public AddAlgoliaIndicesTable(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + Logger.LogDebug("Running migration {MigrationStep}", nameof(AddAlgoliaIndicesTable)); + + if (TableExists(Constants.AlgoliaIndicesTableName)) + Logger.LogDebug("The database table {DbTable} already exists, skipping.", Constants.AlgoliaIndicesTableName); + else + Create.Table().Do(); + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AlgoliaIndex.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AlgoliaIndex.cs new file mode 100644 index 00000000..96b97fe0 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/AlgoliaIndex.cs @@ -0,0 +1,26 @@ +using NPoco; + +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Migrations +{ + [TableName(Constants.AlgoliaIndicesTableName)] + [PrimaryKey("Id", AutoIncrement = true)] + [ExplicitColumns] + public class AlgoliaIndex + { + [PrimaryKeyColumn(AutoIncrement = true, IdentitySeed = 1)] + [Column("Id")] + public int Id { get; set; } + + [Column("Name")] + public string Name { get; set; } + + [Column("SerializedData")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] + public string SerializedData { get; set; } + + [Column("Date")] + public DateTime Date { get; set; } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/RunAlgoliaIndicesMigration.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/RunAlgoliaIndicesMigration.cs new file mode 100644 index 00000000..f2521399 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Migrations/RunAlgoliaIndicesMigration.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Migrations +{ + public class RunAlgoliaIndicesMigration : INotificationHandler + { + private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IMigrationPlanExecutor _migrationPlanExecutor; + private readonly IKeyValueService _keyValueService; + private readonly IRuntimeState _runtimeState; + + public RunAlgoliaIndicesMigration( + ICoreScopeProvider coreScopeProvider, + IMigrationPlanExecutor migrationPlanExecutor, + IKeyValueService keyValueService, + IRuntimeState runtimeState) + { + _coreScopeProvider = coreScopeProvider; + + _migrationPlanExecutor = migrationPlanExecutor; + + _keyValueService = keyValueService; + + _runtimeState = runtimeState; + } + + public void Handle(UmbracoApplicationStartingNotification notification) + { + if (_runtimeState.Level < Core.RuntimeLevel.Run) return; + + var migrationPlan = new MigrationPlan("AlgoliaIndices"); + + migrationPlan.From(string.Empty) + .To("algoliaindices-db"); + + var upgrader = new Upgrader(migrationPlan); + upgrader.Execute(_migrationPlanExecutor, _coreScopeProvider, _keyValueService); + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs new file mode 100644 index 00000000..627bbcad --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs @@ -0,0 +1,10 @@ + +namespace Umbraco.Cms.Integrations.Search.Algolia.Models +{ + public class ContentData + { + public string ContentType { get; set; } + + public string[] Properties { get; set; } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/IndexConfiguration.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/IndexConfiguration.cs new file mode 100644 index 00000000..edfd4f65 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/IndexConfiguration.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Cms.Integrations.Search.Algolia.Models +{ + public class IndexConfiguration + { + public int Id { get; set; } + + public string Name { get; set; } + + public List ContentData { get; set; } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Record.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Record.cs new file mode 100644 index 00000000..9de8b48e --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Record.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Models +{ + public class Record + { + public Record() + { + Data = new Dictionary(); + } + + public string ObjectID { get; set; } + + public Dictionary Data { get; set; } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Response.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Response.cs new file mode 100644 index 00000000..2769e69d --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Response.cs @@ -0,0 +1,14 @@ + +namespace Umbraco.Cms.Integrations.Search.Algolia.Models +{ + public class Response + { + public int ItemsCount { get; set; } + + public int PagesCount { get; set; } + + public int ItemsPerPage { get; set; } + + public List> Hits { get; set; } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Notifications/NewContentPublishedHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Notifications/NewContentPublishedHandler.cs new file mode 100644 index 00000000..c821f06b --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Notifications/NewContentPublishedHandler.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.Logging; + +using System.Text.Json; + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Integrations.Search.Algolia.Builders; +using Umbraco.Cms.Integrations.Search.Algolia.Migrations; +using Umbraco.Cms.Integrations.Search.Algolia.Models; +using Umbraco.Cms.Integrations.Search.Algolia.Services; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Notifications +{ + public class NewContentPublishedHandler : INotificationHandler + { + private readonly ILogger _logger; + + private readonly IScopeService _scopeService; + + private readonly IAlgoliaIndexService _indexService; + + public NewContentPublishedHandler(ILogger logger, + IScopeService scopeService, IAlgoliaIndexService algoliaIndexService) + { + _logger = logger; + + _scopeService = scopeService; + + _indexService = algoliaIndexService; + } + + public void Handle(ContentPublishedNotification notification) + { + try + { + foreach (var publishedItem in notification.PublishedEntities) + { + var indices = _scopeService.GetByContentTypeAlias(publishedItem.ContentType.Alias); + + foreach (var index in indices) + { + var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) + .FirstOrDefault(p => p.ContentType == publishedItem.ContentType.Alias); + if (indexConfiguration == null) continue; + + var record = new RecordBuilder() + .BuildFromContent(publishedItem, (p) => indexConfiguration.Properties.Any(q => q == p.Alias)) + .Build(); + + var result = _indexService.UpdateData(index.Name, record).ConfigureAwait(false).GetAwaiter().GetResult(); + + if (!string.IsNullOrEmpty(result)) + _logger.LogError($"Failed to update data for Algolia index: {result}"); + } + + } + } + catch(Exception ex) + { + _logger.LogError($"Failed to update data for Algolia index: {ex.Message}"); + } + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs new file mode 100644 index 00000000..cc8e11ba --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs @@ -0,0 +1,56 @@ +using Algolia.Search.Clients; +using Algolia.Search.Exceptions; + +using Microsoft.Extensions.Options; + +using Umbraco.Cms.Integrations.Search.Algolia.Configuration; +using Umbraco.Cms.Integrations.Search.Algolia.Models; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Services +{ + public class AlgoliaIndexService : IAlgoliaIndexService + { + private readonly AlgoliaSettings _settings; + + public AlgoliaIndexService(IOptions options) + { + _settings = options.Value; + } + + public string PushData(string name, List payload) + { + try + { + var client = new SearchClient(_settings.ApplicationId, _settings.AdminApiKey); + + var index = client.InitIndex(name); + + index.SaveObjects(payload, autoGenerateObjectId: false).Wait(); + + return string.Empty; + } + catch(AlgoliaException ex) + { + return ex.Message; + } + } + + public async Task UpdateData(string name, Record record) + { + try + { + var client = new SearchClient(_settings.ApplicationId, _settings.AdminApiKey); + + var index = client.InitIndex(name); + + await index.PartialUpdateObjectAsync(record); + + return string.Empty; + } + catch (AlgoliaException ex) + { + return ex.Message; + } + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs new file mode 100644 index 00000000..5bbfcc0c --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs @@ -0,0 +1,31 @@ +using Algolia.Search.Clients; +using Algolia.Search.Models.Search; + +using Microsoft.Extensions.Options; + +using Umbraco.Cms.Integrations.Search.Algolia.Configuration; +using Umbraco.Cms.Integrations.Search.Algolia.Models; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Services +{ + public class AlgoliaSearchService : IAlgoliaSearchService> + { + private readonly AlgoliaSettings _settings; + + public AlgoliaSearchService(IOptions options) + { + _settings = options.Value; + } + + public SearchResponse Search(string indexName, string query) + { + var client = new SearchClient(_settings.ApplicationId, _settings.AdminApiKey); + + var index = client.InitIndex(indexName); + + var results = index.Search(new Query(query)); + + return results; + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs new file mode 100644 index 00000000..4dc4505b --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs @@ -0,0 +1,12 @@ +using System.Dynamic; +using Umbraco.Cms.Integrations.Search.Algolia.Models; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Services +{ + public interface IAlgoliaIndexService + { + string PushData(string name, List payload); + + Task UpdateData(string name, Record record); + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaSearchService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaSearchService.cs new file mode 100644 index 00000000..56c5c300 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaSearchService.cs @@ -0,0 +1,8 @@ + +namespace Umbraco.Cms.Integrations.Search.Algolia.Services +{ + public interface IAlgoliaSearchService + { + T Search(string indexName, string query); + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IScopeService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IScopeService.cs new file mode 100644 index 00000000..54cc4e3e --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IScopeService.cs @@ -0,0 +1,17 @@ + +namespace Umbraco.Cms.Integrations.Search.Algolia.Services +{ + public interface IScopeService + where T : class + { + List Get(); + + T GetById(int id); + + List GetByContentTypeAlias(string alias); + + void AddOrUpdate(T entity); + + void Delete(int id); + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/ScopeService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/ScopeService.cs new file mode 100644 index 00000000..48d9456d --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/ScopeService.cs @@ -0,0 +1,62 @@ +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Cms.Integrations.Search.Algolia.Migrations; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Services +{ + public class ScopeService : IScopeService + { + private readonly IScopeProvider _scopeProvider; + + public ScopeService(IScopeProvider scopeProvider) + { + _scopeProvider = scopeProvider; + } + + public void AddOrUpdate(AlgoliaIndex entity) + { + using var scope = _scopeProvider.CreateScope(); + + if (entity.Id == 0) + scope.Database.Insert(entity); + else + scope.Database.Update(entity); + + scope.Complete(); + } + + public List Get() + { + using var scope = _scopeProvider.CreateScope(); + + return scope.Database.Fetch(); + } + + + public AlgoliaIndex GetById(int id) + { + using var scope = _scopeProvider.CreateScope(); + + return scope.Database.Single("where Id = " + id); + } + + public List GetByContentTypeAlias(string alias) + { + using var scope = _scopeProvider.CreateScope(); + + return scope.Database.Fetch("where SerializedData like '%" + alias + "%'"); + } + + public void Delete(int id) + { + using var scope = _scopeProvider.CreateScope(); + + var entity = scope.Database.SingleById(id); + + scope.Database.Delete(entity); + + scope.Complete(); + } + + + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj new file mode 100644 index 00000000..92feac60 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj @@ -0,0 +1,51 @@ + + + + net6.0 + enable + Nullable + + + + Umbraco.Cms.Integrations.Search.Algolia + Umbraco CMS Integrations: Search - Algolia + An extension for Umbraco CMS providing a custom search engine integration with Algolia. + + https://github.com/umbraco/Umbraco.Cms.Integrations/tree/main/src/Umbraco.Cms.Integrations.Search.Algolia + https://github.com/umbraco/Umbraco.Cms.Integrations + 1.0.0 + Umbraco HQ + Umbraco + Umbraco;Umbraco-Marketplace + + + + + + + + + + + + true + App_Plugins\UmbracoCms.Integrations\Search\Algolia\ + + + True + buildTransitive + + + + + + true + Always + + + + + + + + diff --git a/src/Umbraco.Cms.Integrations.sln b/src/Umbraco.Cms.Integrations.sln index 4564ec35..51fbf64b 100644 --- a/src/Umbraco.Cms.Integrations.sln +++ b/src/Umbraco.Cms.Integrations.sln @@ -61,7 +61,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Integrations.Te EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ActiveCampaign", "ActiveCampaign", "{1A4D3D38-F5B2-4528-92A1-318A7D09949D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Integrations.Crm.ActiveCampaign", "Umbraco.Cms.Integrations.Crm.ActiveCampaign\Umbraco.Cms.Integrations.Crm.ActiveCampaign.csproj", "{8FC3A87F-C10E-4605-9D24-BFF46D472170}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Integrations.Crm.ActiveCampaign", "Umbraco.Cms.Integrations.Crm.ActiveCampaign\Umbraco.Cms.Integrations.Crm.ActiveCampaign.csproj", "{8FC3A87F-C10E-4605-9D24-BFF46D472170}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Search", "Search", "{F56605AE-2258-4F61-B454-4247334DFC26}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Algolia", "Algolia", "{F2CAA1F7-9BED-4EB6-8875-D176B92D393A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Integrations.Search.Algolia", "Umbraco.Cms.Integrations.Search.Algolia\Umbraco.Cms.Integrations.Search.Algolia.csproj", "{54A624E5-5321-4CC8-B74B-11ABF3605242}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -121,6 +127,10 @@ Global {8FC3A87F-C10E-4605-9D24-BFF46D472170}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FC3A87F-C10E-4605-9D24-BFF46D472170}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FC3A87F-C10E-4605-9D24-BFF46D472170}.Release|Any CPU.Build.0 = Release|Any CPU + {54A624E5-5321-4CC8-B74B-11ABF3605242}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54A624E5-5321-4CC8-B74B-11ABF3605242}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54A624E5-5321-4CC8-B74B-11ABF3605242}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54A624E5-5321-4CC8-B74B-11ABF3605242}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -144,6 +154,8 @@ Global {E837A4C4-8518-42AA-AC75-EA1CA0DE3EC4} = {8764ECBB-1B39-4D8A-A86B-ECD0D3F0F3D6} {1A4D3D38-F5B2-4528-92A1-318A7D09949D} = {4BDA951C-9C9D-4231-96CB-B1D9B75AF63B} {8FC3A87F-C10E-4605-9D24-BFF46D472170} = {1A4D3D38-F5B2-4528-92A1-318A7D09949D} + {F2CAA1F7-9BED-4EB6-8875-D176B92D393A} = {F56605AE-2258-4F61-B454-4247334DFC26} + {54A624E5-5321-4CC8-B74B-11ABF3605242} = {F2CAA1F7-9BED-4EB6-8875-D176B92D393A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2FB51E08-A3C8-4DFF-B3CB-E99C2ED021D5} From 9372249dac4e320a349716ffdfe47c38edf970a9 Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu Date: Thu, 17 Nov 2022 20:53:28 +0200 Subject: [PATCH 02/11] PR updates --- .../AlgoliaComposer.cs | 14 +-- .../Search/Algolia/js/algolia.resource.js | 5 ++ .../Search/Algolia/js/algolia.service.js | 1 + .../Search/Algolia/js/dashboard.controller.js | 62 +++++++++---- .../Search/Algolia/views/dashboard.html | 84 ++++++++++-------- .../Search/Algolia/views/index.build.html | 8 ++ .../Controllers/SearchController.cs | 39 +++++--- .../Handlers/ContentDeletedHandler.cs | 57 ++++++++++++ .../ContentPublishedHandler.cs} | 23 +++-- .../Handlers/ContentUnpublishedHandler.cs | 57 ++++++++++++ ...ce.cs => AlgoliaIndexDefinitionStorage.cs} | 11 +-- .../Services/AlgoliaIndexService.cs | 37 +++++++- ...e.cs => IAlgoliaIndexDefinitionStorage.cs} | 4 +- .../Services/IAlgoliaIndexService.cs | 7 +- ...aco.Cms.Integrations.Search.Algolia.csproj | 1 + .../logo/algolia.png | Bin 0 -> 13990 bytes 16 files changed, 312 insertions(+), 98 deletions(-) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/index.build.html create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs rename src/Umbraco.Cms.Integrations.Search.Algolia/{Notifications/NewContentPublishedHandler.cs => Handlers/ContentPublishedHandler.cs} (62%) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs rename src/Umbraco.Cms.Integrations.Search.Algolia/Services/{ScopeService.cs => AlgoliaIndexDefinitionStorage.cs} (77%) rename src/Umbraco.Cms.Integrations.Search.Algolia/Services/{IScopeService.cs => IAlgoliaIndexDefinitionStorage.cs} (70%) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs index 86e7283c..a4cf005b 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/AlgoliaComposer.cs @@ -2,16 +2,14 @@ using Microsoft.Extensions.DependencyInjection; -using System.Dynamic; - using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Integrations.Search.Algolia; using Umbraco.Cms.Integrations.Search.Algolia.Configuration; +using Umbraco.Cms.Integrations.Search.Algolia.Handlers; using Umbraco.Cms.Integrations.Search.Algolia.Migrations; using Umbraco.Cms.Integrations.Search.Algolia.Models; -using Umbraco.Cms.Integrations.Search.Algolia.Notifications; using Umbraco.Cms.Integrations.Search.Algolia.Services; namespace Umbraco.Cms.Integrations.Crm.ActiveCampaign @@ -22,6 +20,12 @@ public void Compose(IUmbracoBuilder builder) { builder.AddNotificationHandler(); + builder.AddNotificationAsyncHandler(); + + builder.AddNotificationAsyncHandler(); + + builder.AddNotificationAsyncHandler(); + var options = builder.Services.AddOptions() .Bind(builder.Config.GetSection(Constants.SettingsPath)); @@ -29,9 +33,7 @@ public void Compose(IUmbracoBuilder builder) builder.Services.AddSingleton>, AlgoliaSearchService>(); - builder.Services.AddScoped, ScopeService>(); - - builder.AddNotificationHandler(); + builder.Services.AddScoped, AlgoliaIndexDefinitionStorage>(); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js index aa2f8b7e..4c1bd82b 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js @@ -14,6 +14,11 @@ $http.post(`${apiEndpoint}/SaveIndex`, { name: name, contentData: contentData }), "Failed"); }, + buildIndex: function (id) { + return umbRequestHelper.resourcePromise( + $http.post(`${apiEndpoint}/BuildIndex`, { id: id }), + "Failed"); + }, deleteIndex: function (id) { return umbRequestHelper.resourcePromise( $http.delete(`${apiEndpoint}/DeleteIndex?id=${id}`), diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js index 9f14dd66..ec99a82b 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js @@ -15,6 +15,7 @@ id: data.groups[i].properties[j].id, alias: data.groups[i].properties[j].alias, name: data.groups[i].properties[j].label, + display: `Group: ${data.groups[i].name} | Property: ${data.groups[i].properties[j].label}`, checked: false }); } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js index 2424f221..d303d63f 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js @@ -1,6 +1,8 @@ -function dashboardController(notificationsService, algoliaService, umbracoCmsIntegrationsSearchAlgoliaResource) { +function dashboardController(notificationsService, overlayService, eventsService, algoliaService, umbracoCmsIntegrationsSearchAlgoliaResource) { var vm = this; + vm.loading = false; + vm.searchQuery = ""; vm.searchIndex = {}; vm.searchResults = {}; @@ -12,8 +14,9 @@ vm.addIndex = addIndex; vm.saveIndex = saveIndex; vm.viewIndex = viewIndex; - vm.deleteIndex = deleteIndex; + vm.buildIndex = buildIndex; vm.search = search; + vm.deleteIndex = deleteIndex; function init() { @@ -59,9 +62,8 @@ } }, showProperties: function (contentType) { - algoliaService.getPropertiesByContentTypeId(contentType.id, (response) => { - vm.manageIndex.properties = response; + this.properties = response; var contentTypeData = this.contentData.find(p => p.contentType == contentType.alias); if (contentTypeData && contentTypeData.properties.length > 0) { @@ -102,13 +104,20 @@ } }; - algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); - getIndices(); } + function getIndices() { + vm.indices = []; + + umbracoCmsIntegrationsSearchAlgoliaResource.getIndices().then(function (data) { + vm.indices = data; + }); + } + function addIndex() { vm.viewState = "manage"; + algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); } function saveIndex() { @@ -117,6 +126,9 @@ notificationsService.error("Index name and content schema are required"); return false; } + + vm.loading = true; + umbracoCmsIntegrationsSearchAlgoliaResource .saveIndex(vm.manageIndex.name, vm.manageIndex.contentData) .then(function (response) { @@ -130,6 +142,8 @@ vm.viewState = "list"; getIndices(); + + vm.loading = false; }); } @@ -144,17 +158,35 @@ algoliaService.getContentTypes((response) => { vm.manageIndex.contentTypes = response; - + for (var i = 0; i < vm.manageIndex.contentData.length; i++) { vm.manageIndex.contentTypes.find(p => p.alias === vm.manageIndex.contentData[i].contentType).checked = true; } }); } - function deleteIndex(index) { - umbracoCmsIntegrationsSearchAlgoliaResource.deleteIndex(index.id).then(function (response) { - getIndices(); - }); + function buildIndex(index) { + const dialogOptions = { + view: "/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/index.build.html", + index: index, + submit: function (model) { + vm.loading = true; + + umbracoCmsIntegrationsSearchAlgoliaResource.buildIndex(model.index.id).then(function (response) { + if (response.length > 0) + notificationsService.warning("An error has occurred while building the index: " + response); + else { + vm.loading = false; + overlayService.close(); + } + }); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(dialogOptions); } function search() { @@ -163,11 +195,9 @@ }); } - function getIndices() { - vm.indices = []; - - umbracoCmsIntegrationsSearchAlgoliaResource.getIndices().then(function (data) { - vm.indices = data; + function deleteIndex(index) { + umbracoCmsIntegrationsSearchAlgoliaResource.deleteIndex(index.id).then(function (response) { + getIndices(); }); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html index 023c7225..81033172 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html @@ -1,5 +1,9 @@ 
+
+ +
+
@@ -20,35 +24,44 @@
-
-
-
-
-
-
-
- -
-
-

{{ item.contentType }} - {{ item.properties.join() }}

-
-
- - - -
-
- - - -
-
-
-
-
-
+
+ + + + + + + + + + + + + + + +
NameSchema
+ + +

{{ item.contentType }} - {{ item.properties.join() }}

+
+ + + + + + +
@@ -56,7 +69,7 @@
- @@ -83,7 +96,7 @@
- +
@@ -103,7 +116,8 @@
Content Types
-
@@ -172,7 +186,7 @@
Properties
- +
+
+ This will cause the index to be built.
+ Depending on how much content there is in your site this could take a while.
+ It is not recommended to rebuild an index during times of high website traffic + or when editors are editing content. +
+
diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs index 0d57a9df..98e260cf 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs @@ -21,19 +21,19 @@ public class SearchController : UmbracoAuthorizedApiController private readonly IAlgoliaSearchService> _searchService; - private readonly IScopeService _scopeService; + private readonly IAlgoliaIndexDefinitionStorage _indexStorage; private readonly UmbracoHelper _umbracoHelper; public SearchController(IAlgoliaIndexService indexService, IAlgoliaSearchService> searchService, - IScopeService scopeService, + IAlgoliaIndexDefinitionStorage indexStorage, UmbracoHelper umbracoHelper) { _indexService = indexService; _searchService = searchService; - _scopeService = scopeService; + _indexStorage = indexStorage; _umbracoHelper = umbracoHelper; } @@ -41,7 +41,7 @@ public SearchController(IAlgoliaIndexService indexService, IAlgoliaSearchService [HttpGet] public IActionResult GetIndices() { - var results = _scopeService.Get().Select(p => new IndexConfiguration + var results = _indexStorage.Get().Select(p => new IndexConfiguration { Id = p.Id, Name = p.Name, @@ -56,7 +56,7 @@ public string SaveIndex([FromBody] IndexConfiguration index) { try { - _scopeService.AddOrUpdate(new AlgoliaIndex + _indexStorage.AddOrUpdate(new AlgoliaIndex { Id = index.Id, Name = index.Name, @@ -64,16 +64,33 @@ public string SaveIndex([FromBody] IndexConfiguration index) Date = DateTime.Now }); + return _indexService.PushData(index.Name); + } + catch(Exception ex) + { + return ex.Message; + } + } + + [HttpPost] + public string BuildIndex([FromBody] IndexConfiguration indexConfiguration) + { + try + { + var index = _indexStorage.GetById(indexConfiguration.Id); + var payload = new List(); - foreach (var item in index.ContentData) + var indexContentData = JsonSerializer.Deserialize>(index.SerializedData); + + foreach (var contentDataItem in indexContentData) { - var contentItems = _umbracoHelper.ContentAtXPath($"//{item.ContentType}"); + var contentItems = _umbracoHelper.ContentAtXPath($"//{contentDataItem.ContentType}"); foreach (var contentItem in contentItems) { var record = new RecordBuilder() - .BuildFromContent(contentItem, (p) => item.Properties.Any(q => q == p.Alias)) + .BuildFromContent(contentItem, (p) => contentDataItem.Properties.Any(q => q == p.Alias)) .Build(); payload.Add(record); @@ -82,7 +99,7 @@ public string SaveIndex([FromBody] IndexConfiguration index) return _indexService.PushData(index.Name, payload); } - catch(Exception ex) + catch (Exception ex) { return ex.Message; } @@ -93,7 +110,7 @@ public string DeleteIndex(int id) { try { - _scopeService.Delete(id); + _indexStorage.Delete(id); return string.Empty; } @@ -106,7 +123,7 @@ public string DeleteIndex(int id) [HttpGet] public IActionResult Search(int indexId, string query) { - var index = _scopeService.GetById(indexId); + var index = _indexStorage.GetById(indexId); var searchResults = _searchService.Search(index.Name, query); diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs new file mode 100644 index 00000000..1d280091 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Logging; + +using System.Text.Json; + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Integrations.Search.Algolia.Migrations; +using Umbraco.Cms.Integrations.Search.Algolia.Models; +using Umbraco.Cms.Integrations.Search.Algolia.Services; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Handlers +{ + public class ContentDeletedHandler : INotificationAsyncHandler + { + private readonly ILogger _logger; + + private readonly IAlgoliaIndexDefinitionStorage _scopeService; + + private readonly IAlgoliaIndexService _indexService; + + public ContentDeletedHandler(ILogger logger, IAlgoliaIndexDefinitionStorage scopeService, IAlgoliaIndexService indexService) + { + _logger = logger; + + _scopeService = scopeService; + + _indexService = indexService; + } + + public async Task HandleAsync(ContentDeletedNotification notification, CancellationToken cancellationToken) + { + try + { + var indices = _scopeService.Get(); + + foreach (var publishedItem in notification.DeletedEntities) + { + foreach (var index in indices) + { + var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) + .FirstOrDefault(p => p.ContentType == publishedItem.ContentType.Alias); + if (indexConfiguration == null || indexConfiguration.ContentType != publishedItem.ContentType.Alias) continue; + + var result = await _indexService.DeleteData(index.Name, publishedItem.Key.ToString()); + + if (!string.IsNullOrEmpty(result)) + _logger.LogError($"Failed to delete data for Algolia index: {result}"); + } + } + } + catch (Exception ex) + { + _logger.LogError($"Failed to delete data for Algolia index: {ex.Message}"); + } + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Notifications/NewContentPublishedHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentPublishedHandler.cs similarity index 62% rename from src/Umbraco.Cms.Integrations.Search.Algolia/Notifications/NewContentPublishedHandler.cs rename to src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentPublishedHandler.cs index c821f06b..071fdf92 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Notifications/NewContentPublishedHandler.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentPublishedHandler.cs @@ -9,45 +9,44 @@ using Umbraco.Cms.Integrations.Search.Algolia.Models; using Umbraco.Cms.Integrations.Search.Algolia.Services; -namespace Umbraco.Cms.Integrations.Search.Algolia.Notifications +namespace Umbraco.Cms.Integrations.Search.Algolia.Handlers { - public class NewContentPublishedHandler : INotificationHandler + public class ContentPublishedHandler : INotificationAsyncHandler { - private readonly ILogger _logger; + private readonly ILogger _logger; - private readonly IScopeService _scopeService; + private readonly IAlgoliaIndexDefinitionStorage _indexStorage; private readonly IAlgoliaIndexService _indexService; - public NewContentPublishedHandler(ILogger logger, - IScopeService scopeService, IAlgoliaIndexService algoliaIndexService) + public ContentPublishedHandler(ILogger logger, + IAlgoliaIndexDefinitionStorage indexStorage, IAlgoliaIndexService algoliaIndexService) { _logger = logger; - _scopeService = scopeService; + _indexStorage = indexStorage; _indexService = algoliaIndexService; } - public void Handle(ContentPublishedNotification notification) + public async Task HandleAsync(ContentPublishedNotification notification, CancellationToken cancellationToken) { try { + var indices = _indexStorage.Get(); foreach (var publishedItem in notification.PublishedEntities) { - var indices = _scopeService.GetByContentTypeAlias(publishedItem.ContentType.Alias); - foreach (var index in indices) { var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) .FirstOrDefault(p => p.ContentType == publishedItem.ContentType.Alias); - if (indexConfiguration == null) continue; + if (indexConfiguration == null || indexConfiguration.ContentType != publishedItem.ContentType.Alias) continue; var record = new RecordBuilder() .BuildFromContent(publishedItem, (p) => indexConfiguration.Properties.Any(q => q == p.Alias)) .Build(); - var result = _indexService.UpdateData(index.Name, record).ConfigureAwait(false).GetAwaiter().GetResult(); + var result = await _indexService.UpdateData(index.Name, record); if (!string.IsNullOrEmpty(result)) _logger.LogError($"Failed to update data for Algolia index: {result}"); diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs new file mode 100644 index 00000000..82afe520 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Logging; + +using System.Text.Json; + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Integrations.Search.Algolia.Migrations; +using Umbraco.Cms.Integrations.Search.Algolia.Models; +using Umbraco.Cms.Integrations.Search.Algolia.Services; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Handlers +{ + public class ContentUnpublishedHandler : INotificationAsyncHandler + { + private readonly ILogger _logger; + + private readonly IAlgoliaIndexDefinitionStorage _scopeService; + + private readonly IAlgoliaIndexService _indexService; + + public ContentUnpublishedHandler(ILogger logger, IAlgoliaIndexDefinitionStorage scopeService, IAlgoliaIndexService indexService) + { + _logger = logger; + + _scopeService = scopeService; + + _indexService = indexService; + } + + public async Task HandleAsync(ContentUnpublishedNotification notification, CancellationToken cancellationToken) + { + try + { + var indices = _scopeService.Get(); + + foreach (var publishedItem in notification.UnpublishedEntities) + { + foreach (var index in indices) + { + var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) + .FirstOrDefault(p => p.ContentType == publishedItem.ContentType.Alias); + if (indexConfiguration == null || indexConfiguration.ContentType != publishedItem.ContentType.Alias) continue; + + var result = await _indexService.DeleteData(index.Name, publishedItem.Key.ToString()); + + if (!string.IsNullOrEmpty(result)) + _logger.LogError($"Failed to delete data for Algolia index: {result}"); + } + } + } + catch (Exception ex) + { + _logger.LogError($"Failed to delete data for Algolia index: {ex.Message}"); + } + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/ScopeService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexDefinitionStorage.cs similarity index 77% rename from src/Umbraco.Cms.Integrations.Search.Algolia/Services/ScopeService.cs rename to src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexDefinitionStorage.cs index 48d9456d..59a808d7 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/ScopeService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexDefinitionStorage.cs @@ -3,11 +3,11 @@ namespace Umbraco.Cms.Integrations.Search.Algolia.Services { - public class ScopeService : IScopeService + public class AlgoliaIndexDefinitionStorage : IAlgoliaIndexDefinitionStorage { private readonly IScopeProvider _scopeProvider; - public ScopeService(IScopeProvider scopeProvider) + public AlgoliaIndexDefinitionStorage(IScopeProvider scopeProvider) { _scopeProvider = scopeProvider; } @@ -39,13 +39,6 @@ public AlgoliaIndex GetById(int id) return scope.Database.Single("where Id = " + id); } - public List GetByContentTypeAlias(string alias) - { - using var scope = _scopeProvider.CreateScope(); - - return scope.Database.Fetch("where SerializedData like '%" + alias + "%'"); - } - public void Delete(int id) { using var scope = _scopeProvider.CreateScope(); diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs index cc8e11ba..83426931 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs @@ -17,7 +17,7 @@ public AlgoliaIndexService(IOptions options) _settings = options.Value; } - public string PushData(string name, List payload) + public string PushData(string name, List payload = null) { try { @@ -25,7 +25,16 @@ public string PushData(string name, List payload) var index = client.InitIndex(name); - index.SaveObjects(payload, autoGenerateObjectId: false).Wait(); + index.SaveObjects(payload != null + ? payload + : new List { + new Record { + ObjectID = Guid.NewGuid().ToString(), + Data = new Dictionary()} + }, autoGenerateObjectId: false).Wait(); + + if(payload == null) + index.ClearObjects().Wait(); return string.Empty; } @@ -43,7 +52,29 @@ public async Task UpdateData(string name, Record record) var index = client.InitIndex(name); - await index.PartialUpdateObjectAsync(record); + var obj = index.GetObjects(new[] { record.ObjectID }).FirstOrDefault(); + if (obj != null) + await index.PartialUpdateObjectAsync(record); + else + await index.SaveObjectAsync(record, autoGenerateObjectId: false); + + return string.Empty; + } + catch (AlgoliaException ex) + { + return ex.Message; + } + } + + public async Task DeleteData(string name, string objectId) + { + try + { + var client = new SearchClient(_settings.ApplicationId, _settings.AdminApiKey); + + var index = client.InitIndex(name); + + await index.DeleteObjectAsync(objectId); return string.Empty; } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IScopeService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexDefinitionStorage.cs similarity index 70% rename from src/Umbraco.Cms.Integrations.Search.Algolia/Services/IScopeService.cs rename to src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexDefinitionStorage.cs index 54cc4e3e..65da599c 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IScopeService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexDefinitionStorage.cs @@ -1,15 +1,13 @@  namespace Umbraco.Cms.Integrations.Search.Algolia.Services { - public interface IScopeService + public interface IAlgoliaIndexDefinitionStorage where T : class { List Get(); T GetById(int id); - List GetByContentTypeAlias(string alias); - void AddOrUpdate(T entity); void Delete(int id); diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs index 4dc4505b..c808a1b0 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs @@ -1,12 +1,13 @@ -using System.Dynamic; -using Umbraco.Cms.Integrations.Search.Algolia.Models; +using Umbraco.Cms.Integrations.Search.Algolia.Models; namespace Umbraco.Cms.Integrations.Search.Algolia.Services { public interface IAlgoliaIndexService { - string PushData(string name, List payload); + string PushData(string name, List payload = null); Task UpdateData(string name, Record record); + + Task DeleteData(string name, string objectId); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj index 92feac60..5acc1dd2 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj @@ -17,6 +17,7 @@ Umbraco HQ Umbraco Umbraco;Umbraco-Marketplace + https://github.com/umbraco/Umbraco.Cms.Integrations/tree/main/src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png b/src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png new file mode 100644 index 0000000000000000000000000000000000000000..263b8c3a2da4050df6a94bc7aeb9612b82a99bc5 GIT binary patch literal 13990 zcmZ{LcQ{;6)c@LDW!31t_ujkcJ!%jkqW9iQ2v(22f<*5j1PM!YtCtWpx>zBIy87xr z-}m>|`^Wp-=ia$@&YaIZXJ*dKJaf*xGtko@z@@CF9_wgMy%Z3y0RBZqfF<)w9BMuqM-4YUFGMm^)GXJqwTa6Tw6#CQ~&f zB~0?axR;5zQIzQ{TyJ`SEVwO9Cd`p%3^*}V&m|Y#^KOQ`^nQ+I@8_QuN;_;_h0C96 zEf)CwJ}vw%cVy&epEcL3_^^zV4EhA31=3=OLeHVx2(Y!75ReLMGb21XW(VUC`zO${ zywbj_v&OTY`7rHR3aEd=qkf&nS z0khffxYSog=^(3nB`W)a0~363Qyt)7ac9XHSdw4?l%SV>d|6{SaHFT<_ZhA$ny5fa zAa?08LuPG?0Ov4kb~DXb1?Pa>vEilA@9aY4E-#a<_wkd#*_^9SSPkPaoB)h!elC1V zPk1V*Wre6l8d<$Drd@Ru-Ck9MS`b1c4Mnk~D9x}tq7^Ir*sX5g+Tl!d51&z54wmU& zT3aiWzoE_X7j=uIJ9o1b4V%N3x4NAXY8d^7xS{W|@ z6f@4_BZ*k~lOcj}#7}k0J@|!^k>$FfuxB=Tt{MJ(mVbddcp}vF@nS-v8J>kURB<*j z2r(Yv^bV#CJmEgr;?B%)os`7CPtc+H0=>rNP6?(MBH9)lyep1 zKUf?454(6GT~9uHc@51Eu4@J3cfwnIY`BO>B^ zVzS>Zp8OUed5}jGVEZC7J4*2ib{k$%ZNxh=|6R-h;jeZ zhO`ANmAR9zCmR_bxd)X^pGZoB2s~2Nue}%Z-R)}9ox#*r35)n>;ja;@2YY}+%sTKr zKHsYwQc}zRq&k}hje!W6^Bw;1BL~VES@2r^tE5J9Z|Se5;DcQZ2Cx7qJnhyd1|swy zszeLfm6sk4y3-PAhJ=+fGU2sAME>S}tcv+)nhx;e$NfOTf%s24vl4#n+Sm#Nzm{ah z+`C@57#^DYP+12XMT-pON;dxpu=}q`h?l! zfy4>3t%Io{!exQ#R1yhO6tlwtlk{na_cJnGLba~ zT{*Fl|6kabZ5f`CF#H}c^;+wtGcrlhX!u3|LqJillRgq6{2#szm?PQ6h0|EpMQ1p) z3o{JTg0UlxF*qz&%gtNr>F4S_eNo3mfApitY8r*_!j^cPJ=mqYC|7NfZrI1kT)C2> z{M$?3o5!2?$pl^l#8q`1VaJ1wuhC`43MG!2e;MaMfFh}l)ua8chE?>?HyL{J+f?+} z@i~9-Ick;c{25`&vLRw8Biu##p0fbqYMd~C<4+j;ls2NmlZv)1oM3c$K>q|^KFxeG znA#+4kn0(ViLxZh)gda`YUCQRy z4;ZT8iM*-$jA#YxH{z4v{{(u1O6e!>^k37K_J?5u+Q}ZC@*qtlRcSMad^0`J%*IFm zju24|H}l(UwH}Ys2E$Q|1aOvh_S(&=AzQ!u8&_r8eM4u`#Z0trSk`kzS~a@6Y2ZFa z7=CTq9_fJ;b#zQAePk;_bT}4iikN{@%%)$P^ZK=Pt^s{qrAFsFV9tV1u^=n7Qu5El zM{3*OpIJ+nzgy(*CwNhY+O+~s5&StJ^iryX$s;Hp@rxf&NS{u-%Urx98Hv*`?%u=c@0v! z>M8b)Wg!{^g3*5B$Afk(7ehaA#Psxl7aT1tOvF5ks34%cinq%b$`&9RVyMoURU~wQ zt*1(2M@Q;UC1T`}5|b-C3BHr9jrA-p-C?WnilWOk0>k8*q7<0!AW1x5B z;8=zCAKy4ipfJm0A!WL0Q3Qt7sVY!LkSXZdKI#L>?Y9gK#|6>Jr4Jl4Q|b5u!#fzS z5{V?9_BS1=w%W?DP=5`N-<}-V!_y4N&+M0?MuT~#!tqcFi1z`(f2s&>kDDNxb7V%0DxmiZ5TRWx7=oY8(QVIaa^0$K$qJTpD)L`A zVTQU}2Ikp!P@?#j!)J&^L}^0o$Iru{TgjcXEUcz^uo%Eo4R3cr-a}$q$E_#SG{j!L z4bz2bPb~pMy8V)EH6anT{c8&`5y@z)tmD$1tm3wrRj!e6>+T z-Fcg@s~$^5rIS9TlB&MDRNUL^nkjU2tZBXzgqBB)BcwHpwPtwXQtu_%4q}(D;jyJ0ClXpli4T?>WgWb0ia$Hm zX^#EO3Ls-^kyVa^yJ~ zpV(K+Gr!5x3^8i9F=M!>yKR1e+BECEppMYL6F`x(P0oU&UFDZ@}V z5_9-ZHamt{wLwY`RK$0ylFMlspIIrMQOWTg63tv57+?Qy7PS^#o$UG_UX;q-F@`}T-!j#YO_oJhR>a=5XuBPXz+Brz= zZjg>#6v{CDU7vr0^e}`!f3gHae(PT>FDIS9gt1!9=72*D=qtzvB#)r}dqI|3wDLmF`|2n&eRNOn|uLC(lgMxGeE4Su8c*at&%WmJ(g_Y5`t1i(=i;%kWv>2k zrg=PJYG~6KXio?#a91xGfeVjnwu*k$P3WRZ;K;mmB=&~X#xmqp32zh~Dny6bPUm_q zqZWzPfO^_(%-r_8@E7U_+g}0Xk@@k{&V0%N?Y_<_`Ekl?L8_QWWn$+ZX<|B+3s6B| z81zzTlhmj&XXFM~#K<7(I}>Xa{a?SAyFn0vZZ+k(Brd)%E=$Fx z%sQ3#fp}z{0aUs60)Yb*NH@*&wt);K_TF%=u{QR9aa4N7TR=;ic1pq_5Y= z(viMTdG>EeR4D1`li-&cEHf__dK2DCe_DvM(pdBe7@Ok~_R!U^zm7`g7%p@^CG35R zcuEX+u*ziRa4t9IEY=A)|~k}?BYAq(Nja)%mn5ZY$9pe;vyk%Gi>UF z0)${>poYhozdd%)XP#R+Ut|YFSA_V}>xwnQ8;X(@@(5MJ;mT6iVw(kgGzUt{9Pfum z>5H6`6k(n98ErF+4*Xj@NVQk#=)VGt`bKHrQ9t6H##GbIujcnuuMxCW%BP~5&#ncg zmBD5)@yz^ga$02K*+a}l?OZzA6D%d`fpXCHjPt68YqXTi=F=DZX+Eh3OPE)h_d%*p zP3yywcd^92sQYbOIX}DwaTCow*#}5MTv9&8cxZlR5=}ue&+CIC2v-TZSf^cL*6iD0 zkbfPvp{mBfs37{LbNbDMFhQABMnx&^lPAXh(;TjH`*9uTWfw{R)M(d_rW{d%aOOB^ zSnz(7MD%qZ5%HbKuj8Z;Y!_EA5QBQJqk&Rmrkv1+;6^3JD6#E2xsX>`e2F zA&fIqrtLW{;s{`RzE=1s?~-9%4kOaVrdoXG2WD4djdn+!~y{6yi#^okb%>;-qbGz0 ztjn7L`Um@y+REKFeWT2}qDq7R6ma}h3zJy)9dfGGI6;hP-Y~MC!W{m{lm`e5d5rtf z#8i;_Fh&t{RA{o`EeEH+In(Ja=*liTn^PD}l224&Lg_Od0h~&3sv2=TaTjoMWc3`1 zV}B(iN~`Aaxs!`jS|;cBJhA>LXg zo1YQyzB0ZHt({XQsxE``qkQ6P#7lkgBukrqSAkRBA4F~>c!mX*ln$?9+_zpt7{Rwf z%+rOzMNALiLf^2DAXo?o7q8*YdRnBwlJ9X~bwszxaC4CGy78dmS**Y2dGH;He}p{QC2baZ?HX)sGeWKe+LL<)zQ8z9)z&)%Rcb z0_^>|=rs@QfAV!L+JC^Qn|n7G?^PC(b9Y&Bzev9@d8{3w9MbUTk2=;#1%s~u)`l$xmcydZCE925oTSID){FGT?0n!Jf_Ke`DkS$)ADMgU)ix-OPk}7 zYlS)&ZWZNt{I|4BGZnng49QYTPp8EWUE}3s)Y>TG`I#Onj(~2{U1%((_%N#1aFZG+ zEeihbm-=Ne2u-l=%N^p5zweCQnMf5a_Ofm&$1U9jCBsDO$CZ|E{GAZp8Nu|9Uy3HZ z5mLB6YDC$tjsCT1lWD<|AR~?)mcCQwx?AufN^PAWX1fK=%S1{lQ#Qs#LyC;vDJGS# zHxa^Z@tDpMp}0zFLE#^}+R24$to4`oI5X#ZpDwB2>=JT;I`J?wlMaRyyXEn>yuX)#Bs-!+1J4Rhx_UU?ix=f2TS$fl6cE=M zV&%()apIhzLx?H}L|PFNEyhUXFzwa{Zmzs~%_(jgMmjTr(~J-Y9rfiemD=L+u&*o2 zo;1D8+!Z#Z-H#6S%*;DhPIOBf_kJcc4}r7KUSx5Bf5+;+`wrKnpoYRH1(Hl8rxg{7 zn#2Xu+|!lHTVfGzgqQh*9h}G@2DW@wkLN4uF4^m&3!ux{-*F4TprPSzWJc3cRpwMn zo-Re{X3VPDtH^j&nwoK+83BRb1Ju7!))tpKj*Yr+H31S#!3F(YV=9gnW9twRrTk$_ z4VQ)JGObo|5=Wj2>?^MFSF{`JZVsNTAFn*@Hf8NwiiMk>dNw7_0p@`^H%`vmELSM5kA$u;Foay#ibU3<{tj)0}o>b?)RGzc`ZxMjxw)8Z{p|m4%*tr zuZq-Rz^n|7=qYd3#F^!%AH8(Wrv~BNo_Hu;M4$L=ojTG|@l<>TH>a%u=r9o2WAd@a z!MA1JT3*} zQWV+Wz^C#@y-)oGwYG?LLdEGX&3Vp$O0DF7`T&@)Kex{irtz+U7%Bex@_tsrIFGpH zDwI4j$VjO0;Q)R2P1UHYl!E$QUc?v;+|(gku7@SNE&UTS-v&-#+e^ZZ!YC~&x~41k zs4G%krZ!;w`RL0Piu+i$zf@)sV(VLsGbs(p^CIIdv7auHzecohYhwj&ur!NBl^m_r zOLw}z^0C+4xWnXnNC*Pfx5OkkkayX@q>ZEO;ox}dJ8_bk;T93x@39fYBQol9qV=;SqjsFwFQ)- z=1T((L^NwUCQqVg5;9jYH|+aoWX^RMPR17XP0-Dtx2Zg#Miq+JSD z2G3^GdqNvYv8zb>$>?Pkg=A|`1L=1WlvX27y{)f7*P)R&@3uSRRX6O>Z6fLmDq#my3Twxl!`_cjw30zL7Z zSx4~he_77F@!2bb)1_L7c`=nV{k}-k_90izos=5k3yCUlw%}Cb?2!qhGCQ>H&b-s& zIzKg-IIKPvu}}1$`jf&+j~DTDG9^#D9rOF-Cf`@A@8TsGIcM)xuA(g7KFDaszvD~s z|0AKU4)ggC7lt5o?jMKwnjmgcI5y}lHVKe^2z|5fwJq=6Z@?AMtd_+M&J79-Yhyi5 zPk8};L^90;x{anb&&Gv`UVJ60{E8}p++J!zE6Xf)xl5Sg`%X{)2=;!Y`U=~g1ICeL zc5;xnmg+z|;zQ(-P_VI%EQRpKsK{fN_zbSWbd0IQ`+m!k(n2}xI0D zn}W1twpA|75?^txOMTfgu{irv7!b(mx=gm9_jPy(>Rk&yN)&VQ>`)t|e_oTYN96(x z)VXH17$$X}@HU2Bdhl%_JUM>opLrSRoA}s9}Qdsqcv;?qq zf?kTfAm$@=B3vMl?9TURw)q6OCHa`d!yhUaFAZ;<#^^u>r*!(zZmY993v@(M{P{Wt zxOjGLSUuL*0tsz|yf6}!5sQ}?9EZaVpKxLgxc8 zky=p!=+LSrlEeF$TTv7Q8N{V}GgzMYf>jQB(;HzuO$1{;iJ7mQ(g!{YsNI*lY-?)f4n^}48!k-61VkG~&P^ke!>aUveQu?>59NvK`Aq-}<3k zwj@z5KCcxXy9s~6kLYS)?+=CDJEC3F9c<9jE3W;2C*?D>DwG(s+LStaBvq$hdBaEK zRD?n;K`7#er^9o<7>j4JV_$0w;URvc&My!^J`7GKbA%0F^B|drZqfjHn1jDoXqbNw zcFG)MAU0p#Xk0GgE({!Nrx}=uRk~3GQ$9?Tn?=G!1l)1+0h-!R>r;$fgMT6j2G|7Bbp|&O^3P zvLp+0Hs5yC?wORZR&kJxh#i>)WBedoL_v!*4CoFDBd4<5*!U?f>Wuf*tV%uub+5lN z{!AA^34OsNV8fviRQUVopewC6W#eN+othx8%Ep!SimrEU|N7+T@QJc% zzDD}`K%%UhaK3c!i(+(u=(NI z%--BX&Xfn2dI-Z}(FStjWBS~}x=&~w(95p7RH05%_Xncq#Oom-{^_|*a{EG6jy^S=8 zbR}>HzWRJXSdP4QrmoeE0mOiVltpuRcD)Ee8d$5w&F?S!%&SJTyq{1Sjb0c25~4>r zL5V%`9zS0sLP#BW6Vp}0$a2&EWKA9zaQM|2U!uEV?+Sr^jOP{(Mq63$Wvq$*>1CXc zNZ3hy4`l>esZ@xTzOB4(8hp~-XM=G$P*05}6B4oS{(@D<(YDhWJz1ix1T+-rzi-O* zuHUtwrkU^pbPsw=O_Z#d6tTMcxm=mIeBb!>W)kY{5_!}Cz3FivA`>WlDyF^#k|a(^ ze@Y@nnd4T8@yq;FedA-_&?_M;6^RAy5sah0ap7v(g4`&=4t1n|=AUk6Lky)$PpqGg z*5bS22tm|r7ROk>E+vg!og-~D z=p@Cu=s|K!PW>Mj?9yN>TN)ARLpqyK-gPXp;&o*_njuc&*tn^OduO)(v4PiA5i~i| zQ7FPg7fN-IQ--k9yu5|JKLnfyo4k3pDLb>X2{ON5rCXvE?D zJ&ZMAcoN-r&N`3fw%_x})SLHEeTw=^mp^bVj1j$)djTBa4VVj9qd>CN0mpu9#C0!8A8|a?15qNj1suI_wtqoRo(VO z>B2fQCy>Q=rvuA@JW%T&L!4ydK~QaGhp;!MFWD_#ZR|-Z)ti+y$Vy^+D*oDlr+NaHi#A*Dcv}TFFM-6=@zUk)en`4e}#{OK-(#SPi#gasc_sE3${m zr#m)e9gU9k8yxY2BiB{tcS@Qs^DS?$(vlu11*QZlKBdp~z8M(A%%Rg~4Va0V6f`Az zphk8>K5TVVE|hFD7vT_}Tk}R}wX-nqz6GC-TAAPeb_kXxDs^{qvx$J0ypVavi1}ON zl|Z61?5s2>NQNZ1ChM{cl;d2aUb%I}RjDF5ZLFiZm0&h(7S3T#rHzf~8-}0Zi5>TO zOM2sYv|4v@?3O*6Yf0=%RxDxyR@8TzxrKf?|It*oS%aqDj2Lbw^qaqVkDG0>BZa3D zl&qZlfnc(I368!rWM8w29UI{+31GtKjRd*;zv4pvodE~vEk;58*>fk51A zx7sRS*R4NYo+3NtLNWR6gk(CnnZpMl}qQ5X$R_*IOl_RGWaB%;$8kBs>0_j^&itZBSWz$Wi4aLs-BYf+Nf8Wy%ZZn3(MP~8o-Sy~TF+ZeXSr7G1KPyA zbO5>II99~{O2zwbCMoYB=t|3!#=t}hIITrDYOPHCZzJ*9fTCE*iYzU&E<4)IGPUHc9CvvCfiE?_a+;QW5`g2I z;iek6jar{v>RV+i@lfv6Zrm8Q`h^B(zk9pQv4^L91ymZcX*n4u5aILY!fO-8@Y%ra5m^zz?I`@t0d! zO>XZt<>nurKL&4pd}G@`LH~5OTW_|g?j2b)vNj}3DzrP!Lcqe8wYJmcU$lZM>fJY5 z5y93Ixm+%#QTDS6h%Um-(D{-QW-kyC-P3SHxSuXYqxLBubW7aQf&O9`Gbm9y;w_!+ z#-YccaU|;T37(}1I`+z_J)cf;-#e}GzZ)^m(41MT>elN>@~&8kVfj)j)YFRMLpd~* z&{HK|Ip0OBY9&ks6_vhG_D7i(1wB#aIK7M5qGnZV+8k3`}=Y%jfyAD1FYzh3ds(5-f0+(4S=S-OgKH zU(J+I@#=qey*lE@Lksm&+yhMl&y2~^FV>sOX`hpH`m=jt;+DDo;2m~Z5HFX|E@UFu z7-F1=Pz73L!Yq9n!4P{xcGZ0)dCFhYU5*|lo+^&N#psdu3GL5D&9P+eR<*sGR(`i! zGdSB)qzm@Ns(zqy<7r^$wbksMemXF*I*@jGB%64tKryu9lR0a9k}!@FCLvu zPxw@}YVx!Txf8=f2&FHF@%VO$?;Iy`%gJdIx@ykYyxPT z=d}ObwWmJ}tEqC11&b9sr&DW!8g-8wxu++SS*U;A6Ydxb*i6Rc{MRvQuIyjyHS~_j zmHPzqlGCyV-<=XJSy+(Pt4&d)=b-&JgLBDv)0@#3syF3=_qkUpNLn<^)XZ9Vy8jlx zN?ki~FFjA`tzgwX#-WJ6v-b0kIcZzI-30d@@1+ZtZaf7 zu?_9cgmaTzD&4=Fo=Za>&0nTIgwa>yrN0G zPdH_}0pLChFD~c*S7H$?mE?@;r9M^o%HTe;iZx(vfCZ^wSXgs7+sRARR73ms@Cuv= z(B_R$fe?9>%H)2WmJgvd!<(+qdIo&NVe~fpsR6%f>Q(w>y#<3eFhwV!0TpbVFQq3P>m@X8L+w~WBm=(n>$^JRHztx~*j03#y zNcD@l5+$bGrs%#r#!#K}_;UqUj|T0l`JGE`Mw@2^bqPeIw;T5B8_36ii7p8iB9O9^ z{T+qnWtelilD^qpqu&bp9_5P9q-Yl{9V%Lw2MiUze!OAB4II!&KjXQ{TkHjf?bGaTlF$@@)PqYuIe3fdG>=T6upQR2=73KTiKlxHq( zba%%bA`Wkx(3jT(NAH)XsJc^k?hKW(Gw~*SHj3UJ(sP6GMDDfDHMqnCaK^cmxpEUH z?xPjbdOH--Y$s+0dATef-VaEGlS<0kvu1o09QyIfx>qLvHMF3zvx&llo2e5=bv%iU z3a859d~w$5)ONglrOzgppGsoNAxd?3!KdojQ#QHIjBC}VM^+mE99_5xD++Z4b$7nj zM9VdxtqPZL%CLQRvCwxn$+kbv7`8sj$ zuCb=4O_$1FCg+#1_#d)b*p^Up%`1xy%-#~djMcG>XR*o?e{n|qQ7P*TjagZWyX%uepSII zY&46n=zBX5~&dJUDynGqN=`yoI^UM7s`Pm;X&L1v{q4uDKwc%5) z=dJ=GPP$23Smcd3pSS9i1$AiCFY{e1Kb#%@d2%=ZA;j6PvwmOx?^`=H*0N%Ymvphp zXP1iah$WS_x`;L1=8_~j*)CPUGQ!t67h; zFUP^guP+g{4`b>TN?1Q;Tfqw-nl1cc=6?@d6ayjy)!qqyR~``&Qs*MyDmTkpOle+*yYl?2d}??dpR=<{fEWCia1;NYgy!^;@*!XU zA66Cz^lOJ8jUi9!ongS~!K7tF-R|j!>1^4~sGCjLlOUbpY>G%bmmjNienr6n8yAYF zp`w{-9%MAKnB|MF5@QgYQ*>{@^lZ0DCK8VkQf&cZZx|or0z4V71>PV;VJh~MdHyW4 zi?ON)eA_``py?N3;H>v+QM=^1d#bxz3dJ~yLp!_({FTACh98e4QZFN@7dx>pZY7jB z%&*=KOM2`B!~CIMwYn$YQg~o8L;D z*h_1*UxSrdv`e?-dms$QhEu1g9l1-9*vq5?z9mD)43(~Hn}n*-I-xP{=OReJl5d*o zZ^oW$7rtsnNm|G*E#<>g2n5=f)cw6}VJlYCAOunyFIbj-bblnS3mlt#I~5c2QFltd zLF*eM$A_~%TcrDvuL@`K?72d6Z^00j`)ds8ydw2i0$nG-yPN)+z{A=Qeoe*m!Cv2M z4to6WTPdn;>XZ1_1&Ep~T#*gKDvOV~+PtuLe{rkV#Y}%68$nl-4p!)Ohl@c~fXyV>5bib8lvT+m~dbH{*{kjUTdrVUMYKiE@YA(0dA(UL))(DMRr8-FW#%eQ{hNAm$_oyn&_Uf zAQuXY-g6buJ(VZVg^~lc%blBEKP47#MWh(Rl$mJh-uwHO<|&`mn*GDJ5zApK$Id60 zX307_lBy?@Spog3YoL7`sp3Y{Sld5H}a5mCrbZO>)Wnfx%iE;}J2am19hu?H}9N84duIl{3Leo8Ts zS)pVSo@LE|rOU*TPRfu{YzLmD;Jip$XMST&NtK9h;v=sDO;IwwBUNJ$W5y_O+YBsj z+n9$^=6X6R>;nh0k&%I*U!PEKX=?}ZNde9edbF)EK8Zdn;)(%E}R$RZl1m7YsL{=QXFQovdvfv`Pz@HN> zmaOyS)AXq**ZS*)YhF^r@Dx9kHudZ)Pcn!YebwkCb<5wrkrFzIhopSMy)U8!tiavjK^9YUA|c?=tdc}L*~)1sau6^=#5B0yS6RQOek_E@) z-sJ~HL|?U0sneJdk@w=!`W_qyRU7#%2<@h#{)cUuOJR&Y)x$-3;8Qp#KPe{fn%Mbq z>(<2h^M$stMz2sZ+wpv{Pu0rk?^IKFjR66Iz65}z(%^OgvatnEv^a5b8}yy!pg)*N z7ML>sQ^?~yHb;$1V~pslQJxK(%9WfYqr+c$hRc9cZlsUjnRv2uf+NS(`?sLXv%|@R zy482aUrkGR8vl;$Pd7XmW$&{VXCpF)F}VpZ>+b;oQ@FqUcBUC#T@1{{2Z6- zyg&A{SvNlGhN|TIQ6rQUI4pUG#aYJC1x;)4_n+zYo%%`aiEmZ*^o>E*o(WgcRKbV@e-B@2GPo+yJ6v(I+HMWybcZcQ8MDfOh3PyaOb3&Q@ZD-a8 zx2Gn&F3B>}>#zQB584p98s1IrMPK<2Ay8#;qr~OWIv*Lx7FLk-;Y=W32!z5YU-~W9 z%PFEXILwht$!h2wX=)ZKEu-jP+@~+8O*HlDHu6ZdNCmr=1FoK;;dsEGG`~Vlp6ij? z^7jMKX*zo^pew_MN^v#{Khv{goNH=X8j;BZ@1zf=iRE1*kb}m=9%4kuS~TrKos}z_ z4xn$7iueuH4xn7c%Bhtb?45XmWBaKYIEQ40jJZs9_V4GE3E6+$;*w~}q;L6=o727^ zf9Ryi!q`z&)}tCEwe-vGgUU-7{G>yFeq&tpRtYYGb(lG z{LkNe!AOBj8%ew~z%~ZuWcH6o#cb^3cNy?*$AkQkd5HBEa2DdsO4rJl|gBDqJI1!R%%srANS3c?$IQDS}yqJ`8 zR=f8#ys9(K3ymy;s{N;d^XL2I6UntHgcjGAVD+ep{l)R8%hH-u&WfLhY6|{5m>`o7 zUeKs$AyHyTJSVZGl-Of~G30`*)Xsl5Fp+ma=1J> zwIut6T(cfy_lw8p*CH7e*ZZA-f^4f85MgTeyFB{c#xCbP%CecII>r%UxL38ry2U|1@wIrJ>94_tAlWRZV>oxBtX2-keG&7s ziYQPe9xB#I_c+Xu?MN^W3RP=#MB$(P`Y9J*ZOKSzk(OvMC-W?6LWWY!ytSljA2S@1 zk0Y0N2BJ|XcL7|mGKhM=++wY~e-ps_NzM1e~vVR>2L~~NPP@O4F{DDmb7uzATC}eg=X@d;uDtXI{B!rJK$|H z@?Y*=oMEMX5Cx${NgD=LyqK7%A;oGA;u>M>pGzAJ&bL7;>k*lEEKLC~1kC$?11wbt z+v_PGytpgyax`)&#kwQ6eKbGjr0Q5(0W5U_mI?a$icTxUr#_V&%ax(Gjjc)UEf_K2 z#Ucy@Xm)LxDGUcpnQ}hm3a}-nrLvmSHtUSLQ+M-1zHss^>Y=?Vdj$gFDt Date: Fri, 18 Nov 2022 11:03:16 +0200 Subject: [PATCH 03/11] Base content handler and Result object --- .../Search/Algolia/js/dashboard.controller.js | 6 +- .../Controllers/SearchController.cs | 22 ++++--- .../Handlers/BaseContentHandler.cs | 64 +++++++++++++++++++ .../Handlers/ContentDeletedHandler.cs | 50 ++------------- .../Handlers/ContentPublishedHandler.cs | 55 ++-------------- .../Handlers/ContentUnpublishedHandler.cs | 47 ++------------ .../Models/Result.cs | 35 ++++++++++ .../Services/AlgoliaIndexService.cs | 26 ++++---- .../Services/IAlgoliaIndexService.cs | 6 +- ...aco.Cms.Integrations.Search.Algolia.csproj | 9 ++- 10 files changed, 156 insertions(+), 164 deletions(-) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Models/Result.cs diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js index d303d63f..82f464bc 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js @@ -132,7 +132,7 @@ umbracoCmsIntegrationsSearchAlgoliaResource .saveIndex(vm.manageIndex.name, vm.manageIndex.contentData) .then(function (response) { - if (response.length == 0) { + if (response.success) { vm.manageIndex.reset(); algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); } else { @@ -173,8 +173,8 @@ vm.loading = true; umbracoCmsIntegrationsSearchAlgoliaResource.buildIndex(model.index.id).then(function (response) { - if (response.length > 0) - notificationsService.warning("An error has occurred while building the index: " + response); + if (response.failure) + notificationsService.warning("An error has occurred while building the index: " + response.error); else { vm.loading = false; overlayService.close(); diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs index 98e260cf..37023236 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs @@ -52,7 +52,7 @@ public IActionResult GetIndices() } [HttpPost] - public string SaveIndex([FromBody] IndexConfiguration index) + public async Task SaveIndex([FromBody] IndexConfiguration index) { try { @@ -64,16 +64,18 @@ public string SaveIndex([FromBody] IndexConfiguration index) Date = DateTime.Now }); - return _indexService.PushData(index.Name); + var result = await _indexService.PushData(index.Name); + + return new JsonResult(result); } catch(Exception ex) { - return ex.Message; + return new JsonResult(Result.Fail(ex.Message)); } } [HttpPost] - public string BuildIndex([FromBody] IndexConfiguration indexConfiguration) + public async Task BuildIndex([FromBody] IndexConfiguration indexConfiguration) { try { @@ -97,26 +99,28 @@ public string BuildIndex([FromBody] IndexConfiguration indexConfiguration) } } - return _indexService.PushData(index.Name, payload); + var result = await _indexService.PushData(index.Name, payload); + + return new JsonResult(result); } catch (Exception ex) { - return ex.Message; + return new JsonResult(Result.Fail(ex.Message)); } } [HttpDelete] - public string DeleteIndex(int id) + public IActionResult DeleteIndex(int id) { try { _indexStorage.Delete(id); - return string.Empty; + return new JsonResult(Result.Ok()); } catch(Exception ex) { - return ex.Message; + return new JsonResult(Result.Fail(ex.Message)); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs new file mode 100644 index 00000000..0abb5d5a --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.Logging; +using System.Text.Json; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Integrations.Search.Algolia.Builders; +using Umbraco.Cms.Integrations.Search.Algolia.Migrations; +using Umbraco.Cms.Integrations.Search.Algolia.Models; +using Umbraco.Cms.Integrations.Search.Algolia.Services; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Handlers +{ + public abstract class BaseContentHandler + { + protected readonly ILogger Logger; + + protected readonly IAlgoliaIndexDefinitionStorage IndexStorage; + + protected readonly IAlgoliaIndexService IndexService; + + public BaseContentHandler(ILogger logger, + IAlgoliaIndexDefinitionStorage indexStorage, + IAlgoliaIndexService indexService) + { + Logger = logger; + + IndexStorage = indexStorage; + + IndexService = indexService; + } + + public async Task RebuildIndex(IEnumerable entities, bool deleteIndexData = false) + { + try + { + var indices = IndexStorage.Get(); + + foreach (var entity in entities) + { + foreach (var index in indices) + { + var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) + .FirstOrDefault(p => p.ContentType == entity.ContentType.Alias); + if (indexConfiguration == null || indexConfiguration.ContentType != entity.ContentType.Alias) continue; + + var record = new RecordBuilder() + .BuildFromContent(entity, (p) => indexConfiguration.Properties.Any(q => q == p.Alias)) + .Build(); + + var result = deleteIndexData + ? await IndexService.DeleteData(index.Name, entity.Key.ToString()) + : await IndexService.UpdateData(index.Name, record); + + if (result.Failure) + Logger.LogError($"Failed to delete data for Algolia index: {result}"); + } + } + } + catch (Exception ex) + { + Logger.LogError($"Failed to delete data for Algolia index: {ex.Message}"); + } + } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs index 1d280091..b01b4a3f 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentDeletedHandler.cs @@ -1,57 +1,19 @@ using Microsoft.Extensions.Logging; -using System.Text.Json; - using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Integrations.Search.Algolia.Migrations; -using Umbraco.Cms.Integrations.Search.Algolia.Models; using Umbraco.Cms.Integrations.Search.Algolia.Services; namespace Umbraco.Cms.Integrations.Search.Algolia.Handlers { - public class ContentDeletedHandler : INotificationAsyncHandler + public class ContentDeletedHandler : BaseContentHandler, INotificationAsyncHandler { - private readonly ILogger _logger; - - private readonly IAlgoliaIndexDefinitionStorage _scopeService; - - private readonly IAlgoliaIndexService _indexService; - - public ContentDeletedHandler(ILogger logger, IAlgoliaIndexDefinitionStorage scopeService, IAlgoliaIndexService indexService) - { - _logger = logger; - - _scopeService = scopeService; - - _indexService = indexService; - } - - public async Task HandleAsync(ContentDeletedNotification notification, CancellationToken cancellationToken) - { - try - { - var indices = _scopeService.Get(); - - foreach (var publishedItem in notification.DeletedEntities) - { - foreach (var index in indices) - { - var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) - .FirstOrDefault(p => p.ContentType == publishedItem.ContentType.Alias); - if (indexConfiguration == null || indexConfiguration.ContentType != publishedItem.ContentType.Alias) continue; - - var result = await _indexService.DeleteData(index.Name, publishedItem.Key.ToString()); + public ContentDeletedHandler(ILogger logger, IAlgoliaIndexDefinitionStorage indexStorage, IAlgoliaIndexService indexService) + : base(logger, indexStorage, indexService) + { } - if (!string.IsNullOrEmpty(result)) - _logger.LogError($"Failed to delete data for Algolia index: {result}"); - } - } - } - catch (Exception ex) - { - _logger.LogError($"Failed to delete data for Algolia index: {ex.Message}"); - } - } + public async Task HandleAsync(ContentDeletedNotification notification, CancellationToken cancellationToken) => + await RebuildIndex(notification.DeletedEntities, true); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentPublishedHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentPublishedHandler.cs index 071fdf92..4ca68c8b 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentPublishedHandler.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentPublishedHandler.cs @@ -1,63 +1,18 @@ using Microsoft.Extensions.Logging; -using System.Text.Json; - using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Integrations.Search.Algolia.Builders; using Umbraco.Cms.Integrations.Search.Algolia.Migrations; -using Umbraco.Cms.Integrations.Search.Algolia.Models; using Umbraco.Cms.Integrations.Search.Algolia.Services; namespace Umbraco.Cms.Integrations.Search.Algolia.Handlers { - public class ContentPublishedHandler : INotificationAsyncHandler + public class ContentPublishedHandler : BaseContentHandler, INotificationAsyncHandler { - private readonly ILogger _logger; - - private readonly IAlgoliaIndexDefinitionStorage _indexStorage; - - private readonly IAlgoliaIndexService _indexService; - - public ContentPublishedHandler(ILogger logger, - IAlgoliaIndexDefinitionStorage indexStorage, IAlgoliaIndexService algoliaIndexService) - { - _logger = logger; - - _indexStorage = indexStorage; - - _indexService = algoliaIndexService; - } - - public async Task HandleAsync(ContentPublishedNotification notification, CancellationToken cancellationToken) - { - try - { - var indices = _indexStorage.Get(); - foreach (var publishedItem in notification.PublishedEntities) - { - foreach (var index in indices) - { - var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) - .FirstOrDefault(p => p.ContentType == publishedItem.ContentType.Alias); - if (indexConfiguration == null || indexConfiguration.ContentType != publishedItem.ContentType.Alias) continue; - - var record = new RecordBuilder() - .BuildFromContent(publishedItem, (p) => indexConfiguration.Properties.Any(q => q == p.Alias)) - .Build(); - - var result = await _indexService.UpdateData(index.Name, record); - - if (!string.IsNullOrEmpty(result)) - _logger.LogError($"Failed to update data for Algolia index: {result}"); - } + public ContentPublishedHandler(ILogger logger, IAlgoliaIndexDefinitionStorage indexStorage, IAlgoliaIndexService indexService) + : base(logger, indexStorage, indexService) + { } - } - } - catch(Exception ex) - { - _logger.LogError($"Failed to update data for Algolia index: {ex.Message}"); - } - } + public async Task HandleAsync(ContentPublishedNotification notification, CancellationToken cancellationToken) => await RebuildIndex(notification.PublishedEntities); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs index 82afe520..aee6a4cf 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/ContentUnpublishedHandler.cs @@ -10,48 +10,13 @@ namespace Umbraco.Cms.Integrations.Search.Algolia.Handlers { - public class ContentUnpublishedHandler : INotificationAsyncHandler + public class ContentUnpublishedHandler : BaseContentHandler, INotificationAsyncHandler { - private readonly ILogger _logger; + public ContentUnpublishedHandler(ILogger logger, IAlgoliaIndexDefinitionStorage indexStorage, IAlgoliaIndexService indexService) + : base(logger, indexStorage, indexService) + { } - private readonly IAlgoliaIndexDefinitionStorage _scopeService; - - private readonly IAlgoliaIndexService _indexService; - - public ContentUnpublishedHandler(ILogger logger, IAlgoliaIndexDefinitionStorage scopeService, IAlgoliaIndexService indexService) - { - _logger = logger; - - _scopeService = scopeService; - - _indexService = indexService; - } - - public async Task HandleAsync(ContentUnpublishedNotification notification, CancellationToken cancellationToken) - { - try - { - var indices = _scopeService.Get(); - - foreach (var publishedItem in notification.UnpublishedEntities) - { - foreach (var index in indices) - { - var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) - .FirstOrDefault(p => p.ContentType == publishedItem.ContentType.Alias); - if (indexConfiguration == null || indexConfiguration.ContentType != publishedItem.ContentType.Alias) continue; - - var result = await _indexService.DeleteData(index.Name, publishedItem.Key.ToString()); - - if (!string.IsNullOrEmpty(result)) - _logger.LogError($"Failed to delete data for Algolia index: {result}"); - } - } - } - catch (Exception ex) - { - _logger.LogError($"Failed to delete data for Algolia index: {ex.Message}"); - } - } + public async Task HandleAsync(ContentUnpublishedNotification notification, CancellationToken cancellationToken) => + await RebuildIndex(notification.UnpublishedEntities, true); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Result.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Result.cs new file mode 100644 index 00000000..87f4ba2d --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/Result.cs @@ -0,0 +1,35 @@ + +using System.Text.Json.Serialization; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Models +{ + public class Result + { + public bool Success { get; set; } + + public string Error { get; set; } + + public bool Failure => !Success; + + protected Result(bool success, string error) + { + if (success && !string.IsNullOrEmpty(error)) + { + throw new ArgumentException("A succesful Result cannot have an error message.", error); + } + + if (!success && string.IsNullOrEmpty(error)) + { + throw new ArgumentException("A failure Result must have an error message.", error); + } + + Success = success; + Error = error; + } + + public static Result Ok() => new (true, string.Empty); + + public static Result Fail(string message) => new (false, message); + + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs index 83426931..f7d16d16 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs @@ -17,7 +17,7 @@ public AlgoliaIndexService(IOptions options) _settings = options.Value; } - public string PushData(string name, List payload = null) + public async Task PushData(string name, List payload = null) { try { @@ -25,26 +25,26 @@ public string PushData(string name, List payload = null) var index = client.InitIndex(name); - index.SaveObjects(payload != null + await index.SaveObjectsAsync(payload != null ? payload : new List { new Record { ObjectID = Guid.NewGuid().ToString(), Data = new Dictionary()} - }, autoGenerateObjectId: false).Wait(); + }, autoGenerateObjectId: false); - if(payload == null) - index.ClearObjects().Wait(); + if (payload == null) + await index.ClearObjectsAsync(); - return string.Empty; + return Result.Ok(); } catch(AlgoliaException ex) { - return ex.Message; + return Result.Fail(ex.Message); } } - public async Task UpdateData(string name, Record record) + public async Task UpdateData(string name, Record record) { try { @@ -58,15 +58,15 @@ public async Task UpdateData(string name, Record record) else await index.SaveObjectAsync(record, autoGenerateObjectId: false); - return string.Empty; + return Result.Ok(); } catch (AlgoliaException ex) { - return ex.Message; + return Result.Fail(ex.Message); } } - public async Task DeleteData(string name, string objectId) + public async Task DeleteData(string name, string objectId) { try { @@ -76,11 +76,11 @@ public async Task DeleteData(string name, string objectId) await index.DeleteObjectAsync(objectId); - return string.Empty; + return Result.Ok(); } catch (AlgoliaException ex) { - return ex.Message; + return Result.Fail(ex.Message); } } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs index c808a1b0..d28e7c16 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs @@ -4,10 +4,10 @@ namespace Umbraco.Cms.Integrations.Search.Algolia.Services { public interface IAlgoliaIndexService { - string PushData(string name, List payload = null); + Task PushData(string name, List payload = null); - Task UpdateData(string name, Record record); + Task UpdateData(string name, Record record); - Task DeleteData(string name, string objectId); + Task DeleteData(string name, string objectId); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj index 5acc1dd2..8d0ed97a 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj @@ -17,7 +17,7 @@ Umbraco HQ Umbraco Umbraco;Umbraco-Marketplace - https://github.com/umbraco/Umbraco.Cms.Integrations/tree/main/src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png + https://github.com/umbraco/Umbraco.Cms.Integrations/raw/feature/algolia-integration/src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png @@ -45,6 +45,13 @@ + + + True + \ + + + From b7720ad163ac9366caacf7db107f78c4895629b5 Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu <95346674+acoumb@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:06:38 +0200 Subject: [PATCH 04/11] Update azure-pipeline - Search.Algolia.yml for Azure Pipelines --- azure-pipeline - Search.Algolia.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/azure-pipeline - Search.Algolia.yml b/azure-pipeline - Search.Algolia.yml index 197dc78a..d9714501 100644 --- a/azure-pipeline - Search.Algolia.yml +++ b/azure-pipeline - Search.Algolia.yml @@ -14,15 +14,19 @@ steps: - task: NuGetToolInstaller@1 displayName: 'Install NuGet' -- task: NuGetCommand@2 +- task: DotNetCoreCLI@2 displayName: 'NuGet Restore' inputs: - restoreSolution: '$(solution)' + command: 'restore' + feedsToUse: 'select' + projects: '$(project)' + includeNuGetOrg: true - task: VSBuild@1 + displayName: 'Build Project' inputs: - solution: '$(solution)' - msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' + solution: '$(project)' + msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"' platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' From 79c33a2279ebb40f66b4b25871e3dbf19165062f Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu Date: Fri, 18 Nov 2022 11:07:31 +0200 Subject: [PATCH 05/11] remove post build events --- .../Umbraco.Cms.Integrations.Search.Algolia.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj index 8d0ed97a..7491d471 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj @@ -52,8 +52,4 @@ - - - - From 1b3d01d7051a81a1cfd00e2294661ff780b48564 Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu Date: Fri, 18 Nov 2022 11:44:27 +0200 Subject: [PATCH 06/11] Package icon and marketplace updates --- ...aco.Cms.Integrations.Search.Algolia.csproj | 7 ++- .../{logo => }/algolia.png | Bin .../umbraco-marketplace.json | 55 ++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) rename src/Umbraco.Cms.Integrations.Search.Algolia/{logo => }/algolia.png (100%) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/umbraco-marketplace.json diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj index 7491d471..1ccbff3b 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj @@ -17,7 +17,7 @@ Umbraco HQ Umbraco Umbraco;Umbraco-Marketplace - https://github.com/umbraco/Umbraco.Cms.Integrations/raw/feature/algolia-integration/src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png + algolia.png @@ -46,9 +46,10 @@ - - True + \ + True + Never diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png b/src/Umbraco.Cms.Integrations.Search.Algolia/algolia.png similarity index 100% rename from src/Umbraco.Cms.Integrations.Search.Algolia/logo/algolia.png rename to src/Umbraco.Cms.Integrations.Search.Algolia/algolia.png diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/umbraco-marketplace.json b/src/Umbraco.Cms.Integrations.Search.Algolia/umbraco-marketplace.json new file mode 100644 index 00000000..6ab3ef9b --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/umbraco-marketplace.json @@ -0,0 +1,55 @@ +{ + "AuthorDetails": { + "Description": "Umbraco HQ", + "Url": "https://umbraco.com/", + "ImageUrl": "https://avatars.githubusercontent.com/u/1419552?s=200&v=4" + }, + "Category": "CMS Extensions", + "LicenseType": "Free", + "PackageType": "Integration", + "PackagesByAuthor": [ + "Umbraco.Cms.Integrations.Commerce.CommerceTools", + "Umbraco.Cms.Integrations.Commerce.Shopify", + "Umbraco.Cms.Integrations.SEO.Semrush", + "Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool", + "Umbraco.Cms.Integrations.Crm.Hubspot", + "Umbraco.Cms.Integrations.Crm.Dynamics", + "Umbraco.Cms.Integrations.Crm.ActiveCampaign", + "Umbraco.Cms.Integrations.Automation.Zapier" + ], + "RelatedPackages": [ + { + "PackageId": "Umbraco.Cms.Integrations.Commerce.CommerceTools", + "Description": "A product and category picker that can be added as a property editor for content, with a value converter providing a strongly typed model for rendering." + }, + { + "PackageId": "Umbraco.Cms.Integrations.Commerce.Shopify", + "Description": "A products picker that can be added as a property editor for content, with a value converter providing a strongly typed model for rendering." + }, + { + "PackageId": "Umbraco.Cms.Integrations.SEO.Semrush", + "Description": "A search tool available as a content app, helping editors research and use appropriate keywords for their content, to help with website search engine optimisation." + }, + { + "PackageId": "Umbraco.Cms.Integrations.SEO.GoogleSearchConsole.URLInspectionTool", + "Description": "A tool allowing programmatic access to URL-level data for properties managed in Google Search Console and the indexed version of a URL." + }, + { + "PackageId": "Umbraco.Cms.Integrations.Crm.Hubspot", + "Description": "A form picker and rendering component for Hubspot forms." + }, + { + "PackageId": "Umbraco.Cms.Integrations.Crm.Dynamics", + "Description": "A form picker and rendering component for Dynamics 365 Marketing forms." + }, + { + "PackageId": "Umbraco.Cms.Integrations.Crm.ActiveCampaign", + "Description": "A form picker and rendering component for ActiveCampaign forms." + }, + { + "PackageId": "Umbraco.Cms.Integrations.Automation.Zapier", + "Description": "A dashboard interface allowing users to vizualize registered subscription hooks for content types and to call Zapier triggers when content gets published." + } + ], + "Tags": [] +} \ No newline at end of file From 453c409c821ac02545071b60befd14fe8cefdece Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu Date: Fri, 18 Nov 2022 12:40:39 +0200 Subject: [PATCH 07/11] bug fixes and save index validation --- .../Search/Algolia/js/dashboard.controller.js | 23 +++++++++++++++---- .../Search/Algolia/views/dashboard.html | 3 ++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js index 82f464bc..f57c29d0 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js @@ -15,8 +15,9 @@ vm.saveIndex = saveIndex; vm.viewIndex = viewIndex; vm.buildIndex = buildIndex; - vm.search = search; + vm.searchIndex = searchIndex; vm.deleteIndex = deleteIndex; + vm.search = search; function init() { @@ -92,6 +93,8 @@ else { const propertyIndex = this.contentData.find(p => p.contentType === this.selectedContentType).properties.indexOf(property.alias); if (propertyIndex > -1) this.contentData.find(p => p.contentType === this.selectedContentType).properties.splice(propertyIndex, 1); + + this.properties.find(p => p.alias == property.alias).checked = false; } }, reset: function () { @@ -127,6 +130,11 @@ return false; } + if (vm.manageIndex.contentData.filter(p => p.properties.length == 0).length > 0) { + notificationsService.error("Selected content types must have at least one property."); + return false; + } + vm.loading = true; umbracoCmsIntegrationsSearchAlgoliaResource @@ -189,10 +197,9 @@ overlayService.open(dialogOptions); } - function search() { - umbracoCmsIntegrationsSearchAlgoliaResource.search(vm.searchIndex.id, vm.searchQuery).then(function (response) { - vm.searchResults = response; - }); + function searchIndex(index) { + vm.viewState = "search"; + vm.searchIndex = index; } function deleteIndex(index) { @@ -200,6 +207,12 @@ getIndices(); }); } + + function search() { + umbracoCmsIntegrationsSearchAlgoliaResource.search(vm.searchIndex.id, vm.searchQuery).then(function (response) { + vm.searchResults = response; + }); + } } angular.module("umbraco") diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html index 81033172..6a52f057 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html @@ -51,7 +51,8 @@ + button-style="success" + action="vm.searchIndex(index)"> Date: Fri, 18 Nov 2022 15:06:19 +0200 Subject: [PATCH 08/11] Edit index bug fix and handler updates. --- .../Search/Algolia/js/algolia.resource.js | 4 ++-- .../Search/Algolia/js/dashboard.controller.js | 4 ++-- .../Handlers/BaseContentHandler.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js index 4c1bd82b..c3cde6ee 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js @@ -9,9 +9,9 @@ $http.get(`${apiEndpoint}/GetIndices`), "Failed"); }, - saveIndex: function (name, contentData) { + saveIndex: function (id, name, contentData) { return umbRequestHelper.resourcePromise( - $http.post(`${apiEndpoint}/SaveIndex`, { name: name, contentData: contentData }), + $http.post(`${apiEndpoint}/SaveIndex`, { id: id, name: name, contentData: contentData }), "Failed"); }, buildIndex: function (id) { diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js index f57c29d0..00baca16 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js @@ -138,13 +138,13 @@ vm.loading = true; umbracoCmsIntegrationsSearchAlgoliaResource - .saveIndex(vm.manageIndex.name, vm.manageIndex.contentData) + .saveIndex(vm.manageIndex.id, vm.manageIndex.name, vm.manageIndex.contentData) .then(function (response) { if (response.success) { vm.manageIndex.reset(); algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); } else { - notificationsService.error(response); + notificationsService.error(response.error); } vm.viewState = "list"; diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs index 0abb5d5a..925af6de 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs @@ -28,7 +28,7 @@ public BaseContentHandler(ILogger logger, IndexService = indexService; } - public async Task RebuildIndex(IEnumerable entities, bool deleteIndexData = false) + protected async Task RebuildIndex(IEnumerable entities, bool deleteIndexData = false) { try { @@ -51,13 +51,13 @@ public async Task RebuildIndex(IEnumerable entities, bool deleteIndexD : await IndexService.UpdateData(index.Name, record); if (result.Failure) - Logger.LogError($"Failed to delete data for Algolia index: {result}"); + Logger.LogError($"Failed to update data for Algolia index: {result}"); } } } catch (Exception ex) { - Logger.LogError($"Failed to delete data for Algolia index: {ex.Message}"); + Logger.LogError($"Failed to update data for Algolia index: {ex.Message}"); } } } From 16a3bec10c13b9ba4d509c620e511dc1b8880340 Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu Date: Fri, 25 Nov 2022 16:38:07 +0200 Subject: [PATCH 09/11] UI updates --- .../Search/Algolia/css/algolia.css | 5 + .../Search/Algolia/js/algolia.service.js | 16 +- .../Search/Algolia/js/dashboard.controller.js | 184 +++++++++++------- .../Search/Algolia/package.manifest | 4 +- .../Search/Algolia/views/dashboard.html | 94 +++++---- .../Models/ContentData.cs | 2 + .../Services/AlgoliaSearchService.cs | 3 +- ...aco.Cms.Integrations.Search.Algolia.csproj | 15 +- .../algolia.png | Bin 13990 -> 23092 bytes 9 files changed, 200 insertions(+), 123 deletions(-) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/css/algolia.css diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/css/algolia.css b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/css/algolia.css new file mode 100644 index 00000000..c88afe9b --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/css/algolia.css @@ -0,0 +1,5 @@ +.umb-content-grid { + display:grid; + grid-template-columns: repeat(3, 1fr); + gap:10px; +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js index ec99a82b..60fb780e 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.service.js @@ -2,7 +2,16 @@ return { getContentTypes: function (callback) { contentTypeResource.getAll().then(function (data) { - callback(data.map((item) => { return { id: item.id, alias: item.alias, name: item.name, checked: false } })); + callback(data.filter(item => item.parentId == -1 && !item.isElement).map((item) => { + return { + id: item.id, + icon: item.icon, + alias: item.alias, + name: item.name, + selected: false, + allowRemove: false + } + })); }); }, getPropertiesByContentTypeId: function (contentTypeId, callback) { @@ -13,10 +22,11 @@ for (var j = 0; j < data.groups[i].properties.length; j++) { properties.push({ id: data.groups[i].properties[j].id, + icon: "icon-indent", alias: data.groups[i].properties[j].alias, name: data.groups[i].properties[j].label, - display: `Group: ${data.groups[i].name} | Property: ${data.groups[i].properties[j].label}`, - checked: false + group: data.groups[i].name, + selected: false }); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js index 00baca16..7a59286b 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js @@ -4,7 +4,7 @@ vm.loading = false; vm.searchQuery = ""; - vm.searchIndex = {}; + vm.selectedSearchIndex = {}; vm.searchResults = {}; vm.viewState = "list"; @@ -20,57 +20,40 @@ vm.search = search; function init() { - // contentData property: // array of objects: // contentType -> string value + // contentTypeIcon -> string value // properties -> string array vm.manageIndex = { id: 0, name: "", - selectedContentType: "", - contentTypes: [], - properties: [], - contentData: [], - selectContentType: function (contentType) { - - this.properties = []; - - var checked = !contentType.checked; - - if (checked) { - - this.selectedContentType = contentType.alias; - this.contentTypes.find(p => p.alias == contentType.alias).checked = true; - - var contentItem = { - contentType: contentType.alias, - properties: [] - }; - - this.contentData.push(contentItem); + selectedContentType: {}, + contentTypesList: [], + propertiesList: [], + includeProperties: [ + { + "alias": "alias", + "header": "Alias" + }, + { + "alias": "group", + "header": "Group" } - else { - this.contentTypes.find(p => p.alias == contentType.alias).checked = false; - - const contentTypeIndex = this.contentData.findIndex((obj) => obj.contentType === contentType.alias); - - if (contentTypeIndex > -1) this.contentData.splice(contentTypeIndex, 1); + ], + contentData: [], + showProperties: function (contentType) { - this.selectedContentType = ""; + this.selectedContentType = contentType; - this.properties = []; - } - }, - showProperties: function (contentType) { algoliaService.getPropertiesByContentTypeId(contentType.id, (response) => { - this.properties = response; + this.propertiesList = response; - var contentTypeData = this.contentData.find(p => p.contentType == contentType.alias); + var contentTypeData = this.contentData.find(obj => obj.contentType == contentType.alias); if (contentTypeData && contentTypeData.properties.length > 0) { - vm.manageIndex.properties = vm.manageIndex.properties.map((obj) => { + vm.manageIndex.propertiesList = vm.manageIndex.propertiesList.map((obj) => { if (contentTypeData.properties.find(p => p == obj.alias)) { - obj.checked = true; + obj.selected = true; } return obj; @@ -78,31 +61,76 @@ } }); }, + removeContentType: function (contentType) { + + const contentTypeIndex = this.contentData.map(obj => obj.contentType).indexOf(contentType.alias); + this.contentData.splice(contentTypeIndex, 1); + + this.selectedContentType = {}; + this.contentTypesList.forEach(obj => { + if (obj.alias == contentType.alias) { + obj.selected = false; + obj.allowRemove = false; + } + }); + this.propertiesList = []; + }, selectProperty: function (property) { - var checked = !property.checked; - if (this.contentData.length == 0 || this.contentData.find(p => p.contentType === this.selectedContentType) === undefined) { - notificationsService.warning("Please select the property matching content type."); - return false; - } + var contentDataItem = vm.manageIndex.contentData.find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias); + + var selected = !property.selected; + if (selected) { + // mark item selected + vm.manageIndex.propertiesList.find(obj => obj.alias == property.alias).selected = true; + + // check if content type exists in the contentData array + if (!contentDataItem) { + var contentItem = { + contentType: vm.manageIndex.selectedContentType.alias, + contentTypeIcon: vm.manageIndex.selectedContentType.icon, + properties: [] + }; + vm.manageIndex.contentData.push(contentItem); + + // select content type + vm.manageIndex.contentTypesList.forEach(obj => { + if (obj.alias == vm.manageIndex.selectedContentType.alias) { + obj.selected = true; + obj.allowRemove = true; + } + }); + } - if (checked) { - this.properties.find(p => p.alias == property.alias).checked = true; - this.contentData.find(p => p.contentType === this.selectedContentType).properties.push(property.alias); + // add property + vm.manageIndex.contentData.find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.push(property.alias); } else { - const propertyIndex = this.contentData.find(p => p.contentType === this.selectedContentType).properties.indexOf(property.alias); - if (propertyIndex > -1) this.contentData.find(p => p.contentType === this.selectedContentType).properties.splice(propertyIndex, 1); + // deselect item + vm.manageIndex.propertiesList.find(obj => obj.alias == property.alias).selected = false; + + // remove property item + const propertyIndex = vm.manageIndex.contentData + .find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.indexOf(property.alias); + vm.manageIndex.contentData + .find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.splice(propertyIndex, 1); - this.properties.find(p => p.alias == property.alias).checked = false; + // remove content type item with no properties and deselect + if (vm.manageIndex.contentData.find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.length == 0) { + vm.manageIndex.contentTypesList.find(obj => obj.alias == vm.manageIndex.selectedContentType.alias).selected = false; + + const contentTypeIndex = vm.manageIndex.contentData.map(obj => obj.contentType).indexOf(vm.manageIndex.selectedContentType.alias); + vm.manageIndex.contentData.splice(contentTypeIndex, 1); + } } }, reset: function () { this.visible = false; + this.id = 0; this.name = ""; - this.selectedContentType = ""; - this.contentTypes = []; - this.properties = []; + this.selectedContentType = {}; + this.contentTypesList = []; + this.propertiesList = []; this.contentData = []; } }; @@ -120,21 +148,16 @@ function addIndex() { vm.viewState = "manage"; - algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); + algoliaService.getContentTypes((response) => vm.manageIndex.contentTypesList = response); } function saveIndex() { if (vm.manageIndex.name.length == 0 || vm.manageIndex.contentData.length == 0) { - notificationsService.error("Index name and content schema are required"); - return false; - } - - if (vm.manageIndex.contentData.filter(p => p.properties.length == 0).length > 0) { - notificationsService.error("Selected content types must have at least one property."); + notificationsService.error("Algolia", "Index name and content schema are required."); return false; } - + vm.loading = true; umbracoCmsIntegrationsSearchAlgoliaResource @@ -143,8 +166,9 @@ if (response.success) { vm.manageIndex.reset(); algoliaService.getContentTypes((response) => vm.manageIndex.contentTypes = response); + notificationsService.success("Algolia", "Index saved."); } else { - notificationsService.error(response.error); + notificationsService.error("Algolia", response.error); } vm.viewState = "list"; @@ -165,10 +189,16 @@ algoliaService.getContentTypes((response) => { - vm.manageIndex.contentTypes = response; + vm.manageIndex.contentTypesList = response; for (var i = 0; i < vm.manageIndex.contentData.length; i++) { - vm.manageIndex.contentTypes.find(p => p.alias === vm.manageIndex.contentData[i].contentType).checked = true; + + vm.manageIndex.contentTypesList.forEach(obj => { + if (obj.alias == vm.manageIndex.contentData[i].contentType) { + obj.selected = true; + obj.allowRemove = true; + } + }); } }); } @@ -182,8 +212,9 @@ umbracoCmsIntegrationsSearchAlgoliaResource.buildIndex(model.index.id).then(function (response) { if (response.failure) - notificationsService.warning("An error has occurred while building the index: " + response.error); + notificationsService.warning("Algolia", "An error has occurred while building the index: " + response.error); else { + notificationsService.success("Algolia", "Index built successfully."); vm.loading = false; overlayService.close(); } @@ -199,17 +230,32 @@ function searchIndex(index) { vm.viewState = "search"; - vm.searchIndex = index; + vm.selectedSearchIndex = index; } function deleteIndex(index) { - umbracoCmsIntegrationsSearchAlgoliaResource.deleteIndex(index.id).then(function (response) { - getIndices(); - }); + const dialogOptions = { + title: "Delete", + content: "Are you sure you want to delete index " + index.name + "?", + confirmType: "delete", + submit: function () { + umbracoCmsIntegrationsSearchAlgoliaResource.deleteIndex(index.id).then(function (response) { + if (response.success) { + notificationsService.success("Algolia", "Index deleted."); + getIndices(); + } else + notificationsService.error("Algolia", response.error); + + overlayService.close(); + }); + } + }; + + overlayService.confirm(dialogOptions); } function search() { - umbracoCmsIntegrationsSearchAlgoliaResource.search(vm.searchIndex.id, vm.searchQuery).then(function (response) { + umbracoCmsIntegrationsSearchAlgoliaResource.search(vm.selectedSearchIndex.id, vm.searchQuery).then(function (response) { vm.searchResults = response; }); } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest index 606ae75c..6cc9bd85 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/package.manifest @@ -7,5 +7,7 @@ "~/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js", "~/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/algolia.resource.js" ], - "css": [] + "css": [ + "~/App_Plugins/UmbracoCms.Integrations/Search/Algolia/css/algolia.css" + ] } \ No newline at end of file diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html index 6a52f057..4fb8d8fc 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html @@ -15,7 +15,22 @@
Manage Algolia Indices
-
Search the index and view the results
+
+

+ Algolia is an AI-powered search and discovery platform allowing you to create cutting-edge customer experiences for their websites or mobile apps. + It's like the perfect mediator between your website and customers, making sure the conversation is as smooth and efficient as possible. +

+

+ The Algolia model provides Search as a Service through an externally hosted search engine, offering web search across the website based + on the content payload pushed from the website to Algolia. +

+

+ To get started, you need to create an index and define the content schema - document types and properties. + Then you can build your index, push data to Algolia and run searches across created indices. +
+ read more +

+
-

{{ item.contentType }} - {{ item.properties.join() }}

+ + - - - @@ -103,43 +124,28 @@
-
-
Content Types
-
-
-
- - -
-
- -
-
-
+
+
Document Types
+ +
-
-
Properties
-
-
-
- - -
+
+
{{ vm.manageIndex.selectedContentType.name }} Properties
+
+
+ +
@@ -163,7 +169,7 @@
Properties
- @@ -180,7 +186,9 @@
Properties
Search over index
-
Please select the content types you want to index
+
+ Please enter the query you want to search by against index {{ vm.selectedSearchIndex.name }} +
diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs index 627bbcad..1109bc9e 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs @@ -5,6 +5,8 @@ public class ContentData { public string ContentType { get; set; } + public string ContentTypeIcon { get; set; } + public string[] Properties { get; set; } } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs index 5bbfcc0c..9a1d01bf 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs @@ -1,4 +1,5 @@ using Algolia.Search.Clients; +using Algolia.Search.Http; using Algolia.Search.Models.Search; using Microsoft.Extensions.Options; @@ -22,7 +23,7 @@ public SearchResponse Search(string indexName, string query) var client = new SearchClient(_settings.ApplicationId, _settings.AdminApiKey); var index = client.InitIndex(indexName); - + var results = index.Search(new Query(query)); return results; diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj index 1ccbff3b..7d9f947d 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Umbraco.Cms.Integrations.Search.Algolia.csproj @@ -44,13 +44,16 @@ Always - + - - \ - True - Never - + + true + \ + + + + + diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/algolia.png b/src/Umbraco.Cms.Integrations.Search.Algolia/algolia.png index 263b8c3a2da4050df6a94bc7aeb9612b82a99bc5..3bbce791a39dd1643e879c9875b9b5a69a97a874 100644 GIT binary patch literal 23092 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 ZLNBb_hIfHdK7e3R2ke|_1^fIi{RdnSP@w<- literal 13990 zcmZ{LcQ{;6)c@LDW!31t_ujkcJ!%jkqW9iQ2v(22f<*5j1PM!YtCtWpx>zBIy87xr z-}m>|`^Wp-=ia$@&YaIZXJ*dKJaf*xGtko@z@@CF9_wgMy%Z3y0RBZqfF<)w9BMuqM-4YUFGMm^)GXJqwTa6Tw6#CQ~&f zB~0?axR;5zQIzQ{TyJ`SEVwO9Cd`p%3^*}V&m|Y#^KOQ`^nQ+I@8_QuN;_;_h0C96 zEf)CwJ}vw%cVy&epEcL3_^^zV4EhA31=3=OLeHVx2(Y!75ReLMGb21XW(VUC`zO${ zywbj_v&OTY`7rHR3aEd=qkf&nS z0khffxYSog=^(3nB`W)a0~363Qyt)7ac9XHSdw4?l%SV>d|6{SaHFT<_ZhA$ny5fa zAa?08LuPG?0Ov4kb~DXb1?Pa>vEilA@9aY4E-#a<_wkd#*_^9SSPkPaoB)h!elC1V zPk1V*Wre6l8d<$Drd@Ru-Ck9MS`b1c4Mnk~D9x}tq7^Ir*sX5g+Tl!d51&z54wmU& zT3aiWzoE_X7j=uIJ9o1b4V%N3x4NAXY8d^7xS{W|@ z6f@4_BZ*k~lOcj}#7}k0J@|!^k>$FfuxB=Tt{MJ(mVbddcp}vF@nS-v8J>kURB<*j z2r(Yv^bV#CJmEgr;?B%)os`7CPtc+H0=>rNP6?(MBH9)lyep1 zKUf?454(6GT~9uHc@51Eu4@J3cfwnIY`BO>B^ zVzS>Zp8OUed5}jGVEZC7J4*2ib{k$%ZNxh=|6R-h;jeZ zhO`ANmAR9zCmR_bxd)X^pGZoB2s~2Nue}%Z-R)}9ox#*r35)n>;ja;@2YY}+%sTKr zKHsYwQc}zRq&k}hje!W6^Bw;1BL~VES@2r^tE5J9Z|Se5;DcQZ2Cx7qJnhyd1|swy zszeLfm6sk4y3-PAhJ=+fGU2sAME>S}tcv+)nhx;e$NfOTf%s24vl4#n+Sm#Nzm{ah z+`C@57#^DYP+12XMT-pON;dxpu=}q`h?l! zfy4>3t%Io{!exQ#R1yhO6tlwtlk{na_cJnGLba~ zT{*Fl|6kabZ5f`CF#H}c^;+wtGcrlhX!u3|LqJillRgq6{2#szm?PQ6h0|EpMQ1p) z3o{JTg0UlxF*qz&%gtNr>F4S_eNo3mfApitY8r*_!j^cPJ=mqYC|7NfZrI1kT)C2> z{M$?3o5!2?$pl^l#8q`1VaJ1wuhC`43MG!2e;MaMfFh}l)ua8chE?>?HyL{J+f?+} z@i~9-Ick;c{25`&vLRw8Biu##p0fbqYMd~C<4+j;ls2NmlZv)1oM3c$K>q|^KFxeG znA#+4kn0(ViLxZh)gda`YUCQRy z4;ZT8iM*-$jA#YxH{z4v{{(u1O6e!>^k37K_J?5u+Q}ZC@*qtlRcSMad^0`J%*IFm zju24|H}l(UwH}Ys2E$Q|1aOvh_S(&=AzQ!u8&_r8eM4u`#Z0trSk`kzS~a@6Y2ZFa z7=CTq9_fJ;b#zQAePk;_bT}4iikN{@%%)$P^ZK=Pt^s{qrAFsFV9tV1u^=n7Qu5El zM{3*OpIJ+nzgy(*CwNhY+O+~s5&StJ^iryX$s;Hp@rxf&NS{u-%Urx98Hv*`?%u=c@0v! z>M8b)Wg!{^g3*5B$Afk(7ehaA#Psxl7aT1tOvF5ks34%cinq%b$`&9RVyMoURU~wQ zt*1(2M@Q;UC1T`}5|b-C3BHr9jrA-p-C?WnilWOk0>k8*q7<0!AW1x5B z;8=zCAKy4ipfJm0A!WL0Q3Qt7sVY!LkSXZdKI#L>?Y9gK#|6>Jr4Jl4Q|b5u!#fzS z5{V?9_BS1=w%W?DP=5`N-<}-V!_y4N&+M0?MuT~#!tqcFi1z`(f2s&>kDDNxb7V%0DxmiZ5TRWx7=oY8(QVIaa^0$K$qJTpD)L`A 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 Date: Tue, 29 Nov 2022 18:46:08 +0200 Subject: [PATCH 10/11] UI and JS functionality updates; Deleting Algolia index operation. --- .../Search/Algolia/js/dashboard.controller.js | 56 +++++++++++++------ .../Search/Algolia/views/dashboard.html | 27 ++++----- .../Controllers/SearchController.cs | 8 ++- .../Handlers/BaseContentHandler.cs | 6 +- .../Models/ContentData.cs | 12 ++-- .../Models/ContentEntity.cs | 17 ++++++ .../Services/AlgoliaIndexService.cs | 18 ++++++ .../Services/AlgoliaSearchService.cs | 1 - .../Services/IAlgoliaIndexService.cs | 2 + 9 files changed, 107 insertions(+), 40 deletions(-) create mode 100644 src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentEntity.cs diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js index 7a59286b..d3b1b71d 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/js/dashboard.controller.js @@ -20,11 +20,23 @@ vm.search = search; function init() { - // contentData property: - // array of objects: - // contentType -> string value - // contentTypeIcon -> string value - // properties -> string array + /* contentData property: + [ + { + "contentType": { + "alias": "", + "name": "", + "icon": "" + }, + "properties": [ + { + "alias": "", + "name": "" + } + ] + } + ] + */ vm.manageIndex = { id: 0, name: "", @@ -49,10 +61,10 @@ algoliaService.getPropertiesByContentTypeId(contentType.id, (response) => { this.propertiesList = response; - var contentTypeData = this.contentData.find(obj => obj.contentType == contentType.alias); + var contentTypeData = this.contentData.find(obj => obj.contentType.alias == contentType.alias); if (contentTypeData && contentTypeData.properties.length > 0) { vm.manageIndex.propertiesList = vm.manageIndex.propertiesList.map((obj) => { - if (contentTypeData.properties.find(p => p == obj.alias)) { + if (contentTypeData.properties.find(p => p.alias == obj.alias)) { obj.selected = true; } @@ -63,7 +75,7 @@ }, removeContentType: function (contentType) { - const contentTypeIndex = this.contentData.map(obj => obj.contentType).indexOf(contentType.alias); + const contentTypeIndex = this.contentData.map(obj => obj.contentType.alias).indexOf(contentType.alias); this.contentData.splice(contentTypeIndex, 1); this.selectedContentType = {}; @@ -77,7 +89,7 @@ }, selectProperty: function (property) { - var contentDataItem = vm.manageIndex.contentData.find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias); + var contentDataItem = vm.manageIndex.contentData.find(obj => obj.contentType.alias == vm.manageIndex.selectedContentType.alias); var selected = !property.selected; if (selected) { @@ -87,8 +99,11 @@ // check if content type exists in the contentData array if (!contentDataItem) { var contentItem = { - contentType: vm.manageIndex.selectedContentType.alias, - contentTypeIcon: vm.manageIndex.selectedContentType.icon, + contentType: { + alias: vm.manageIndex.selectedContentType.alias, + name: vm.manageIndex.selectedContentType.name, + icon: vm.manageIndex.selectedContentType.icon + }, properties: [] }; vm.manageIndex.contentData.push(contentItem); @@ -103,7 +118,12 @@ } // add property - vm.manageIndex.contentData.find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.push(property.alias); + vm.manageIndex.contentData + .find(obj => obj.contentType.alias == vm.manageIndex.selectedContentType.alias) + .properties.push({ + alias: property.alias, + name: property.name + }); } else { // deselect item @@ -111,15 +131,17 @@ // remove property item const propertyIndex = vm.manageIndex.contentData - .find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.indexOf(property.alias); + .find(obj => obj.contentType.alias == vm.manageIndex.selectedContentType.alias) + .properties.map(obj => obj.alias).indexOf(property.alias); vm.manageIndex.contentData - .find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.splice(propertyIndex, 1); + .find(obj => obj.contentType.alias == vm.manageIndex.selectedContentType.alias).properties.splice(propertyIndex, 1); // remove content type item with no properties and deselect - if (vm.manageIndex.contentData.find(obj => obj.contentType == vm.manageIndex.selectedContentType.alias).properties.length == 0) { + if (vm.manageIndex.contentData.find(obj => obj.contentType.alias == vm.manageIndex.selectedContentType.alias).properties.length == 0) { vm.manageIndex.contentTypesList.find(obj => obj.alias == vm.manageIndex.selectedContentType.alias).selected = false; + vm.manageIndex.contentTypesList.find(obj => obj.alias == vm.manageIndex.selectedContentType.alias).allowRemove = false; - const contentTypeIndex = vm.manageIndex.contentData.map(obj => obj.contentType).indexOf(vm.manageIndex.selectedContentType.alias); + const contentTypeIndex = vm.manageIndex.contentData.map(obj => obj.contentType.alias).indexOf(vm.manageIndex.selectedContentType.alias); vm.manageIndex.contentData.splice(contentTypeIndex, 1); } } @@ -194,7 +216,7 @@ for (var i = 0; i < vm.manageIndex.contentData.length; i++) { vm.manageIndex.contentTypesList.forEach(obj => { - if (obj.alias == vm.manageIndex.contentData[i].contentType) { + if (obj.alias == vm.manageIndex.contentData[i].contentType.alias) { obj.selected = true; obj.allowRemove = true; } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html index 4fb8d8fc..6f885762 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html @@ -27,15 +27,17 @@

To get started, you need to create an index and define the content schema - document types and properties. Then you can build your index, push data to Algolia and run searches across created indices. -
- read more +
+ + Read more about integrating Algolia Search +

+ label="Add New Index Definition">
@@ -57,11 +59,11 @@ + description="item.propertiesDescription.join(', ')"> @@ -100,16 +102,12 @@
- Manage Index + {{ vm.manageIndex.id == 0 ? "Add" : "Edit" }} Index Definition
-
-
Content Types
-
Please select the content types you want to index
-
@@ -126,6 +124,9 @@
Document Types
+
+ Please select the document types you would like to index,
and click Open to choose the fields to include. +
{{ vm.manageIndex.selectedContentType.name }} Properties
Search over index
Please enter the query you want to search by against index {{ vm.selectedSearchIndex.name }} -
+
diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs index 37023236..070e74bc 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Controllers/SearchController.cs @@ -92,7 +92,7 @@ public async Task BuildIndex([FromBody] IndexConfiguration indexC foreach (var contentItem in contentItems) { var record = new RecordBuilder() - .BuildFromContent(contentItem, (p) => contentDataItem.Properties.Any(q => q == p.Alias)) + .BuildFromContent(contentItem, (p) => contentDataItem.Properties.Any(q => q.Alias == p.Alias)) .Build(); payload.Add(record); @@ -110,12 +110,16 @@ public async Task BuildIndex([FromBody] IndexConfiguration indexC } [HttpDelete] - public IActionResult DeleteIndex(int id) + public async Task DeleteIndex(int id) { try { + var indexName = _indexStorage.GetById(id).Name; + _indexStorage.Delete(id); + await _indexService.DeleteIndex(indexName); + return new JsonResult(Result.Ok()); } catch(Exception ex) diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs index 925af6de..9aa6b626 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Handlers/BaseContentHandler.cs @@ -39,11 +39,11 @@ protected async Task RebuildIndex(IEnumerable entities, bool deleteInd foreach (var index in indices) { var indexConfiguration = JsonSerializer.Deserialize>(index.SerializedData) - .FirstOrDefault(p => p.ContentType == entity.ContentType.Alias); - if (indexConfiguration == null || indexConfiguration.ContentType != entity.ContentType.Alias) continue; + .FirstOrDefault(p => p.ContentType.Alias == entity.ContentType.Alias); + if (indexConfiguration == null || indexConfiguration.ContentType.Alias != entity.ContentType.Alias) continue; var record = new RecordBuilder() - .BuildFromContent(entity, (p) => indexConfiguration.Properties.Any(q => q == p.Alias)) + .BuildFromContent(entity, (p) => indexConfiguration.Properties.Any(q => q.Alias == p.Alias)) .Build(); var result = deleteIndexData diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs index 1109bc9e..4c4d1690 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentData.cs @@ -1,12 +1,16 @@ - +using System.Text.Json.Serialization; + namespace Umbraco.Cms.Integrations.Search.Algolia.Models { public class ContentData { - public string ContentType { get; set; } + [JsonPropertyName("contentType")] + public ContentEntity ContentType { get; set; } - public string ContentTypeIcon { get; set; } + [JsonPropertyName("properties")] + public IEnumerable Properties { get; set; } - public string[] Properties { get; set; } + [JsonPropertyName("propertiesDescription")] + public IEnumerable PropertiesDescription => Properties.Select(p => p.Name); } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentEntity.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentEntity.cs new file mode 100644 index 00000000..cbe5c303 --- /dev/null +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Models/ContentEntity.cs @@ -0,0 +1,17 @@ + +using System.Text.Json.Serialization; + +namespace Umbraco.Cms.Integrations.Search.Algolia.Models +{ + public class ContentEntity + { + [JsonPropertyName("alias")] + public string Alias { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("icon")] + public string Icon { get; set; } + } +} diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs index f7d16d16..a9618356 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaIndexService.cs @@ -83,5 +83,23 @@ public async Task DeleteData(string name, string objectId) return Result.Fail(ex.Message); } } + + public async Task DeleteIndex(string name) + { + try + { + var client = new SearchClient(_settings.ApplicationId, _settings.AdminApiKey); + + var index = client.InitIndex(name); + + await index.DeleteAsync(); + + return Result.Ok(); + } + catch (AlgoliaException ex) + { + return Result.Fail(ex.Message); + } + } } } diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs index 9a1d01bf..0b3c3315 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/AlgoliaSearchService.cs @@ -1,5 +1,4 @@ using Algolia.Search.Clients; -using Algolia.Search.Http; using Algolia.Search.Models.Search; using Microsoft.Extensions.Options; diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs index d28e7c16..7b1f775b 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/Services/IAlgoliaIndexService.cs @@ -9,5 +9,7 @@ public interface IAlgoliaIndexService Task UpdateData(string name, Record record); Task DeleteData(string name, string objectId); + + Task DeleteIndex(string name); } } From ef764ffb6c70dddec0094d438867d35f46dfae45 Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu Date: Tue, 29 Nov 2022 18:49:01 +0200 Subject: [PATCH 11/11] Update label --- .../UmbracoCms.Integrations/Search/Algolia/views/dashboard.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html index 6f885762..fb4ce875 100644 --- a/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html +++ b/src/Umbraco.Cms.Integrations.Search.Algolia/App_Plugins/UmbracoCms.Integrations/Search/Algolia/views/dashboard.html @@ -102,7 +102,7 @@
- {{ vm.manageIndex.id == 0 ? "Add" : "Edit" }} Index Definition + {{ vm.manageIndex.id == 0 ? "Create" : "Edit" }} Index Definition