Skip to content

Commit

Permalink
Merge pull request #24 from Kentico/feat/set-index-names
Browse files Browse the repository at this point in the history
Enable index name customization
  • Loading branch information
kentico-ericd authored Jul 26, 2022
2 parents c01cce0 + cea1279 commit c880963
Show file tree
Hide file tree
Showing 21 changed files with 460 additions and 231 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using CMS.Helpers;
using CMS.Modules;

using Kentico.Xperience.AlgoliaSearch.Attributes;
using Kentico.Xperience.AlgoliaSearch.Models;
using Kentico.Xperience.AlgoliaSearch.Services;

using System;
Expand All @@ -23,7 +23,7 @@ protected void Page_Load(object sender, EventArgs e)
var siteIndexes = algoliaRegistrationService.RegisteredIndexes.Where(i => i.SiteNames == null || i.SiteNames.Contains(CurrentSiteName));
if (siteIndexes.Count() == 0)
{
ShowInformation("No Algolia indexes registered. See <a target='_blank' href='https://github.com/Kentico/xperience-algolia#creating-and-registering-an-algolia-index'>our instructions</a> to read more about creating and registering Algolia indexes.");
ShowInformation("No Algolia indexes registered. See <a target='_blank' href='https://github.com/Kentico/xperience-algolia#gear-creating-and-registering-an-algolia-index'>our instructions</a> to read more about creating and registering Algolia indexes.");
return;
}

Expand All @@ -42,7 +42,7 @@ private void ShowTaskCount()
}


private void LoadIndexes(IEnumerable<RegisterAlgoliaIndexAttribute> indexes)
private void LoadIndexes(IEnumerable<AlgoliaIndex> indexes)
{
var indexesToList = new List<IndicesResponse>();
var indexStatistics = algoliaSearchService.GetStatistics();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using CMS.Helpers;

using Kentico.Xperience.AlgoliaSearch.Attributes;
using Kentico.Xperience.AlgoliaSearch.Models;

using System;
using System.Collections.Generic;
Expand All @@ -12,7 +13,7 @@ namespace Kentico.Xperience.AlgoliaSearch.Pages
public partial class AlgoliaSearch_IndexedContent : AlgoliaUIPage
{
private string indexName;
private RegisterAlgoliaIndexAttribute registerAlgoliaIndexAttribute;
private AlgoliaIndex algoliaIndex;


protected override void OnLoad(EventArgs e)
Expand All @@ -26,16 +27,16 @@ protected override void OnLoad(EventArgs e)
return;
}

var registerIndexAttribute = algoliaRegistrationService.RegisteredIndexes.FirstOrDefault(i => i.IndexName == indexName);
if (registerIndexAttribute == null || registerIndexAttribute.Type == null)
var foundIndex = algoliaRegistrationService.RegisteredIndexes.FirstOrDefault(i => i.IndexName == indexName);
if (foundIndex == null)
{
ShowError("Unable to load index search model class.");
ShowError($"Error loading registered Algolia index '{indexName}.'");
return;
}

ShowInformation($"The indexed columns and pages are defined in the class <b>{registerIndexAttribute.Type}</b>. To modify them, please contact your developer.");
ShowInformation($"The indexed columns and pages are defined in the class <b>{foundIndex.Type}</b>. To modify them, please contact your developer.");

registerAlgoliaIndexAttribute = registerIndexAttribute;
algoliaIndex = foundIndex;
LoadProperties();
LoadPaths();
}
Expand All @@ -44,7 +45,7 @@ protected override void OnLoad(EventArgs e)
private void LoadProperties()
{
var indexedProperties = new List<IndexedProperty>();
var modelProperties = registerAlgoliaIndexAttribute.Type.GetProperties();
var modelProperties = algoliaIndex.Type.GetProperties();

foreach (var property in modelProperties)
{
Expand Down Expand Up @@ -72,7 +73,7 @@ private void LoadProperties()
private void LoadPaths()
{
var includedContent = new List<IncludedContent>();
var includedPathAttributes = registerAlgoliaIndexAttribute.Type.GetCustomAttributes(typeof(IncludedPathAttribute), false);
var includedPathAttributes = algoliaIndex.Type.GetCustomAttributes(typeof(IncludedPathAttribute), false);
foreach (var includedPathAttribute in includedPathAttributes)
{
var includedPath = includedPathAttribute as IncludedPathAttribute;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public partial class AlgoliaSearch_Preview : AlgoliaUIPage
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
indexName = QueryHelper.GetString("indexName", "");
if (indexName == null)
indexName = QueryHelper.GetString("indexName", String.Empty);
if (String.IsNullOrEmpty(indexName))
{
ShowError("Unable to load index name.");
searchPnl.Visible = false;
Expand All @@ -31,8 +31,8 @@ protected override void OnLoad(EventArgs e)

if (!RequestHelper.IsPostBack())
{
string searchText = QueryHelper.GetString("searchtext", "");
if (!string.IsNullOrEmpty(searchText))
string searchText = QueryHelper.GetString("searchtext", String.Empty);
if (!String.IsNullOrEmpty(searchText))
{
var searchIndexService = Service.Resolve<IAlgoliaIndexService>();
var searchIndex = searchIndexService.InitializeIndex(indexName);
Expand Down
119 changes: 70 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,24 @@ We recommend that you to create a new [.NET Standard 2.0](https://docs.microsoft
}
```

4. In the live-site project's startup code, call the `AddAlgolia()` extension method:

```cs
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAlgolia(Configuration);
}
```

5. In your administration project's `web.config` files's `appSettings` section, add the following keys:
4. In your administration project's `web.config` files's `appSettings` section, add the following keys:

```xml
<add key="AlgoliaApplicationId" value="<your application ID>"/>
<add key="AlgoliaApiKey" value="<your Admin API key>"/>
```

5. Register the `IAlgoliaIndexRegister` service and any indexes by following [this guide](#gear-creating-and-registering-an-algolia-index).
6. (Optional) Import the [Xperience Algolia module](#chart_with_upwards_trend-algolia-search-application-for-administration-interface) in your Xperience website.

## :gear: Creating and registering an Algolia index

An Algolia index and its attributes are defined within a single class file, in which your custom class extends the [`AlgoliaSearchModel`](https://github.com/Kentico/xperience-algolia/blob/master/src/Models/AlgoliaSearchModel.cs) class. Within the class, you define the attributes of the index by creating properties which match the names of Xperience page fields to index. The Xperience fields available may come from the `TreeNode` object, `SKUTreeNode` for products, or any custom page type fields.

The index is registered via the [`RegisterAlgoliaIndex`](https://github.com/Kentico/xperience-algolia/blob/master/src/Attributes/RegisterAlgoliaIndexAttribute.cs) attribute which requires the type of the search model class and the code name of the Algolia index. Optionally, you can provide a list of `SiteNames` to which the index is assigned. If not provided, pages from all sites are included.

```cs
using Kentico.Xperience.AlgoliaSearch.Models;
using System;

[assembly: RegisterAlgoliaIndex(typeof(AlgoliaSiteSearchModel), AlgoliaSiteSearchModel.IndexName, SiteNames = new string[] { "DancingGoatCore" })]
namespace DancingGoat
{
public class AlgoliaSiteSearchModel : AlgoliaSearchModel
Expand All @@ -71,6 +59,43 @@ namespace DancingGoat

> :ab: The property names (and names used in the [SourceAttribute](#source-attribute)) are __case-insensitive__. This means that your search model can contain an "articletext" property, or an "ArticleText" property- both will work.
Indexes must be registered during application startup in both the administration application and the live-site application. In the __administration__ project, create a [custom module](https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code) and use the `OnPreInit` method to create an `IAlgoliaIndexRegister`, add your indexes, and register the service:

```cs
protected override void OnPreInit()
{
base.OnPreInit();

Service.Use<IAlgoliaIndexRegister>(new DefaultAlgoliaIndexRegister()
.Add<AlgoliaSiteSearchModel>(AlgoliaSiteSearchModel.IndexName)
);
}
```

The `Add` method also accepts an optional list of `SiteNames` to which the index is assigned. If not provided, pages from all sites are included. In the __live-site__ project's startup code, call the `AddAlgolia()` extension method and add your indexes to the `IAlgoliaIndexRegister`:

```cs
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAlgolia(Configuration,
new DefaultAlgoliaIndexRegister()
.Add<AlgoliaSiteSearchModel>(AlgoliaSiteSearchModel.IndexName)
);
}
```

If you're developing your search solution in multiple environments (e.g. "DEV" and "STG"), it is recommended that you create a unique Algolia index per environment. With this approach, the search functionality can be tested in each environment individually and changes to the index structure or content will not affect other environments. This can be implemented any way you'd like, including some custom service which transforms the index names. The simplest approach would be to prepend some environment name to the index, which is stored in the application settings:

```cs
var environment = ConfigurationManager.AppSettings["Environment"];
Service.Use<IAlgoliaIndexRegister>(new DefaultAlgoliaIndexRegister()
.Add<AlgoliaSiteSearchModel>($"{environment}-{AlgoliaSiteSearchModel.IndexName}")
);
```

> :triangular_flag_on_post: The method of registering indexes via [`RegisterAlgoliaIndex`](https://github.com/Kentico/xperience-algolia/blob/master/src/Attributes/RegisterAlgoliaIndexAttribute.cs) is still supported, but no longer recommended.
### Determining which pages to index

While the above sample code will create an Algolia index, pages in the content tree will not be indexed until one or more [`IncludedPathAttribute`](https://github.com/Kentico/xperience-algolia/blob/master/src/Attributes/IncludedPathAttribute.cs) attributes are applied to the class. The `IncludedPathAttribute` has three properties to configure:
Expand All @@ -89,48 +114,44 @@ All pages under the specified __AliasPath__ will be indexed regardless of the [p
Below is an example of an Algolia index which includes multiple paths and page types:

```cs
[assembly: RegisterAlgoliaIndex(typeof(AlgoliaSiteSearchModel), AlgoliaSiteSearchModel.IndexName)]
namespace DancingGoat
[IncludedPath("/Articles/%", PageTypes = new string[] { Article.CLASS_NAME })]
[IncludedPath("/Store/%", PageTypes = new string[] { "DancingGoatCore.Brewer", "DancingGoatCore.Coffee", "DancingGoatCore.ElectricGrinder", "DancingGoatCore.FilterPack", "DancingGoatCore.ManualGrinder", "DancingGoatCore.Tableware" })]
public class AlgoliaSiteSearchModel : AlgoliaSearchModel
{
[IncludedPath("/Articles/%", PageTypes = new string[] { Article.CLASS_NAME })]
[IncludedPath("/Store/%", PageTypes = new string[] { "DancingGoatCore.Brewer", "DancingGoatCore.Coffee", "DancingGoatCore.ElectricGrinder", "DancingGoatCore.FilterPack", "DancingGoatCore.ManualGrinder", "DancingGoatCore.Tableware" })]
public class AlgoliaSiteSearchModel : AlgoliaSearchModel
{
public const string IndexName = "DancingGoatSiteSearch";
public const string IndexName = "DancingGoatSiteSearch";

[Searchable, Retrievable]
public string DocumentName { get; set; }
[Searchable, Retrievable]
public string DocumentName { get; set; }

[Url, Retrievable]
[Source(new string[] { nameof(SKUTreeNode.SKU.SKUImagePath), nameof(Article.ArticleTeaser) })]
public string Thumbnail { get; set; }
[Url, Retrievable]
[Source(new string[] { nameof(SKUTreeNode.SKU.SKUImagePath), nameof(Article.ArticleTeaser) })]
public string Thumbnail { get; set; }

[Searchable]
[Source(new string[] { nameof(SKUTreeNode.DocumentSKUDescription), nameof(Article.ArticleText) })]
public string Content { get; set; }
[Searchable]
[Source(new string[] { nameof(SKUTreeNode.DocumentSKUDescription), nameof(Article.ArticleText) })]
public string Content { get; set; }

[Searchable, Retrievable]
[Source(new string[] { nameof(SKUTreeNode.DocumentSKUShortDescription), nameof(Article.ArticleSummary) })]
public string ShortDescription { get; set; }
[Searchable, Retrievable]
[Source(new string[] { nameof(SKUTreeNode.DocumentSKUShortDescription), nameof(Article.ArticleSummary) })]
public string ShortDescription { get; set; }

[Retrievable]
public int SKUID { get; set; }
[Retrievable]
public int SKUID { get; set; }

[Facetable, Retrievable]
public decimal? SKUPrice { get; set; }
[Facetable, Retrievable]
public decimal? SKUPrice { get; set; }

[Retrievable]
public int SKUPublicStatusID { get; set; }
[Retrievable]
public int SKUPublicStatusID { get; set; }

[Retrievable]
public DateTime DocumentCreatedWhen { get; set; }
[Retrievable]
public DateTime DocumentCreatedWhen { get; set; }

[Facetable]
public string CoffeeProcessing { get; set; }
[Facetable]
public string CoffeeProcessing { get; set; }

[Facetable]
public bool CoffeeIsDecaf { get; set; }
}
[Facetable]
public bool CoffeeIsDecaf { get; set; }
}
```

Expand Down Expand Up @@ -445,7 +466,6 @@ Algolia provides [autocomplete](https://www.algolia.com/doc/ui-libraries/autocom

```cshtml
@inject IConfiguration configuration
@{
var algoliaOptions = configuration.GetSection(AlgoliaOptions.SECTION_NAME).Get<AlgoliaOptions>();
}
Expand Down Expand Up @@ -700,6 +720,7 @@ private SearchResponse<AlgoliaSiteSearchModel> Search(IAlgoliaFacetFilter filter
Facets = facetsToRetrieve
};


var searchIndex = _indexService.InitializeIndex(AlgoliaSiteSearchModel.IndexName);
return searchIndex.Search<AlgoliaSiteSearchModel>(query);
}
Expand Down Expand Up @@ -942,6 +963,7 @@ You can also log an event when a visitor simply views a page with the `LogPageVi
public async Task<IActionResult> Detail([FromServices] ArticleRepository articleRepository)
{
var article = articleRepository.GetCurrent();

await _insightsService.LogPageViewed(article.DocumentID, "Article viewed", AlgoliaSiteSearchModel.IndexName);

return new TemplateResult(article);
Expand Down Expand Up @@ -1011,7 +1033,7 @@ In the appropriate controller, create the action which accepts the facet paramet
```cs
[HttpPost]
public Task<ActionResult> FacetClicked(string facet)
public async Task<ActionResult> FacetClicked(string facet)
{
if (String.IsNullOrEmpty(facet))
{
Expand Down Expand Up @@ -1066,7 +1088,6 @@ endpoints.MapControllerRoute(
```cshtml
@inject IConfiguration configuration

@{
var algoliaOptions = configuration.GetSection(AlgoliaOptions.SECTION_NAME).Get<AlgoliaOptions>();
}
Expand Down Expand Up @@ -1105,7 +1126,7 @@ endpoints.MapControllerRoute(
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4"></script>
<script type="text/javascript">
const search = instantsearch({
indexName: '@DancingGoatSiteIndexModel.IndexName',
indexName: '@AlgoliaSiteSearchModel.IndexName',
searchClient: algoliasearch('@algoliaOptions.ApplicationId', '@algoliaOptions.SearchKey'),
});

Expand Down
9 changes: 6 additions & 3 deletions src/AlgoliaStartupExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Algolia.Search.Clients;

using Kentico.Xperience.AlgoliaSearch.Models;
using Kentico.Xperience.AlgoliaSearch.Services;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -15,12 +16,13 @@ namespace Kentico.Xperience.AlgoliaSearch
public static class AlgoliaStartupExtensions
{
/// <summary>
/// Registers instances of <see cref="IInsightsClient"/> and <see cref="ISearchClient"/>
/// with Dependency Injection.
/// Registers instances of <see cref="IInsightsClient"/>, <see cref="ISearchClient"/>, and
/// <see cref="IAlgoliaIndexRegister"/> with Dependency Injection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configuration">The application configuration.</param>
public static IServiceCollection AddAlgolia(this IServiceCollection services, IConfiguration configuration)
/// <param name="register">The implementation of <see cref="IAlgoliaIndexRegister"/> to register.</param>
public static IServiceCollection AddAlgolia(this IServiceCollection services, IConfiguration configuration, IAlgoliaIndexRegister register)
{
var algoliaOptions = configuration.GetSection(AlgoliaOptions.SECTION_NAME).Get<AlgoliaOptions>();
if (String.IsNullOrEmpty(algoliaOptions.ApplicationId) || String.IsNullOrEmpty(algoliaOptions.ApiKey))
Expand All @@ -36,6 +38,7 @@ public static IServiceCollection AddAlgolia(this IServiceCollection services, IC

services.AddSingleton<IInsightsClient>(insightsClient);
services.AddSingleton<ISearchClient>(searchClient);
services.AddSingleton(register);

return services;
}
Expand Down
1 change: 1 addition & 0 deletions src/Attributes/RegisterAlgoliaIndexAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public Type Type
public string IndexName
{
get;
internal set;
}


Expand Down
2 changes: 1 addition & 1 deletion src/Kentico.Xperience.AlgoliaSearch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PropertyGroup>
<Title>Xperience Algolia Search</Title>
<PackageId>Kentico.Xperience.AlgoliaSearch</PackageId>
<Version>2.4.0</Version>
<Version>3.0.0</Version>
<Authors>Kentico Software</Authors>
<Company>Kentico Software</Company>
<PackageIcon>icon.png</PackageIcon>
Expand Down
Loading

0 comments on commit c880963

Please sign in to comment.