Skip to content

Commit

Permalink
Merge pull request #6625 from umbraco/v8/feature/AB2913-DataTypeTracking
Browse files Browse the repository at this point in the history
Data type usage reporting and deletion warning
  • Loading branch information
bergmania committed Oct 21, 2019
2 parents 9ffb3be + c072015 commit ed6fee4
Show file tree
Hide file tree
Showing 31 changed files with 3,037 additions and 2,370 deletions.
Expand Up @@ -7,5 +7,12 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IDataTypeRepository : IReadWriteQueryRepository<int, IDataType>
{
IEnumerable<MoveEventInfo<IDataType>> Move(IDataType toMove, EntityContainer container);

/// <summary>
/// Returns a dictionary of content type <see cref="Udi"/>s and the property type aliases that use a <see cref="IDataType"/>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id);
}
}
Expand Up @@ -279,6 +279,28 @@ public IEnumerable<MoveEventInfo<IDataType>> Move(IDataType toMove, EntityContai
return moveInfo;
}

public IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id)
{
if (id == default)
return new Dictionary<Udi, IEnumerable<string>>();

var sql = Sql()
.Select<ContentTypeDto>(ct => ct.Select(node => node.NodeDto))
.AndSelect<PropertyTypeDto>(pt => Alias(pt.Alias, "ptAlias"), pt => Alias(pt.Name, "ptName"))
.From<PropertyTypeDto>()
.InnerJoin<ContentTypeDto>().On<ContentTypeDto, PropertyTypeDto>(ct => ct.NodeId, pt => pt.ContentTypeId)
.InnerJoin<NodeDto>().On<NodeDto, ContentTypeDto>(n => n.NodeId, ct => ct.NodeId)
.Where<PropertyTypeDto>(pt => pt.DataTypeId == id)
.OrderBy<NodeDto>(node => node.NodeId)
.AndBy<PropertyTypeDto>(pt => pt.Alias);

var dtos = Database.FetchOneToMany<ContentTypeReferenceDto>(ct => ct.PropertyTypes, sql);

return dtos.ToDictionary(
x => (Udi)new GuidUdi(ObjectTypes.GetUdiType(x.NodeDto.NodeObjectType.Value), x.NodeDto.UniqueId).EnsureClosed(),
x => (IEnumerable<string>)x.PropertyTypes.Select(p => p.Alias).ToList());
}

private string EnsureUniqueNodeName(string nodeName, int id = 0)
{
var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql
Expand All @@ -291,5 +313,24 @@ private string EnsureUniqueNodeName(string nodeName, int id = 0)

return SimilarNodeName.GetUniqueName(names, id, nodeName);
}


[TableName(Constants.DatabaseSchema.Tables.ContentType)]
private class ContentTypeReferenceDto : ContentTypeDto
{
[ResultColumn]
[Reference(ReferenceType.Many)]
public List<PropertyTypeReferenceDto> PropertyTypes { get; set; }
}

[TableName(Constants.DatabaseSchema.Tables.PropertyType)]
private class PropertyTypeReferenceDto
{
[Column("ptAlias")]
public string Alias { get; set; }

[Column("ptName")]
public string Name { get; set; }
}
}
}
3 changes: 2 additions & 1 deletion src/Umbraco.Core/Services/IContentTypeServiceBase.cs
Expand Up @@ -25,7 +25,7 @@ public interface IContentTypeBaseService<TItem> : IContentTypeBaseService, IServ
/// <summary>
/// Gets a content type.
/// </summary>
TItem Get(int id);
new TItem Get(int id);

/// <summary>
/// Gets a content type.
Expand All @@ -40,6 +40,7 @@ public interface IContentTypeBaseService<TItem> : IContentTypeBaseService, IServ
int Count();

IEnumerable<TItem> GetAll(params int[] ids);
IEnumerable<TItem> GetAll(IEnumerable<Guid> ids);

IEnumerable<TItem> GetDescendants(int id, bool andSelf); // parent-child axis
IEnumerable<TItem> GetComposedOf(int id); // composition axis
Expand Down
7 changes: 7 additions & 0 deletions src/Umbraco.Core/Services/IDataTypeService.cs
Expand Up @@ -10,6 +10,13 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IDataTypeService : IService
{
/// <summary>
/// Returns a dictionary of content type <see cref="Udi"/>s and the property type aliases that use a <see cref="IDataType"/>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id);

Attempt<OperationResult<OperationResultType, EntityContainer>> CreateContainer(int parentId, string name, int userId = Constants.Security.SuperUserId);
Attempt<OperationResult> SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
EntityContainer GetContainer(int containerId);
Expand Down
Expand Up @@ -252,12 +252,12 @@ public IEnumerable<TItem> GetAll(params int[] ids)
}
}

public IEnumerable<TItem> GetAll(params Guid[] ids)
public IEnumerable<TItem> GetAll(IEnumerable<Guid> ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(ReadLockIds);
return Repository.GetMany(ids);
return Repository.GetMany(ids.ToArray());
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/Umbraco.Core/Services/Implement/DataTypeService.cs
Expand Up @@ -466,6 +466,14 @@ public void Delete(IDataType dataType, int userId = Constants.Security.SuperUser
}
}

public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete:true))
{
return _dataTypeRepository.FindUsages(id);
}
}

private void Audit(AuditType type, int userId, int objectId)
{
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType)));
Expand Down
Expand Up @@ -28,6 +28,68 @@ private EntityContainerRepository CreateContainerRepository(IScopeAccessor scope
return new EntityContainerRepository(scopeAccessor, AppCaches.Disabled, Logger, Constants.ObjectTypes.DataTypeContainer);
}

[Test]
public void Can_Find_Usages()
{
var provider = TestObjects.GetScopeProvider(Logger);

using (provider.CreateScope())
{
var dtRepo = CreateRepository();
IDataType dataType1 = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService)) { Name = "dt1" };
dtRepo.Save(dataType1);
IDataType dataType2 = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService)) { Name = "dt2" };
dtRepo.Save(dataType2);

var ctRepo = Factory.GetInstance<IContentTypeRepository>();
IContentType ct = new ContentType(-1)
{
Alias = "ct1",
Name = "CT1",
AllowedAsRoot = true,
Icon = "icon-home",
PropertyGroups = new PropertyGroupCollection
{
new PropertyGroup(true)
{
Name = "PG1",
PropertyTypes = new PropertyTypeCollection(true)
{
new PropertyType(dataType1, "pt1")
{
Name = "PT1"
},
new PropertyType(dataType1, "pt2")
{
Name = "PT2"
},
new PropertyType(dataType2, "pt3")
{
Name = "PT3"
}
}
}
}
};
ctRepo.Save(ct);

var usages = dtRepo.FindUsages(dataType1.Id);

var key = usages.First().Key;
Assert.AreEqual(ct.Key, ((GuidUdi)key).Guid);
Assert.AreEqual(2, usages[key].Count());
Assert.AreEqual("pt1", usages[key].ElementAt(0));
Assert.AreEqual("pt2", usages[key].ElementAt(1));

usages = dtRepo.FindUsages(dataType2.Id);

key = usages.First().Key;
Assert.AreEqual(ct.Key, ((GuidUdi)key).Guid);
Assert.AreEqual(1, usages[key].Count());
Assert.AreEqual("pt3", usages[key].ElementAt(0));
}
}

[Test]
public void Can_Move()
{
Expand Down
5 changes: 5 additions & 0 deletions src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs
Expand Up @@ -329,6 +329,11 @@ public Attempt<OperationResult<MoveOperationStatusType>> Move(IDataType toMove,
{
throw new NotImplementedException();
}

public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id)
{
throw new NotImplementedException();
}
}

#endregion
Expand Down
4 changes: 4 additions & 0 deletions src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less
Expand Up @@ -241,3 +241,7 @@ table th[class*="span"],
background-color: darken(@infoBackground, 5%);
}
}

.table .icon {
vertical-align: bottom;
}
Expand Up @@ -55,7 +55,8 @@ function confirmDirective() {
onConfirm: '=',
onCancel: '=',
caption: '@',
confirmButtonStyle: '@'
confirmButtonStyle: '@',
confirmLabelKey: '@'
},
link: function (scope, element, attr, ctrl) {
scope.showCancel = false;
Expand Down
@@ -0,0 +1,20 @@
/**
* @ngdoc filter
* @name umbraco.filters.filter:CMS_joinArray
* @namespace umbCmsJoinArray
*
* param {array} array of string or objects, if an object use the third argument to specify which prop to list.
* param {seperator} string containing the seperator to add between joined values.
* param {prop} string used if joining an array of objects, set the name of properties to join.
*
* @description
* Join an array of string or an array of objects, with a costum seperator.
*
*/
angular.module("umbraco.filters").filter('umbCmsJoinArray', function () {
return function join(array, separator, prop) {
return (!angular.isUndefined(prop) ? array.map(function (item) {
return item[prop];
}) : array).join(separator || '');
};
});
Expand Up @@ -43,6 +43,30 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) {
"Failed to retrieve pre values for editor alias " + editorAlias);
},

/**
* @ngdoc method
* @name umbraco.resources.dataTypeResource#getReferences
* @methodOf umbraco.resources.dataTypeResource
*
* @description
* Retrieves references of a given data type.
*
* @param {Int} id id of datatype to retrieve references for
* @returns {Promise} resourcePromise object.
*
*/
getReferences: function (id) {

return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"dataTypeApiBaseUrl",
"GetReferences",
{ id: id })),
"Failed to retrieve usages for data type of id " + id);

},

/**
* @ngdoc method
* @name umbraco.resources.dataTypeResource#getById
Expand Down
4 changes: 2 additions & 2 deletions src/Umbraco.Web.UI.Client/src/init.js
Expand Up @@ -138,14 +138,14 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService',

var toRetain = navigationService.retainQueryStrings(currentRouteParams, next.params);

//if toRetain is not null it means that there are missing query strings and we need to update the current params
//if toRetain is not null it means that there are missing query strings and we need to update the current params.
if (toRetain) {
$route.updateParams(toRetain);
}

//check if the location being changed is only due to global/state query strings which means the location change
//isn't actually going to cause a route change.
if (!toRetain && navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) {
if (navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) {

//The location change will cause a route change, continue the route if the query strings haven't been updated.
$route.reload();
Expand Down
15 changes: 12 additions & 3 deletions src/Umbraco.Web.UI.Client/src/less/components/umb-table.less
Expand Up @@ -160,6 +160,7 @@ input.umb-table__input {
font-size: 20px;
line-height: 20px;
color: @ui-option-type;
vertical-align: bottom;
}

.umb-table-body__checkicon,
Expand Down Expand Up @@ -245,7 +246,7 @@ input.umb-table__input {
.umb-table-cell {
display: flex;
flex-flow: row nowrap;
flex: 1 1 1%; //NOTE 1% is a Internet Explore hack, so that cells don't collapse
flex: 1 1 5%;
position: relative;
margin: auto 14px;
padding: 6px 2px;
Expand All @@ -258,6 +259,11 @@ input.umb-table__input {
white-space: nowrap; //NOTE Disable/Enable this to keep textstring on one line
text-overflow: ellipsis;
}
.umb-table-cell.--noOverflow > * {
overflow: visible;
white-space: normal;
text-overflow: unset;
}

.umb-table-cell:first-of-type:not(.not-fixed) {
flex: 0 0 25px;
Expand All @@ -269,6 +275,9 @@ input.umb-table__input {
flex: 0 0 auto !important;
}

.umb-table-cell--nano {
flex: 0 0 50px;
}
.umb-table-cell--small {
flex: .5 .5 1%;
max-width: 12.5%;
Expand All @@ -285,8 +294,8 @@ input.umb-table__input {

// Increases the space for the name cell
.umb-table__name {
flex: 1 1 25%;
max-width: 25%;
flex: 1 1 20%;
max-width: 300px;
}

.umb-table__loading-overlay {
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Web.UI.Client/src/less/tables.less
Expand Up @@ -62,7 +62,7 @@ table {
}


.table tr > td:first-child {
.table:not(.table-bordered) tr > td:first-child {
border-left: 4px solid transparent;
}
.table tr.--selected > td:first-child {
Expand Down
Expand Up @@ -14,7 +14,7 @@
action="confirm()"
button-style="{{confirmButtonStyle || 'primary'}}"
state="confirmButtonState"
label-key="general_ok">
label-key="{{confirmLabelKey || 'general_ok'}}">
</umb-button>
</div>
</div>
Expand Down

0 comments on commit ed6fee4

Please sign in to comment.