Compare commits

...

13 Commits

Author SHA1 Message Date
Shannon Deminick 218f523022 Merge pull request #1843 from umbraco/temp-U4-9691
Fixes the Examine mgmt dashboard to look better and to detect if an i…
2017-04-03 16:28:36 +10:00
Shannon Deminick 50c6fb036a Merge pull request #1839 from umbraco/temp-uaasscrum-827
uaasscrum-827 - more robust umbraco.config
2017-04-03 16:19:27 +10:00
Shannon Deminick bc2115ff0a Merge pull request #1847 from umbraco/temp-u4-9683
Upgrade to ModelsBuilder 3.0.6
2017-04-03 16:03:13 +10:00
Shannon Deminick 9eee5c2bc0 Merge pull request #1840 from umbraco/temp-U4-9689
U4-9689 Content type description isn't always saved the same way
2017-04-03 15:53:30 +10:00
Mads Rasmussen ed3096642d Merge pull request #1844 from ryanology/temp-U4-9697
U4-9697 - Listview create new node ordering
2017-03-30 10:31:51 +02:00
Stephan 95508c4fb7 Upgrade nuspec accordingly 2017-03-30 09:32:22 +02:00
Stephan 4677faff38 Upgrade to ModelsBuilder 3.0.6 2017-03-30 08:54:01 +02:00
Shannon 725b3c5eca Merge branch 'dev-v7' into temp-U4-9691 2017-03-30 16:37:24 +11:00
Shannon bf6d084f82 Merge branch 'dev-v7' into temp-U4-9691 2017-03-30 15:50:16 +11:00
rrueckl 08ca75bb35 U4-9697 - Listview create new node ordering 2017-03-29 13:59:23 -04:00
Shannon 0512eaa7fd Fixes the Examine mgmt dashboard to look better and to detect if an index is unreadable in which case it allows you to rebuild it. 2017-03-29 21:43:00 +11:00
Claus e8df5f9eb4 U4-9689 Content type description isn't always saved the same way
also fixed for member types (and added test for media types also using the ContentTypeService)
2017-03-29 11:08:57 +02:00
Stephan c46678c766 uaasscrum-827 - more robust umbraco.config 2017-03-29 10:03:02 +02:00
15 changed files with 443 additions and 282 deletions
+2 -2
View File
@@ -17,8 +17,8 @@
<dependencies>
<dependency id="UmbracoCms.Core" version="[$version$]" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.5, 4.0.0)" />
<dependency id="ImageProcessor.Web.Config" version="[2.3.0, 3.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.6, 4.0.0)" />
<dependency id="ImageProcessor.Web.Config" version="[2.3.0, 3.0.0)" />
</dependencies>
</metadata>
<files>
+60 -58
View File
@@ -1,22 +1,15 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Threading;
using AutoMapper;
using Umbraco.Core.Auditing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
@@ -26,14 +19,15 @@ namespace Umbraco.Core.Services
/// </summary>
public class ContentTypeService : ContentTypeServiceBase, IContentTypeService
{
private readonly IContentService _contentService;
private readonly IContentService _contentService;
private readonly IMediaService _mediaService;
//Support recursive locks because some of the methods that require locking call other methods that require locking.
//for example, the Move method needs to be locked but this calls the Save method which also needs to be locked.
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public ContentTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IMediaService mediaService)
public ContentTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService,
IMediaService mediaService)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
{
if (contentService == null) throw new ArgumentNullException("contentService");
@@ -154,8 +148,8 @@ namespace Umbraco.Core.Services
}
if (savingEvent.IsRaisedEventCancelled(
new SaveEventArgs<EntityContainer>(container, evtMsgs),
this))
new SaveEventArgs<EntityContainer>(container, evtMsgs),
this))
{
return OperationStatus.Cancelled(evtMsgs);
}
@@ -214,7 +208,7 @@ namespace Umbraco.Core.Services
public IEnumerable<EntityContainer> GetMediaTypeContainers(IMediaType mediaType)
{
var ancestorIds = mediaType.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
var ancestorIds = mediaType.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
.Select(x =>
{
var asInt = x.TryConvertTo<int>();
@@ -290,8 +284,8 @@ namespace Umbraco.Core.Services
if (container == null) return OperationStatus.NoOperation(evtMsgs);
if (DeletingContentTypeContainer.IsRaisedEventCancelled(
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
{
return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs));
}
@@ -316,8 +310,8 @@ namespace Umbraco.Core.Services
if (container == null) return OperationStatus.NoOperation(evtMsgs);
if (DeletingMediaTypeContainer.IsRaisedEventCancelled(
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
{
return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs));
}
@@ -421,7 +415,7 @@ namespace Umbraco.Core.Services
clone.Name = name;
var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList();
var compositionAliases = clone.CompositionAliases().Except(new[] {alias}).ToList();
//remove all composition that is not it's current alias
foreach (var a in compositionAliases)
{
@@ -680,7 +674,7 @@ namespace Umbraco.Core.Services
var comparer = new DelegateEqualityComparer<IContentTypeComposition>((x, y) => x.Id == y.Id, x => x.Id);
var dependencies = new HashSet<IContentTypeComposition>(compositions, comparer);
var stack = new Stack<IContentTypeComposition>();
indirectReferences.ForEach(stack.Push);//Push indirect references to a stack, so we can add recursively
indirectReferences.ForEach(stack.Push); //Push indirect references to a stack, so we can add recursively
while (stack.Count > 0)
{
var indirectReference = stack.Pop();
@@ -734,6 +728,8 @@ namespace Umbraco.Core.Services
{
ValidateLocked(contentType); // throws if invalid
contentType.CreatorId = userId;
if (contentType.Description == string.Empty)
contentType.Description = null;
repository.AddOrUpdate(contentType);
uow.Commit();
@@ -742,7 +738,7 @@ namespace Umbraco.Core.Services
UpdateContentXmlStructure(contentType);
}
SavedContentType.RaiseEvent(new SaveEventArgs<IContentType>(contentType, false), this);
Audit(AuditType.Save, string.Format("Save ContentType performed by user"), userId, contentType.Id);
Audit(AuditType.Save, string.Format("Save ContentType performed by user"), userId, contentType.Id);
}
/// <summary>
@@ -755,7 +751,7 @@ namespace Umbraco.Core.Services
var asArray = contentTypes.ToArray();
if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs<IContentType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
@@ -770,6 +766,8 @@ namespace Umbraco.Core.Services
foreach (var contentType in asArray)
{
contentType.CreatorId = userId;
if (contentType.Description == string.Empty)
contentType.Description = null;
repository.AddOrUpdate(contentType);
}
@@ -780,7 +778,7 @@ namespace Umbraco.Core.Services
UpdateContentXmlStructure(asArray.Cast<IContentTypeBase>().ToArray());
}
SavedContentType.RaiseEvent(new SaveEventArgs<IContentType>(asArray, false), this);
Audit(AuditType.Save, string.Format("Save ContentTypes performed by user"), userId, -1);
Audit(AuditType.Save, string.Format("Save ContentTypes performed by user"), userId, -1);
}
/// <summary>
@@ -791,8 +789,8 @@ namespace Umbraco.Core.Services
/// <remarks>Deleting a <see cref="IContentType"/> will delete all the <see cref="IContent"/> objects based on this <see cref="IContentType"/></remarks>
public void Delete(IContentType contentType, int userId = 0)
{
if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs<IContentType>(contentType), this))
return;
if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs<IContentType>(contentType), this))
return;
using (new WriteLock(Locker))
{
@@ -836,7 +834,7 @@ namespace Umbraco.Core.Services
var asArray = contentTypes.ToArray();
if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs<IContentType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
@@ -1004,8 +1002,8 @@ namespace Umbraco.Core.Services
var evtMsgs = EventMessagesFactory.Get();
if (MovingMediaType.IsRaisedEventCancelled(
new MoveEventArgs<IMediaType>(evtMsgs, new MoveEventInfo<IMediaType>(toMove, toMove.Path, containerId)),
this))
new MoveEventArgs<IMediaType>(evtMsgs, new MoveEventInfo<IMediaType>(toMove, toMove.Path, containerId)),
this))
{
return Attempt.Fail(
new OperationStatus<MoveOperationStatusType>(
@@ -1047,8 +1045,8 @@ namespace Umbraco.Core.Services
var evtMsgs = EventMessagesFactory.Get();
if (MovingContentType.IsRaisedEventCancelled(
new MoveEventArgs<IContentType>(evtMsgs, new MoveEventInfo<IContentType>(toMove, toMove.Path, containerId)),
this))
new MoveEventArgs<IContentType>(evtMsgs, new MoveEventInfo<IContentType>(toMove, toMove.Path, containerId)),
this))
{
return Attempt.Fail(
new OperationStatus<MoveOperationStatusType>(
@@ -1178,8 +1176,8 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional Id of the user saving the MediaType</param>
public void Save(IMediaType mediaType, int userId = 0)
{
if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs<IMediaType>(mediaType), this))
return;
if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs<IMediaType>(mediaType), this))
return;
using (new WriteLock(Locker))
{
@@ -1188,6 +1186,8 @@ namespace Umbraco.Core.Services
{
ValidateLocked(mediaType); // throws if invalid
mediaType.CreatorId = userId;
if (mediaType.Description == string.Empty)
mediaType.Description = null;
repository.AddOrUpdate(mediaType);
uow.Commit();
@@ -1197,7 +1197,7 @@ namespace Umbraco.Core.Services
}
SavedMediaType.RaiseEvent(new SaveEventArgs<IMediaType>(mediaType, false), this);
Audit(AuditType.Save, string.Format("Save MediaType performed by user"), userId, mediaType.Id);
Audit(AuditType.Save, string.Format("Save MediaType performed by user"), userId, mediaType.Id);
}
/// <summary>
@@ -1210,7 +1210,7 @@ namespace Umbraco.Core.Services
var asArray = mediaTypes.ToArray();
if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs<IMediaType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
@@ -1225,6 +1225,8 @@ namespace Umbraco.Core.Services
foreach (var mediaType in asArray)
{
mediaType.CreatorId = userId;
if (mediaType.Description == string.Empty)
mediaType.Description = null;
repository.AddOrUpdate(mediaType);
}
@@ -1236,7 +1238,7 @@ namespace Umbraco.Core.Services
}
SavedMediaType.RaiseEvent(new SaveEventArgs<IMediaType>(asArray, false), this);
Audit(AuditType.Save, string.Format("Save MediaTypes performed by user"), userId, -1);
Audit(AuditType.Save, string.Format("Save MediaTypes performed by user"), userId, -1);
}
/// <summary>
@@ -1247,8 +1249,8 @@ namespace Umbraco.Core.Services
/// <remarks>Deleting a <see cref="IMediaType"/> will delete all the <see cref="IMedia"/> objects based on this <see cref="IMediaType"/></remarks>
public void Delete(IMediaType mediaType, int userId = 0)
{
if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs<IMediaType>(mediaType), this))
return;
if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs<IMediaType>(mediaType), this))
return;
using (new WriteLock(Locker))
{
_mediaService.DeleteMediaOfType(mediaType.Id, userId);
@@ -1280,7 +1282,7 @@ namespace Umbraco.Core.Services
var asArray = mediaTypes.ToArray();
if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs<IMediaType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
foreach (var mediaType in asArray)
@@ -1390,40 +1392,40 @@ namespace Umbraco.Core.Services
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IContentType>> DeletingContentType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IContentType>> DeletedContentType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IContentType>> DeletedContentType;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletingMediaType;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletingMediaType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletedMediaType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletedMediaType;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavingContentType;
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavingContentType;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavedContentType;
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavedContentType;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavingMediaType;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavingMediaType;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavedMediaType;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavedMediaType;
/// <summary>
/// Occurs before Move
@@ -2,12 +2,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core.Auditing;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
@@ -89,6 +87,8 @@ namespace Umbraco.Core.Services
using (var repository = RepositoryFactory.CreateMemberTypeRepository(uow))
{
memberType.CreatorId = userId;
if (memberType.Description == string.Empty)
memberType.Description = null;
repository.AddOrUpdate(memberType);
uow.Commit();
@@ -114,6 +114,8 @@ namespace Umbraco.Core.Services
foreach (var memberType in asArray)
{
memberType.CreatorId = userId;
if (memberType.Description == string.Empty)
memberType.Description = null;
repository.AddOrUpdate(memberType);
}
@@ -1,4 +1,3 @@
using System.Runtime.Remoting;
using NUnit.Framework;
using System;
using System.Collections.Generic;
@@ -8,7 +7,6 @@ using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Services;
using Umbraco.Tests.CodeFirst.TestModels.Composition;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -1420,6 +1418,38 @@ namespace Umbraco.Tests.Services
Assert.That(descriptionPropertyTypeReloaded.PropertyGroupId.IsValueCreated, Is.False);
}
[Test]
public void Empty_Description_Is_Always_Null_After_Saving_Content_Type()
{
var service = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Description = null;
service.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("basePage2", "Base Page 2");
contentType2.Description = string.Empty;
service.Save(contentType2);
Assert.IsNull(contentType.Description);
Assert.IsNull(contentType2.Description);
}
[Test]
public void Empty_Description_Is_Always_Null_After_Saving_Media_Type()
{
var service = ServiceContext.ContentTypeService;
var mediaType = MockedContentTypes.CreateSimpleMediaType("mediaType", "Media Type");
mediaType.Description = null;
service.Save(mediaType);
var mediaType2 = MockedContentTypes.CreateSimpleMediaType("mediaType2", "Media Type 2");
mediaType2.Description = string.Empty;
service.Save(mediaType2);
Assert.IsNull(mediaType.Description);
Assert.IsNull(mediaType2.Description);
}
private ContentType CreateComponent()
{
var component = new ContentType(-1)
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using umbraco.cms.presentation.create.controls;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
@@ -183,6 +181,22 @@ namespace Umbraco.Tests.Services
Assert.Throws<ArgumentException>(() => ServiceContext.MemberTypeService.Save(memberType));
}
[Test]
public void Empty_Description_Is_Always_Null_After_Saving_Member_Type()
{
var service = ServiceContext.MemberTypeService;
var memberType = MockedContentTypes.CreateSimpleMemberType();
memberType.Description = null;
service.Save(memberType);
var memberType2 = MockedContentTypes.CreateSimpleMemberType("memberType2", "Member Type 2");
memberType2.Description = string.Empty;
service.Save(memberType2);
Assert.IsNull(memberType.Description);
Assert.IsNull(memberType2.Description);
}
//[Test]
//public void Can_Save_MemberType_Structure_And_Create_A_Member_Based_On_It()
//{
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Models;
@@ -3,232 +3,285 @@
<h3>Examine Management</h3>
<div ng-show="loading">
Loading...
<div class="umb-healthcheck-group__details-status-overlay"></div>
<umb-load-indicator></umb-load-indicator>
</div>
<h4>Indexers</h4>
<div ng-hide="loading" class="umb-healthcheck-group__details">
<ul ng-hide="loading">
<li class="provider" ng-repeat="indexer in indexerDetails">
<div class="umb-healthcheck-group__details-group-title">
<div class="umb-healthcheck-group__details-group-name">Indexers</div>
</div>
<a class="btn-link -underline" href="" ng-click="toggle(indexer, 'showProperties')">
{{indexer.name}}
</a>
<div class="umb-healthcheck-group__details-checks">
<div class="umb-healthcheck-group__details-check">
<div class="umb-healthcheck-group__details-check-title">
<div class="umb-healthcheck-group__details-check-name">Manage Examine's indexes</div>
<div class="umb-healthcheck-group__details-check-description">Allows you to view the details of each index and provides some tools for managing the indexes</div>
</div>
<ul ng-show="indexer.showProperties">
<div class="umb-healthcheck-group__details-status" ng-repeat="indexer in indexerDetails">
<li>
<div class="umb-healthcheck-group__details-status-icon-container">
<i class="umb-healthcheck-status-icon" ng-class="{'icon-check color-green' : indexer.isHealthy, 'icon-delete color-red' : !indexer.isHealthy}"></i>
</div>
<a href="" ng-click="toggle(indexer, 'showTools')">Index info & tools</a>
<div ng-show="indexer.showTools && indexer.isLuceneIndex">
<div>
<br />
<div ng-show="!indexer.isProcessing && (!indexer.processingAttempts || indexer.processingAttempts < 100)" class="btn-group">
<button type="button" class="btn btn-warning" ng-click="rebuildIndex(indexer)">Rebuild index</button>
<button type="button" class="btn btn-warning" ng-click="optimizeIndex(indexer)" ng-show="indexer.documentCount > 0">Optimize index</button>
<div class="umb-healthcheck-group__details-status-content">
<div class="umb-healthcheck-group__details-status-text">
<div ng-show="!indexer.isHealthy">
{{indexer.name}}
</div>
<div ng-show="indexer.isProcessing" class="umb-loader-wrapper" ng-show="actionInProgress">
<div class="umb-loader"></div>
</div>
<div class="error" ng-show="indexer.processingAttempts >= 100">
The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation
<a class="btn-link -underline" href="" ng-click="toggle(indexer, 'showProperties')" ng-show="indexer.isHealthy">
{{indexer.name}}
</a>
<div ng-if="!indexer.isHealthy" class="text-error">
The index cannot be read and will need to be rebuilt
</div>
</div>
<table class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<tr>
<th>Documents in index</th>
<td>{{indexer.documentCount}}</td>
</tr>
<tr>
<th>Fields in index</th>
<td>{{indexer.fieldCount}}</td>
</tr>
<tr>
<th>Has deletions?</th>
<td>
<span>{{indexer.deletionCount > 0}}</span>
(<span>{{indexer.deletionCount}}</span>)
</td>
</tr>
<tr>
<th>Optimized?</th>
<td>
<span>{{indexer.isOptimized}}</span>
</td>
</tr>
</table>
</div>
</li>
<li ng-show="indexer.indexCriteria.IncludeNodeTypes.length > 0 || indexer.indexCriteria.ExcludeNodeTypes.length > 0 || indexer.indexCriteria.ParentNodeId">
<a href="" ng-click="toggle(indexer, 'showNodeTypes')">Node types</a>
<table ng-show="indexer.showNodeTypes" class="table table-bordered table-condensed">
<tr ng-show="indexer.indexCriteria.IncludeNodeTypes.length > 0">
<th>Include node types</th>
<td>{{indexer.indexCriteria.IncludeNodeTypes | json}}</td>
</tr>
<tr ng-show="indexer.indexCriteria.ExcludeNodeTypes.length > 0">
<th>Exclude node types</th>
<td>{{indexer.indexCriteria.ExcludeNodeTypes | json}}</td>
</tr>
<tr ng-show="indexer.indexCriteria.ParentNodeId">
<th>Parent node id</th>
<td>{{indexer.indexCriteria.ParentNodeId}}</td>
</tr>
</table>
</li>
<li ng-show="indexer.indexCriteria.StandardFields.length > 0">
<a href="" ng-click="toggle(indexer, 'showSystemFields')">System fields</a>
<table ng-show="indexer.showSystemFields" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<thead>
<tr>
<th>Name</th>
<th>Enable sorting</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="field in indexer.indexCriteria.StandardFields">
<th>{{field.Name}}</th>
<td>{{field.EnableSorting}}</td>
<td>{{field.Type}}</td>
</tr>
</tbody>
</table>
</li>
<li ng-show="indexer.indexCriteria.UserFields.length > 0">
<a href="" ng-click="toggle(indexer, 'showUserFields')">User fields</a>
<table ng-show="indexer.showUserFields" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<thead>
<tr>
<th>Name</th>
<th>Enable sorting</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="field in indexer.indexCriteria.UserFields">
<th>{{field.Name}}</th>
<td>{{field.EnableSorting}}</td>
<td>{{field.Type}}</td>
</tr>
</tbody>
</table>
</li>
<li>
<a href="" ng-click="toggle(indexer, 'showProviderProperties')">Provider properties</a>
<table ng-show="indexer.showProviderProperties" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<tr ng-repeat="(key, val) in indexer.providerProperties track by $index">
<th>{{key}}</th>
<td>{{val}}</td>
</tr>
</table>
</li>
</ul>
</li>
</ul>
<h4>Searchers</h4>
<ul ng-hide="loading">
<li class="provider" ng-repeat="searcher in searcherDetails">
<a class="btn-link -underline" href="" ng-click="toggle(searcher, 'showProperties')">
{{searcher.name}}
</a>
<ul ng-show="searcher.showProperties">
<li class="search-tools">
<a href="" ng-click="toggle(searcher, 'showTools')">Search tools</a>
<div ng-show="searcher.showTools">
<a class="hide" href="" ng-click="closeSearch(searcher)" ng-show="searcher.isSearching">Hide search results</a>
<br />
<form>
<div class="row form-search">
<div class="span8 input-append">
<input type="text" class="search-query" ng-model="searcher.searchText" no-dirty-check />
<button type="button" class="btn btn-info" ng-click="search(searcher)" ng-disabled="searcher.isProcessing">Search</button>
</div>
</div>
<div class="row">
<label for="{{searcher.name}}-textSearch" class="radio inline">
<input type="radio" name="searchType" id="{{searcher.name}}-textSearch" value="text" ng-model="searcher.searchType" no-dirty-check />
Text Search
</label>
<label for="{{searcher.name}}-luceneSearch" class="radio inline">
<input type="radio" name="searchType" id="{{searcher.name}}-luceneSearch" value="lucene" ng-model="searcher.searchType" no-dirty-check />
Lucene Search
</label>
<div class="umb-healthcheck-group__details-status-actions" ng-if="!indexer.isHealthy">
<div class="umb-healthcheck-group__details-status-action">
<button type="button" class="umb-era-button -blue"
ng-show="!indexer.isProcessing && (!indexer.processingAttempts || indexer.processingAttempts < 100)"
ng-click="rebuildIndex(indexer)">
Rebuild index
</button>
</div>
</div>
<div class="umb-healthcheck-group__details-status-actions" ng-show="indexer.isHealthy && indexer.showProperties">
<ul>
<li>
<a href="" ng-click="toggle(indexer, 'showTools')">Index info & tools</a>
</form>
<div ng-show="indexer.showTools && indexer.isLuceneIndex">
<div>
<br />
<div class="search-results" ng-show="searcher.isSearching">
<div ng-show="!indexer.isProcessing && (!indexer.processingAttempts || indexer.processingAttempts < 100)"
class="umb-healthcheck-group__details-status-action">
<button type="button" class="umb-era-button -blue" ng-click="rebuildIndex(indexer)">Rebuild index</button>
</div>
<div ng-show="indexer.isProcessing" class="umb-loader-wrapper" ng-show="indexer.isProcessing">
<div class="umb-loader"></div>
</div>
<div ng-show="indexer.processingAttempts >= 100">
The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation
</div>
</div>
<table class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<tr>
<th>Documents in index</th>
<td>{{indexer.documentCount}}</td>
</tr>
<tr>
<th>Fields in index</th>
<td>{{indexer.fieldCount}}</td>
</tr>
<tr>
<th>Has deletions?</th>
<td>
<span>{{indexer.deletionCount > 0}}</span>
(<span>{{indexer.deletionCount}}</span>)
</td>
</tr>
<tr>
<th>Optimized?</th>
<td>
<span>{{indexer.isOptimized}}</span>
</td>
</tr>
<table ng-hide="searcher.isProcessing" class="table table-bordered table-condensed">
<thead>
<tr>
<th class="score">Score</th>
<th class="id">Id</th>
<th>Values</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="result in searcher.searchResults">
<td>{{result.Score}}</td>
<td>{{result.Id}}</td>
<td>
<span ng-repeat="(key,val) in result.Fields track by $index">
<span class=""><em>{{key}}</em>:</span>
<span class="text-info">{{val}}</span>
</span>
</td>
</tr>
</tbody>
</table>
</table>
</div>
</li>
<li ng-show="indexer.indexCriteria.IncludeNodeTypes.length > 0 || indexer.indexCriteria.ExcludeNodeTypes.length > 0 || indexer.indexCriteria.ParentNodeId">
<a href="" ng-click="toggle(indexer, 'showNodeTypes')">Node types</a>
<table ng-show="indexer.showNodeTypes" class="table table-bordered table-condensed">
<tr ng-show="indexer.indexCriteria.IncludeNodeTypes.length > 0">
<th>Include node types</th>
<td>{{indexer.indexCriteria.IncludeNodeTypes | json}}</td>
</tr>
<tr ng-show="indexer.indexCriteria.ExcludeNodeTypes.length > 0">
<th>Exclude node types</th>
<td>{{indexer.indexCriteria.ExcludeNodeTypes | json}}</td>
</tr>
<tr ng-show="indexer.indexCriteria.ParentNodeId">
<th>Parent node id</th>
<td>{{indexer.indexCriteria.ParentNodeId}}</td>
</tr>
</table>
</li>
<li ng-show="indexer.indexCriteria.StandardFields.length > 0">
<a href="" ng-click="toggle(indexer, 'showSystemFields')">System fields</a>
<table ng-show="indexer.showSystemFields" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<thead>
<tr>
<th>Name</th>
<th>Enable sorting</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="field in indexer.indexCriteria.StandardFields">
<th>{{field.Name}}</th>
<td>{{field.EnableSorting}}</td>
<td>{{field.Type}}</td>
</tr>
</tbody>
</table>
</li>
<li ng-show="indexer.indexCriteria.UserFields.length > 0">
<a href="" ng-click="toggle(indexer, 'showUserFields')">User fields</a>
<table ng-show="indexer.showUserFields" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<thead>
<tr>
<th>Name</th>
<th>Enable sorting</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="field in indexer.indexCriteria.UserFields">
<th>{{field.Name}}</th>
<td>{{field.EnableSorting}}</td>
<td>{{field.Type}}</td>
</tr>
</tbody>
</table>
</li>
<li>
<a href="" ng-click="toggle(indexer, 'showProviderProperties')">Provider properties</a>
<table ng-show="indexer.showProviderProperties" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<tr ng-repeat="(key, val) in indexer.providerProperties track by $index">
<th>{{key}}</th>
<td>{{val}}</td>
</tr>
</table>
</li>
</ul>
</div>
</div>
</li>
<li>
<a href="" ng-click="toggle(searcher, 'showProviderProperties')">Provider properties</a>
<table ng-show="searcher.showProviderProperties" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<tr ng-repeat="(key, val) in searcher.providerProperties track by $index">
<th>{{key}}</th>
<td>{{val}}</td>
</tr>
</table>
</li>
<div ng-show="indexer.isProcessing">
<div class="umb-healthcheck-group__details-status-overlay"></div>
<umb-load-indicator></umb-load-indicator>
</div>
</div>
</div>
</div>
<br />
<div class="umb-healthcheck-group__details-group-title">
<div class="umb-healthcheck-group__details-group-name">Searchers</div>
</div>
<div class="umb-healthcheck-group__details-checks">
<div class="umb-healthcheck-group__details-check">
<div class="umb-healthcheck-group__details-check-title">
<div class="umb-healthcheck-group__details-check-name">Search indexes</div>
<div class="umb-healthcheck-group__details-check-description">Allows you to search the indexes and view the searcher properties</div>
</div>
<div class="umb-healthcheck-group__details-status" ng-repeat="searcher in searcherDetails">
<div class="umb-healthcheck-group__details-status-icon-container">
<i class="umb-healthcheck-status-icon icon-info"></i>
</div>
<div class="umb-healthcheck-group__details-status-content">
<div class="umb-healthcheck-group__details-status-text">
<a class="btn-link -underline" href="" ng-click="toggle(searcher, 'showProperties')">
{{searcher.name}}
</a>
</div>
<div class="umb-healthcheck-group__details-status-actions" ng-show="searcher.showProperties">
<ul>
<li class="search-tools">
<a href="" ng-click="toggle(searcher, 'showTools')">Search tools</a>
<div ng-show="searcher.showTools">
<a class="hide" href="" ng-click="closeSearch(searcher)" ng-show="searcher.isSearching">Hide search results</a>
<br />
<form>
<div class="row form-search">
<div class="span8 input-append">
<input type="text" class="search-query" ng-model="searcher.searchText" no-dirty-check />
<button type="button" class="btn btn-info" ng-click="search(searcher)" ng-disabled="searcher.isProcessing">Search</button>
</div>
</div>
<div class="row">
<label for="{{searcher.name}}-textSearch" class="radio inline">
<input type="radio" name="searchType" id="{{searcher.name}}-textSearch" value="text" ng-model="searcher.searchType" no-dirty-check />
Text Search
</label>
<label for="{{searcher.name}}-luceneSearch" class="radio inline">
<input type="radio" name="searchType" id="{{searcher.name}}-luceneSearch" value="lucene" ng-model="searcher.searchType" no-dirty-check />
Lucene Search
</label>
</div>
</form>
<div class="search-results" ng-show="searcher.isSearching">
<div ng-show="indexer.isProcessing" class="umb-loader-wrapper" ng-show="indexer.isProcessing">
<div class="umb-loader"></div>
</div>
<table ng-hide="searcher.isProcessing" class="table table-bordered table-condensed">
<thead>
<tr>
<th class="score">Score</th>
<th class="id">Id</th>
<th>Values</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="result in searcher.searchResults">
<td>{{result.Score}}</td>
<td>{{result.Id}}</td>
<td>
<span ng-repeat="(key,val) in result.Fields track by $index">
<span class=""><em>{{key}}</em>:</span>
<span class="text-info">{{val}}</span>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</li>
<li>
<a href="" ng-click="toggle(searcher, 'showProviderProperties')">Provider properties</a>
<table ng-show="searcher.showProviderProperties" class="table table-bordered table-condensed">
<caption>&nbsp;</caption>
<tr ng-repeat="(key, val) in searcher.providerProperties track by $index">
<th>{{key}}</th>
<td>{{val}}</td>
</tr>
</table>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</ul>
</li>
</ul>
</div>
@@ -17,7 +17,7 @@
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li ng-repeat="contentType in listViewAllowedTypes">
<li ng-repeat="contentType in listViewAllowedTypes | orderBy:'name':false">
<a href="#/{{entityType}}/{{entityType}}/edit/{{contentId}}?doctype={{contentType.alias}}&create=true">
<i class="icon-{{contentType.cssClass}}"></i>
{{contentType.name}}
+2 -2
View File
@@ -350,8 +350,8 @@
<Name>umbraco.providers</Name>
</ProjectReference>
<Reference Include="System.Xml.Linq" />
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.5.96, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.5\lib\Umbraco.ModelsBuilder.dll</HintPath>
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.6.97, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.6\lib\Umbraco.ModelsBuilder.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="UrlRewritingNet.UrlRewriter, Version=2.0.7.0, Culture=neutral, processorArchitecture=MSIL">
+1 -1
View File
@@ -37,6 +37,6 @@
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
<package id="System.Collections.Immutable" version="1.1.36" targetFramework="net45" />
<package id="System.Reflection.Metadata" version="1.0.21" targetFramework="net45" />
<package id="Umbraco.ModelsBuilder" version="3.0.5" targetFramework="net45" />
<package id="Umbraco.ModelsBuilder" version="3.0.6" targetFramework="net45" />
<package id="UrlRewritingNet.UrlRewriter" version="2.0.7" targetFramework="net45" />
</packages>
@@ -8,6 +8,7 @@ namespace Umbraco.Web.Search
[DataContract(Name = "indexer", Namespace = "")]
public class ExamineIndexerModel : ExamineSearcherModel
{
[DataMember(Name = "indexCriteria")]
public IIndexCriteria IndexCriteria { get; set; }
@@ -14,6 +14,18 @@ namespace Umbraco.Web.Search
ProviderProperties = new Dictionary<string, string>();
}
/// <summary>
/// If the index is not healthy this represents the index error state
/// </summary>
[DataMember(Name = "error")]
public string Error { get; set; }
/// <summary>
/// If the index can be open/read
/// </summary>
[DataMember(Name = "isHealthy")]
public bool IsHealthy { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
@@ -15,6 +15,28 @@ namespace Umbraco.Web.Search
/// </summary>
internal static class ExamineExtensions
{
/// <summary>
/// Checks if the index can be read/opened
/// </summary>
/// <param name="indexer"></param>
/// <param name="ex">The exception returned if there was an error</param>
/// <returns></returns>
public static bool IsHealthy(this LuceneIndexer indexer, out Exception ex)
{
try
{
using (indexer.GetIndexWriter().GetReader())
{
ex = null;
return true;
}
}
catch (Exception e)
{
ex = e;
return false;
}
}
/// <summary>
/// Return the number of indexed documents in Lucene
@@ -86,7 +86,7 @@ namespace Umbraco.Web.WebServices
var model = new List<ExamineSearcherModel>(
ExamineManager.Instance.SearchProviderCollection.Cast<BaseSearchProvider>().Select(searcher =>
{
var indexerModel = new ExamineIndexerModel()
var indexerModel = new ExamineSearcherModel()
{
Name = searcher.Name
};
@@ -260,6 +260,7 @@ namespace Umbraco.Web.WebServices
IndexCriteria = indexer.IndexerData,
Name = indexer.Name
};
var props = TypeHelper.CachedDiscoverableProperties(indexer.GetType(), mustWrite: false)
//ignore these properties
.Where(x => new[] {"IndexerData", "Description", "WorkingFolder"}.InvariantContains(x.Name) == false)
@@ -281,11 +282,21 @@ namespace Umbraco.Web.WebServices
var luceneIndexer = indexer as LuceneIndexer;
if (luceneIndexer != null)
{
{
indexerModel.IsLuceneIndex = true;
if (luceneIndexer.IndexExists())
{
Exception indexError;
indexerModel.IsHealthy = luceneIndexer.IsHealthy(out indexError);
if (indexerModel.IsHealthy == false)
{
//we cannot continue at this point
indexerModel.Error = indexError.ToString();
return indexerModel;
}
indexerModel.DocumentCount = luceneIndexer.GetIndexDocumentCount();
indexerModel.FieldCount = luceneIndexer.GetIndexFieldCount();
indexerModel.IsOptimized = luceneIndexer.IsIndexOptimized();
@@ -431,7 +431,7 @@ namespace umbraco
{
XmlNode x;
//Hack: this is here purely for backwards compat if someone for some reason is using the
//Hack: this is here purely for backwards compat if someone for some reason is using the
// ClearDocumentCache(int documentId) method and expecting it to remove the xml
if (removeDbXmlEntry)
{
@@ -870,8 +870,15 @@ namespace umbraco
catch (Exception e)
{
// if something goes wrong remove the file
DeleteXmlFile();
try
{
DeleteXmlFile();
}
catch
{
// don't make it worse: could be that we failed to write because we cannot
// access the file, in which case we won't be able to delete it either
}
LogHelper.Error<content>("Failed to save Xml to file.", e);
}
}
@@ -933,7 +940,15 @@ namespace umbraco
catch (Exception e)
{
LogHelper.Error<content>("Failed to load Xml from file.", e);
DeleteXmlFile();
try
{
DeleteXmlFile();
}
catch
{
// don't make it worse: could be that we failed to read because we cannot
// access the file, in which case we won't be able to delete it either
}
return null;
}
}