-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enhance AzureAISearch to support indexes independent of content items (…
- Loading branch information
1 parent
ca19f85
commit b2e052f
Showing
49 changed files
with
1,445 additions
and
449 deletions.
There are no files selected for viewing
251 changes: 103 additions & 148 deletions
251
src/OrchardCore.Modules/OrchardCore.Search.AzureAI/Controllers/AdminController.cs
Large diffs are not rendered by default.
Oops, something went wrong.
89 changes: 89 additions & 0 deletions
89
...ore.Modules/OrchardCore.Search.AzureAI/Drivers/AzureAISearchIndexSettingsDisplayDriver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using Microsoft.AspNetCore.Mvc.Rendering; | ||
using Microsoft.Extensions.Localization; | ||
using Microsoft.Extensions.Options; | ||
using OrchardCore.DisplayManagement.Handlers; | ||
using OrchardCore.DisplayManagement.Views; | ||
using OrchardCore.Mvc.ModelBinding; | ||
using OrchardCore.Search.AzureAI.Models; | ||
using OrchardCore.Search.AzureAI.Services; | ||
using OrchardCore.Search.AzureAI.ViewModels; | ||
|
||
namespace OrchardCore.Search.AzureAI.Drivers; | ||
|
||
internal sealed class AzureAISearchIndexSettingsDisplayDriver : DisplayDriver<AzureAISearchIndexSettings> | ||
{ | ||
private readonly AzureAISearchIndexManager _indexManager; | ||
private readonly AzureAISearchDefaultOptions _azureAIOptions; | ||
private readonly IStringLocalizer S; | ||
|
||
public AzureAISearchIndexSettingsDisplayDriver( | ||
AzureAISearchIndexManager indexManager, | ||
IOptions<AzureAISearchDefaultOptions> azureAIOptions, | ||
IStringLocalizer<AzureAISearchIndexSettingsDisplayDriver> stringLocalizer) | ||
{ | ||
_indexManager = indexManager; | ||
_azureAIOptions = azureAIOptions.Value; | ||
S = stringLocalizer; | ||
} | ||
|
||
public override Task<IDisplayResult> DisplayAsync(AzureAISearchIndexSettings settings, BuildDisplayContext context) | ||
{ | ||
return CombineAsync( | ||
View("AzureAISearchIndexSettings_Fields_SummaryAdmin", settings).Location("Content:1"), | ||
View("AzureAISearchIndexSettings_Buttons_SummaryAdmin", settings).Location("Actions:5"), | ||
View("AzureAISearchIndexSettings_DefaultTags_SummaryAdmin", settings).Location("Tags:5") | ||
); | ||
} | ||
|
||
public override IDisplayResult Edit(AzureAISearchIndexSettings settings, BuildEditorContext context) | ||
{ | ||
return Initialize<AzureAISettingsViewModel>("AzureAISearchIndexSettingsFields_Edit", model => | ||
{ | ||
model.AnalyzerName = settings.AnalyzerName ?? AzureAISearchDefaultOptions.DefaultAnalyzer; | ||
model.IndexName = settings.IndexName; | ||
model.IsNew = context.IsNew; | ||
model.Analyzers = _azureAIOptions.Analyzers.Select(x => new SelectListItem(x, x)); | ||
}).Location("Content:1"); | ||
} | ||
|
||
public override async Task<IDisplayResult> UpdateAsync(AzureAISearchIndexSettings settings, UpdateEditorContext context) | ||
{ | ||
var model = new AzureAISettingsViewModel(); | ||
|
||
await context.Updater.TryUpdateModelAsync(model, Prefix); | ||
|
||
if (context.IsNew) | ||
{ | ||
if (string.IsNullOrWhiteSpace(model.IndexName)) | ||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, nameof(model.IndexName), S["The index name is required."]); | ||
} | ||
else if (!AzureAISearchIndexNamingHelper.TryGetSafeIndexName(model.IndexName, out var indexName) || indexName != model.IndexName) | ||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, nameof(model.IndexName), S["The index name contains forbidden characters."]); | ||
} | ||
else if (await _indexManager.ExistsAsync(model.IndexName)) | ||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, nameof(AzureAISettingsViewModel.IndexName), S["An index named <em>{0}</em> already exist in Azure AI Search server.", model.IndexName]); | ||
} | ||
|
||
settings.IndexName = model.IndexName; | ||
settings.IndexFullName = _indexManager.GetFullIndexName(model.IndexName); | ||
} | ||
|
||
settings.AnalyzerName = model.AnalyzerName; | ||
settings.QueryAnalyzerName = model.AnalyzerName; | ||
|
||
if (string.IsNullOrEmpty(settings.AnalyzerName)) | ||
{ | ||
settings.AnalyzerName = AzureAISearchDefaultOptions.DefaultAnalyzer; | ||
} | ||
|
||
if (string.IsNullOrEmpty(settings.QueryAnalyzerName)) | ||
{ | ||
settings.QueryAnalyzerName = settings.AnalyzerName; | ||
} | ||
|
||
return Edit(settings, context); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
...ules/OrchardCore.Search.AzureAI/Drivers/ContentAzureAISearchIndexSettingsDisplayDriver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using System.Globalization; | ||
using Microsoft.AspNetCore.Mvc.Rendering; | ||
using Microsoft.Extensions.Localization; | ||
using OrchardCore.DisplayManagement.Handlers; | ||
using OrchardCore.DisplayManagement.Views; | ||
using OrchardCore.Entities; | ||
using OrchardCore.Mvc.ModelBinding; | ||
using OrchardCore.Search.AzureAI.Models; | ||
using OrchardCore.Search.AzureAI.ViewModels; | ||
|
||
namespace OrchardCore.Search.AzureAI.Drivers; | ||
|
||
internal sealed class ContentAzureAISearchIndexSettingsDisplayDriver : DisplayDriver<AzureAISearchIndexSettings> | ||
{ | ||
internal readonly IStringLocalizer S; | ||
|
||
public ContentAzureAISearchIndexSettingsDisplayDriver(IStringLocalizer<ContentAzureAISearchIndexSettingsDisplayDriver> stringLocalizer) | ||
{ | ||
S = stringLocalizer; | ||
} | ||
|
||
public override IDisplayResult Edit(AzureAISearchIndexSettings settings, BuildEditorContext context) | ||
{ | ||
if (!string.Equals(AzureAISearchConstants.ContentsIndexSource, settings.Source, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return null; | ||
} | ||
|
||
return Initialize<ContentIndexMetadataViewModel>("ContentIndexMetadata_Edit", model => | ||
{ | ||
var metadata = settings.As<ContentIndexMetadata>(); | ||
|
||
model.IndexLatest = metadata.IndexLatest; | ||
model.IndexedContentTypes = metadata.IndexedContentTypes; | ||
model.Culture = metadata.Culture; | ||
|
||
model.Cultures = CultureInfo.GetCultures(CultureTypes.AllCultures) | ||
.Select(x => new SelectListItem { Text = $"{x.Name} ({x.DisplayName})", Value = x.Name }); | ||
|
||
}).Location("Content:5"); | ||
} | ||
|
||
public override async Task<IDisplayResult> UpdateAsync(AzureAISearchIndexSettings settings, UpdateEditorContext context) | ||
{ | ||
if (!string.Equals(AzureAISearchConstants.ContentsIndexSource, settings.Source, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return null; | ||
} | ||
|
||
var model = new ContentIndexMetadataViewModel(); | ||
|
||
await context.Updater.TryUpdateModelAsync(model, Prefix); | ||
|
||
if (model.IndexedContentTypes is null || model.IndexedContentTypes.Length == 0) | ||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, nameof(model.IndexedContentTypes), S["At least one content type must be selected."]); | ||
} | ||
|
||
settings.Alter<ContentIndexMetadata>(m => | ||
{ | ||
m.IndexLatest = model.IndexLatest; | ||
m.IndexedContentTypes = model.IndexedContentTypes ?? []; | ||
m.Culture = model.Culture; | ||
}); | ||
|
||
return Edit(settings, context); | ||
} | ||
} |
138 changes: 138 additions & 0 deletions
138
...ore.Modules/OrchardCore.Search.AzureAI/Migrations/AzureAISearchIndexSettingsMigrations.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
using System.Text.Json.Nodes; | ||
using Dapper; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using OrchardCore.Data; | ||
using OrchardCore.Data.Migration; | ||
using OrchardCore.Documents; | ||
using OrchardCore.Entities; | ||
using OrchardCore.Environment.Shell.Scope; | ||
using OrchardCore.Search.AzureAI.Models; | ||
using YesSql; | ||
using YesSql.Sql; | ||
|
||
namespace OrchardCore.Search.AzureAI.Migrations; | ||
|
||
/// <summary> | ||
/// In version 3, we introduced Source, Id and the ability to add metadata to index settings. | ||
/// This migration will migrate any index that was created before v3 to use the new structure. | ||
/// This migration will be removed in future releases. | ||
/// </summary> | ||
internal sealed class AzureAISearchIndexSettingsMigrations : DataMigration | ||
{ | ||
#pragma warning disable CA1822 // Mark members as static | ||
public int Create() | ||
#pragma warning restore CA1822 // Mark members as static | ||
{ | ||
ShellScope.AddDeferredTask(async scope => | ||
{ | ||
var store = scope.ServiceProvider.GetRequiredService<IStore>(); | ||
var dbConnectionAccessor = scope.ServiceProvider.GetRequiredService<IDbConnectionAccessor>(); | ||
var settingsManager = scope.ServiceProvider.GetRequiredService<IDocumentManager<AzureAISearchIndexSettingsDocument>>(); | ||
|
||
var documentTableName = store.Configuration.TableNameConvention.GetDocumentTable(); | ||
var table = $"{store.Configuration.TablePrefix}{documentTableName}"; | ||
var dialect = store.Configuration.SqlDialect; | ||
var quotedTableName = dialect.QuoteForTableName(table, store.Configuration.Schema); | ||
var quotedContentColumnName = dialect.QuoteForColumnName("Content"); | ||
var quotedTypeColumnName = dialect.QuoteForColumnName("Type"); | ||
|
||
var sqlBuilder = new SqlBuilder(store.Configuration.TablePrefix, store.Configuration.SqlDialect); | ||
sqlBuilder.AddSelector(quotedContentColumnName); | ||
sqlBuilder.From(quotedTableName); | ||
sqlBuilder.WhereAnd($" {quotedTypeColumnName} = 'OrchardCore.Search.AzureAI.Models.AzureAISearchIndexSettingsDocument, OrchardCore.Search.AzureAI.Core' "); | ||
sqlBuilder.Take("1"); | ||
|
||
await using var connection = dbConnectionAccessor.CreateConnection(); | ||
await connection.OpenAsync(); | ||
var jsonContent = await connection.QueryFirstOrDefaultAsync<string>(sqlBuilder.ToSqlString()); | ||
|
||
if (string.IsNullOrEmpty(jsonContent)) | ||
{ | ||
return; | ||
} | ||
|
||
var jsonObject = JsonNode.Parse(jsonContent); | ||
|
||
if (jsonObject["IndexSettings"] is not JsonObject indexesObject) | ||
{ | ||
return; | ||
} | ||
|
||
var document = await settingsManager.GetOrCreateMutableAsync(); | ||
|
||
foreach (var indexObject in indexesObject) | ||
{ | ||
var source = indexObject.Value["Source"]?.GetValue<string>(); | ||
|
||
if (!string.IsNullOrEmpty(source)) | ||
{ | ||
// No migration is needed. | ||
continue; | ||
} | ||
|
||
var indexName = indexObject.Value["IndexName"]?.GetValue<string>(); | ||
|
||
if (string.IsNullOrEmpty(indexName)) | ||
{ | ||
// Bad index! this is a scenario that should never happen. | ||
continue; | ||
} | ||
|
||
if (!document.IndexSettings.TryGetValue(indexName, out var indexSettings)) | ||
{ | ||
// Bad index! this is a scenario that should never happen. | ||
continue; | ||
} | ||
|
||
indexSettings.Source = AzureAISearchConstants.ContentsIndexSource; | ||
|
||
if (string.IsNullOrEmpty(indexSettings.Id)) | ||
{ | ||
indexSettings.Id = IdGenerator.GenerateId(); | ||
} | ||
|
||
var metadata = indexSettings.As<ContentIndexMetadata>(); | ||
|
||
if (string.IsNullOrEmpty(metadata.Culture)) | ||
{ | ||
metadata.Culture = indexObject.Value[nameof(ContentIndexMetadata.Culture)]?.GetValue<string>(); | ||
} | ||
|
||
var indexLatest = indexObject.Value[nameof(ContentIndexMetadata.IndexLatest)]?.GetValue<bool>(); | ||
|
||
if (indexLatest.HasValue) | ||
{ | ||
metadata.IndexLatest = indexLatest.Value; | ||
} | ||
|
||
var indexContentTypes = indexObject.Value[nameof(ContentIndexMetadata.IndexedContentTypes)]?.AsArray(); | ||
|
||
if (indexContentTypes is not null) | ||
{ | ||
var items = new HashSet<string>(); | ||
|
||
foreach (var indexContentType in indexContentTypes) | ||
{ | ||
var value = indexContentType.GetValue<string>(); | ||
|
||
if (!string.IsNullOrEmpty(value)) | ||
{ | ||
items.Add(value); | ||
} | ||
} | ||
|
||
metadata.IndexedContentTypes = items.ToArray(); | ||
} | ||
|
||
indexSettings.Put(metadata); | ||
|
||
document.IndexSettings.Remove(indexName); | ||
document.IndexSettings[indexSettings.Id] = indexSettings; | ||
} | ||
|
||
await settingsManager.UpdateAsync(document); | ||
}); | ||
|
||
return 1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/OrchardCore.Modules/OrchardCore.Search.AzureAI/ViewModels/AzureAIIndexEntry.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using OrchardCore.DisplayManagement; | ||
using OrchardCore.Search.AzureAI.Models; | ||
|
||
namespace OrchardCore.Search.AzureAI.ViewModels; | ||
|
||
public class AzureAIIndexEntry | ||
{ | ||
public AzureAISearchIndexSettings Index { get; set; } | ||
|
||
public IShape Shape { get; set; } | ||
} |
Oops, something went wrong.