Skip to content

Commit bdf9007

Browse files
committed
Applications and Test data source can be deleted now
1 parent 72cca5a commit bdf9007

File tree

26 files changed

+315
-96
lines changed

26 files changed

+315
-96
lines changed

src/Pixel.Automation.AppExplorer.ViewModels/Application/ApplicationExplorerViewModel.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Pixel.Persistence.Services.Client;
88
using Serilog;
99
using System.ComponentModel;
10+
using System.Windows;
1011
using System.Windows.Data;
1112
using System.Windows.Input;
1213

@@ -105,7 +106,10 @@ public ApplicationExplorerViewModel(IEventAggregator eventAggregator, IApplicati
105106
var applications = this.applicationDataManager.GetAllApplications();
106107
foreach (var application in applications)
107108
{
108-
this.Applications.Add(new ApplicationDescriptionViewModel(application));
109+
if(!application.IsDeleted)
110+
{
111+
this.Applications.Add(new ApplicationDescriptionViewModel(application));
112+
}
109113
}
110114
Applications.OrderBy(a => a.ApplicationName);
111115
}
@@ -225,6 +229,29 @@ public async Task SaveApplicationAsync(ApplicationDescriptionViewModel applicati
225229
this.Applications.Add(applicationDescriptionViewModel);
226230
}
227231

232+
/// <summary>
233+
/// Mark the application as deleted and remove from application explorer views
234+
/// </summary>
235+
/// <param name="applicationDescriptionViewModel"></param>
236+
/// <returns></returns>
237+
public async Task DeleteApplicationAsync(ApplicationDescriptionViewModel applicationDescriptionViewModel)
238+
{
239+
Guard.Argument(applicationDescriptionViewModel, nameof(applicationDescriptionViewModel)).NotNull();
240+
try
241+
{
242+
MessageBoxResult result = MessageBox.Show("Are you sure you want to delete this application?", "Confirm Delete", MessageBoxButton.OKCancel);
243+
if (result == MessageBoxResult.OK)
244+
{
245+
await this.applicationDataManager.DeleteApplicationAsync(applicationDescriptionViewModel.Model);
246+
this.Applications.Remove(applicationDescriptionViewModel);
247+
}
248+
}
249+
catch (Exception ex)
250+
{
251+
logger.Error(ex, ex.Message);
252+
}
253+
}
254+
228255
/// <summary>
229256
/// Create a new screen for the application. Screens are used to group application controls.
230257
/// </summary>

src/Pixel.Automation.AppExplorer.Views/Application/ApplicationExplorerView.xaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,10 @@
203203
<ContextMenu.Template>
204204
<ControlTemplate>
205205
<StackPanel>
206-
<MenuItem x:Name="Open" Header="Open" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action OpenApplication($dataContext)]" ></MenuItem>
207-
<!--<MenuItem x:Name="Save" Header="Save" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action SaveApplicationAsync($dataContext)]" ></MenuItem>-->
206+
<MenuItem x:Name="Open" Header="Open" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action OpenApplication($dataContext)]" ></MenuItem>
208207
<MenuItem x:Name="Edit" Header="Edit" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action EditApplicationAsync($dataContext)]" ></MenuItem>
209-
<MenuItem x:Name="Rename" Header="Rename" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action ToggleRename($dataContext)]" ></MenuItem>
208+
<MenuItem x:Name="Rename" Header="Rename" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action ToggleRename($dataContext)]" ></MenuItem>
209+
<MenuItem x:Name="Delete" Header="Delete" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action DeleteApplicationAsync($dataContext)]" ></MenuItem>
210210
</StackPanel>
211211
</ControlTemplate>
212212
</ContextMenu.Template>

src/Pixel.Automation.Core/Models/ApplicationDescription.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ public string ApplicationName
6060
[DataMember(Order = 50)]
6161
public Dictionary<string, List<string>> AvailablePrefabs { get; private set; } = new();
6262

63+
/// <summary>
64+
/// Indicates if the TestCase is deleted. Deleted test cases are not loaded in explorer.
65+
/// </summary>
66+
[DataMember(IsRequired = false, Order = 1000)]
67+
public bool IsDeleted { get; set; }
68+
6369
/// <summary>
6470
/// constructor
6571
/// </summary>

src/Pixel.Automation.Core/TestData/TestCase.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Pixel.Automation.Core.Attributes;
22
using Pixel.Automation.Core.Enums;
33
using System;
4-
using System.Diagnostics.SymbolStore;
54
using System.Runtime.Serialization;
65
using System.Text.Json.Serialization;
76

src/Pixel.Automation.Editor.Core/Interfaces/ITestDataRepository.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Pixel.Automation.Core.TestData;
2+
using System.Threading.Tasks;
23

34
namespace Pixel.Automation.Editor.Core.Interfaces
45
{
@@ -10,17 +11,17 @@ public interface ITestDataRepository
1011
/// <summary>
1112
/// Show the wizard for creating a coded test data source
1213
/// </summary>
13-
void CreateCodedTestDataSource();
14+
Task CreateCodedTestDataSource();
1415

1516
/// <summary>
1617
/// Show the wizard for creating a csv backed test data source
1718
/// </summary>
18-
void CreateCsvTestDataSource();
19+
Task CreateCsvTestDataSource();
1920

2021
/// <summary>
2122
/// Open an existing TestData source for edit
2223
/// </summary>
2324
/// <param name="testDataSource"></param>
24-
void EditDataSource(TestDataSource testDataSource);
25+
Task EditDataSource(TestDataSource testDataSource);
2526
}
2627
}

src/Pixel.Automation.TestData.Repository.ViewModels/TestDataRepositoryViewModel.cs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Text;
1616
using System.Threading;
1717
using System.Threading.Tasks;
18+
using System.Windows;
1819
using System.Windows.Data;
1920

2021
namespace Pixel.Automation.TestData.Repository.ViewModels
@@ -87,7 +88,10 @@ private async Task LoadDataSourcesAsync()
8788
await this.testDataManager.DownloadAllTestDataSourcesAsync();
8889
foreach (var testDataSource in this.projectFileSystem.GetTestDataSources())
8990
{
90-
this.TestDataSourceCollection.Add(testDataSource);
91+
if(!testDataSource.IsDeleted)
92+
{
93+
this.TestDataSourceCollection.Add(testDataSource);
94+
}
9195
}
9296
}
9397

@@ -137,15 +141,15 @@ private void CreateCollectionView()
137141
#region Create Test Data Source
138142

139143
///<inheritdoc/>
140-
public void CreateCodedTestDataSource()
144+
public async Task CreateCodedTestDataSource()
141145
{
142-
_ = CreateDataSource(DataSource.Code);
146+
await CreateDataSource(DataSource.Code);
143147
}
144148

145149
///<inheritdoc/>
146-
public void CreateCsvTestDataSource()
150+
public async Task CreateCsvTestDataSource()
147151
{
148-
_ = CreateDataSource(DataSource.CsvFile);
152+
await CreateDataSource(DataSource.CsvFile);
149153
}
150154

151155
private async Task CreateDataSource(DataSource dataSourceType)
@@ -222,17 +226,17 @@ private async Task CreateEmptyDataSourceAsync()
222226
#region Edit Test Data Source
223227

224228
///<inheritdoc/>
225-
public void EditDataSource(TestDataSource testDataSource)
229+
public async Task EditDataSource(TestDataSource testDataSource)
226230
{
227231
try
228232
{
229233
switch (testDataSource.DataSource)
230234
{
231235
case DataSource.Code:
232-
EditCodedDataSource(testDataSource);
236+
await EditCodedDataSource(testDataSource);
233237
break;
234238
case DataSource.CsvFile:
235-
EditCsvDataSource(testDataSource);
239+
await EditCsvDataSource(testDataSource);
236240
break;
237241
}
238242
}
@@ -247,7 +251,7 @@ public void EditDataSource(TestDataSource testDataSource)
247251
/// show script editor screen to edit the script for Code data source
248252
/// </summary>
249253
/// <param name="testDataSource"></param>
250-
private async void EditCodedDataSource(TestDataSource testDataSource)
254+
private async Task EditCodedDataSource(TestDataSource testDataSource)
251255
{
252256
string projectName = testDataSource.DataSourceId;
253257
this.scriptEditorFactory.AddProject(projectName, Array.Empty<string>(), typeof(EmptyModel));
@@ -265,7 +269,7 @@ private async void EditCodedDataSource(TestDataSource testDataSource)
265269
/// Show the TestDataSource screen to edit the details for CSV data source
266270
/// </summary>
267271
/// <param name="testDataSource"></param>
268-
private async void EditCsvDataSource(TestDataSource testDataSource)
272+
private async Task EditCsvDataSource(TestDataSource testDataSource)
269273
{
270274
TestDataSourceViewModel dataSourceViewModel = new TestDataSourceViewModel(this.windowManager, this.projectFileSystem, testDataSource);
271275
TestDataSourceBuilderViewModel testDataSourceBuilder = new TestDataSourceBuilderViewModel(new IStagedScreen[] { dataSourceViewModel });
@@ -277,10 +281,34 @@ private async void EditCsvDataSource(TestDataSource testDataSource)
277281
}
278282
}
279283

284+
285+
/// <summary>
286+
/// Mark the test data source as deleted
287+
/// </summary>
288+
/// <param name="testDataSource"></param>
289+
public async Task DeleteDataSource(TestDataSource testDataSource)
290+
{
291+
Guard.Argument(testDataSource, nameof(testDataSource)).NotNull();
292+
try
293+
{
294+
MessageBoxResult result = MessageBox.Show("Are you sure you want to delete data source", "Confirm Delete", MessageBoxButton.OKCancel);
295+
if (result == MessageBoxResult.OK)
296+
{
297+
await this.testDataManager.DeleteTestDataSourceAsync(testDataSource);
298+
this.TestDataSourceCollection.Remove(testDataSource);
299+
logger.Information("TestDataSource : '{0}' was deleted ", testDataSource.Name);
300+
}
301+
}
302+
catch (Exception ex)
303+
{
304+
logger.Error(ex, ex.Message);
305+
}
306+
}
307+
280308
#endregion Edit Data Source
281309

282310
#region life cycle
283-
311+
284312
/// <summary>
285313
/// Called just before view is activiated for the first time.
286314
/// Available TestDataSource are loaded from local storage during initialization.

src/Pixel.Automation.TestData.Repository.Views/TestDataRepositoryView.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@
104104
<ContextMenu.Template>
105105
<ControlTemplate>
106106
<StackPanel>
107-
<MenuItem x:Name="Edit" Header="Edit" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action EditDataSource($dataContext)]" ></MenuItem>
107+
<MenuItem x:Name="Edit" Header="Edit" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action EditDataSource($dataContext)]"></MenuItem>
108+
<MenuItem x:Name="Delete" Header="Delete" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" cal:Message.Attach="[Event Click] = [Action DeleteDataSource($dataContext)]"></MenuItem>
108109
</StackPanel>
109110
</ControlTemplate>
110111
</ContextMenu.Template>

src/Pixel.Persistence.Respository/ApplicationRepository.cs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Dawn;
2+
using Microsoft.Extensions.Logging;
23
using MongoDB.Bson;
34
using MongoDB.Driver;
5+
using Pixel.Persistence.Core.Models;
46
using System;
57
using System.Collections.Generic;
68
using System.Threading.Tasks;
@@ -9,17 +11,27 @@ namespace Pixel.Persistence.Respository
911
{
1012
public class ApplicationRepository : IApplicationRepository
1113
{
14+
private readonly ILogger logger;
1215
private readonly IMongoCollection<BsonDocument> applicationsCollection;
16+
private readonly IMongoCollection<ProjectReferences> referencesCollection;
17+
private readonly IControlRepository controlRepository;
18+
private readonly IPrefabsRepository prefabsRepository;
1319

14-
public ApplicationRepository(IMongoDbSettings dbSettings)
20+
public ApplicationRepository(ILogger<ApplicationRepository> logger, IMongoDbSettings dbSettings,
21+
IControlRepository controlRepository, IPrefabsRepository prefabsRepository)
1522
{
23+
this.logger = Guard.Argument(logger, nameof(logger)).NotNull().Value;
24+
this.controlRepository = Guard.Argument(controlRepository, nameof(controlRepository)).NotNull().Value;
25+
this.prefabsRepository = Guard.Argument(prefabsRepository, nameof(prefabsRepository)).NotNull().Value;
26+
1627
var client = new MongoClient(dbSettings.ConnectionString);
1728
var database = client.GetDatabase(dbSettings.DatabaseName);
1829
applicationsCollection = database.GetCollection<BsonDocument>(dbSettings.ApplicationsCollectionName);
30+
referencesCollection = database.GetCollection<ProjectReferences>(dbSettings.ProjectReferencesCollectionName);
1931
}
2032

2133
///<inheritdoc/>
22-
public async Task<object> GetApplication(string applicationId)
34+
public async Task<object> FindByIdAsync(string applicationId)
2335
{
2436
Guard.Argument(applicationId).NotNull().NotEmpty();
2537

@@ -54,24 +66,54 @@ public async Task AddOrUpdate(string applicationDescriptionJson)
5466

5567
//Add these extra fields while storing. However, these will be dropped off in response while retrieving
5668
document.Add("ApplicationId", applicationId);
57-
document.Add("LastUpdated", DateTime.Now.ToUniversalTime());
69+
document.Add("LastUpdated", DateTime.UtcNow);
5870

5971
var filter = Builders<BsonDocument>.Filter.Eq(x => x["ApplicationDetails.ApplicationId"], applicationId);
6072

6173
//if document with application id already exists, replace it
6274
if (applicationsCollection.CountDocuments<BsonDocument>(x => x["ApplicationDetails.ApplicationId"].Equals(applicationId),
6375
new CountOptions { Limit = 1 }) > 0)
6476
{
65-
await applicationsCollection.FindOneAndReplaceAsync<BsonDocument>(filter, document);
77+
await applicationsCollection.FindOneAndReplaceAsync<BsonDocument>(filter, document);
78+
logger.LogInformation("Application : '{0}' was updated", document["ApplicationDetails"]["ApplicationName"]);
6679
}
6780
else
6881
{
69-
await applicationsCollection.InsertOneAsync(document);
82+
await applicationsCollection.InsertOneAsync(document);
83+
logger.LogInformation("Application : '{0}' was added", document["ApplicationDetails"]["ApplicationName"]);
7084
}
7185
return;
7286

7387
}
7488
throw new ArgumentException("Failed to parse application data in to BsonDocument");
7589
}
90+
91+
///<inheritdoc/>
92+
public async Task DeleteAsync(string applicationId)
93+
{
94+
Guard.Argument(applicationId, nameof(applicationId)).NotNull().NotEmpty();
95+
96+
var applicationFilter = Builders<BsonDocument>.Filter.Eq(x => x["ApplicationId"], applicationId);
97+
var application = await (applicationsCollection.Find<BsonDocument>(applicationFilter)).SingleOrDefaultAsync();
98+
99+
//Check if application is used by any of the projects
100+
var referenceFilter = Builders<ProjectReferences>.Filter.ElemMatch(x => x.ControlReferences, Builders<ControlReference>.Filter.Eq(c => c.ApplicationId, applicationId))
101+
| Builders<ProjectReferences>.Filter.ElemMatch(x => x.PrefabReferences, Builders<PrefabReference>.Filter.Eq(p => p.ApplicationId, applicationId));
102+
103+
long count = await this.referencesCollection.CountDocumentsAsync(referenceFilter);
104+
if (count > 0)
105+
{
106+
throw new InvalidOperationException($"Application : '{application["ApplicationDetails"]["ApplicationName"]}' is in use by one or more projects");
107+
}
108+
109+
var updateDefinition = Builders<BsonDocument>.Update
110+
.Set("ApplicationDetails.IsDeleted", true)
111+
.Set("ApplicationDetails.LastUpdated", DateTime.UtcNow);
112+
await applicationsCollection.FindOneAndUpdateAsync(applicationFilter, updateDefinition);
113+
logger.LogInformation("Application : '{0}' was marked deleted", application["ApplicationDetails"]["ApplicationName"]);
114+
115+
await this.controlRepository.DeleteAllControlsForApplicationAsync(applicationId);
116+
await this.prefabsRepository.DeleteAllPrefabsForApplicationAsync(applicationId);
117+
}
76118
}
77119
}

0 commit comments

Comments
 (0)