Merge pull request #17794 from umbraco/v15/bugfix/corrupt-examine-index-dashboard
Make sure Examine dashboard still functions when an index is corrupt
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
using Examine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Indexer;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
using Umbraco.Cms.Infrastructure.Services;
|
||||
|
||||
@@ -11,16 +14,34 @@ public class IndexPresentationFactory : IIndexPresentationFactory
|
||||
private readonly IIndexDiagnosticsFactory _indexDiagnosticsFactory;
|
||||
private readonly IIndexRebuilder _indexRebuilder;
|
||||
private readonly IIndexingRebuilderService _indexingRebuilderService;
|
||||
private readonly ILogger<IndexPresentationFactory> _logger;
|
||||
|
||||
public IndexPresentationFactory(IIndexDiagnosticsFactory indexDiagnosticsFactory, IIndexRebuilder indexRebuilder, IIndexingRebuilderService indexingRebuilderService)
|
||||
public IndexPresentationFactory(
|
||||
IIndexDiagnosticsFactory indexDiagnosticsFactory,
|
||||
IIndexRebuilder indexRebuilder,
|
||||
IIndexingRebuilderService indexingRebuilderService,
|
||||
ILogger<IndexPresentationFactory> logger)
|
||||
{
|
||||
_indexDiagnosticsFactory = indexDiagnosticsFactory;
|
||||
_indexRebuilder = indexRebuilder;
|
||||
_indexingRebuilderService = indexingRebuilderService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Obsolete("Use the non obsolete method instead. Scheduled for removal in v17")]
|
||||
public IndexPresentationFactory(IIndexDiagnosticsFactory indexDiagnosticsFactory, IIndexRebuilder indexRebuilder, IIndexingRebuilderService indexingRebuilderService)
|
||||
:this(
|
||||
indexDiagnosticsFactory,
|
||||
indexRebuilder,
|
||||
indexingRebuilderService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<IndexPresentationFactory>>())
|
||||
{
|
||||
}
|
||||
|
||||
public IndexResponseModel Create(IIndex index)
|
||||
{
|
||||
var isCorrupt = !TryGetSearcherName(index, out var searcherName);
|
||||
|
||||
if (_indexingRebuilderService.IsRebuilding(index.Name))
|
||||
{
|
||||
return new IndexResponseModel
|
||||
@@ -28,9 +49,9 @@ public class IndexPresentationFactory : IIndexPresentationFactory
|
||||
Name = index.Name,
|
||||
HealthStatus = new HealthStatusResponseModel
|
||||
{
|
||||
Status = HealthStatus.Rebuilding,
|
||||
Status = isCorrupt ? HealthStatus.Corrupt : HealthStatus.Rebuilding,
|
||||
},
|
||||
SearcherName = index.Searcher.Name,
|
||||
SearcherName = searcherName,
|
||||
DocumentCount = 0,
|
||||
FieldCount = 0,
|
||||
};
|
||||
@@ -55,21 +76,78 @@ public class IndexPresentationFactory : IIndexPresentationFactory
|
||||
}
|
||||
}
|
||||
|
||||
if (TryGetDocumentCount(indexDiag, index, out var documentCount) is false)
|
||||
{
|
||||
isCorrupt = true;
|
||||
}
|
||||
|
||||
if (TryGetFieldNameCount(indexDiag, index, out var fieldNameCount) is false)
|
||||
{
|
||||
isCorrupt = true;
|
||||
}
|
||||
|
||||
var indexerModel = new IndexResponseModel
|
||||
{
|
||||
Name = index.Name,
|
||||
HealthStatus = new HealthStatusResponseModel
|
||||
{
|
||||
Status = isHealthyAttempt.Success ? HealthStatus.Healthy : HealthStatus.Unhealthy,
|
||||
Status = isCorrupt
|
||||
? HealthStatus.Corrupt
|
||||
: isHealthyAttempt.Success ? HealthStatus.Healthy : HealthStatus.Unhealthy,
|
||||
Message = isHealthyAttempt.Result,
|
||||
},
|
||||
CanRebuild = _indexRebuilder.CanRebuild(index.Name),
|
||||
SearcherName = index.Searcher.Name,
|
||||
DocumentCount = indexDiag.GetDocumentCount(),
|
||||
FieldCount = indexDiag.GetFieldNames().Count(),
|
||||
CanRebuild = isCorrupt is false && _indexRebuilder.CanRebuild(index.Name),
|
||||
SearcherName = searcherName,
|
||||
DocumentCount = documentCount,
|
||||
FieldCount = fieldNameCount,
|
||||
ProviderProperties = properties,
|
||||
};
|
||||
|
||||
return indexerModel;
|
||||
}
|
||||
|
||||
private bool TryGetSearcherName(IIndex index, out string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
name = index.Searcher.Name;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "An error occured trying to get the searcher name of index {IndexName}", index.Name);
|
||||
name = "Could not determine searcher name because of error.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetDocumentCount(IIndexDiagnostics indexDiag, IIndex index, out long documentCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
documentCount = indexDiag.GetDocumentCount();
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "An error occured trying to get the document count of index {IndexName}", index.Name);
|
||||
documentCount = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetFieldNameCount(IIndexDiagnostics indexDiag, IIndex index, out int fieldNameCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
fieldNameCount = indexDiag.GetFieldNames().Count();
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "An error occured trying to get the field name count of index {IndexName}", index.Name);
|
||||
fieldNameCount = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38339,6 +38339,7 @@
|
||||
"enum": [
|
||||
"Healthy",
|
||||
"Unhealthy",
|
||||
"Corrupt",
|
||||
"Rebuilding"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -40758,7 +40759,8 @@
|
||||
"format": "uuid"
|
||||
},
|
||||
"packagePath": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -42742,22 +42744,26 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationReferenceModel"
|
||||
}
|
||||
]
|
||||
],
|
||||
"readOnly": true
|
||||
},
|
||||
"child": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationReferenceModel"
|
||||
}
|
||||
]
|
||||
],
|
||||
"readOnly": true
|
||||
},
|
||||
"createDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
"format": "date-time",
|
||||
"readOnly": true
|
||||
},
|
||||
"comment": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
"nullable": true,
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -44939,7 +44945,8 @@
|
||||
}
|
||||
},
|
||||
"packagePath": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -46093,4 +46100,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@ public enum HealthStatus
|
||||
{
|
||||
Healthy,
|
||||
Unhealthy,
|
||||
Rebuilding
|
||||
Rebuilding,
|
||||
Corrupt
|
||||
}
|
||||
|
||||
@@ -187,8 +187,7 @@ public class ExamineIndexRebuilder : IIndexRebuilder
|
||||
{
|
||||
// If an index exists but it has zero docs we'll consider it empty and rebuild
|
||||
IIndex[] indexes = (onlyEmptyIndexes
|
||||
? _examineManager.Indexes.Where(x =>
|
||||
!x.IndexExists() || (x is IIndexStats stats && stats.GetDocumentCount() == 0))
|
||||
? _examineManager.Indexes.Where(x => ShouldRebuild(x))
|
||||
: _examineManager.Indexes).ToArray();
|
||||
|
||||
if (indexes.Length == 0)
|
||||
@@ -228,4 +227,17 @@ public class ExamineIndexRebuilder : IIndexRebuilder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldRebuild(IIndex index)
|
||||
{
|
||||
try
|
||||
{
|
||||
return !index.IndexExists() || (index is IIndexStats stats && stats.GetDocumentCount() == 0);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "An error occured trying to get determine index shouldRebuild status for index {IndexName}. The index will NOT be considered for rebuilding", index.Name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,6 +653,8 @@ export default {
|
||||
'The process is taking longer than expected, check the Umbraco log to see if there have been any errors during this operation',
|
||||
indexCannotRebuild: 'This index cannot be rebuilt because it has no assigned',
|
||||
iIndexPopulator: 'IIndexPopulator',
|
||||
corruptStatus: 'Possible corrupt index detected',
|
||||
corruptErrorDescription: 'Error received when evaluating the index:'
|
||||
},
|
||||
placeholders: {
|
||||
username: 'Enter your username',
|
||||
|
||||
@@ -1027,6 +1027,7 @@ export type HealthCheckWithResultPresentationModel = {
|
||||
export enum HealthStatusModel {
|
||||
HEALTHY = 'Healthy',
|
||||
UNHEALTHY = 'Unhealthy',
|
||||
CORRUPT = 'Corrupt',
|
||||
REBUILDING = 'Rebuilding'
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,12 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
|
||||
switch (healthStatus.status) {
|
||||
case HealthStatusModel.HEALTHY:
|
||||
return html`<umb-icon name="icon-check color-green"></umb-icon>${msg}`;
|
||||
case HealthStatusModel.CORRUPT:
|
||||
return html`<umb-icon name="icon-alert color-red"></umb-icon><div>
|
||||
<a href="https://umbra.co/corrupt-indexes" target="_blank"><umb-localize key="examineManagement_corruptStatus">Possible corrupt index detected</umb-localize></a>
|
||||
<p><umb-localize key="examineManagement_corruptErrorDescription">Error received when evaluating the index:</umb-localize> </br> </p>${msg}</div>`;
|
||||
case HealthStatusModel.UNHEALTHY:
|
||||
return html`<umb-icon name="icon-error color-red"></umb-icon>${msg}`;
|
||||
return html`<umb-icon name="icon-alert color-red"></umb-icon>${msg}`;
|
||||
case HealthStatusModel.REBUILDING:
|
||||
return html`<umb-icon name="icon-time color-yellow"></umb-icon>${msg}`;
|
||||
default:
|
||||
@@ -174,9 +178,14 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
|
||||
css`
|
||||
#health-status {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
gap: var(--uui-size-6);
|
||||
}
|
||||
|
||||
#health-status umb-icon {
|
||||
margin-top: var(--uui-size-1);
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,9 @@ export class UmbDashboardExamineOverviewElement extends UmbLitElement {
|
||||
switch (status) {
|
||||
case HealthStatusModel.HEALTHY:
|
||||
return html`<umb-icon name="icon-check color-green"></umb-icon>`;
|
||||
case HealthStatusModel.CORRUPT:
|
||||
case HealthStatusModel.UNHEALTHY:
|
||||
return html`<umb-icon name="icon-error color-red"></umb-icon>`;
|
||||
return html`<umb-icon name="icon-alert color-red"></umb-icon>`;
|
||||
case HealthStatusModel.REBUILDING:
|
||||
return html`<umb-icon name="icon-time color-yellow"></umb-icon>`;
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Examine;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -51,7 +52,8 @@ public class IndexPresentationFactoryTests
|
||||
var factory = new IndexPresentationFactory(
|
||||
indexDiagnosticsFactoryMock.Object,
|
||||
indexRebuilderMock.Object,
|
||||
indexRebuilderServiceMock.Object);
|
||||
indexRebuilderServiceMock.Object,
|
||||
Mock.Of<ILogger<IndexPresentationFactory>>());
|
||||
|
||||
|
||||
// act
|
||||
|
||||
Reference in New Issue
Block a user