Skip to content

Commit

Permalink
Allow prefabs to be deleted
Browse files Browse the repository at this point in the history
1. Prefabs can be deleted if they are not in use by any project.
2. Added checks to prevent usage of prefab from clients with stale data if a prefab has already been deleted by other clients.
  • Loading branch information
Nfactor26 committed Dec 11, 2022
1 parent 73cd72c commit 2fb744b
Show file tree
Hide file tree
Showing 17 changed files with 262 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Pixel.Persistence.Services.Client.Interfaces;
using Serilog;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace Pixel.Automation.AppExplorer.ViewModels.Prefab
Expand Down Expand Up @@ -113,6 +114,10 @@ private List<PrefabProjectViewModel> LoadPrefabs(ApplicationDescriptionViewModel
var prefabs = this.prefabDataManager.GetPrefabsForScreen(applicationDescriptionViewModel.Model, screenName).ToList();
foreach (var prefab in prefabs)
{
if(prefab.IsDeleted)
{
continue;
}
var prefabProjectViewModel = new PrefabProjectViewModel(prefab);
applicationDescriptionViewModel.AddPrefab(prefabProjectViewModel, screenName);
prefabsList.Add(prefabProjectViewModel);
Expand Down Expand Up @@ -231,6 +236,26 @@ public async Task ManagePrefab(PrefabProjectViewModel targetPrefab)
}
}

/// <summary>
/// Delete the prefab
/// </summary>
/// <param name="prefabToDelete"></param>
public async Task DeletePrefabAsync(PrefabProjectViewModel prefabToDelete)
{
try
{
Guard.Argument(prefabToDelete, nameof(prefabToDelete)).NotNull();
await this.prefabDataManager.DeletePrefbAsync(prefabToDelete.PrefabProject);
this.Prefabs.Remove(prefabToDelete);
}
catch (Exception ex)
{
logger.Error(ex, "There was an error while trying to delete prefab : {0}", prefabToDelete.PrefabName);
MessageBox.Show($"Error while deleting prefab : {prefabToDelete.PrefabName}", "Delete Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}


/// <summary>
/// Broadcast a FilterTestMessage which is processed by Test explorer view to filter and show only those test cases
/// which uses this prefab
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Pixel.Persistence.Services.Client.Interfaces;
using Pixel.Scripting.Editor.Core.Contracts;
using Serilog;
using System.Windows;

namespace Pixel.Automation.AppExplorer.ViewModels.Prefab
{
Expand Down Expand Up @@ -61,7 +62,8 @@ public PrefabVersionManagerViewModel(PrefabProject prefabProject, IWorkspaceMana
public async Task PublishAsync(PrefabVersionViewModel prefabVersionViewModel)
{
try
{
{
Guard.Argument(prefabVersionViewModel, nameof(prefabVersionViewModel)).NotNull();
if (!prefabVersionViewModel.IsPublished)
{
bool isLatestActieVersion = prefabProject.LatestActiveVersion.Version.Equals(prefabVersionViewModel.Version);
Expand All @@ -75,7 +77,8 @@ public async Task PublishAsync(PrefabVersionViewModel prefabVersionViewModel)
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
logger.Error(ex, "There was an error while trying to publish verion : {0} of prefab : {1}", prefabVersionViewModel.Version, prefabVersionViewModel.PrefabName);
MessageBox.Show($"Error while publishing version {prefabVersionViewModel.Version} of prefab {prefabVersionViewModel.PrefabName}", "Publish Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

Expand All @@ -87,6 +90,7 @@ public async Task CloneAsync(PrefabVersionViewModel prefabVersionViewModel)
{
try
{
Guard.Argument(prefabVersionViewModel, nameof(prefabVersionViewModel)).NotNull();
if (prefabVersionViewModel.IsPublished)
{
PrefabVersion newVersion = await prefabVersionViewModel.CloneAsync(this.prefabDataManager);
Expand All @@ -98,7 +102,8 @@ public async Task CloneAsync(PrefabVersionViewModel prefabVersionViewModel)
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
logger.Error(ex, "There was an error while trying to create a new verion of prefab : {0} from version : {1}", prefabVersionViewModel.PrefabName, prefabVersionViewModel.Version);
MessageBox.Show($"Error while cloning version {prefabVersionViewModel.Version} of prefab {prefabVersionViewModel.PrefabName}", "Clone Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public class PrefabVersionViewModel : PropertyChangedBase
private readonly IPrefabFileSystem fileSystem;
private readonly Lazy<IReferenceManager> referenceManager;

public string PrefabName
{
get => prefabProject.PrefabName;
}

public Version Version
{
get => prefabVersion.Version;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
using Pixel.Automation.Editor.Core.ViewModels;
using Pixel.Automation.Reference.Manager;
using Pixel.Automation.Reference.Manager.Contracts;
using Pixel.Persistence.Services.Client.Interfaces;
using Serilog;
using System.IO;
using System.Windows;

namespace Pixel.Automation.AppExplorer.ViewModels.PrefabDropHandler
{
Expand Down Expand Up @@ -86,13 +88,13 @@ public string OutputMappingScriptFile
/// <param name="prefabProjectViewModel">Prefab which needs to be added to automation process</param>
/// <param name="dropTarget">EntityComponentViewModel wrapping an Entity to which Prefab needs to be added</param>
public PrefabVersionSelectorViewModel(IProjectFileSystem projectFileSystem, IPrefabFileSystem prefabFileSystem,
IReferenceManager projectReferenceManager, PrefabEntity prefabEntity,
IReferenceManager projectReferenceManager, PrefabEntity prefabEntity,
PrefabProjectViewModel prefabProjectViewModel, EntityComponentViewModel dropTarget)
{
this.DisplayName = "(1/3) Select prefab version and mapping scripts";
this.projectFileSystem = projectFileSystem;
this.prefabFileSystem = prefabFileSystem;
this.projectReferenceManager = projectReferenceManager;
this.projectReferenceManager = projectReferenceManager;
this.prefabEntity = prefabEntity;
this.prefabProject = prefabProjectViewModel.PrefabProject;
this.dropTarget = dropTarget;
Expand Down Expand Up @@ -142,16 +144,24 @@ public void PickOutputMappingScriptFile()
/// <inheritdoc/>
public override async Task<bool> TryProcessStage()
{
//TODO : Can we make this asyc ?
if(this.SelectedVersion != null && !dropTarget.ComponentCollection.Any(a => a.Model.Equals(prefabEntity)))
try
{
await UpdatePrefabReferencesAsync();
await UpdateControlReferencesAsync();
dropTarget.AddComponent(prefabEntity);
this.CanChangeVersion = false;
logger.Information("Added version {0} of {1} to {2}.", this.SelectedVersion, this.prefabProject, this.dropTarget);
}
return true;
if (this.SelectedVersion != null && !dropTarget.ComponentCollection.Any(a => a.Model.Equals(prefabEntity)))
{
await UpdatePrefabReferencesAsync();
await UpdateControlReferencesAsync();
dropTarget.AddComponent(prefabEntity);
this.CanChangeVersion = false;
logger.Information("Added version {0} of {1} to {2}.", this.SelectedVersion, this.prefabProject, this.dropTarget);
}
return true;
}
catch (Exception ex)
{
logger.Error(ex, "There was an error while trying to process stage for the prefab version selector screen");
MessageBox.Show("Error while trying to add prefab", "Add Prefab Error", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action ManagePrefab($dataContext)]"></MenuItem>
<MenuItem x:Name="ShowUsage" Header="Show Usage" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action ShowUsage($dataContext)]"></MenuItem>
<MenuItem x:Name="MoveToScreen" Header="Move To Screen" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action MoveToScreen($dataContext)]"></MenuItem>
<MenuItem x:Name="Delete" Header="Delete" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action DeletePrefabAsync($dataContext)]" ></MenuItem>
</StackPanel>
</ControlTemplate>
</ContextMenu.Template>
Expand Down
6 changes: 6 additions & 0 deletions src/Pixel.Automation.Core/Models/PrefabProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ public class PrefabProject : ICloneable
[DataMember(Order = 70)]
public string GroupName { get; set; } = "Default";

/// <summary>
/// Indicates if Prefab project is marked deleted.
/// </summary>
[DataMember(Order = 1000)]
public bool IsDeleted { get; set; }

/// <summary>
/// Get all the versions that are active.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public interface IControlRepository
Task<bool> IsControlDeleted(string applicationId, string controlId);

/// <summary>
/// Set IsDeleted flag on all versionf of control to true.
/// Set IsDeleted flag on all versions of control to true.
/// </summary>
/// <param name="applicationId"></param>
/// <param name="controlId"></param>
Expand Down
14 changes: 14 additions & 0 deletions src/Pixel.Persistence.Respository/Interfaces/IPrefabsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,19 @@ public interface IPrefabsRepository
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task UpdatePrefabVersionAsync(string prefabId, PrefabVersion version, CancellationToken cancellationToken);

/// <summary>
/// Check if a Prefab is marked deleted
/// </summary>
/// <param name="prefabId"></param>
/// <returns></returns>
Task<bool> IsPrefabDeleted(string prefabId);

/// <summary>
/// Mark prefab as deleted.
/// </summary>
/// <param name="prefabId"></param>
/// <returns></returns>
Task DeletePrefabAsync(string prefabId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ public interface IReferencesRepository
/// <returns></returns>
Task UpdateControlReference(string projectId, string projectVersion, ControlReference controlReference);


/// <summary>
/// Check if any version of prefab is in use by any version of any project
/// </summary>
/// <param name="prefabId"></param>
/// <returns></returns>
Task<bool> IsPrefabInUse(string prefabId);

/// <summary>
/// Check if any of the projets has a reference to any version of this prefab
/// </summary>
/// <param name="projectId"></param>
/// <param name="projectVersion"></param>
/// <param name="controlReference"></param>
/// <returns></returns>
//Task<bool> HasPrefabReference(string projectId, string projectVersion, PrefabReference prefabReference);

/// <summary>
/// Add or update PrefabReferences for a given version of project
/// </summary>
Expand Down
35 changes: 32 additions & 3 deletions src/Pixel.Persistence.Respository/PrefabsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Pixel.Persistence.Respository
public class PrefabsRepository : IPrefabsRepository
{
private readonly ILogger logger;
private readonly IMongoCollection<PrefabProject> prefabsCollection;
private readonly IMongoCollection<PrefabProject> prefabsCollection;
private readonly IPrefabFilesRepository prefabFilesRepossitory;
private readonly IReferencesRepository referencesRepository;
private static readonly InsertOneOptions InsertOneOptions = new InsertOneOptions();
Expand Down Expand Up @@ -105,7 +105,9 @@ public async Task AddPrefabVersionAsync(string prefabId, PrefabVersion newVersio
}

filter = Builders<PrefabProject>.Filter.Eq(x => x.PrefabId, prefabId);
var push = Builders<PrefabProject>.Update.Push(t => t.AvailableVersions, newVersion);
var push = Builders<PrefabProject>.Update.Push(t => t.AvailableVersions, newVersion)
.Set(t => t.LastUpdated, DateTime.UtcNow)
.Inc(t => t.Revision, 1);
await this.prefabsCollection.UpdateOneAsync(filter, push);
}

Expand All @@ -125,12 +127,39 @@ public async Task UpdatePrefabVersionAsync(string prefabId, PrefabVersion prefab
{
prefabVersion.PublishedOn = DateTime.UtcNow;
}
var update = Builders<PrefabProject>.Update.Set(x => x.AvailableVersions[-1], prefabVersion);
var update = Builders<PrefabProject>.Update.Set(x => x.AvailableVersions[-1], prefabVersion)
.Set(t => t.LastUpdated, DateTime.UtcNow)
.Inc(t => t.Revision, 1); ;
await this.prefabsCollection.UpdateOneAsync(filter, update);
logger.LogInformation("Project version {0} was updated for project : {1}", prefabVersion, prefabId);
return;
}
throw new InvalidOperationException($"Version {prefabVersion} doesn't exist on prefab {prefabId}");
}

///<inheritdoc/>
public async Task<bool> IsPrefabDeleted(string prefabId)
{
Guard.Argument(prefabId, nameof(prefabId)).NotNull().NotEmpty();
var filter = Builders<PrefabProject>.Filter.Eq(x => x.PrefabId, prefabId) &
Builders<PrefabProject>.Filter.Eq(x => x.IsDeleted, true);
long count = await this.prefabsCollection.CountDocumentsAsync(filter, new CountOptions() { Limit = 1 });
return count > 0;
}

/// <inheritdoc/>
public async Task DeletePrefabAsync(string prefabId)
{
Guard.Argument(prefabId, nameof(prefabId)).NotNull();
if(await this.referencesRepository.IsPrefabInUse(prefabId))
{
throw new InvalidOperationException("Prefab is in use across one or more projects");
}
var updateFilter = Builders<PrefabProject>.Filter.Eq(x => x.PrefabId, prefabId);
var updateDefinition = Builders<PrefabProject>.Update.Set(x => x.IsDeleted, true)
.Set(x => x.LastUpdated, DateTime.UtcNow)
.Inc(t => t.Revision, 1);
await this.prefabsCollection.FindOneAndUpdateAsync<PrefabProject>(updateFilter, updateDefinition);
}
}
}
10 changes: 10 additions & 0 deletions src/Pixel.Persistence.Respository/ReferencesRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;

namespace Pixel.Persistence.Respository;

Expand Down Expand Up @@ -80,6 +81,15 @@ public async Task UpdateControlReference(string projectId, string projectVersion
logger.LogInformation("Control reference {0} was added to project : {1}", controlReference, projectId);
}

/// <inheritdoc/>
public async Task<bool> IsPrefabInUse(string prefabId)
{
var filter = Builders<ProjectReferences>.Filter.ElemMatch(x => x.PrefabReferences,
Builders<PrefabReference>.Filter.Eq(x => x.PrefabId, prefabId));
long count = await this.referencesCollection.CountDocumentsAsync(filter);
return count > 0;
}

/// <inheritdoc/>
public async Task AddOrUpdatePrefabReference(string projectId, string projectVersion, PrefabReference prefabReference)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ public async Task<ActionResult<ProjectVersion>> AddPrefabVersionAsync([FromRoute
Guard.Argument(request, nameof(request)).NotNull();
Guard.Argument(request, nameof(request)).Require(r => r.NewVersion != null, r => "NewVersion is required");
Guard.Argument(request, nameof(request)).Require(r => r.CloneFrom != null, r => "CloneFrom is required");

if (await prefabsRepository.IsPrefabDeleted(prefabId))
{
return Conflict($"Prefab : {prefabId} is marked deleted. New version can't be added.");
}
await prefabsRepository.AddPrefabVersionAsync(prefabId, request.NewVersion, request.CloneFrom, CancellationToken.None);
return Ok(request.NewVersion);
}
Expand All @@ -125,6 +130,10 @@ public async Task<ActionResult<PrefabVersion>> UpdatePrefabVersionAsync([FromRou
{
Guard.Argument(prefabId, nameof(prefabId)).NotNull().NotEmpty();
Guard.Argument(prefabVersion, nameof(prefabVersion)).NotNull();
if (await prefabsRepository.IsPrefabDeleted(prefabId))
{
return Conflict($"Prefab : {prefabId} is marked deleted. Version : {prefabVersion} can't be added.");
}
await prefabsRepository.UpdatePrefabVersionAsync(prefabId, prefabVersion, CancellationToken.None);
return Ok(prefabVersion);
}
Expand All @@ -134,5 +143,27 @@ public async Task<ActionResult<PrefabVersion>> UpdatePrefabVersionAsync([FromRou
return Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
}


[HttpGet("isdeleted/{prefabId}")]
public async Task<ActionResult<bool>> IsDeleted(string prefabId)
{
bool isDeleted = await this.prefabsRepository.IsPrefabDeleted(prefabId);
return Ok(isDeleted);
}

[HttpDelete("{prefabId}")]
public async Task<IActionResult> DeletePrefab(string prefabId)
{
try
{
await prefabsRepository.DeletePrefabAsync(prefabId);
return Ok();
}
catch (InvalidOperationException ex)
{
return Conflict(ex.Message);
}
}
}
}
Loading

0 comments on commit 2fb744b

Please sign in to comment.