diff --git a/Example/simple.css b/Example/simple.css index 8677795..41d63b9 100644 --- a/Example/simple.css +++ b/Example/simple.css @@ -135,6 +135,7 @@ h1 border-left:solid 1px black; } + .tasks h1 { left:0px; diff --git a/WhiskWork.Core/AdvancedWorkflowRepository.cs b/WhiskWork.Core/AdvancedWorkflowRepository.cs deleted file mode 100644 index 6116f06..0000000 --- a/WhiskWork.Core/AdvancedWorkflowRepository.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace WhiskWork.Core -{ - public class AdvancedWorkflowRepository : IWorkStepRepository - { - private readonly IWorkStepRepository _workStepRepository; - - public AdvancedWorkflowRepository(IWorkStepRepository workStepRepository) - { - _workStepRepository = workStepRepository; - } - - public void DeleteWorkStepsRecursively(WorkStep step) - { - foreach (var workStep in _workStepRepository.GetChildWorkSteps(step.Path)) - { - DeleteWorkStepsRecursively(workStep); - } - - DeleteWorkStep(step.Path); - } - - - public void CreateWorkStep(WorkStep workStep) - { - _workStepRepository.CreateWorkStep(workStep); - } - - public IEnumerable GetChildWorkSteps(string path) - { - return _workStepRepository.GetChildWorkSteps(path); - } - - public WorkStep GetWorkStep(string path) - { - return _workStepRepository.GetWorkStep(path); - } - - public void DeleteWorkStep(string path) - { - _workStepRepository.DeleteWorkStep(path); - } - - public bool ExistsWorkStep(string path) - { - return _workStepRepository.ExistsWorkStep(path); - } - } -} diff --git a/WhiskWork.Core/IWorkflow.cs b/WhiskWork.Core/IWorkflow.cs new file mode 100644 index 0000000..3fdb4ac --- /dev/null +++ b/WhiskWork.Core/IWorkflow.cs @@ -0,0 +1,15 @@ +using System.Collections.Specialized; + +namespace WhiskWork.Core +{ + public interface IWorkflow + { + bool ExistsWorkItem(string workItemId); + bool ExistsWorkStep(string path); + void UpdateWorkItem(WorkItem workItem); + void CreateWorkStep(WorkStep workStep); + void CreateWorkItem(WorkItem workItem); + WorkItem GetWorkItem(string workItemId); + void DeleteWorkItem(string workItemId); + } +} \ No newline at end of file diff --git a/WhiskWork.Core/Synchronization/CachingSynchronizationAgent.cs b/WhiskWork.Core/Synchronization/CachingSynchronizationAgent.cs index 6b52628..af20002 100644 --- a/WhiskWork.Core/Synchronization/CachingSynchronizationAgent.cs +++ b/WhiskWork.Core/Synchronization/CachingSynchronizationAgent.cs @@ -48,9 +48,9 @@ public void UpdateStatus(SynchronizationEntry entry) _getAllCache[index] = entry; } - public void UpdateProperties(SynchronizationEntry entry) + public void UpdateData(SynchronizationEntry entry) { - _innerAgent.UpdateProperties(entry); + _innerAgent.UpdateData(entry); var index = IndexOf(entry.Id); _getAllCache[index] = entry; } diff --git a/WhiskWork.Core/Synchronization/CreationSynchronizer.cs b/WhiskWork.Core/Synchronization/CreationSynchronizer.cs index 803f819..5531422 100644 --- a/WhiskWork.Core/Synchronization/CreationSynchronizer.cs +++ b/WhiskWork.Core/Synchronization/CreationSynchronizer.cs @@ -53,7 +53,8 @@ private bool TryGetSlaveEntry(SynchronizationEntry masterEntry, out Synchronizat var slaveStatus = _map.GetMappedValue(_master, masterEntry.Status); - slaveEntry = new SynchronizationEntry(masterEntry.Id, slaveStatus, masterEntry.Properties); + slaveEntry = new SynchronizationEntry(masterEntry.Id, slaveStatus, masterEntry.Properties) + {Ordinal = masterEntry.Ordinal}; return true; } diff --git a/WhiskWork.Core/Synchronization/DataSynchronizer.cs b/WhiskWork.Core/Synchronization/DataSynchronizer.cs new file mode 100644 index 0000000..56aeddf --- /dev/null +++ b/WhiskWork.Core/Synchronization/DataSynchronizer.cs @@ -0,0 +1,37 @@ +using System.Linq; + +namespace WhiskWork.Core.Synchronization +{ + public class DataSynchronizer + { + private readonly ISynchronizationAgent _master; + private readonly ISynchronizationAgent _slave; + + public DataSynchronizer(ISynchronizationAgent master, ISynchronizationAgent slave) + { + _master = master; + _slave = slave; + } + + public void Synchronize() + { + var masterEntries = _master.GetAll().ToDictionary(e => e.Id); + var slaveEntries = _slave.GetAll().ToDictionary(e => e.Id); + + foreach (var masterId in masterEntries.Keys) + { + if (slaveEntries.ContainsKey(masterId)) + { + var slaveEntry = slaveEntries[masterId]; + + var updateEntry = new SynchronizationEntry(masterId, slaveEntry.Status, + masterEntries[masterId].Properties) + {Ordinal = masterEntries[masterId].Ordinal}; + + _slave.UpdateData(updateEntry); + } + } + + } + } +} \ No newline at end of file diff --git a/WhiskWork.Core/Synchronization/ISynchronizationAgent.cs b/WhiskWork.Core/Synchronization/ISynchronizationAgent.cs index c052248..eab4be4 100644 --- a/WhiskWork.Core/Synchronization/ISynchronizationAgent.cs +++ b/WhiskWork.Core/Synchronization/ISynchronizationAgent.cs @@ -9,6 +9,6 @@ public interface ISynchronizationAgent void Create(SynchronizationEntry entry); void Delete(SynchronizationEntry entry); void UpdateStatus(SynchronizationEntry entry); - void UpdateProperties(SynchronizationEntry entry); + void UpdateData(SynchronizationEntry entry); } } \ No newline at end of file diff --git a/WhiskWork.Core/Synchronization/StatusSynchronizer.cs b/WhiskWork.Core/Synchronization/StatusSynchronizer.cs index e259137..cd21807 100644 --- a/WhiskWork.Core/Synchronization/StatusSynchronizer.cs +++ b/WhiskWork.Core/Synchronization/StatusSynchronizer.cs @@ -26,7 +26,7 @@ public void Synchronize() if(slaveEntries.ContainsKey(masterId)) { SynchronizationEntry masterMappedSlaveEntry; - if(!TryToMasterEntry(slaveEntries[masterId],out masterMappedSlaveEntry)) + if(!TryGetMasterEntry(slaveEntries[masterId],out masterMappedSlaveEntry)) { continue; } @@ -34,7 +34,7 @@ public void Synchronize() if (masterMappedSlaveEntry.Status != masterEntries[masterId].Status) { SynchronizationEntry slaveEntry; - if (TryToSlaveEntry(masterEntries[masterId], out slaveEntry)) + if (TryGetSlaveEntry(masterEntries[masterId], out slaveEntry)) { _slave.UpdateStatus(slaveEntry); } @@ -43,7 +43,7 @@ public void Synchronize() } } - private bool TryToSlaveEntry(SynchronizationEntry masterEntry, out SynchronizationEntry slaveEntry) + private bool TryGetSlaveEntry(SynchronizationEntry masterEntry, out SynchronizationEntry slaveEntry) { if (!_map.ContainsKey(_master, masterEntry.Status)) { @@ -58,7 +58,7 @@ private bool TryToSlaveEntry(SynchronizationEntry masterEntry, out Synchronizati return true; } - private bool TryToMasterEntry(SynchronizationEntry slaveEntry, out SynchronizationEntry masterEntry) + private bool TryGetMasterEntry(SynchronizationEntry slaveEntry, out SynchronizationEntry masterEntry) { if(!_map.ContainsKey(_slave, slaveEntry.Status)) { @@ -73,37 +73,4 @@ private bool TryToMasterEntry(SynchronizationEntry slaveEntry, out Synchronizati } } - - public class PropertySynchronizer - { - private readonly ISynchronizationAgent _master; - private readonly ISynchronizationAgent _slave; - - public PropertySynchronizer(ISynchronizationAgent master, ISynchronizationAgent slave) - { - _master = master; - _slave = slave; - } - - public void Synchronize() - { - var masterEntries = _master.GetAll().ToDictionary(e => e.Id); - var slaveEntries = _slave.GetAll().ToDictionary(e => e.Id); - - foreach (var masterId in masterEntries.Keys) - { - if (slaveEntries.ContainsKey(masterId)) - { - var slaveEntry = slaveEntries[masterId]; - - var updateEntry = new SynchronizationEntry(masterId, slaveEntry.Status, - masterEntries[masterId].Properties); - - _slave.UpdateProperties(updateEntry); - } - } - - } - } - } \ No newline at end of file diff --git a/WhiskWork.Core/Synchronization/SynchronizationEntry.cs b/WhiskWork.Core/Synchronization/SynchronizationEntry.cs index 0c26b24..ece8e6f 100644 --- a/WhiskWork.Core/Synchronization/SynchronizationEntry.cs +++ b/WhiskWork.Core/Synchronization/SynchronizationEntry.cs @@ -28,6 +28,11 @@ public string Status get { return _status; } } + public int? Ordinal + { + get; set; + } + public Dictionary Properties { get { return new Dictionary(_properties); } @@ -47,6 +52,7 @@ public override bool Equals(object obj) result &= _id == entry._id; result &= _status == entry._status; result &= _properties.SequenceEqual(entry._properties); + result &= Ordinal == entry.Ordinal; return result; } @@ -56,6 +62,7 @@ public override int GetHashCode() var hc = _id != null ? _id.GetHashCode() : 1; hc ^= _status != null ? _status.GetHashCode() : 2; hc ^= _properties.Count > 0 ? _properties.Select(kv => kv.Key.GetHashCode() ^ kv.Value.GetHashCode()).Aggregate((hash, next) => hash ^ next) : 4; + hc ^= Ordinal.HasValue ? Ordinal.Value.GetHashCode() : 8; return hc; } @@ -65,7 +72,8 @@ public override string ToString() var sb = new StringBuilder(); sb.AppendFormat("Id={0},", _id); sb.AppendFormat("Status={0},", _status); - sb.AppendFormat("Properties={0}", _properties.Count() > 0 ? _properties.Select(kv => kv.Key + ":" + kv.Value).Aggregate((current, next) => current + "&" + next) : string.Empty); + sb.AppendFormat("Properties={0},", _properties.Count() > 0 ? _properties.Select(kv => kv.Key + ":" + kv.Value).Aggregate((current, next) => current + "&" + next) : string.Empty); + sb.AppendFormat("Ordinal={0}", Ordinal.HasValue ? Ordinal.Value.ToString() : ""); return sb.ToString(); } diff --git a/WhiskWork.Core/WhiskWork.Core.csproj b/WhiskWork.Core/WhiskWork.Core.csproj index 118dca8..4949f91 100644 --- a/WhiskWork.Core/WhiskWork.Core.csproj +++ b/WhiskWork.Core/WhiskWork.Core.csproj @@ -45,8 +45,13 @@ - + + + + + + @@ -58,16 +63,16 @@ + - + - diff --git a/WhiskWork.Core/WorkItem.cs b/WhiskWork.Core/WorkItem.cs index 4d1debd..7ba4440 100644 --- a/WhiskWork.Core/WorkItem.cs +++ b/WhiskWork.Core/WorkItem.cs @@ -52,14 +52,15 @@ IEnumerator IEnumerable.GetEnumerator() public class WorkItem { private readonly NameValueCollection _properties; - private WorkItem(string id, string path, IEnumerable workItemClasses, WorkItemStatus status, string parentId, int ordinal, NameValueCollection properties) + private readonly int? _ordinal; + private WorkItem(string id, string path, IEnumerable workItemClasses, WorkItemStatus status, string parentId, int? ordinal, NameValueCollection properties) { Id = id; Path = path; Classes = workItemClasses; Status = status; ParentId = parentId; - Ordinal = ordinal; + _ordinal = ordinal; _properties = properties; } @@ -75,12 +76,12 @@ public static WorkItem New(string id, string path, NameValueCollection propertie throw new ArgumentException("Id can only consist of letters, numbers and hyphen"); } - return new WorkItem(id, path, new string[0], WorkItemStatus.Normal, null,0, properties); + return new WorkItem(id, path, new string[0], WorkItemStatus.Normal, null,null, properties); } - public static WorkItem NewUnchecked(string id, string path, NameValueCollection properties) + public static WorkItem NewUnchecked(string id, string path, int? ordinal, NameValueCollection properties) { - return new WorkItem(id, path, new string[0], WorkItemStatus.Normal, null, 0, properties); + return new WorkItem(id, path, new string[0], WorkItemStatus.Normal, null, ordinal, properties); } @@ -89,7 +90,22 @@ public static WorkItem NewUnchecked(string id, string path, NameValueCollection public IEnumerable Classes { get; private set; } public WorkItemStatus Status { get; private set; } public string ParentId { get; private set; } - public int Ordinal { get; private set; } + public int Ordinal + { + get + { + return _ordinal.HasValue ? _ordinal.Value : -1; + } + } + + public bool HasOrdinal + { + get + { + return _ordinal.HasValue; + } + } + public WorkItemProperties Properties { get @@ -98,25 +114,27 @@ public WorkItemProperties Properties } } + + public WorkItem MoveTo(WorkStep step) { - return new WorkItem(Id,step.Path,Classes,Status,ParentId, Ordinal,_properties); + return new WorkItem(Id,step.Path,Classes,Status,ParentId, _ordinal,_properties); } public WorkItem UpdateStatus(WorkItemStatus status) { - return new WorkItem(Id, Path, Classes, status, ParentId, Ordinal, _properties); + return new WorkItem(Id, Path, Classes, status, ParentId, _ordinal, _properties); } public WorkItem CreateChildItem(string id) { - return new WorkItem(id, Path, Classes, Status, Id, Ordinal, _properties); + return new WorkItem(id, Path, Classes, Status, Id, _ordinal, _properties); } public WorkItem UpdateParent(WorkItem parentItem) { - return new WorkItem(Id, Path, Classes, Status, parentItem.Id, Ordinal, _properties); + return new WorkItem(Id, Path, Classes, Status, parentItem.Id, _ordinal, _properties); } public WorkItem UpdateOrdinal(int ordinal) @@ -128,7 +146,7 @@ public WorkItem AddClass(string workItemClass) { var newClasses = new List(Classes) { workItemClass }; - return new WorkItem(Id, Path, newClasses, Status, ParentId, Ordinal, _properties); + return new WorkItem(Id, Path, newClasses, Status, ParentId, _ordinal, _properties); } public WorkItem RemoveClass(string workItemClass) @@ -136,12 +154,42 @@ public WorkItem RemoveClass(string workItemClass) var newClasses = new List(Classes); newClasses.Remove(workItemClass); - return new WorkItem(Id, Path, newClasses, Status, ParentId, Ordinal, _properties); + return new WorkItem(Id, Path, newClasses, Status, ParentId, _ordinal, _properties); } public WorkItem ReplacesClasses(IEnumerable newClasses) { - return new WorkItem(Id, Path, newClasses, Status, ParentId, Ordinal, _properties); + return new WorkItem(Id, Path, newClasses, Status, ParentId, _ordinal, _properties); + } + + public WorkItem UpdatePropertiesAndOrdinalFrom(WorkItem item) + { + var modifiedProperties = new NameValueCollection(_properties); + var modifiedOrdinal = _ordinal; + + foreach (var key in item.Properties.AllKeys) + { + modifiedProperties[key] = item.Properties[key]; + } + + if(item.HasOrdinal) + { + modifiedOrdinal = item.Ordinal; + } + + return new WorkItem(Id, Path, Classes, Status, ParentId, modifiedOrdinal, modifiedProperties); + } + + public WorkItem UpdateProperties(WorkItemProperties properties) + { + var modifiedProperties = new NameValueCollection(_properties); + + foreach (var key in properties.AllKeys) + { + modifiedProperties[key] = properties[key]; + } + + return new WorkItem(Id, Path, Classes, Status, ParentId, _ordinal, modifiedProperties); } public WorkItem UpdateProperties(NameValueCollection properties) @@ -153,9 +201,10 @@ public WorkItem UpdateProperties(NameValueCollection properties) modifiedProperties[key] = properties[key]; } - return new WorkItem(Id, Path, Classes, Status, ParentId, Ordinal, modifiedProperties); + return new WorkItem(Id, Path, Classes, Status, ParentId, _ordinal, modifiedProperties); } + public override bool Equals(object obj) { if(!(obj is WorkItem)) diff --git a/WhiskWork.Core/WorkItemCreator.cs b/WhiskWork.Core/WorkItemCreator.cs new file mode 100644 index 0000000..64f7fb8 --- /dev/null +++ b/WhiskWork.Core/WorkItemCreator.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using System.Linq; + +namespace WhiskWork.Core +{ + internal class WorkItemCreator : WorkflowRepositoryInteraction + { + public WorkItemCreator(IWorkStepRepository workStepRepository, IWorkItemRepository workItemRepository) : base(workStepRepository, workItemRepository) + { + } + + public void CreateWorkItem(WorkItem newWorkItem) + { + var leafStep = WorkStepRepository.GetLeafStep(newWorkItem.Path); + + if (leafStep.Type != WorkStepType.Begin) + { + throw new InvalidOperationException("Can only create work items in begin step"); + } + + var classes = WorkStepRepository.GetWorkItemClasses(leafStep); + + newWorkItem = newWorkItem.MoveTo(leafStep).ReplacesClasses(classes); + + WorkStep transientStep; + if (WorkStepRepository.IsWithinTransientStep(leafStep, out transientStep)) + { + var workItems = WorkItemRepository.GetWorkItems(transientStep.Path); + Debug.Assert(workItems.Count() == 1); + + var parentItem = workItems.ElementAt(0); + WorkItemRepository.UpdateWorkItem(parentItem.UpdateStatus(WorkItemStatus.ExpandLocked)); + + newWorkItem = newWorkItem.MoveTo(leafStep).UpdateParent(parentItem); + + foreach (var workItemClass in newWorkItem.Classes) + { + foreach (var rootClass in WorkItemClass.FindRootClasses(workItemClass)) + { + newWorkItem = newWorkItem.AddClass(rootClass); + } + } + } + else if (WorkStepRepository.IsWithinExpandStep(leafStep)) + { + throw new InvalidOperationException("Cannot create item directly under expand step"); + } + + if (!newWorkItem.HasOrdinal) + { + newWorkItem = newWorkItem.UpdateOrdinal(WorkItemRepository.GetNextOrdinal(newWorkItem)); + } + + WorkItemRepository.CreateWorkItem(newWorkItem); + } + + } +} \ No newline at end of file diff --git a/WhiskWork.Core/WorkItemMover.cs b/WhiskWork.Core/WorkItemMover.cs new file mode 100644 index 0000000..585e0f7 --- /dev/null +++ b/WhiskWork.Core/WorkItemMover.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace WhiskWork.Core +{ + public class WorkflowRepositoryInteraction + { + public WorkflowRepositoryInteraction(IWorkStepRepository workStepRepository, IWorkItemRepository workItemRepository) + { + WorkStepRepository = workStepRepository; + WorkItemRepository = workItemRepository; + } + + protected IWorkStepRepository WorkStepRepository { get; private set; } + + protected IWorkItemRepository WorkItemRepository { get; private set; } + } + + internal class WorkItemMover : WorkflowRepositoryInteraction + { + public WorkItemMover(IWorkStepRepository workStepRepository, IWorkItemRepository workItemRepository) : base(workStepRepository, workItemRepository) + { + } + + public void MoveWorkItem(WorkItem workItem, WorkStep toStep) + { + var transition = new WorkItemTransition(workItem, toStep); + + ThrowIfMovingParallelLockedWorkItem(transition); + + ThrowIfMovingExpandLockedWorkItem(transition); + + ThrowIfMovingFromTransientStepToParallelStep(transition); + + transition = CreateTransitionIfMovingToWithinParallelStep(transition); + + ThrowIfMovingToWithinExpandStep(transition); + + ThrowIfMovingToStepWithWrongClass(transition); + + transition = CreateTransitionIfMovingToExpandStep(transition); + + transition = CleanUpIfMovingFromTransientStep(transition); + + transition = AttemptMergeIfMovingChildOfParallelledWorkItem(transition); + + var resultTransition = DoMove(transition); + + TryUpdatingExpandLockIfMovingChildOfExpandedWorkItem(resultTransition); + + } + + private void ThrowIfMovingParallelLockedWorkItem(WorkItemTransition transition) + { + if (WorkItemRepository.IsParallelLockedWorkItem(transition.WorkItem)) + { + throw new InvalidOperationException("Work item is locked for parallel work"); + } + } + + private void ThrowIfMovingExpandLockedWorkItem(WorkItemTransition transition) + { + if (WorkItemRepository.IsExpandLockedWorkItem(transition.WorkItem)) + { + throw new InvalidOperationException("Item is expandlocked and cannot be moved"); + } + } + + private void ThrowIfMovingFromTransientStepToParallelStep(WorkItemTransition transition) + { + WorkStep transientStep; + + var isInTransientStep = WorkStepRepository.IsInTransientStep(transition.WorkItem, out transientStep); + + WorkStep parallelStepRoot; + var isWithinParallelStep = WorkStepRepository.IsWithinParallelStep(transition.WorkStep, out parallelStepRoot); + + if (isInTransientStep && isWithinParallelStep) + { + throw new InvalidOperationException("Cannot move directly from transient step to parallelstep"); + } + } + + private WorkItemTransition CreateTransitionIfMovingToWithinParallelStep(WorkItemTransition transition) + { + WorkStep parallelStep; + if (WorkStepRepository.IsWithinParallelStep(transition.WorkStep, out parallelStep)) + { + if (!WorkItemRepository.IsChildOfParallelledWorkItem(transition.WorkItem)) + { + var idToMove = ParallelStepHelper.GetParallelId(transition.WorkItem.Id, parallelStep, transition.WorkStep); + var workItemToMove = MoveAndLockAndSplitForParallelism(transition.WorkItem, parallelStep).First(wi => wi.Id == idToMove); + + return new WorkItemTransition(workItemToMove, transition.WorkStep); + } + } + return transition; + } + + private void ThrowIfMovingToWithinExpandStep(WorkItemTransition transition) + { + WorkStep dummyStep; + if (!WorkStepRepository.IsExpandStep(transition.WorkStep) && !WorkStepRepository.IsWithinTransientStep(transition.WorkStep, out dummyStep) && WorkStepRepository.IsWithinExpandStep(transition.WorkStep)) + { + throw new InvalidOperationException("Cannot move item to within expand step"); + } + } + + private void ThrowIfMovingToStepWithWrongClass(WorkItemTransition transition) + { + if (!WorkStepRepository.IsValidWorkStepForWorkItem(transition.WorkItem, transition.WorkStep)) + { + throw new InvalidOperationException("Invalid step for work item"); + } + } + + private WorkItemTransition CreateTransitionIfMovingToExpandStep(WorkItemTransition transition) + { + if (WorkStepRepository.IsExpandStep(transition.WorkStep)) + { + var stepToMoveTo = CreateTransientWorkSteps(transition.WorkItem, transition.WorkStep); + var workItemToMove = transition.WorkItem.AddClass(stepToMoveTo.WorkItemClass); + + transition = new WorkItemTransition(workItemToMove, stepToMoveTo); + } + return transition; + } + + private WorkItemTransition CleanUpIfMovingFromTransientStep(WorkItemTransition transition) + { + var remover = new WorkItemRemover(WorkStepRepository, WorkItemRepository); + + transition = new WorkItemTransition(remover.CleanUpIfInTransientStep(transition.WorkItem), transition.WorkStep); + return transition; + } + + private WorkItemTransition AttemptMergeIfMovingChildOfParallelledWorkItem(WorkItemTransition transition) + { + if (WorkItemRepository.IsMergeableParallelledChild(transition.WorkItem, transition.WorkStep)) + { + var workItemToMove = MergeParallelWorkItems(transition.WorkItem); + + transition = new WorkItemTransition(workItemToMove, transition.WorkStep); + } + + return transition; + } + + private WorkItemTransition DoMove(WorkItemTransition transition) + { + var fromStepPath = transition.WorkItem.Path; + + var movedWorkItem = transition.WorkItem.MoveTo(transition.WorkStep); + + var ordinal = WorkItemRepository.GetNextOrdinal(movedWorkItem); + movedWorkItem = movedWorkItem.UpdateOrdinal(ordinal); + + WorkItemRepository.UpdateWorkItem(movedWorkItem); + WorkItemRepository.RenumOrdinals(fromStepPath); + + return new WorkItemTransition(movedWorkItem, transition.WorkStep); + } + + private void TryUpdatingExpandLockIfMovingChildOfExpandedWorkItem(WorkItemTransition resultTransition) + { + if (IsChildOfExpandedWorkItem(resultTransition.WorkItem)) + { + TryUpdatingExpandLockOnParent(resultTransition.WorkItem); + } + } + + private IEnumerable MoveAndLockAndSplitForParallelism(WorkItem item, WorkStep parallelRootStep) + { + var lockedAndMovedItem = item.MoveTo(parallelRootStep).UpdateStatus(WorkItemStatus.ParallelLocked); + WorkItemRepository.UpdateWorkItem(lockedAndMovedItem); + + var helper = new ParallelStepHelper(WorkStepRepository); + + var splitWorkItems = helper.SplitForParallelism(item, parallelRootStep); + + foreach (var splitWorkItem in splitWorkItems) + { + WorkItemRepository.CreateWorkItem(splitWorkItem); + } + + return splitWorkItems; + } + + private WorkStep CreateTransientWorkSteps(WorkItem item, WorkStep expandStep) + { + Debug.Assert(expandStep.Type == WorkStepType.Expand); + + var transientRootPath = WorkStep.CombinePath(expandStep.Path, item.Id); + + CreateTransientWorkStepsRecursively(transientRootPath, expandStep, item.Id); + + var workItemClass = WorkItemClass.Combine(expandStep.WorkItemClass, item.Id); + var transientWorkStep = new WorkStep(transientRootPath, expandStep.Path, expandStep.Ordinal, WorkStepType.Transient, workItemClass, expandStep.Title); + WorkStepRepository.CreateWorkStep(transientWorkStep); + + return transientWorkStep; + } + + private WorkItem MergeParallelWorkItems(WorkItem item) + { + var unlockedParentWorkItem = WorkItemRepository.GetWorkItem(item.ParentId).UpdateStatus(WorkItemStatus.Normal); + WorkItemRepository.UpdateWorkItem(unlockedParentWorkItem); + + foreach (var childWorkItem in WorkItemRepository.GetChildWorkItems(item.ParentId).ToList()) + { + WorkItemRepository.DeleteWorkItem(childWorkItem); + } + + return unlockedParentWorkItem; + } + + private void TryUpdatingExpandLockOnParent(WorkItem item) + { + var parent = WorkItemRepository.GetWorkItem(item.ParentId); + + if (WorkItemRepository.GetChildWorkItems(parent.Id).All(IsDone)) + { + parent = parent.UpdateStatus(WorkItemStatus.Normal); + } + else + { + parent = parent.UpdateStatus(WorkItemStatus.ExpandLocked); + } + + WorkItemRepository.UpdateWorkItem(parent); + } + + private void CreateTransientWorkStepsRecursively(string transientRootPath, WorkStep rootStep, string workItemId) + { + var subSteps = WorkStepRepository.GetChildWorkSteps(rootStep.Path).Where(ws => ws.Type != WorkStepType.Transient); + foreach (var childStep in subSteps) + { + var offset = childStep.Path.Remove(0, rootStep.Path.Length); + + var childTransientPath = transientRootPath + offset; + + var workItemClass = WorkItemClass.Combine(childStep.WorkItemClass, workItemId); + WorkStepRepository.CreateWorkStep(new WorkStep(childTransientPath, transientRootPath, childStep.Ordinal, childStep.Type, workItemClass, childStep.Title)); + + CreateTransientWorkStepsRecursively(childTransientPath, childStep, workItemId); + } + } + + private bool IsChildOfExpandedWorkItem(WorkItem item) + { + if (item.ParentId == null) + { + return false; + } + + var parent = WorkItemRepository.GetWorkItem(item.ParentId); + var workStep = WorkStepRepository.GetWorkStep(parent.Path); + + return workStep.Type == WorkStepType.Transient; + } + + + + private bool IsDone(WorkItem item) + { + return WorkStepRepository.GetWorkStep(item.Path).Type == WorkStepType.End; + } + + + + } +} \ No newline at end of file diff --git a/WhiskWork.Core/WorkItemQuery.cs b/WhiskWork.Core/WorkItemQuery.cs deleted file mode 100644 index ed1f634..0000000 --- a/WhiskWork.Core/WorkItemQuery.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace WhiskWork.Core -{ - public class WorkItemQuery - { - private readonly IWorkStepRepository _workStepRepository; - private readonly IWorkItemRepository _workItemRepository; - - public WorkItemQuery(IWorkStepRepository repository, IWorkItemRepository workItems) - { - _workStepRepository = repository; - _workItemRepository = workItems; - } - - public bool IsDone(WorkItem item) - { - return _workStepRepository.GetWorkStep(item.Path).Type == WorkStepType.End; - } - - public bool TryLocateWorkItem(string id, out WorkItem item) - { - item = _workItemRepository.GetWorkItem(id); - - return item != null; - } - - public bool IsChildOfExpandedWorkItem(WorkItem item) - { - if (item.ParentId == null) - { - return false; - } - - var parent = _workItemRepository.GetWorkItem(item.ParentId); - var workStep = _workStepRepository.GetWorkStep(parent.Path); - - return workStep.Type == WorkStepType.Transient; - } - - public bool IsExpandLocked(WorkItem item) - { - return item.Status == WorkItemStatus.ExpandLocked; - } - - public bool IsChildOfParallelledWorkItem(WorkItem workItem) - { - if (workItem.ParentId != null) - { - WorkItem parent = _workItemRepository.GetWorkItem(workItem.ParentId); - if (parent.Status == WorkItemStatus.ParallelLocked) - { - return true; - } - } - - return false; - } - - - - - public bool IsParallelLockedWorkItem(WorkItem workItem) - { - return workItem.Status == WorkItemStatus.ParallelLocked; - } - - public int GetNextOrdinal(WorkItem workItem) - { - var workItemsInStep = _workItemRepository.GetWorkItems(workItem.Path); - - return workItemsInStep.Count()>0 ? workItemsInStep.Max(wi => wi.Ordinal) + 1 : 1; - } - } -} \ No newline at end of file diff --git a/WhiskWork.Core/WorkItemRemover.cs b/WhiskWork.Core/WorkItemRemover.cs new file mode 100644 index 0000000..70e8a2a --- /dev/null +++ b/WhiskWork.Core/WorkItemRemover.cs @@ -0,0 +1,74 @@ +using System; +using System.Linq; + +namespace WhiskWork.Core +{ + internal class WorkItemRemover : WorkflowRepositoryInteraction + { + public WorkItemRemover(IWorkStepRepository workStepRepository, IWorkItemRepository workItemRepository) : base(workStepRepository, workItemRepository) + { + } + + public WorkItem CleanUpIfInTransientStep(WorkItem workItemToMove) + { + WorkStep transientStep; + if (WorkStepRepository.IsInTransientStep(workItemToMove, out transientStep)) + { + DeleteChildWorkItems(workItemToMove); + WorkStepRepository.DeleteWorkStepsRecursively(transientStep); + workItemToMove = workItemToMove.RemoveClass(transientStep.WorkItemClass); + } + return workItemToMove; + } + + private void DeleteChildWorkItems(WorkItem workItem) + { + foreach (var childWorkItem in WorkItemRepository.GetChildWorkItems(workItem.Id)) + { + DeleteWorkItem(childWorkItem.Id); + } + } + + public void DeleteWorkItem(string id) + { + var workItem = WorkItemRepository.GetWorkItem(id); + + ThrowInvalidOperationExceptionIfParentIsParallelLocked(workItem); + + DeleteWorkItemRecursively(workItem); + } + + private void ThrowInvalidOperationExceptionIfParentIsParallelLocked(WorkItem workItem) + { + if (workItem.ParentId != null) + { + var parent = WorkItemRepository.GetWorkItem(workItem.ParentId); + if (parent.Status == WorkItemStatus.ParallelLocked) + { + throw new InvalidOperationException("Cannot delete workitem which is child of paralleled workitem"); + } + } + } + + private void DeleteWorkItemRecursively(WorkItem workItem) + { + var childWorkItems = WorkItemRepository.GetChildWorkItems(workItem.Id); + + if (childWorkItems.Count() > 0) + { + foreach (var childWorkItem in childWorkItems) + { + DeleteWorkItemRecursively(childWorkItem); + } + } + + + WorkItemRepository.DeleteWorkItem(workItem); + WorkItemRepository.RenumOrdinals(workItem.Path); + CleanUpIfInTransientStep(workItem); + } + + + + } +} \ No newline at end of file diff --git a/WhiskWork.Core/WorkItemRepositoryExtension.cs b/WhiskWork.Core/WorkItemRepositoryExtension.cs new file mode 100644 index 0000000..ba90f85 --- /dev/null +++ b/WhiskWork.Core/WorkItemRepositoryExtension.cs @@ -0,0 +1,75 @@ +using System.Linq; + +namespace WhiskWork.Core +{ + public static class WorkItemRepositoryExtension + { + public static void RenumOrdinals(this IWorkItemRepository workItemRepository, string path) + { + var ordinal = 1; + foreach (var workItem in workItemRepository.GetWorkItems(path).OrderBy(wi => wi.Ordinal)) + { + workItemRepository.UpdateWorkItem(workItem.UpdateOrdinal(ordinal++)); + } + + } + + public static bool TryLocateWorkItem(this IWorkItemRepository workItemRepository, string id, out WorkItem item) + { + item = workItemRepository.GetWorkItem(id); + + return item != null; + } + + + public static bool IsExpandLockedWorkItem(this IWorkItemRepository workItemRepository, WorkItem item) + { + return item.Status == WorkItemStatus.ExpandLocked; + } + + public static bool IsChildOfParallelledWorkItem(this IWorkItemRepository workItemRepository, WorkItem workItem) + { + if (workItem.ParentId != null) + { + var parent = workItemRepository.GetWorkItem(workItem.ParentId); + if (parent.Status == WorkItemStatus.ParallelLocked) + { + return true; + } + } + + return false; + } + + public static bool IsMergeableParallelledChild(this IWorkItemRepository workItemRepository, WorkItem item, WorkStep toStep) + { + if (!workItemRepository.IsChildOfParallelledWorkItem(item)) + { + return false; + } + + var isMergeable = true; + foreach (var childWorkItem in workItemRepository.GetChildWorkItems(item.ParentId).Where(wi => wi.Id != item.Id)) + { + isMergeable &= childWorkItem.Path == toStep.Path; + } + return isMergeable; + } + + + + public static bool IsParallelLockedWorkItem(this IWorkItemRepository workItemRepository, WorkItem workItem) + { + return workItem.Status == WorkItemStatus.ParallelLocked; + } + + public static int GetNextOrdinal(this IWorkItemRepository workItemRepository, WorkItem workItem) + { + var workItemsInStep = workItemRepository.GetWorkItems(workItem.Path); + + return workItemsInStep.Count() > 0 ? workItemsInStep.Max(wi => wi.Ordinal) + 1 : 1; + } + + + } +} \ No newline at end of file diff --git a/WhiskWork.Core/WorkItemTransition.cs b/WhiskWork.Core/WorkItemTransition.cs new file mode 100644 index 0000000..e7763b3 --- /dev/null +++ b/WhiskWork.Core/WorkItemTransition.cs @@ -0,0 +1,14 @@ +namespace WhiskWork.Core +{ + internal class WorkItemTransition + { + public WorkItemTransition(WorkItem workItem, WorkStep workStep) + { + WorkItem = workItem; + WorkStep = workStep; + } + + public WorkItem WorkItem { get; private set; } + public WorkStep WorkStep{ get; private set; } + } +} \ No newline at end of file diff --git a/WhiskWork.Core/WorkStep.cs b/WhiskWork.Core/WorkStep.cs index 575edf3..9e74ad4 100644 --- a/WhiskWork.Core/WorkStep.cs +++ b/WhiskWork.Core/WorkStep.cs @@ -26,6 +26,7 @@ public WorkStep(string path, string parentPath, int ordinal, WorkStepType workSt ThrowIfIllegalPath(path, "path"); ThrowIfIllegalPath(path, "parentPath"); ThrowIfParentPathIsNotProperSubPathOfPath(path, parentPath); + ThrowIfIllegalWorkItemClass(workItemClass, "workItemClass"); } Path = path; @@ -36,7 +37,6 @@ public WorkStep(string path, string parentPath, int ordinal, WorkStepType workSt Title = title; } - public static WorkStep Root { get @@ -47,39 +47,15 @@ public static WorkStep Root public static string CombinePath(string path1, string path2) { - path1 = path1.EndsWith("/") ? path1.Remove(path1.Length - 1, 1) : path1; - path2 = path2.StartsWith("/") ? path2.Remove(0, 1) : path2; + path1 = path1.EndsWith(Separator) ? path1.Remove(path1.Length - 1, 1) : path1; + path2 = path2.StartsWith(Separator) ? path2.Remove(0, 1) : path2; - var result = path1 + "/" + path2; + var result = path1 + Separator + path2; return result; } - private static void ThrowIfParentPathIsNotProperSubPathOfPath(string path, string parentPath) - { - var separator = parentPath == Root.Path ? string.Empty : Separator; - var regex = new Regex(parentPath + separator + @"[a-z,A-Z,0-9,\-]+$"); - if (!regex.IsMatch(path)) - { - throw new ArgumentException(string.Format("parent path '{0}' is not sub path of path '{1}'", parentPath, path)); - } - } - - private static void ThrowIfIllegalPath(string path, string paramName) - { - if(path==null) - { - throw new ArgumentNullException(paramName); - } - - var regex = new Regex(@"^(\/)$|^(\/[0-9,a-z,A-Z,\-]+)+$"); - - if(!regex.IsMatch(path)) - { - throw new ArgumentException(paramName, "Path must start with '/' but was '"+path+"'"); - } - } public string Path { get; private set; } public string ParentPath { get; private set; } @@ -134,5 +110,48 @@ public override string ToString() return sb.ToString(); } + private static void ThrowIfParentPathIsNotProperSubPathOfPath(string path, string parentPath) + { + var separator = parentPath == Root.Path ? string.Empty : Separator; + + var regex = new Regex(parentPath + separator + @"[a-z,A-Z,0-9,\-]+$"); + if (!regex.IsMatch(path)) + { + throw new ArgumentException(string.Format("parent path '{0}' is not sub path of path '{1}'", parentPath, path)); + } + } + + private static void ThrowIfIllegalPath(string path, string paramName) + { + if (path == null) + { + throw new ArgumentNullException(paramName); + } + + var regex = new Regex(@"^(\/)$|^(\/[0-9,a-z,A-Z,\-]+)+$"); + + if (!regex.IsMatch(path)) + { + throw new ArgumentException(paramName, "Path must start with '/' but was '" + path + "'"); + } + } + + private static void ThrowIfIllegalWorkItemClass(string workItemClass, string paramName) + { + if (workItemClass == null) + { + throw new ArgumentNullException(paramName); + } + + var regex = new Regex(@"^[0-9,a-z,A-Z,\-]+$"); + + if (!regex.IsMatch(workItemClass)) + { + throw new ArgumentException(paramName, "WorkItem class must only contain a-z,A-Z, 0-9, and - but was '" + workItemClass + "'"); + } + + } + + } } \ No newline at end of file diff --git a/WhiskWork.Core/WorkStepQuery.cs b/WhiskWork.Core/WorkStepQuery.cs deleted file mode 100644 index 6782e10..0000000 --- a/WhiskWork.Core/WorkStepQuery.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace WhiskWork.Core -{ - public class WorkStepQuery - { - private readonly IWorkStepRepository _workStepRepository; - public WorkStepQuery(IWorkStepRepository repository) - { - _workStepRepository = repository; - } - - public bool IsLeafStep(WorkStep step) - { - return GetLeafStep(step).Path == step.Path; - } - - public WorkStep GetLeafStep(WorkStep workStep) - { - return GetLeafStep(workStep.Path); - } - - public WorkStep GetLeafStep(string path) - { - var currentWorkStep = _workStepRepository.GetWorkStep(path); - while (true) - { - if (currentWorkStep.Type == WorkStepType.Expand) - { - break; - } - - var subSteps = _workStepRepository.GetChildWorkSteps(currentWorkStep.Path); - if (subSteps.Count() == 0) - { - break; - } - - currentWorkStep = subSteps.OrderBy(subStep => subStep.Ordinal).ElementAt(0); - } - - return currentWorkStep; - } - - - public bool IsWithinExpandStep(WorkStep workStep) - { - WorkStep expandStep; - return TryLocateFirstAncestorStepOfType(workStep, WorkStepType.Expand, out expandStep); - } - - public bool IsWithinExpandStep(WorkStep workStep, out WorkStep expandStep) - { - return TryLocateFirstAncestorStepOfType(workStep, WorkStepType.Expand, out expandStep); - } - - - public bool IsWithinTransientStep(WorkStep workStep, out WorkStep transientStep) - { - return TryLocateFirstAncestorStepOfType(workStep, WorkStepType.Transient, out transientStep); - } - - public bool IsWithinParallelStep(WorkStep workStep, out WorkStep parallelStepRoot) - { - return TryLocateFirstAncestorStepOfType(workStep, WorkStepType.Parallel, out parallelStepRoot); - } - - public bool IsInTransientStep(WorkItem workItem, out WorkStep transientStep) - { - transientStep = null; - bool isInTransientStep = _workStepRepository.GetWorkStep(workItem.Path).Type == WorkStepType.Transient; - - if(isInTransientStep) - { - transientStep = _workStepRepository.GetWorkStep(workItem.Path); - } - - return isInTransientStep; - } - - public bool IsExpandStep(WorkStep step) - { - return step.Type == WorkStepType.Expand; - } - - public bool IsParallelStep(string path) - { - return !IsRoot(path) && IsParallelStep(_workStepRepository.GetWorkStep(path)); - } - - public bool IsParallelStep(WorkStep step) - { - return step.Type == WorkStepType.Parallel; - } - - public bool IsRoot(string path) - { - return path == WorkStep.Root.Path; - } - - public bool IsValidWorkStepForWorkItem(WorkItem item, WorkStep workStep) - { - return item.Classes.Contains(workStep.WorkItemClass); - } - - public IEnumerable GetWorkItemClasses(WorkStep workStep) - { - yield return workStep.WorkItemClass; - } - - - private bool TryLocateFirstAncestorStepOfType(WorkStep workStep, WorkStepType stepType, out WorkStep ancestorStep) - { - var currentPath = workStep.Path; - do - { - var currentWorkStep = _workStepRepository.GetWorkStep(currentPath); - if (currentWorkStep.Type == stepType) - { - ancestorStep = currentWorkStep; - return true; - } - - currentPath = currentWorkStep.ParentPath; - } - while (currentPath != WorkStep.Root.Path); - - ancestorStep = null; - return false; - - } - - - } -} \ No newline at end of file diff --git a/WhiskWork.Core/WorkStepRepositoryExtensions.cs b/WhiskWork.Core/WorkStepRepositoryExtensions.cs new file mode 100644 index 0000000..56b4b36 --- /dev/null +++ b/WhiskWork.Core/WorkStepRepositoryExtensions.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WhiskWork.Core +{ + public static class WorkStepRepositoryExtensions + { + public static void DeleteWorkStepsRecursively(this IWorkStepRepository workStepRepository, WorkStep step) + { + foreach (var workStep in workStepRepository.GetChildWorkSteps(step.Path)) + { + DeleteWorkStepsRecursively(workStepRepository, workStep); + } + + workStepRepository.DeleteWorkStep(step.Path); + } + + public static bool IsLeafStep(this IWorkStepRepository workStepRepository, WorkStep step) + { + return GetLeafStep(workStepRepository, step).Path == step.Path; + } + + public static WorkStep GetLeafStep(this IWorkStepRepository workStepRepository, WorkStep workStep) + { + return GetLeafStep(workStepRepository, workStep.Path); + } + + public static WorkStep GetLeafStep(this IWorkStepRepository workStepRepository, string path) + { + var currentWorkStep = workStepRepository.GetWorkStep(path); + while (true) + { + if (currentWorkStep.Type == WorkStepType.Expand) + { + break; + } + + var subSteps = workStepRepository.GetChildWorkSteps(currentWorkStep.Path); + if (subSteps.Count() == 0) + { + break; + } + + currentWorkStep = subSteps.OrderBy(subStep => subStep.Ordinal).ElementAt(0); + } + + return currentWorkStep; + } + + + public static bool IsWithinExpandStep(this IWorkStepRepository workStepRepository, WorkStep workStep) + { + WorkStep expandStep; + return TryLocateFirstAncestorStepOfType(workStepRepository, workStep, WorkStepType.Expand, out expandStep); + } + + public static bool IsWithinExpandStep(this IWorkStepRepository workStepRepository, WorkStep workStep, out WorkStep expandStep) + { + return TryLocateFirstAncestorStepOfType(workStepRepository, workStep, WorkStepType.Expand, out expandStep); + } + + + public static bool IsWithinTransientStep(this IWorkStepRepository workStepRepository, WorkStep workStep, out WorkStep transientStep) + { + return TryLocateFirstAncestorStepOfType(workStepRepository, workStep, WorkStepType.Transient, out transientStep); + } + + public static bool IsWithinParallelStep(this IWorkStepRepository workStepRepository, WorkStep workStep, out WorkStep parallelStepRoot) + { + return TryLocateFirstAncestorStepOfType(workStepRepository, workStep, WorkStepType.Parallel, out parallelStepRoot); + } + + public static bool IsInTransientStep(this IWorkStepRepository workStepRepository, WorkItem workItem, out WorkStep transientStep) + { + transientStep = null; + bool isInTransientStep = workStepRepository.GetWorkStep(workItem.Path).Type == WorkStepType.Transient; + + if (isInTransientStep) + { + transientStep = workStepRepository.GetWorkStep(workItem.Path); + } + + return isInTransientStep; + } + + public static bool IsExpandStep(this IWorkStepRepository workStepRepository, WorkStep step) + { + return step.Type == WorkStepType.Expand; + } + + public static bool IsParallelStep(this IWorkStepRepository workStepRepository, string path) + { + return !IsRoot(workStepRepository, path) && IsParallelStep(workStepRepository, workStepRepository.GetWorkStep(path)); + } + + public static bool IsParallelStep(this IWorkStepRepository workStepRepository, WorkStep step) + { + return step.Type == WorkStepType.Parallel; + } + + public static bool IsRoot(this IWorkStepRepository workStepRepository, string path) + { + return path == WorkStep.Root.Path; + } + + public static bool IsValidWorkStepForWorkItem(this IWorkStepRepository workStepRepository, WorkItem item, WorkStep workStep) + { + return item.Classes.Contains(workStep.WorkItemClass); + } + + public static IEnumerable GetWorkItemClasses(this IWorkStepRepository workStepRepository, WorkStep workStep) + { + yield return workStep.WorkItemClass; + } + + + private static bool TryLocateFirstAncestorStepOfType(this IWorkStepRepository workStepRepository, WorkStep workStep, WorkStepType stepType, out WorkStep ancestorStep) + { + var currentPath = workStep.Path; + do + { + var currentWorkStep = workStepRepository.GetWorkStep(currentPath); + if (currentWorkStep.Type == stepType) + { + ancestorStep = currentWorkStep; + return true; + } + + currentPath = currentWorkStep.ParentPath; + } + while (currentPath != WorkStep.Root.Path); + + ancestorStep = null; + return false; + + } + + } +} diff --git a/WhiskWork.Core/Workflow.cs b/WhiskWork.Core/Workflow.cs index 073f114..80dc424 100644 --- a/WhiskWork.Core/Workflow.cs +++ b/WhiskWork.Core/Workflow.cs @@ -1,477 +1,88 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; -using System.Diagnostics; namespace WhiskWork.Core { - internal class WorkItemTransition + public class Workflow : WorkflowRepositoryInteraction, IWorkflow { - public WorkItemTransition(WorkItem workItem, WorkStep workStep) + public Workflow(IWorkStepRepository workStepRepository, IWorkItemRepository workItemRepository) : base(workStepRepository, workItemRepository) { - WorkItem = workItem; - WorkStep = workStep; - } - - public WorkItem WorkItem { get; private set; } - public WorkStep WorkStep{ get; private set; } - } - - public class Workflow : IWorkflow - { - private readonly AdvancedWorkflowRepository _workflowRepository; - private readonly IWorkItemRepository _workItemRepository; - private readonly WorkStepQuery _workStepQuery; - private readonly WorkItemQuery _workItemQuery; - - public Workflow(IWorkStepRepository workStepRepository, IWorkItemRepository workItemRepository) - { - _workflowRepository = new AdvancedWorkflowRepository(workStepRepository); - _workItemRepository = workItemRepository; - _workStepQuery = new WorkStepQuery(_workflowRepository); - _workItemQuery = new WorkItemQuery(_workflowRepository, _workItemRepository); } public IEnumerable GetWorkItems(string path) { - return _workItemRepository.GetWorkItems(path).Where(wi => wi.Status != WorkItemStatus.ParallelLocked); - } - - public void CreateWorkItem(string id, string path) - { - CreateWorkItem(id,path,new NameValueCollection()); - } - - public void CreateWorkItem(string id, string path, NameValueCollection properties) - { - CreateWorkItem(WorkItem.New(id,path,properties)); + return WorkItemRepository.GetWorkItems(path).Where(wi => wi.Status != WorkItemStatus.ParallelLocked); } public void CreateWorkItem(WorkItem newWorkItem) { - var leafStep = _workStepQuery.GetLeafStep(newWorkItem.Path); - - if(leafStep.Type!=WorkStepType.Begin) - { - throw new InvalidOperationException("Can only create work items in begin step"); - } - - var classes = _workStepQuery.GetWorkItemClasses(leafStep); - - newWorkItem = newWorkItem.MoveTo(leafStep).ReplacesClasses(classes); - - WorkStep transientStep; - if(_workStepQuery.IsWithinTransientStep(leafStep, out transientStep)) - { - var workItems = _workItemRepository.GetWorkItems(transientStep.Path); - Debug.Assert(workItems.Count()==1); - - var parentItem = workItems.ElementAt(0); - _workItemRepository.UpdateWorkItem(parentItem.UpdateStatus(WorkItemStatus.ExpandLocked)); - - newWorkItem = newWorkItem.MoveTo(leafStep).UpdateParent(parentItem); - - foreach (var workItemClass in newWorkItem.Classes) - { - foreach (var rootClass in WorkItemClass.FindRootClasses(workItemClass)) - { - newWorkItem = newWorkItem.AddClass(rootClass); - } - } - } - else if(_workStepQuery.IsWithinExpandStep(leafStep)) - { - throw new InvalidOperationException("Cannot create item directly under expand step"); - } - - newWorkItem = newWorkItem.UpdateOrdinal(_workItemQuery.GetNextOrdinal(newWorkItem)); - - _workItemRepository.CreateWorkItem(newWorkItem); - } - - public void UpdateWorkItem(WorkItem workItem) - { - var nvc = new NameValueCollection(); - foreach (var property in workItem.Properties) - { - nvc.Add(property.Key,property.Value); - } - - UpdateWorkItem(workItem.Id, workItem.Path, nvc); - } - - public void UpdateWorkItem(string id, string path, NameValueCollection properties) - { - WorkItem workItem; - if(!_workItemQuery.TryLocateWorkItem(id, out workItem)) - { - throw new ArgumentException("Work item was not found"); - } - - var leafStep = _workStepQuery.GetLeafStep(path); - - if (properties.Count > 0) - { - workItem = UpdateWorkItem(workItem, properties); - } - - if(workItem.Path!=leafStep.Path) - { - MoveWorkItem(workItem, leafStep); - } - } - - private WorkItem UpdateWorkItem(WorkItem workItem, NameValueCollection properties) - { - workItem = workItem.UpdateProperties(properties); - _workItemRepository.UpdateWorkItem(workItem); - return workItem; - } - - private void MoveWorkItem(WorkItem workItem, WorkStep toStep) - { - var transition = new WorkItemTransition(workItem, toStep); - - ThrowIfMovingParallelLockedWorkItem(transition); - - ThrowIfMovingExpandLockedWorkItem(transition); - - ThrowIfMovingFromTransientStepToParallelStep(transition); - - transition = CreateTransitionIfMovingToWithinParallelStep(transition); - - ThrowIfMovingToWithinExpandStep(transition); - - ThrowIfMovingToStepWithWrongClass(transition); - - transition = CreateTransitionIfMovingToExpandStep(transition); - - transition = AttemptMergeIfMovingChildOfParallelledWorkItem(transition); - - transition = CleanUpIfMovingFromTransientStep(transition); - - var resultTransition = DoMove(transition); - - TryUpdatingExpandLockIfMovingChildOfExpandedWorkItem(resultTransition); - } - - private void TryUpdatingExpandLockIfMovingChildOfExpandedWorkItem(WorkItemTransition resultTransition) - { - if (_workItemQuery.IsChildOfExpandedWorkItem(resultTransition.WorkItem)) - { - TryUpdatingExpandLockOnParent(resultTransition.WorkItem); - } - } - - private WorkItemTransition CleanUpIfMovingFromTransientStep(WorkItemTransition transition) - { - transition = new WorkItemTransition(CleanUpIfInTransientStep(transition.WorkItem),transition.WorkStep); - return transition; - } - - private WorkItemTransition AttemptMergeIfMovingChildOfParallelledWorkItem(WorkItemTransition transition) - { - if (_workItemQuery.IsChildOfParallelledWorkItem(transition.WorkItem)) - { - if (IsMergeable(transition.WorkItem, transition.WorkStep)) - { - var workItemToMove = MergeParallelWorkItems(transition.WorkItem); - - transition = new WorkItemTransition(workItemToMove, transition.WorkStep); - } - } - return transition; - } - - private WorkItemTransition CreateTransitionIfMovingToExpandStep(WorkItemTransition transition) - { - if (_workStepQuery.IsExpandStep(transition.WorkStep)) - { - var stepToMoveTo = CreateTransientWorkSteps(transition.WorkItem, transition.WorkStep); - var workItemToMove = transition.WorkItem.AddClass(stepToMoveTo.WorkItemClass); - - transition = new WorkItemTransition(workItemToMove, stepToMoveTo); - } - return transition; - } - - private void ThrowIfMovingToStepWithWrongClass(WorkItemTransition transition) - { - if (!_workStepQuery.IsValidWorkStepForWorkItem(transition.WorkItem, transition.WorkStep)) - { - throw new InvalidOperationException("Invalid step for work item"); - } - } - - private void ThrowIfMovingToWithinExpandStep(WorkItemTransition transition) - { - WorkStep dummyStep; - if (!_workStepQuery.IsExpandStep(transition.WorkStep) && !_workStepQuery.IsWithinTransientStep(transition.WorkStep,out dummyStep) && _workStepQuery.IsWithinExpandStep(transition.WorkStep)) - { - throw new InvalidOperationException("Cannot move item to within expand step"); - } - } - - private WorkItemTransition CreateTransitionIfMovingToWithinParallelStep(WorkItemTransition transition) - { - WorkStep parallelStep; - if (_workStepQuery.IsWithinParallelStep(transition.WorkStep, out parallelStep)) - { - if (!_workItemQuery.IsChildOfParallelledWorkItem(transition.WorkItem)) - { - var idToMove = ParallelStepHelper.GetParallelId(transition.WorkItem.Id, parallelStep, transition.WorkStep); - var workItemToMove = MoveAndLockAndSplitForParallelism(transition.WorkItem, parallelStep).First(wi => wi.Id==idToMove); - - return new WorkItemTransition(workItemToMove,transition.WorkStep); - } - } - return transition; - } - - private void ThrowIfMovingExpandLockedWorkItem(WorkItemTransition transition) - { - if (_workItemQuery.IsExpandLocked(transition.WorkItem)) - { - throw new InvalidOperationException("Item is expandlocked and cannot be moved"); - } - } + var creator = new WorkItemCreator(WorkStepRepository, WorkItemRepository); - private void ThrowIfMovingParallelLockedWorkItem(WorkItemTransition transition) - { - if (_workItemQuery.IsParallelLockedWorkItem(transition.WorkItem)) - { - throw new InvalidOperationException("Work item is locked for parallel work"); - } + creator.CreateWorkItem(newWorkItem); } - - private void ThrowIfMovingFromTransientStepToParallelStep(WorkItemTransition transition) + public void UpdateWorkItem(WorkItem changedWorkItem) { - WorkStep transientStep; - - var isInTransientStep = _workStepQuery.IsInTransientStep(transition.WorkItem, out transientStep); + var currentWorkItem = GetWorkItemOrThrow(changedWorkItem.Id); - WorkStep parallelStepRoot; - var isWithinParallelStep = _workStepQuery.IsWithinParallelStep(transition.WorkStep, out parallelStepRoot); - - if(isInTransientStep && isWithinParallelStep) - { - throw new InvalidOperationException("Cannot move directly from transient step to parallelstep"); - } - } + currentWorkItem = currentWorkItem.UpdatePropertiesAndOrdinalFrom(changedWorkItem); - private WorkItem CleanUpIfInTransientStep(WorkItem workItemToMove) - { - WorkStep transientStep; - if (_workStepQuery.IsInTransientStep(workItemToMove, out transientStep)) - { - DeleteChildWorkItems(workItemToMove); - _workflowRepository.DeleteWorkStepsRecursively(transientStep); - workItemToMove = workItemToMove.RemoveClass(transientStep.WorkItemClass); - } - return workItemToMove; - } + var leafStep = WorkStepRepository.GetLeafStep(changedWorkItem.Path); - private void DeleteChildWorkItems(WorkItem workItem) - { - foreach (var childWorkItem in _workItemRepository.GetChildWorkItems(workItem.Id)) + if (currentWorkItem.Path != leafStep.Path) { - DeleteWorkItem(childWorkItem.Id); - } - } - - private WorkItemTransition DoMove(WorkItemTransition transition) - { - var fromStepPath = transition.WorkItem.Path; - - var movedWorkItem = transition.WorkItem.MoveTo(transition.WorkStep); - - var ordinal = _workItemQuery.GetNextOrdinal(movedWorkItem); - movedWorkItem = movedWorkItem.UpdateOrdinal(ordinal); - - _workItemRepository.UpdateWorkItem(movedWorkItem); - - RenumOrdinals(fromStepPath); - - return new WorkItemTransition(movedWorkItem, transition.WorkStep); - } - - private void RenumOrdinals(string path) - { - var ordinal = 1; - foreach (var workItem in _workItemRepository.GetWorkItems(path).OrderBy(wi=>wi.Ordinal)) - { - _workItemRepository.UpdateWorkItem(workItem.UpdateOrdinal(ordinal++)); - } - } - - private void TryUpdatingExpandLockOnParent(WorkItem item) - { - var parent = _workItemRepository.GetWorkItem(item.ParentId); - - if (_workItemRepository.GetChildWorkItems(parent.Id).All(_workItemQuery.IsDone)) - { - parent = parent.UpdateStatus(WorkItemStatus.Normal); + var mover = new WorkItemMover(WorkStepRepository, WorkItemRepository); + mover.MoveWorkItem(currentWorkItem, leafStep); } else { - parent = parent.UpdateStatus(WorkItemStatus.ExpandLocked); + WorkItemRepository.UpdateWorkItem(currentWorkItem); } - _workItemRepository.UpdateWorkItem(parent); } - - - private WorkStep CreateTransientWorkSteps(WorkItem item, WorkStep expandStep) - { - Debug.Assert(expandStep.Type==WorkStepType.Expand); - - var transientRootPath = WorkStep.CombinePath(expandStep.Path,item.Id); - - CreateTransientWorkStepsRecursively(transientRootPath,expandStep, item.Id); - - var workItemClass = WorkItemClass.Combine(expandStep.WorkItemClass, item.Id); - var transientWorkStep = new WorkStep(transientRootPath, expandStep.Path, expandStep.Ordinal, WorkStepType.Transient, workItemClass, expandStep.Title); - _workflowRepository.CreateWorkStep(transientWorkStep); - - return transientWorkStep; - } - - private void CreateTransientWorkStepsRecursively(string transientRootPath, WorkStep rootStep, string workItemId) - { - var subSteps = _workflowRepository.GetChildWorkSteps(rootStep.Path).Where(ws=>ws.Type!=WorkStepType.Transient); - foreach (var childStep in subSteps) - { - var offset = childStep.Path.Remove(0, rootStep.Path.Length); - - var childTransientPath = transientRootPath + offset; - - var workItemClass = WorkItemClass.Combine(childStep.WorkItemClass,workItemId); - _workflowRepository.CreateWorkStep(new WorkStep(childTransientPath, transientRootPath, childStep.Ordinal, childStep.Type, workItemClass, childStep.Title)); - - CreateTransientWorkStepsRecursively(childTransientPath, childStep, workItemId); - } - } - - - private WorkItem MergeParallelWorkItems(WorkItem item) - { - WorkItem unlockedParent = _workItemRepository.GetWorkItem(item.ParentId).UpdateStatus(WorkItemStatus.Normal); - _workItemRepository.UpdateWorkItem(unlockedParent); - - foreach (WorkItem child in _workItemRepository.GetChildWorkItems(item.ParentId).ToList()) - { - _workItemRepository.DeleteWorkItem(child); - } - - return unlockedParent; - } - - private bool IsMergeable(WorkItem item, WorkStep toStep) - { - bool isMergeable = true; - foreach (WorkItem child in _workItemRepository.GetChildWorkItems(item.ParentId).Where(wi=>wi.Id!=item.Id)) - { - isMergeable &= child.Path == toStep.Path; - } - return isMergeable; - } - - - private IEnumerable MoveAndLockAndSplitForParallelism(WorkItem item, WorkStep parallelRootStep) - { - var lockedAndMovedItem = item.MoveTo(parallelRootStep).UpdateStatus(WorkItemStatus.ParallelLocked); - _workItemRepository.UpdateWorkItem(lockedAndMovedItem); - - var helper = new ParallelStepHelper(_workflowRepository); - - var splitWorkItems = helper.SplitForParallelism(item, parallelRootStep); - - foreach (var splitWorkItem in splitWorkItems) - { - _workItemRepository.CreateWorkItem(splitWorkItem); - } - - return splitWorkItems; - } - - public void DeleteWorkItem(string id) { - var workItem = _workItemRepository.GetWorkItem(id); - - ThrowInvalidOperationExceptionIfParentIsParallelLocked(workItem); - - DeleteWorkItemRecursively(workItem); + var workItemRemover = new WorkItemRemover(WorkStepRepository, WorkItemRepository); + workItemRemover.DeleteWorkItem(id); } - private void ThrowInvalidOperationExceptionIfParentIsParallelLocked(WorkItem workItem) - { - if(workItem.ParentId!=null) - { - var parent = _workItemRepository.GetWorkItem(workItem.ParentId); - if(parent.Status == WorkItemStatus.ParallelLocked) - { - throw new InvalidOperationException("Cannot delete workitem which is child of paralleled workitem"); - } - } - } - - private void DeleteWorkItemRecursively(WorkItem workItem) - { - var childWorkItems = _workItemRepository.GetChildWorkItems(workItem.Id); - - if(childWorkItems.Count()>0) - { - foreach (var childWorkItem in childWorkItems) - { - DeleteWorkItemRecursively(childWorkItem); - } - } - - - _workItemRepository.DeleteWorkItem(workItem); - RenumOrdinals(workItem.Path); - CleanUpIfInTransientStep(workItem); - } public bool ExistsWorkItem(string workItemId) { - return _workItemRepository.ExistsWorkItem(workItemId); + return WorkItemRepository.ExistsWorkItem(workItemId); } public WorkItem GetWorkItem(string id) { - return _workItemRepository.GetWorkItem(id); + return WorkItemRepository.GetWorkItem(id); } public bool ExistsWorkStep(string path) { - return _workflowRepository.ExistsWorkStep(path); + return WorkStepRepository.ExistsWorkStep(path); } public WorkStep GetWorkStep(string path) { - return _workflowRepository.GetWorkStep(path); + return WorkStepRepository.GetWorkStep(path); } public void CreateWorkStep(WorkStep workStep) { - _workflowRepository.CreateWorkStep(workStep); + WorkStepRepository.CreateWorkStep(workStep); + } + + private WorkItem GetWorkItemOrThrow(string workItemId) + { + WorkItem currentWorkItem; + if (!WorkItemRepository.TryLocateWorkItem(workItemId, out currentWorkItem)) + { + throw new ArgumentException("Work item was not found"); + } + return currentWorkItem; } - } - public interface IWorkflow - { - bool ExistsWorkItem(string workItemId); - bool ExistsWorkStep(string path); - void UpdateWorkItem(WorkItem workItem); - void UpdateWorkItem(string workItemId, string path, NameValueCollection properties); - void CreateWorkStep(WorkStep workStep); - void CreateWorkItem(WorkItem workItem); - WorkItem GetWorkItem(string workItemId); - void DeleteWorkItem(string workItemId); } } \ No newline at end of file diff --git a/WhiskWork.Synchronizer/EManagerSynchronizationAgent.cs b/WhiskWork.Synchronizer/EManagerSynchronizationAgent.cs index 84f448d..9d4a6c4 100644 --- a/WhiskWork.Synchronizer/EManagerSynchronizationAgent.cs +++ b/WhiskWork.Synchronizer/EManagerSynchronizationAgent.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Configuration; using System.Data; +using System.Globalization; using DominoInterOp; using WhiskWork.Core.Synchronization; using System.Web; @@ -51,6 +52,7 @@ public IEnumerable GetAll() var project = (string) row[5]; var unid = (string) row[6]; var status = (string)row[7]; + int? ordinal = ToNullableInt((string)row[8]); if (team != _team || release != _release) { @@ -66,25 +68,19 @@ public IEnumerable GetAll() {"team", team}, {"release", release}, {"project", project}, - {"leanstatus",leanStatus} + {"leanstatus",leanStatus}, + {"priority",ordinal.HasValue ? ordinal.Value.ToString() : string.Empty} }; - entries.Add(new SynchronizationEntry(id, status, properties)); + var entry = new SynchronizationEntry(id, status, properties) {Ordinal = ordinal}; + entries.Add(entry); } return entries; } - public void Create(SynchronizationEntry entry) - { - throw new NotImplementedException(); - } - public void Delete(SynchronizationEntry entry) - { - throw new NotImplementedException(); - } public void UpdateStatus(SynchronizationEntry entry) { @@ -99,7 +95,17 @@ public void UpdateStatus(SynchronizationEntry entry) dominoSource.Open(statusUpdatePath); } - public void UpdateProperties(SynchronizationEntry entry) + public void Create(SynchronizationEntry entry) + { + throw new NotImplementedException(); + } + + public void Delete(SynchronizationEntry entry) + { + throw new NotImplementedException(); + } + + public void UpdateData(SynchronizationEntry entry) { throw new NotImplementedException(); } @@ -119,6 +125,21 @@ private static DominoAuthenticatingHtmlSource Login() return dominoSource; } + private static int? ToNullableInt(string rowItem) + { + if (string.IsNullOrEmpty(rowItem)) + { + return null; + } + + decimal value; + if(!decimal.TryParse(rowItem, NumberStyles.Number, CultureInfo.CreateSpecificCulture("en"), out value)) + { + return null; + } + + return Convert.ToInt32(Math.Round(value)); + } } } \ No newline at end of file diff --git a/WhiskWork.Synchronizer/Program.cs b/WhiskWork.Synchronizer/Program.cs index 5379dfd..34ce094 100644 --- a/WhiskWork.Synchronizer/Program.cs +++ b/WhiskWork.Synchronizer/Program.cs @@ -24,13 +24,6 @@ private static void Main(string[] args) var map = new StatusSynchronizationMap(eManagerAgent, whiskWorkAgent); map.AddReciprocalEntry("0a - Scheduled for development", "/scheduled"); - //map.AddReciprocalEntry("2 - Development", "/wip/analysis/inprocess"); - //map.AddReverseEntry("/wip/anlysis/done", "2 - Development"); - //map.AddReverseEntry("/wip/development/inprocess", "2 - Development"); - //map.AddReciprocalEntry("3 - Ready for test", "/wip/development/done"); - //map.AddReverseEntry("/wip/feedback/review", "3 - Ready for test"); - //map.AddReverseEntry("/wip/feedback/test", "3 - Ready for test"); - map.AddReciprocalEntry("2 - Development", "/analysis/inprocess"); map.AddReverseEntry("/anlysis/done", "2 - Development"); map.AddReverseEntry("/development/inprocess", "2 - Development"); @@ -38,7 +31,6 @@ private static void Main(string[] args) map.AddReverseEntry("/feedback/review", "3 - Ready for test"); map.AddReverseEntry("/feedback/test", "3 - Ready for test"); - map.AddReciprocalEntry("4a ACCEPTED - In Dev", "/done"); map.AddForwardEntry("4a FAILED - In Dev", "/done"); map.AddForwardEntry("4b ACCEPTED - In Test", "/done"); @@ -50,22 +42,17 @@ private static void Main(string[] args) var creationSynchronizer = new CreationSynchronizer(map, eManagerAgent, whiskWorkAgent); var statusSynchronizer = new StatusSynchronizer(map, whiskWorkAgent, eManagerAgent); - - //foreach (var synchronizationEntry in eManagerAgent.GetAll()) - //{ - // Console.WriteLine(synchronizationEntry); - //} - - - //foreach (var entry in whiskWorkAgent.GetAll()) - //{ - // Console.WriteLine(entry); - //} + var dataSynchronizer = new DataSynchronizer(eManagerAgent, whiskWorkAgent); Console.WriteLine("Synchronizing existence (eManager->whiteboard)"); creationSynchronizer.Synchronize(); + Console.WriteLine("Synchronizing status whiteboard->eManager"); statusSynchronizer.Synchronize(); + + Console.WriteLine("Synchronizing data eManager->whiteboard"); + dataSynchronizer.Synchronize(); + } } } \ No newline at end of file diff --git a/WhiskWork.Synchronizer/WhiskWorkSynchronizationAgent.cs b/WhiskWork.Synchronizer/WhiskWorkSynchronizationAgent.cs index 523f34c..bbf5373 100644 --- a/WhiskWork.Synchronizer/WhiskWorkSynchronizationAgent.cs +++ b/WhiskWork.Synchronizer/WhiskWorkSynchronizationAgent.cs @@ -127,7 +127,7 @@ public void UpdateStatus(SynchronizationEntry entry) PostCsv(payload, entry.Status); } - public void UpdateProperties(SynchronizationEntry entry) + public void UpdateData(SynchronizationEntry entry) { var payload = CreatePayload(entry); @@ -139,6 +139,11 @@ private static string CreatePayload(SynchronizationEntry entry) var payloadBuilder = new StringBuilder(); payloadBuilder.AppendFormat("id={0}", entry.Id); + if(entry.Ordinal.HasValue) + { + payloadBuilder.AppendFormat(",ordinal={0}", entry.Ordinal.Value); + } + foreach (var keyValuePair in entry.Properties) { payloadBuilder.AppendFormat(",{0}={1}", HttpUtility.HtmlEncode(keyValuePair.Key), HttpUtility.HtmlEncode(keyValuePair.Value)); @@ -146,7 +151,6 @@ private static string CreatePayload(SynchronizationEntry entry) return payloadBuilder.ToString(); } - private void PostCsv(string payload, string path) { var request = (HttpWebRequest)WebRequest.Create(_site + path); diff --git a/WhiskWork.UnitTest/ExpandWorkStepsTest.cs b/WhiskWork.UnitTest/ExpandWorkStepsTest.cs index 51c6e6a..1e63a26 100644 --- a/WhiskWork.UnitTest/ExpandWorkStepsTest.cs +++ b/WhiskWork.UnitTest/ExpandWorkStepsTest.cs @@ -41,14 +41,17 @@ public void Init() public void ShouldNotCreateWorkItemInExpandStep() { AssertUtils.AssertThrows( - () => _wp.CreateWorkItem("cr1", "/development/inprocess")); + () => + { + _wp.CreateWorkItem(WorkItem.New("cr1","/development/inprocess")); + }); } [TestMethod] public void ShouldMoveToTransientStepWhenEnteringExpandStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.AreEqual("/development/inprocess/cr1", _workItemRepository.GetWorkItem("cr1").Path); } @@ -56,8 +59,8 @@ public void ShouldMoveToTransientStepWhenEnteringExpandStep() [TestMethod] public void ShouldCreateTransientWorkStepsWhenWorkItemEnterExpandStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.AreEqual(WorkStepType.Transient, _workflowRepository.GetWorkStep("/development/inprocess/cr1").Type); Assert.AreEqual(WorkStepType.Normal, @@ -73,8 +76,8 @@ public void ShouldCreateTransientWorkStepsWhenWorkItemEnterExpandStep() [TestMethod] public void ShouldCreateTitleOnTransientChildSteps() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.AreEqual("Tasks", _workflowRepository.GetWorkStep("/development/inprocess/cr1/tasks").Title); } @@ -83,10 +86,10 @@ public void ShouldCreateTitleOnTransientChildSteps() [TestMethod] public void ShouldNotIncludeTransientStepsOfEarlierWorkItemsWhenCreatingTransientSteps() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); - _wp.CreateWorkItem("cr2", "/analysis"); - _wp.UpdateWorkItem("cr2", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); + _wp.CreateWorkItem(WorkItem.New("cr2","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr2", "/development", new NameValueCollection())); AssertUtils.AssertThrows( () => _workflowRepository.GetWorkStep("/development/inprocess/cr2/cr1") @@ -97,28 +100,28 @@ public void ShouldNotIncludeTransientStepsOfEarlierWorkItemsWhenCreatingTransien [TestMethod] public void ShouldCreateChildItemOfExpandedWorkItemInLeafStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.AreEqual("/development/inprocess/cr1/tasks/new", _workItemRepository.GetWorkItem("cr1-1").Path); } [TestMethod] public void ShouldCreateChildItemOfExpandedWorkItemAsProperChildItem() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.AreEqual("cr1", _workItemRepository.GetWorkItem("cr1-1").ParentId); } [TestMethod] public void ShouldBeAbleToDeleteExpandedWorkItemWithoutChildren() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); Assert.IsTrue(_workItemRepository.ExistsWorkItem("cr1")); _wp.DeleteWorkItem("cr1"); @@ -128,20 +131,20 @@ public void ShouldBeAbleToDeleteExpandedWorkItemWithoutChildren() [TestMethod] public void ShouldLockExpandedWorkItemWhenChildItemIsCreated() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); Assert.AreEqual(WorkItemStatus.Normal, _workItemRepository.GetWorkItem("cr1").Status); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); } [TestMethod] public void ShouldBeAbleToDeleteChildOfExpandedWorkItem() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.IsTrue(_workItemRepository.ExistsWorkItem("cr1-1")); _wp.DeleteWorkItem("cr1-1"); Assert.IsFalse(_workItemRepository.ExistsWorkItem("cr1-1")); @@ -150,35 +153,35 @@ public void ShouldBeAbleToDeleteChildOfExpandedWorkItem() [TestMethod] public void ShouldMoveUnlockedExpandedWorkItemFromTransientStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); Assert.AreEqual("/development/inprocess/cr1", _workItemRepository.GetWorkItem("cr1").Path); - _wp.UpdateWorkItem("cr1", "/development/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/done", new NameValueCollection())); Assert.AreEqual("/development/done", _workItemRepository.GetWorkItem("cr1").Path); } [TestMethod] public void ShouldNotMoveLockedExpandedWorkItemFromExpandStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); AssertUtils.AssertThrows( () => - _wp.UpdateWorkItem("cr1", "/development/done", - new NameValueCollection()) - ); + { + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/done", new NameValueCollection())); + }); } [TestMethod] public void ShouldAlsoDeleteChildrenWhenDeletingExpandedWorkItem() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development/inprocess", new NameValueCollection()); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.IsTrue(_wp.ExistsWorkItem("cr1")); Assert.IsTrue(_wp.ExistsWorkItem("cr1-1")); @@ -192,8 +195,8 @@ public void ShouldAlsoDeleteChildrenWhenDeletingExpandedWorkItem() [TestMethod] public void ShouldDeleteChildStepsWhenMovingFromTransientExpandStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.IsTrue(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1")); Assert.IsTrue(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1/tasks")); @@ -201,7 +204,7 @@ public void ShouldDeleteChildStepsWhenMovingFromTransientExpandStep() Assert.IsTrue(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1/tasks/inprocess")); Assert.IsTrue(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1/tasks/done")); - _wp.UpdateWorkItem("cr1", "/development/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/done", new NameValueCollection())); Assert.IsFalse(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1")); Assert.IsFalse(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1/tasks")); Assert.IsFalse(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1/tasks/new")); @@ -212,8 +215,8 @@ public void ShouldDeleteChildStepsWhenMovingFromTransientExpandStep() [TestMethod] public void ShouldAlsoDeleteTransientChildStepsWhenDeletingExpandedWorkItem() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); _wp.DeleteWorkItem("cr1"); Assert.IsFalse(_workflowRepository.ExistsWorkStep("/development/inprocess/cr1")); @@ -226,8 +229,8 @@ public void ShouldAlsoDeleteTransientChildStepsWhenDeletingExpandedWorkItem() [TestMethod] public void ShouldSetWorkItemClassOnTransientSteps() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.AreEqual("cr-cr1", _workflowRepository.GetWorkStep("/development/inprocess/cr1").WorkItemClass); Assert.AreEqual("task-cr1", @@ -243,23 +246,25 @@ public void ShouldSetWorkItemClassOnTransientSteps() [TestMethod] public void ShouldNotMoveOtherWorkItemToTransientStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.AreEqual("/development/inprocess/cr1", _workItemRepository.GetWorkItem("cr1").Path); - _wp.CreateWorkItem("cr2", "/analysis"); + _wp.CreateWorkItem(WorkItem.New("cr2","/analysis")); AssertUtils.AssertThrows( - () => _wp.UpdateWorkItem("cr2", "/development/inprocess/cr1", new NameValueCollection()) - ); + () => + { + _wp.UpdateWorkItem(WorkItem.New("cr2", "/development/inprocess/cr1", new NameValueCollection())); + }); } [TestMethod] public void ShouldGiveChildOfExpandedItemCorrectClasses() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1/tasks/new"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1/tasks/new")); Assert.IsTrue(_workItemRepository.GetWorkItem("cr1-1").Classes.SetEquals("task", "task-cr1")); } @@ -267,21 +272,20 @@ public void ShouldGiveChildOfExpandedItemCorrectClasses() [TestMethod] public void ShouldNotMoveChildItemsCrossTransientSteps() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.CreateWorkItem("cr2", "/analysis"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.CreateWorkItem(WorkItem.New("cr2","/analysis")); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); - _wp.UpdateWorkItem("cr2", "/development", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); + _wp.UpdateWorkItem(WorkItem.New("cr2", "/development", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); - _wp.CreateWorkItem("cr2-1", "/development/inprocess/cr2/tasks/new"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); + _wp.CreateWorkItem(WorkItem.New("cr2-1","/development/inprocess/cr2/tasks/new")); AssertUtils.AssertThrows( () => - _wp.UpdateWorkItem("cr1-1", - "/development/inprocess/cr2/tasks/inprocess", - new NameValueCollection()) - ); + { + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/cr2/tasks/inprocess", new NameValueCollection())); + }); } [TestMethod] @@ -289,63 +293,66 @@ public void ShouldNotCreateWorkItemsBelowExpandStep() { AssertUtils.AssertThrows( () => - _wp.CreateWorkItem("cr1-1", "/development/inprocess/tasks/new") - ); + { + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/tasks/new")); + }); } [TestMethod] public void ShouldNotMoveChildOfWorkItemInTransientStepDirectlyToExpandStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); AssertUtils.AssertThrows( () => - _wp.UpdateWorkItem("cr1-1", "/development/inprocess", new NameValueCollection()) - ); + { + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess", new NameValueCollection())); + }); } [TestMethod] public void ShouldNotMoveChildOfWorkItemInTransientStepToStepUnderExpandStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); AssertUtils.AssertThrows( () => - _wp.UpdateWorkItem("cr1-1", "/development/inprocess/tasks/new", new NameValueCollection()) - ); + { + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/tasks/new", new NameValueCollection())); + }); } [TestMethod] public void ShouldRemoveExpandLockWhenSingleChildItemIsDone() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); - _wp.UpdateWorkItem("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection())); Assert.AreEqual(WorkItemStatus.Normal, _workItemRepository.GetWorkItem("cr1").Status); } [TestMethod] public void ShouldNotRemoveExpandLockWhenOneOfTwoChildItemsIsDone() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); - _wp.CreateWorkItem("cr1-2", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); + _wp.CreateWorkItem(WorkItem.New("cr1-2","/development/inprocess/cr1")); Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); - _wp.UpdateWorkItem("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection())); Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); } @@ -353,41 +360,41 @@ public void ShouldNotRemoveExpandLockWhenOneOfTwoChildItemsIsDone() [TestMethod] public void ShouldSetExpandLockWhenASingleDoneChildIsMovedToNotEndStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); - _wp.UpdateWorkItem("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection())); Assert.AreEqual(WorkItemStatus.Normal, _workItemRepository.GetWorkItem("cr1").Status); - _wp.UpdateWorkItem("cr1-1", "/development/inprocess/cr1/tasks/new", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/cr1/tasks/new", new NameValueCollection())); Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); } [TestMethod] public void ShouldRemoveTransientStepClassWhenMovedFromTransientStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.IsTrue(_workItemRepository.GetWorkItem("cr1").Classes.SetEquals("cr", "cr-cr1")); - _wp.UpdateWorkItem("cr1", "/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/done", new NameValueCollection())); Assert.IsTrue(_workItemRepository.GetWorkItem("cr1").Classes.SetEquals("cr")); } [TestMethod] public void ShouldDeleteChildItemsWhenWorkItemIsMovedOutOfTransientStep() { - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1"); - _wp.UpdateWorkItem("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1")); + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection())); - _wp.UpdateWorkItem("cr1", "/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/done", new NameValueCollection())); Assert.IsFalse(_wp.ExistsWorkItem("cr1-1")); } diff --git a/WhiskWork.UnitTest/ParallelWorkStepsTest.cs b/WhiskWork.UnitTest/ParallelWorkStepsTest.cs index b31a462..3c4ca21 100644 --- a/WhiskWork.UnitTest/ParallelWorkStepsTest.cs +++ b/WhiskWork.UnitTest/ParallelWorkStepsTest.cs @@ -26,8 +26,8 @@ public void Init() public void ShouldGetAllSubsteps() { _workflowRepository.Add("/feedback", "/", 1, WorkStepType.Parallel, "cr"); - _workflowRepository.Add("/feedback/review", "/feedback", 1, WorkStepType.Normal, "cr cr-review"); - _workflowRepository.Add("/feedback/test", "/feedback", 2, WorkStepType.Normal, "cr cr-test"); + _workflowRepository.Add("/feedback/review", "/feedback", 1, WorkStepType.Normal, "cr"); + _workflowRepository.Add("/feedback/test", "/feedback", 2, WorkStepType.Normal, "cr"); Assert.AreEqual(2, _workflowRepository.GetChildWorkSteps("/feedback").Count()); } @@ -35,7 +35,7 @@ public void ShouldGetAllSubsteps() public void ShouldFindSingleWorkItemAddedToAStep() { _workflowRepository.Add("/development", "/", 1, WorkStepType.Begin, "cr"); - _wp.CreateWorkItem("cr1", "/development"); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); WorkItem item = _workItemRepository.GetWorkItem("cr1"); @@ -50,7 +50,10 @@ public void ShouldNotCreateWorkItemInParallelStep() { CreateSimpleParallelWorkflow(); AssertUtils.AssertThrows( - () => _wp.CreateWorkItem("cr1", "/feedback")); + () => + { + _wp.CreateWorkItem(WorkItem.New("cr1","/feedback")); + }); } [TestMethod] @@ -58,7 +61,7 @@ public void ShoudSplitWorkItem() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); WorkItem item = _workItemRepository.GetWorkItem("cr1"); var parallelStepHelper = new ParallelStepHelper(_workflowRepository); @@ -83,10 +86,10 @@ public void ShouldLockWorkItemAndCreateChildWorkItemsWhenMovedToParallelStep() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); Assert.AreEqual(1, _wp.GetWorkItems("/development").Select(wi => wi.Id == "cr1").Count()); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/review").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(1, _wp.GetWorkItems("/development").Where(wi => wi.Id == "cr1-test").Count()); @@ -98,8 +101,8 @@ public void ShouldAlsoDeleteChildrenOfParalleledWorkItem() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); Assert.IsTrue(_wp.ExistsWorkItem("cr1")); Assert.IsTrue(_wp.ExistsWorkItem("cr1-review")); @@ -118,8 +121,8 @@ public void ShouldNotListParallelLockedWorkItem() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); Assert.AreEqual(0, _wp.GetWorkItems("/feedback").Where(wi => wi.Id == "cr1").Count()); } @@ -129,12 +132,14 @@ public void ShouldNotBeAbleToMoveParallelLockedWorkItem() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); AssertUtils.AssertThrows( - () => _wp.UpdateWorkItem("cr1", "/done", new NameValueCollection()) - ); + () => + { + _wp.UpdateWorkItem(WorkItem.New("cr1", "/done", new NameValueCollection())); + }); } @@ -143,8 +148,8 @@ public void ShouldLockWorkItemAndCreateChildWorkItemsWhenMovedToRootOfParallelSt { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/review").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(1, _wp.GetWorkItems("/development").Where(wi => wi.Id == "cr1-test").Count()); @@ -155,13 +160,13 @@ public void ShouldMoveSecondChildItemWhenParentParallelized() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/review").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(1, _wp.GetWorkItems("/development").Where(wi => wi.Id == "cr1-test").Count()); - _wp.UpdateWorkItem("cr1-test", "/feedback/test", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/feedback/test", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/test").Where(wi => wi.Id == "cr1-test").Count()); } @@ -170,8 +175,8 @@ public void ShouldOnlyBeAbleToMoveChildItemToDedicatedParallelStep() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/review").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(1, _wp.GetWorkItems("/development").Where(wi => wi.Id == "cr1-test").Count()); @@ -179,8 +184,9 @@ public void ShouldOnlyBeAbleToMoveChildItemToDedicatedParallelStep() AssertUtils.AssertThrows( ()=> - _wp.UpdateWorkItem("cr1-test", "/feedback/review", new NameValueCollection()) - ); + { + _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/feedback/review", new NameValueCollection())); + }); Assert.AreEqual(1, _wp.GetWorkItems("/development").Where(wi => wi.Id == "cr1-test").Count()); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/review").Where(wi => wi.Id == "cr1-review").Count()); @@ -191,8 +197,8 @@ public void ShouldNotBeAbleToDeleteChildOfParalleledWorkItem() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); AssertUtils.AssertThrows( () => @@ -205,39 +211,38 @@ public void ShouldMergeChildItemsWhenMovedToSameStepOutsideParallelization() { CreateSimpleParallelWorkflow(); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/review").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(1, _wp.GetWorkItems("/development").Where(wi => wi.Id == "cr1-test").Count()); - _wp.UpdateWorkItem("cr1-test", "/done", new NameValueCollection()); - _wp.UpdateWorkItem("cr1-review", "/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/done", new NameValueCollection())); + _wp.UpdateWorkItem(WorkItem.New("cr1-review", "/done", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/done").Where(wi => wi.Id == "cr1").Count()); Assert.AreEqual(0, _wp.GetWorkItems("/done").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(0, _wp.GetWorkItems("/done").Where(wi => wi.Id == "cr1-test").Count()); } [TestMethod] - public void ShouldMergeChildItemsWhenMovedToSameStepOutsideParallelizationAndChildWorkItemWasCreatedInExpandStep - () + public void ShouldMergeChildItemsWhenMovedToSameStepOutsideParallelizationAndChildWorkItemWasCreatedInExpandStep () { CreateParallelWorkflowWithExpandStep(); - _wp.CreateWorkItem("cr1", "/scheduled"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); - _wp.CreateWorkItem("cr1-1", "/development/inprocess/cr1/tasks"); - _wp.UpdateWorkItem("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection()); - _wp.UpdateWorkItem("cr1", "/development/done", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/scheduled")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); + _wp.CreateWorkItem(WorkItem.New("cr1-1","/development/inprocess/cr1/tasks")); + _wp.UpdateWorkItem(WorkItem.New("cr1-1", "/development/inprocess/cr1/tasks/done", new NameValueCollection())); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/done", new NameValueCollection())); - _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()); - _wp.UpdateWorkItem("cr1-test", "/feedback/test", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); + _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/feedback/test", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/review").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(1, _wp.GetWorkItems("/feedback/test").Where(wi => wi.Id == "cr1-test").Count()); - _wp.UpdateWorkItem("cr1-test", "/done", new NameValueCollection()); - _wp.UpdateWorkItem("cr1-review", "/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/done", new NameValueCollection())); + _wp.UpdateWorkItem(WorkItem.New("cr1-review", "/done", new NameValueCollection())); Assert.AreEqual(1, _wp.GetWorkItems("/done").Where(wi => wi.Id == "cr1").Count()); Assert.AreEqual(0, _wp.GetWorkItems("/done").Where(wi => wi.Id == "cr1-review").Count()); Assert.AreEqual(0, _wp.GetWorkItems("/done").Where(wi => wi.Id == "cr1-test").Count()); @@ -248,59 +253,79 @@ public void ShouldNotBeAbleToMoveFromTransientStepToParallelStep() { CreateParallelWorkflowWithExpandStep(); - _wp.CreateWorkItem("cr1", "/scheduled"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/scheduled")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); AssertUtils.AssertThrows( - () => _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()) - ); + () => + { + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); + }); } - [TestMethod, Ignore] - public void ShouldBeAbleToMoveFromTransientStepToParallelStepAndCreateNewTransientStepForParallelledSibling() - { - CreateParallelWorkflowWithExpandStep(); + //[TestMethod, Ignore] + //public void ShouldBeAbleToMoveFromTransientStepToParallelStepAndCreateNewTransientStepForParallelledSibling() + //{ + // CreateParallelWorkflowWithExpandStep(); - _wp.CreateWorkItem("cr1", "/scheduled"); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); - Assert.IsTrue(_wp.ExistsWorkStep("/development/inprocess/cr1")); + // _wp.CreateWorkItem("cr1", "/scheduled"); + // _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + // Assert.IsTrue(_wp.ExistsWorkStep("/development/inprocess/cr1")); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + // _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); - Assert.IsFalse(_wp.ExistsWorkStep("/development/inprocess/cr1")); - Assert.IsTrue(_wp.ExistsWorkStep("/development/inprocess/cr1-test")); + // Assert.IsFalse(_wp.ExistsWorkStep("/development/inprocess/cr1")); + // Assert.IsTrue(_wp.ExistsWorkStep("/development/inprocess/cr1-test")); - Assert.AreEqual("/development/inprocess/cr1-test", _wp.GetWorkItem("cr1-test").Path); - Assert.AreEqual("/feedback/review", _wp.GetWorkItem("cr1-review").Path); + // Assert.AreEqual("/development/inprocess/cr1-test", _wp.GetWorkItem("cr1-test").Path); + // Assert.AreEqual("/feedback/review", _wp.GetWorkItem("cr1-review").Path); - } + //} [TestMethod] public void ShouldBeAbleToMoveParalleledWorkItemToExpandStep() { CreateParallelWorkflowWithExpandStep(); - _wp.CreateWorkItem("cr1", "/scheduled"); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/scheduled")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); - _wp.UpdateWorkItem("cr1-review", "/development/inprocess", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-review", "/development/inprocess", new NameValueCollection())); var workItem = _wp.GetWorkItem("cr1-review"); Assert.AreEqual("/development/inprocess/cr1-review", workItem.Path); } + [TestMethod] + public void ShouldRemoveTransientStepWhenChildrenOfParallelledWorkItemAreMerged() + { + CreateParallelWorkflowWithExpandStep(); + + _wp.CreateWorkItem(WorkItem.New("cr1","/scheduled")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); + + _wp.UpdateWorkItem(WorkItem.New("cr1-review", "/development/inprocess", new NameValueCollection())); + Assert.IsTrue(_wp.ExistsWorkStep("/development/inprocess/cr1-review")); + + _wp.UpdateWorkItem(WorkItem.New("cr1-review", "/scheduled", new NameValueCollection())); + Assert.IsFalse(_wp.ExistsWorkStep("/development/inprocess/cr1-review")); + } + [TestMethod] public void ShouldNotBeAbleToMoveParallelledWorkItemToTransientStepOfSibling() { CreateParallelWorkflowWithExpandStep(); - _wp.CreateWorkItem("cr1", "/scheduled"); - _wp.UpdateWorkItem("cr1", "/feedback/review", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/scheduled")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback/review", new NameValueCollection())); - _wp.UpdateWorkItem("cr1-review", "/development/inprocess", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1-review", "/development/inprocess", new NameValueCollection())); AssertUtils.AssertThrows( - () => _wp.UpdateWorkItem("cr1-test", "/development/inprocess/cr1-review", new NameValueCollection())); + () => + { + _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/development/inprocess/cr1-review", new NameValueCollection())); + }); } private void CreateSimpleParallelWorkflow() diff --git a/WhiskWork.UnitTest/SynchronizerTest.cs b/WhiskWork.UnitTest/SynchronizerTest.cs index 3e8682f..322179d 100644 --- a/WhiskWork.UnitTest/SynchronizerTest.cs +++ b/WhiskWork.UnitTest/SynchronizerTest.cs @@ -139,7 +139,6 @@ public void ShouldIgnoreMissingMappingWhenSynchronizingStatus() } - [TestMethod] public void ShouldSynchronizeStatusIfSlaveEntriesAreMissing() { @@ -173,14 +172,14 @@ public void ShouldUpdateProperties() var masterStub = _mocks.Stub(); var slaveMock = _mocks.DynamicMock(); - var synchronizer = new PropertySynchronizer(masterStub, slaveMock); + var synchronizer = new DataSynchronizer(masterStub, slaveMock); using (_mocks.Record()) { SetupResult.For(masterStub.GetAll()).Return(new[] { Entry("1", "/done","Name","name1","Dev","dev1") }); Expect.Call(slaveMock.GetAll()).Return(new[] { Entry("1", "Development","Name","name2") }); - slaveMock.UpdateProperties(Entry("1", "Development", "Name","name1","Dev","dev1")); + slaveMock.UpdateData(Entry("1", "Development", "Name","name1","Dev","dev1")); LastCall.Repeat.Once(); } @@ -191,8 +190,6 @@ public void ShouldUpdateProperties() } - - private static SynchronizationEntry Entry(string id, string status) { return new SynchronizationEntry(id, status, new Dictionary()); diff --git a/WhiskWork.UnitTest/WorkStepTest.cs b/WhiskWork.UnitTest/WorkStepTest.cs index fcdf966..1d7fcb2 100644 --- a/WhiskWork.UnitTest/WorkStepTest.cs +++ b/WhiskWork.UnitTest/WorkStepTest.cs @@ -99,5 +99,36 @@ public void TwoEquallyCreatedWorkItemInstancesShouldHaveSameToString() Assert.AreEqual(ws1.ToString(), ws2.ToString()); } + + [TestMethod] + public void ShouldNotAllowNullWorkItemClass() + { + AssertUtils.AssertThrows( + () => new WorkStep("/step", "/", 1, WorkStepType.Normal, null) + ); + } + + [TestMethod] + public void ShouldNotAllowEmptyWorkItemClass() + { + AssertUtils.AssertThrows( + () => new WorkStep("/step", "/", 1, WorkStepType.Normal, string.Empty) + ); + } + + [TestMethod] + public void ShouldNotAllowWorkItemClassWithSpace() + { + AssertUtils.AssertThrows( + ( ) => new WorkStep("/step", "/", 1, WorkStepType.Normal, "class 1") + ); + } + + [TestMethod] + public void ShouldAllowWorkItemClassWithHyphen() + { + new WorkStep("/step", "/", 1, WorkStepType.Normal, "class-1"); + } + } } \ No newline at end of file diff --git a/WhiskWork.UnitTest/WorkflowTest.cs b/WhiskWork.UnitTest/WorkflowTest.cs index 4613459..dedcd9e 100644 --- a/WhiskWork.UnitTest/WorkflowTest.cs +++ b/WhiskWork.UnitTest/WorkflowTest.cs @@ -25,7 +25,7 @@ public void ShouldCreateWorkItemInBeginStep() _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); _workflowRepository.Add("/development", "/", 2, WorkStepType.Normal, "cr"); - _wp.CreateWorkItem("cr1", "/analysis"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); Assert.AreEqual("/analysis", _workItemRepository.GetWorkItem("cr1").Path); } @@ -36,7 +36,10 @@ public void ShouldNotCreateWorkItemInNormalStep() _workflowRepository.Add("/development", "/", 2, WorkStepType.Normal, "cr"); AssertUtils.AssertThrows( - () => _wp.CreateWorkItem("cr1", "/development")); + () => + { + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + }); } [TestMethod] @@ -44,7 +47,7 @@ public void ShouldCreateWorkItemWithSingleProperty() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection { { "Name", "CR1" } }); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "Name", "CR1" } })); var workItem = _wp.GetWorkItem("cr1"); Assert.AreEqual(1, workItem.Properties.Count); @@ -56,7 +59,7 @@ public void ShouldCreateWorkItemWithTwoProperties() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection { { "Name", "CR1" }, { "Developers", "A, B" } }); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "Name", "CR1" }, { "Developers", "A, B" } })); var workItem = _wp.GetWorkItem("cr1"); Assert.AreEqual(2, workItem.Properties.Count); @@ -69,8 +72,8 @@ public void ShouldUpdateOneOfTwoProperties() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection { { "Name", "CR1" }, { "Developer", "A" } }); - _wp.UpdateWorkItem("cr1", "/analysis", new NameValueCollection { { "Developer", "B" } }); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "Name", "CR1" }, { "Developer", "A" } })); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/analysis", new NameValueCollection { { "Developer", "B" } })); var workItem = _wp.GetWorkItem("cr1"); Assert.AreEqual(2, workItem.Properties.Count); @@ -79,13 +82,13 @@ public void ShouldUpdateOneOfTwoProperties() } [TestMethod] - public void ShouldMoveUpdateOneOfTwoProperties() + public void ShouldMoveAndUpdateOneOfTwoProperties() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); _workflowRepository.Add("/development", "/", 2, WorkStepType.Normal, "cr"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection { { "Name", "CR1" }, { "Developer", "A" } }); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection { { "Developer", "B" } }); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "Name", "CR1" }, { "Developer", "A" } })); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection { { "Developer", "B" } })); var workItem = _wp.GetWorkItem("cr1"); Assert.AreEqual(2, workItem.Properties.Count); @@ -94,6 +97,17 @@ public void ShouldMoveUpdateOneOfTwoProperties() Assert.AreEqual("B", workItem.Properties["Developer"]); } + [TestMethod] + public void ShouldUpdateWorkItemOrdinalWhenNotMoving() + { + _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); + + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "Name", "CR1" }, { "Developer", "A" } })); + _wp.UpdateWorkItem(WorkItem.New("cr1","/analysis").UpdateOrdinal(3)); + var workItem = _wp.GetWorkItem("cr1"); + + Assert.AreEqual(3, workItem.Ordinal); + } [TestMethod] @@ -108,8 +122,8 @@ public void ShouldSetCorrectOrdinalWhenMovingWorkItem() { SetUpAndTestBasicOrdinal(); - _wp.UpdateWorkItem("cr2", "/development", new NameValueCollection()); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr2", "/development", new NameValueCollection())); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.AreEqual(1, _workItemRepository.GetWorkItem("cr2").Ordinal); Assert.AreEqual(2, _workItemRepository.GetWorkItem("cr1").Ordinal); @@ -120,7 +134,7 @@ public void ShouldRenumOrdinalsInFromStepWhenMovingWorkItem() { SetUpAndTestBasicOrdinal(); - _wp.UpdateWorkItem("cr1", "/development", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); Assert.AreEqual(1, _workItemRepository.GetWorkItem("cr2").Ordinal); Assert.AreEqual(2, _workItemRepository.GetWorkItem("cr3").Ordinal); @@ -137,15 +151,28 @@ public void ShouldRenumOrdinalsWhenDeletingWorkItem() Assert.AreEqual(2, _workItemRepository.GetWorkItem("cr3").Ordinal); } + [TestMethod] + public void ShouldCreateWorkItemWithOrdinalsProvided() + { + _workflowRepository.Add("/development", "/", 1, WorkStepType.Begin, "cr"); + + _wp.CreateWorkItem(WorkItem.New("cr1", "/development").UpdateOrdinal(3)); + _wp.CreateWorkItem(WorkItem.New("cr2", "/development").UpdateOrdinal(2)); + _wp.CreateWorkItem(WorkItem.New("cr3", "/development").UpdateOrdinal(1)); + + Assert.AreEqual(3, _workItemRepository.GetWorkItem("cr1").Ordinal); + Assert.AreEqual(2, _workItemRepository.GetWorkItem("cr2").Ordinal); + Assert.AreEqual(1, _workItemRepository.GetWorkItem("cr3").Ordinal); + } private void SetUpAndTestBasicOrdinal() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); _workflowRepository.Add("/development", "/", 1, WorkStepType.End, "cr"); - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.CreateWorkItem("cr2", "/analysis"); - _wp.CreateWorkItem("cr3", "/analysis"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.CreateWorkItem(WorkItem.New("cr2","/analysis")); + _wp.CreateWorkItem(WorkItem.New("cr3","/analysis")); Assert.AreEqual(1, _workItemRepository.GetWorkItem("cr1").Ordinal); Assert.AreEqual(2, _workItemRepository.GetWorkItem("cr2").Ordinal); diff --git a/WhiskWork.Web.UnitTest/CsvRequestMessageParserTest.cs b/WhiskWork.Web.UnitTest/CsvRequestMessageParserTest.cs index f438ceb..2bc7dc1 100644 --- a/WhiskWork.Web.UnitTest/CsvRequestMessageParserTest.cs +++ b/WhiskWork.Web.UnitTest/CsvRequestMessageParserTest.cs @@ -18,7 +18,6 @@ public void ShouldParseIdAsWorkItem() Assert.AreEqual("id1", item.Id); } - [TestMethod] public void ShouldParseWorkItemProperties() { @@ -31,11 +30,21 @@ public void ShouldParseWorkItemProperties() Assert.AreEqual("dev1", item.Properties["dev"]); } + [TestMethod] + public void ShouldParseWorkItemOrdinal() + { + var parser = new CsvRequestMessageParser(); + var node = (WorkItemNode)parser.Parse(CreateStream("id=id1,ordinal=2")); + + var item = node.GetWorkItem("/"); + Assert.AreEqual(2,item.Ordinal); + } + [TestMethod] public void ShouldParseStepAsWorkStep() { var parser = new CsvRequestMessageParser(); - var node = parser.Parse(CreateStream("step=step1")) as WorkStepNode; + var node = parser.Parse(CreateStream("step=step1,class=cr")) as WorkStepNode; Assert.IsNotNull(node); var step = node.GetWorkStep("/"); diff --git a/WhiskWork.Web.UnitTest/HtmlRenderTest.cs b/WhiskWork.Web.UnitTest/HtmlRenderTest.cs index 4313be5..45b3871 100644 --- a/WhiskWork.Web.UnitTest/HtmlRenderTest.cs +++ b/WhiskWork.Web.UnitTest/HtmlRenderTest.cs @@ -92,7 +92,7 @@ public void ShouldRenderClasses() public void ShouldRenderSingleWorkItemInSingleStepWorkflow() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr", "Analysis"); - _wp.CreateWorkItem("cr1","/analysis"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); var doc = GetFullDocument(); @@ -103,7 +103,7 @@ public void ShouldRenderSingleWorkItemInSingleStepWorkflow() public void ShouldRenderSingleWorkItemProperty() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr", "Analysis"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection {{"Name","CR1"}}); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection {{"Name","CR1"}})); var doc = GetFullDocument(); @@ -115,7 +115,7 @@ public void ShouldRenderSingleWorkItemProperty() public void ShouldRenderTwoWorkItemProperties() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr", "Analysis"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection { { "Name", "CR1" },{"Developer","A"} }); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "Name", "CR1" },{"Developer","A"} })); var doc = GetFullDocument(); @@ -130,7 +130,7 @@ public void ShouldRenderTwoWorkItemProperties() public void ShouldLowerCasePropertyKeyInClass() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr", "Analysis"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection { { "Name", "value" } }); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "Name", "value" } })); var doc = GetFullDocument(); @@ -142,20 +142,30 @@ public void ShouldLowerCasePropertyKeyInClass() public void ShouldHtmlEncodePropertyValues() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr", "Analysis"); - _wp.CreateWorkItem("cr1", "/analysis", new NameValueCollection { { "name", "&<> " } }); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis",new NameValueCollection { { "name", "&<> " } })); var doc = GetFullDocument(); Assert.AreEqual("&<> ", doc.SelectSingleNode("//li[@id=\"cr1\"]/dl/dd[@class='name']").InnerText); - } + [TestMethod] + public void ShouldHtmlEncodeTitle() + { + _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr", ""); + + var doc = GetFullDocument(); + + Assert.AreEqual("", doc.SelectSingleNode("//h1").InnerText.Trim()); + } + + [TestMethod] public void ShouldRenderTwoWorkItemsInSingleStepWorkflow() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr", "Analysis"); - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.CreateWorkItem("cr2", "/analysis"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.CreateWorkItem(WorkItem.New("cr2","/analysis")); var doc = GetFullDocument(); @@ -169,12 +179,12 @@ public void ShouldRenderAWorkItemInEachStepForAThreeStepWorkflow() _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); _workflowRepository.Add("/development", "/", 2, WorkStepType.Normal, "cr"); _workflowRepository.Add("/done", "/", 3, WorkStepType.End, "cr"); - _wp.CreateWorkItem("cr1", "/analysis"); - _wp.CreateWorkItem("cr2", "/analysis"); - _wp.CreateWorkItem("cr3", "/analysis"); + _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); + _wp.CreateWorkItem(WorkItem.New("cr2","/analysis")); + _wp.CreateWorkItem(WorkItem.New("cr3","/analysis")); - _wp.UpdateWorkItem("cr2","/development", new NameValueCollection()); - _wp.UpdateWorkItem("cr3", "/done", new NameValueCollection()); + _wp.UpdateWorkItem(WorkItem.New("cr2", "/development", new NameValueCollection())); + _wp.UpdateWorkItem(WorkItem.New("cr3", "/done", new NameValueCollection())); var doc = GetFullDocument(); @@ -223,7 +233,7 @@ public void ShouldNotRenderH1TagIfWorkStepTitleIsEmpty() public void ShouldRenderWorkItemWithRightClasses() { _workflowRepository.Add("/development", "/", 1, WorkStepType.Begin, "cr"); - _wp.CreateWorkItem("cr1", "/development"); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); var doc = GetFullDocument(); @@ -236,7 +246,7 @@ public void ShouldRenderWorkItemWithRightClasses() public void ShouldRenderFromLeafStep() { _workflowRepository.Add("/scheduled", "/", 1, WorkStepType.Begin, "cr"); - _wp.CreateWorkItem("cr1", "/scheduled"); + _wp.CreateWorkItem(WorkItem.New("cr1","/scheduled")); var doc = GetFullDocument(_workflowRepository.GetWorkStep("/scheduled")); @@ -263,9 +273,9 @@ public void ShouldRenderParalleledChildItemsWithRightClasses() _workflowRepository.Add("/feedback/review", "/feedback", 1, WorkStepType.Normal, "cr-review"); _workflowRepository.Add("/feedback/test", "/feedback", 2, WorkStepType.Normal, "cr-test"); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()); - + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); + var doc = GetFullDocument(); Assert.AreEqual("workitem cr cr-test", doc.SelectSingleNode("//li[@id=\"development\"]/ol/li[@id=\"cr1-test\"]/@class").Value); @@ -280,8 +290,8 @@ public void ShouldNotRenderParallelLockedWorkItem() _workflowRepository.Add("/feedback/review", "/feedback", 1, WorkStepType.Normal, "cr-review"); _workflowRepository.Add("/feedback/test", "/feedback", 2, WorkStepType.Normal, "cr-test"); - _wp.CreateWorkItem("cr1", "/development"); - _wp.UpdateWorkItem("cr1", "/feedback", new NameValueCollection()); + _wp.CreateWorkItem(WorkItem.New("cr1","/development")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/feedback", new NameValueCollection())); var doc = GetFullDocument(); @@ -449,12 +459,18 @@ public void FullFeatureTest() private void Create(string path, params string[] workItemIds) { - Array.ForEach(workItemIds, workItemId => _wp.CreateWorkItem(workItemId,path) ); + Array.ForEach(workItemIds, workItemId => + { + _wp.CreateWorkItem(WorkItem.New(workItemId,path)); + }); } private void Move(string path, params string[] workItemIds) { - Array.ForEach(workItemIds, workItemId => _wp.UpdateWorkItem(workItemId, path, new NameValueCollection())); + Array.ForEach(workItemIds, workItemId => + { + _wp.UpdateWorkItem(WorkItem.New(workItemId, path, new NameValueCollection())); + }); } diff --git a/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs b/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs index 2e0565c..6c908a4 100644 --- a/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs +++ b/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs @@ -217,12 +217,12 @@ private void AssertExceptionIsCaughtAndHttpStatusCodeReturnedForCreateWorkStep(E { using (_mocks.Record()) { - _workflow.CreateWorkStep(new WorkStep("/scheduled", "/", 0, WorkStepType.Normal, null, null)); + _workflow.CreateWorkStep(new WorkStep("/scheduled", "/", 0, WorkStepType.Normal, "cr", null)); LastCall.Throw(exception); } using (_mocks.Playback()) { - var request = CreateCsvRequest("post", "/", "step=/scheduled"); + var request = CreateCsvRequest("post", "/", "step=/scheduled,class=cr"); Assert.AreEqual(httpStatusCode, _httpHandler.HandleRequest(request).HttpStatusCode); } } diff --git a/WhiskWork.Web/CsvRequestMessageParser.cs b/WhiskWork.Web/CsvRequestMessageParser.cs index e5844ca..bc3c169 100644 --- a/WhiskWork.Web/CsvRequestMessageParser.cs +++ b/WhiskWork.Web/CsvRequestMessageParser.cs @@ -1,82 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using WhiskWork.Core; using System.Linq; namespace WhiskWork.Web { - public abstract class RequestMessageParser : IRequestMessageParser - { - private Dictionary _values; - - public IWorkflowNode Parse(Stream messageStream) - { - var reader = new StreamReader(messageStream); - var content = reader.ReadToEnd(); - - if(string.IsNullOrEmpty(content)) - { - throw new ArgumentException("Missing data"); - } - - _values = GetKeyValueMap(content); - - if(_values.ContainsKey("id")) - { - return ParseWorkItem(); - } - - if(_values.ContainsKey("step")) - { - return ParseWorkStep(_values); - } - - throw new ArgumentException("Unrecognized data"); - } - - protected abstract Dictionary GetKeyValueMap(string content); - - private IWorkflowNode ParseWorkStep(Dictionary contentParts) - { - //,,,,,<ordinal> - - var step = ExtractValue("step",s=>s,null); - var type = ExtractValue("type", s => (WorkStepType) Enum.Parse(typeof (WorkStepType), s,true),WorkStepType.Normal); - var workItemClass = ExtractValue("class",s=>s,null); - var ordinal = ExtractValue("ordinal", s=> int.Parse(s), 0); - var title = ExtractValue("title", s => s, null); - - return new WorkStepNode(step, ordinal, type, workItemClass, title); - } - - private IWorkflowNode ParseWorkItem() - { - var id = ExtractValue("id", s => s, null); - - var properties = new NameValueCollection(); - - foreach (KeyValuePair<string,string> keyValuePair in _values) - { - properties.Add(keyValuePair.Key, keyValuePair.Value); - } - return new WorkItemNode(id, properties); - } - - private T ExtractValue<T>(string key, Converter<string,T> convert, T defaultValue) - { - if(_values.ContainsKey(key)) - { - var convertedValue = convert(_values[key]); - _values.Remove(key); - return convertedValue; - } - - return defaultValue; - } - } - public class CsvRequestMessageParser : RequestMessageParser { #region IRequestMessageParser Members diff --git a/WhiskWork.Web/HtmlRenderer.cs b/WhiskWork.Web/HtmlRenderer.cs index 7d6830c..86406c8 100644 --- a/WhiskWork.Web/HtmlRenderer.cs +++ b/WhiskWork.Web/HtmlRenderer.cs @@ -61,13 +61,11 @@ private void RenderWorkStepsRecursively(HtmlTextWriter writer, WorkStep workStep { RenderWorkItemList(writer, workStep); - var query = new WorkStepQuery(_workStepRepository); - - if (query.IsParallelStep(workStep)) + if (_workStepRepository.IsParallelStep(workStep)) { RenderParallelStep(writer, workStep); } - else if(query.IsExpandStep(workStep)) + else if (_workStepRepository.IsExpandStep(workStep)) { RenderExpandStep(writer, workStep); } @@ -112,7 +110,7 @@ private void RenderWorkStepListItems(HtmlTextWriter writer, IEnumerable<WorkStep private void RenderWorkStepListItem(HtmlTextWriter writer, WorkStep workStep) { - if(!new WorkStepQuery(_workStepRepository).IsExpandStep(workStep) ) + if(!_workStepRepository.IsExpandStep(workStep) ) { var id = GenerateWorkStepId(workStep); writer.AddAttribute(HtmlTextWriterAttribute.Id, id); @@ -134,23 +132,23 @@ private void RenderWorkStepListItem(HtmlTextWriter writer, WorkStep workStep) private void RenderExpandStep(HtmlTextWriter writer, WorkStep workStep) { writer.RenderBeginTag(HtmlTextWriterTag.Ol); - RenderExpandTransientListItems(writer, workStep); - RenderExpandTemplateListItem(writer, workStep); + RenderTransientListItems(writer, workStep); + RenderExpandListItem(writer, workStep); writer.RenderEndTag(); //ol } - private void RenderExpandTransientListItems(HtmlTextWriter writer, WorkStep step) + private void RenderTransientListItems(HtmlTextWriter writer, WorkStep step) { var transientSteps = _workStepRepository.GetChildWorkSteps(step.Path).Where(ws => ws.Type == WorkStepType.Transient); foreach (var transientStep in transientSteps) { - RenderExpandTransientListItem(writer,transientStep); + RenderTransientListItem(writer,transientStep); } } - private void RenderExpandTransientListItem(HtmlTextWriter writer, WorkStep transientStep) + private void RenderTransientListItem(HtmlTextWriter writer, WorkStep transientStep) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "transient"); writer.RenderBeginTag(HtmlTextWriterTag.Li); @@ -173,7 +171,7 @@ private void RenderExpandTransientListItem(HtmlTextWriter writer, WorkStep trans writer.RenderEndTag(); //li } - private void RenderExpandTemplateListItem(HtmlTextWriter writer, WorkStep workStep) + private void RenderExpandListItem(HtmlTextWriter writer, WorkStep workStep) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "expand"); writer.RenderBeginTag(HtmlTextWriterTag.Li); @@ -204,7 +202,7 @@ private static void RenderTitle(HtmlTextWriter writer, WorkStep step) } writer.RenderBeginTag(HtmlTextWriterTag.H1); - writer.Write(step.Title); + writer.Write(HttpUtility.HtmlEncode(step.Title)); writer.RenderEndTag(); //h1 } @@ -270,9 +268,8 @@ private static string GenerateWorkItemClass(WorkItem item) private string GenerateWorkStepClass(WorkStep workStep) { var classes = new List<string>(); - var query = new WorkStepQuery(_workStepRepository); - if(!query.IsExpandStep(workStep) && query.IsLeafStep(workStep)) + if(!_workStepRepository.IsExpandStep(workStep) && _workStepRepository.IsLeafStep(workStep)) { classes.AddRange(GetLeafStepClasses(workStep)); } diff --git a/WhiskWork.Web/RequestMessageParser.cs b/WhiskWork.Web/RequestMessageParser.cs new file mode 100644 index 0000000..ed95909 --- /dev/null +++ b/WhiskWork.Web/RequestMessageParser.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using WhiskWork.Core; + +namespace WhiskWork.Web +{ + public abstract class RequestMessageParser : IRequestMessageParser + { + private Dictionary<string, string> _values; + + public IWorkflowNode Parse(Stream messageStream) + { + var reader = new StreamReader(messageStream); + var content = reader.ReadToEnd(); + + if(string.IsNullOrEmpty(content)) + { + throw new ArgumentException("Missing data"); + } + + _values = GetKeyValueMap(content); + + if(_values.ContainsKey("id")) + { + return ParseWorkItem(); + } + + if(_values.ContainsKey("step")) + { + return ParseWorkStep(_values); + } + + throw new ArgumentException("Unrecognized data"); + } + + protected abstract Dictionary<string,string> GetKeyValueMap(string content); + + private IWorkflowNode ParseWorkStep(Dictionary<string,string> contentParts) + { + //<path>,<parentPath>,<worksteptype>,<workItemClass>,<title>,<ordinal> + + var step = ExtractValue("step",s=>s,null); + var type = ExtractValue("type", s => (WorkStepType) Enum.Parse(typeof (WorkStepType), s,true),WorkStepType.Normal); + var workItemClass = ExtractValue("class",s=>s,null); + var ordinal = ExtractValue("ordinal", s=> int.Parse(s), 0); + var title = ExtractValue("title", s => s, null); + + return new WorkStepNode(step, ordinal, type, workItemClass, title); + } + + private IWorkflowNode ParseWorkItem() + { + var id = ExtractValue("id", s => s, null); + var ordinal = ExtractValue<int?>("ordinal", s => int.Parse(s), null); + + var properties = new NameValueCollection(); + + foreach (var keyValuePair in _values) + { + properties.Add(keyValuePair.Key, keyValuePair.Value); + } + return new WorkItemNode(id, ordinal, properties); + } + + private T ExtractValue<T>(string key, Converter<string,T> convert, T defaultValue) + { + if(_values.ContainsKey(key)) + { + var convertedValue = convert(_values[key]); + _values.Remove(key); + return convertedValue; + } + + return defaultValue; + } + } +} \ No newline at end of file diff --git a/WhiskWork.Web/WhiskWork.Web.csproj b/WhiskWork.Web/WhiskWork.Web.csproj index 5fee1f7..4004a61 100644 --- a/WhiskWork.Web/WhiskWork.Web.csproj +++ b/WhiskWork.Web/WhiskWork.Web.csproj @@ -56,6 +56,7 @@ <Compile Include="IWorkStepRenderer.cs" /> <Compile Include="IWorkStepRendererFactory.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="RequestMessageParser.cs" /> <Compile Include="RequestMessageParserFactory.cs" /> <Compile Include="WebCommunication.cs" /> <Compile Include="WorkflowHttpHandler.cs" /> diff --git a/WhiskWork.Web/WorkItemNode.cs b/WhiskWork.Web/WorkItemNode.cs index 42bd2dd..7a972b4 100644 --- a/WhiskWork.Web/WorkItemNode.cs +++ b/WhiskWork.Web/WorkItemNode.cs @@ -6,10 +6,12 @@ namespace WhiskWork.Web public class WorkItemNode : IWorkflowNode { private readonly NameValueCollection _properties; - private readonly string _id; - public WorkItemNode(string id, NameValueCollection properties) + private readonly string _id; + private readonly int? _ordinal; + public WorkItemNode(string id, int? ordinal, NameValueCollection properties) { _id = id; + _ordinal = ordinal; _properties = properties; } @@ -20,7 +22,7 @@ public void AcceptVisitor(IWorkflowNodeVisitor visitor) public WorkItem GetWorkItem(string path) { - return WorkItem.NewUnchecked(_id, path, _properties); + return WorkItem.NewUnchecked(_id, path, _ordinal, _properties); } } } \ No newline at end of file