diff --git a/WhiskWork.Core/Properties/AssemblyInfo.cs b/WhiskWork.Core/Properties/AssemblyInfo.cs index eb34623..eedbf13 100644 --- a/WhiskWork.Core/Properties/AssemblyInfo.cs +++ b/WhiskWork.Core/Properties/AssemblyInfo.cs @@ -1,10 +1,6 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. [assembly: AssemblyTitle("WhiskWork.Core")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] diff --git a/WhiskWork.Core/WorkItem.cs b/WhiskWork.Core/WorkItem.cs index 7ba4440..1090c17 100644 --- a/WhiskWork.Core/WorkItem.cs +++ b/WhiskWork.Core/WorkItem.cs @@ -164,13 +164,9 @@ public WorkItem ReplacesClasses(IEnumerable newClasses) 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]; - } + var modifiedProperties = GetModifiedProperties(item.Properties); if(item.HasOrdinal) { @@ -180,18 +176,35 @@ public WorkItem UpdatePropertiesAndOrdinalFrom(WorkItem item) return new WorkItem(Id, Path, Classes, Status, ParentId, modifiedOrdinal, modifiedProperties); } + public WorkItem UpdateProperties(WorkItemProperties properties) + { + var modifiedProperties = GetModifiedProperties(properties); + + return new WorkItem(Id, Path, Classes, Status, ParentId, _ordinal, modifiedProperties); + } + + private NameValueCollection GetModifiedProperties(WorkItemProperties propertyUpdate) { var modifiedProperties = new NameValueCollection(_properties); - foreach (var key in properties.AllKeys) + foreach (var key in propertyUpdate.AllKeys) { - modifiedProperties[key] = properties[key]; + var newValue = propertyUpdate[key]; + + if (string.IsNullOrEmpty(newValue)) + { + modifiedProperties.Remove(key); + } + else + { + modifiedProperties[key] = newValue; + } } - - return new WorkItem(Id, Path, Classes, Status, ParentId, _ordinal, modifiedProperties); + return modifiedProperties; } + public WorkItem UpdateProperties(NameValueCollection properties) { var modifiedProperties = new NameValueCollection(_properties); diff --git a/WhiskWork.Core/WorkItemCreator.cs b/WhiskWork.Core/WorkItemCreator.cs index 64f7fb8..82d7390 100644 --- a/WhiskWork.Core/WorkItemCreator.cs +++ b/WhiskWork.Core/WorkItemCreator.cs @@ -26,10 +26,7 @@ public void CreateWorkItem(WorkItem newWorkItem) WorkStep transientStep; if (WorkStepRepository.IsWithinTransientStep(leafStep, out transientStep)) { - var workItems = WorkItemRepository.GetWorkItems(transientStep.Path); - Debug.Assert(workItems.Count() == 1); - - var parentItem = workItems.ElementAt(0); + WorkItem parentItem = GetTransientParentWorkItem(transientStep); WorkItemRepository.UpdateWorkItem(parentItem.UpdateStatus(WorkItemStatus.ExpandLocked)); newWorkItem = newWorkItem.MoveTo(leafStep).UpdateParent(parentItem); @@ -54,6 +51,12 @@ public void CreateWorkItem(WorkItem newWorkItem) WorkItemRepository.CreateWorkItem(newWorkItem); } - + + private WorkItem GetTransientParentWorkItem(WorkStep transientStep) + { + var workItemId = transientStep.Path.Split(WorkStep.Separator).Last(); + + return WorkItemRepository.GetWorkItem(workItemId); + } } } \ No newline at end of file diff --git a/WhiskWork.Core/WorkItemMover.cs b/WhiskWork.Core/WorkItemMover.cs index 585e0f7..d04c1f3 100644 --- a/WhiskWork.Core/WorkItemMover.cs +++ b/WhiskWork.Core/WorkItemMover.cs @@ -32,7 +32,7 @@ public void MoveWorkItem(WorkItem workItem, WorkStep toStep) ThrowIfMovingExpandLockedWorkItem(transition); - ThrowIfMovingFromTransientStepToParallelStep(transition); + ThrowIfMovingFromExpandStepToParallelStep(transition); transition = CreateTransitionIfMovingToWithinParallelStep(transition); @@ -42,7 +42,7 @@ public void MoveWorkItem(WorkItem workItem, WorkStep toStep) transition = CreateTransitionIfMovingToExpandStep(transition); - transition = CleanUpIfMovingFromTransientStep(transition); + transition = CleanUpIfMovingFromExpandStep(transition); transition = AttemptMergeIfMovingChildOfParallelledWorkItem(transition); @@ -68,18 +68,18 @@ private void ThrowIfMovingExpandLockedWorkItem(WorkItemTransition transition) } } - private void ThrowIfMovingFromTransientStepToParallelStep(WorkItemTransition transition) + private void ThrowIfMovingFromExpandStepToParallelStep(WorkItemTransition transition) { - WorkStep transientStep; + WorkStep expandStep; - var isInTransientStep = WorkStepRepository.IsInTransientStep(transition.WorkItem, out transientStep); + var isInExpandStep = WorkStepRepository.IsInExpandStep(transition.WorkItem, out expandStep); WorkStep parallelStepRoot; var isWithinParallelStep = WorkStepRepository.IsWithinParallelStep(transition.WorkStep, out parallelStepRoot); - if (isInTransientStep && isWithinParallelStep) + if (isInExpandStep && isWithinParallelStep) { - throw new InvalidOperationException("Cannot move directly from transient step to parallelstep"); + throw new InvalidOperationException("Cannot move directly from expand step to parallelstep"); } } @@ -120,15 +120,12 @@ private WorkItemTransition CreateTransitionIfMovingToExpandStep(WorkItemTransiti { if (WorkStepRepository.IsExpandStep(transition.WorkStep)) { - var stepToMoveTo = CreateTransientWorkSteps(transition.WorkItem, transition.WorkStep); - var workItemToMove = transition.WorkItem.AddClass(stepToMoveTo.WorkItemClass); - - transition = new WorkItemTransition(workItemToMove, stepToMoveTo); + CreateTransientWorkSteps(transition.WorkItem, transition.WorkStep); } return transition; } - private WorkItemTransition CleanUpIfMovingFromTransientStep(WorkItemTransition transition) + private WorkItemTransition CleanUpIfMovingFromExpandStep(WorkItemTransition transition) { var remover = new WorkItemRemover(WorkStepRepository, WorkItemRepository); @@ -192,7 +189,7 @@ private WorkStep CreateTransientWorkSteps(WorkItem item, WorkStep expandStep) { Debug.Assert(expandStep.Type == WorkStepType.Expand); - var transientRootPath = WorkStep.CombinePath(expandStep.Path, item.Id); + var transientRootPath = ExpandedWorkStep.GetTransientPath(expandStep, item); CreateTransientWorkStepsRecursively(transientRootPath, expandStep, item.Id); @@ -258,7 +255,7 @@ private bool IsChildOfExpandedWorkItem(WorkItem item) var parent = WorkItemRepository.GetWorkItem(item.ParentId); var workStep = WorkStepRepository.GetWorkStep(parent.Path); - return workStep.Type == WorkStepType.Transient; + return workStep.Type == WorkStepType.Expand; } diff --git a/WhiskWork.Core/WorkItemRemover.cs b/WhiskWork.Core/WorkItemRemover.cs index 70e8a2a..8a955d4 100644 --- a/WhiskWork.Core/WorkItemRemover.cs +++ b/WhiskWork.Core/WorkItemRemover.cs @@ -11,12 +11,15 @@ public WorkItemRemover(IWorkStepRepository workStepRepository, IWorkItemReposito public WorkItem CleanUpIfInTransientStep(WorkItem workItemToMove) { - WorkStep transientStep; - if (WorkStepRepository.IsInTransientStep(workItemToMove, out transientStep)) + WorkStep expandStep; + if (WorkStepRepository.IsInExpandStep(workItemToMove, out expandStep)) { + var transientStepPath = ExpandedWorkStep.GetTransientPath(expandStep, workItemToMove); + var transientStep = WorkStepRepository.GetWorkStep(transientStepPath); + DeleteChildWorkItems(workItemToMove); WorkStepRepository.DeleteWorkStepsRecursively(transientStep); - workItemToMove = workItemToMove.RemoveClass(transientStep.WorkItemClass); + //workItemToMove = workItemToMove.RemoveClass(transientStep.WorkItemClass); } return workItemToMove; } diff --git a/WhiskWork.Core/WorkStep.cs b/WhiskWork.Core/WorkStep.cs index 9e74ad4..18ad737 100644 --- a/WhiskWork.Core/WorkStep.cs +++ b/WhiskWork.Core/WorkStep.cs @@ -5,10 +5,19 @@ namespace WhiskWork.Core { - public class WorkStep + public static class ExpandedWorkStep { - public const string Separator = "/"; + public static string GetTransientPath(WorkStep expandedWorkStep, WorkItem workItem) + { + return WorkStep.CombinePath(expandedWorkStep.Path, workItem.Id); + } + } + public class WorkStep + { + public const char Separator = '/'; + private static readonly string _separator = new string(new [] {Separator}); + public WorkStep(string path, string parentPath, int ordinal, WorkStepType workStepType, string workItemClass) : this(path,parentPath,ordinal,workStepType,workItemClass,null, true) { } @@ -41,16 +50,16 @@ public static WorkStep Root { get { - return new WorkStep(Separator, null, 0, WorkStepType.Normal, null,null,false); + return new WorkStep(_separator, null, 0, WorkStepType.Normal, null,null,false); } } public static string CombinePath(string path1, string path2) { - path1 = path1.EndsWith(Separator) ? path1.Remove(path1.Length - 1, 1) : path1; - path2 = path2.StartsWith(Separator) ? 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 + Separator + path2; + var result = path1 + _separator + path2; return result; } @@ -112,7 +121,7 @@ public override string ToString() private static void ThrowIfParentPathIsNotProperSubPathOfPath(string path, string parentPath) { - var separator = parentPath == Root.Path ? string.Empty : Separator; + var separator = parentPath == Root.Path ? string.Empty : _separator; var regex = new Regex(parentPath + separator + @"[a-z,A-Z,0-9,\-]+$"); if (!regex.IsMatch(path)) diff --git a/WhiskWork.Core/WorkStepRepositoryExtensions.cs b/WhiskWork.Core/WorkStepRepositoryExtensions.cs index 56b4b36..8f731e2 100644 --- a/WhiskWork.Core/WorkStepRepositoryExtensions.cs +++ b/WhiskWork.Core/WorkStepRepositoryExtensions.cs @@ -71,19 +71,21 @@ public static bool IsWithinParallelStep(this IWorkStepRepository workStepReposit return TryLocateFirstAncestorStepOfType(workStepRepository, workStep, WorkStepType.Parallel, out parallelStepRoot); } - public static bool IsInTransientStep(this IWorkStepRepository workStepRepository, WorkItem workItem, out WorkStep transientStep) + + public static bool IsInExpandStep(this IWorkStepRepository workStepRepository, WorkItem workItem, out WorkStep expandStep) { - transientStep = null; - bool isInTransientStep = workStepRepository.GetWorkStep(workItem.Path).Type == WorkStepType.Transient; + expandStep = null; + bool isInExpandStep = workStepRepository.GetWorkStep(workItem.Path).Type == WorkStepType.Expand; - if (isInTransientStep) + if (isInExpandStep) { - transientStep = workStepRepository.GetWorkStep(workItem.Path); + expandStep = workStepRepository.GetWorkStep(workItem.Path); } - return isInTransientStep; + return isInExpandStep; } + public static bool IsExpandStep(this IWorkStepRepository workStepRepository, WorkStep step) { return step.Type == WorkStepType.Expand; diff --git a/WhiskWork.UnitTest/ExpandWorkStepsTest.cs b/WhiskWork.UnitTest/ExpandWorkStepsTest.cs index 1e63a26..be5837f 100644 --- a/WhiskWork.UnitTest/ExpandWorkStepsTest.cs +++ b/WhiskWork.UnitTest/ExpandWorkStepsTest.cs @@ -48,12 +48,12 @@ public void ShouldNotCreateWorkItemInExpandStep() } [TestMethod] - public void ShouldMoveToTransientStepWhenEnteringExpandStep() + public void ShouldMoveToExpandStep() { _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); - Assert.AreEqual("/development/inprocess/cr1", _workItemRepository.GetWorkItem("cr1").Path); + Assert.AreEqual("/development/inprocess", _workItemRepository.GetWorkItem("cr1").Path); } [TestMethod] @@ -151,11 +151,11 @@ public void ShouldBeAbleToDeleteChildOfExpandedWorkItem() } [TestMethod] - public void ShouldMoveUnlockedExpandedWorkItemFromTransientStep() + public void ShouldMoveUnlockedExpandedWorkItemFromExpandStep() { _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection())); - Assert.AreEqual("/development/inprocess/cr1", _workItemRepository.GetWorkItem("cr1").Path); + Assert.AreEqual("/development/inprocess", _workItemRepository.GetWorkItem("cr1").Path); _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/done", new NameValueCollection())); Assert.AreEqual("/development/done", _workItemRepository.GetWorkItem("cr1").Path); @@ -170,10 +170,7 @@ public void ShouldNotMoveLockedExpandedWorkItemFromExpandStep() Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); AssertUtils.AssertThrows( - () => - { - _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/done", new NameValueCollection())); - }); + () => _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/done", new NameValueCollection()))); } [TestMethod] @@ -193,7 +190,7 @@ public void ShouldAlsoDeleteChildrenWhenDeletingExpandedWorkItem() } [TestMethod] - public void ShouldDeleteChildStepsWhenMovingFromTransientExpandStep() + public void ShouldDeleteChildStepsWhenMovingFromExpandStep() { _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); @@ -243,21 +240,6 @@ public void ShouldSetWorkItemClassOnTransientSteps() _workflowRepository.GetWorkStep("/development/inprocess/cr1/tasks/done").WorkItemClass); } - [TestMethod] - public void ShouldNotMoveOtherWorkItemToTransientStep() - { - _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(WorkItem.New("cr2","/analysis")); - AssertUtils.AssertThrows( - () => - { - _wp.UpdateWorkItem(WorkItem.New("cr2", "/development/inprocess/cr1", new NameValueCollection())); - }); - } - [TestMethod] public void ShouldGiveChildOfExpandedItemCorrectClasses() @@ -299,7 +281,7 @@ public void ShouldNotCreateWorkItemsBelowExpandStep() } [TestMethod] - public void ShouldNotMoveChildOfWorkItemInTransientStepDirectlyToExpandStep() + public void ShouldNotMoveChildOfWorkItemInExapndStepDirectlyToExpandStep() { _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); @@ -314,7 +296,7 @@ public void ShouldNotMoveChildOfWorkItemInTransientStepDirectlyToExpandStep() } [TestMethod] - public void ShouldNotMoveChildOfWorkItemInTransientStepToStepUnderExpandStep() + public void ShouldNotMoveChildOfWorkItemInExpandStepToStepUnderExpandStep() { _wp.CreateWorkItem(WorkItem.New("cr1","/analysis")); _wp.UpdateWorkItem(WorkItem.New("cr1", "/development", new NameValueCollection())); @@ -373,17 +355,6 @@ public void ShouldSetExpandLockWhenASingleDoneChildIsMovedToNotEndStep() Assert.AreEqual(WorkItemStatus.ExpandLocked, _workItemRepository.GetWorkItem("cr1").Status); } - [TestMethod] - public void ShouldRemoveTransientStepClassWhenMovedFromTransientStep() - { - _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(WorkItem.New("cr1", "/done", new NameValueCollection())); - Assert.IsTrue(_workItemRepository.GetWorkItem("cr1").Classes.SetEquals("cr")); - } [TestMethod] public void ShouldDeleteChildItemsWhenWorkItemIsMovedOutOfTransientStep() diff --git a/WhiskWork.UnitTest/ParallelWorkStepsTest.cs b/WhiskWork.UnitTest/ParallelWorkStepsTest.cs index 3c4ca21..c125679 100644 --- a/WhiskWork.UnitTest/ParallelWorkStepsTest.cs +++ b/WhiskWork.UnitTest/ParallelWorkStepsTest.cs @@ -183,10 +183,7 @@ public void ShouldOnlyBeAbleToMoveChildItemToDedicatedParallelStep() AssertUtils.AssertThrows( - ()=> - { - _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/feedback/review", new NameValueCollection())); - }); + ()=> _wp.UpdateWorkItem(WorkItem.New("cr1-test", "/feedback/review"))); 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()); @@ -249,7 +246,7 @@ public void ShouldMergeChildItemsWhenMovedToSameStepOutsideParallelizationAndChi } [TestMethod] - public void ShouldNotBeAbleToMoveFromTransientStepToParallelStep() + public void ShouldNotBeAbleToMoveFromExpandStepToParallelStep() { CreateParallelWorkflowWithExpandStep(); @@ -293,7 +290,7 @@ public void ShouldBeAbleToMoveParalleledWorkItemToExpandStep() _wp.UpdateWorkItem(WorkItem.New("cr1-review", "/development/inprocess", new NameValueCollection())); var workItem = _wp.GetWorkItem("cr1-review"); - Assert.AreEqual("/development/inprocess/cr1-review", workItem.Path); + Assert.AreEqual("/development/inprocess", workItem.Path); } [TestMethod] @@ -360,4 +357,4 @@ private void CreateParallelWorkflowWithExpandStep() _workflowRepository.Add("/done", "/", 4, WorkStepType.End, "cr", "Done"); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/WhiskWork.UnitTest/WorkflowTest.cs b/WhiskWork.UnitTest/WorkflowTest.cs index dedcd9e..4838766 100644 --- a/WhiskWork.UnitTest/WorkflowTest.cs +++ b/WhiskWork.UnitTest/WorkflowTest.cs @@ -81,6 +81,20 @@ public void ShouldUpdateOneOfTwoProperties() Assert.AreEqual("B", workItem.Properties["Developer"]); } + [TestMethod] + public void ShouldRemovePropertyIfValueIsEmpty() + { + _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", new NameValueCollection { { "Developer", "" } })); + var workItem = _wp.GetWorkItem("cr1"); + + Assert.AreEqual(1, workItem.Properties.Count); + Assert.AreEqual("CR1", workItem.Properties["Name"]); + } + + [TestMethod] public void ShouldMoveAndUpdateOneOfTwoProperties() { @@ -97,6 +111,20 @@ public void ShouldMoveAndUpdateOneOfTwoProperties() Assert.AreEqual("B", workItem.Properties["Developer"]); } + [TestMethod] + public void ShouldUpdatePropertyOfWorkItemInExpandStep() + { + _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); + _workflowRepository.Add("/development", "/", 2, WorkStepType.Normal, "cr"); + _workflowRepository.Add("/development/inprocess", "/development", 1, WorkStepType.Expand, "cr"); + + _wp.CreateWorkItem(WorkItem.New("cr1", "/analysis")); + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess")); + + _wp.UpdateWorkItem(WorkItem.New("cr1", "/development/inprocess", new NameValueCollection { { "Developer", "B" } })); + + } + [TestMethod] public void ShouldUpdateWorkItemOrdinalWhenNotMoving() { @@ -165,6 +193,7 @@ public void ShouldCreateWorkItemWithOrdinalsProvided() Assert.AreEqual(1, _workItemRepository.GetWorkItem("cr3").Ordinal); } + private void SetUpAndTestBasicOrdinal() { _workflowRepository.Add("/analysis", "/", 1, WorkStepType.Begin, "cr"); diff --git a/WhiskWork.Web.UnitTest/HtmlRendererTest.cs b/WhiskWork.Web.UnitTest/HtmlRendererTest.cs index 9f2b199..925dcc0 100644 --- a/WhiskWork.Web.UnitTest/HtmlRendererTest.cs +++ b/WhiskWork.Web.UnitTest/HtmlRendererTest.cs @@ -378,6 +378,8 @@ public void ShouldRenderTransientStepBeforeExpandStep() var doc = GetFullDocument(); + //Assert.AreEqual(string.Empty, doc.InnerXml); + XmlNode developmentInProcess = doc.SelectSingleNode("//li[@id=\"development\"]/ol/li"); Assert.AreEqual("transient", developmentInProcess.SelectSingleNode("ol/li[position()=1]/@class").Value); diff --git a/WhiskWork.Web.UnitTest/JsonRendererTest.cs b/WhiskWork.Web.UnitTest/JsonRendererTest.cs index c439c1f..ae5e363 100644 --- a/WhiskWork.Web.UnitTest/JsonRendererTest.cs +++ b/WhiskWork.Web.UnitTest/JsonRendererTest.cs @@ -93,7 +93,7 @@ public void ShouldRenderNestedWorkSteps() [TestMethod] - public void ShouldRenderTransientStepWithOneChildWorkItem() + public void ShouldRenderExpandedWorkItemWithOneChildWorkItem() { _wp.CreateWorkStep(new WorkStep("/analysis", "/", 1, WorkStepType.Begin, "cr")); _wp.CreateWorkStep(new WorkStep("/development","/",2, WorkStepType.Expand,"cr")); @@ -110,7 +110,7 @@ public void ShouldRenderTransientStepWithOneChildWorkItem() } [TestMethod] - public void ShouldRenderTwoTransientSteps() + public void ShouldRenderTwoWorkItemsinExpandStep() { _wp.CreateWorkStep(new WorkStep("/analysis", "/", 1, WorkStepType.Begin, "cr")); _wp.CreateWorkStep(new WorkStep("/development", "/", 2, WorkStepType.Expand, "cr")); @@ -125,6 +125,22 @@ public void ShouldRenderTwoTransientSteps() Assert.AreEqual(expected, json); } + [TestMethod] + public void ShouldSortAccordingToOrdinal() + { + _wp.CreateWorkStep(new WorkStep("/analysis", "/", 1, WorkStepType.Begin, "cr")); + + _wp.Create("/analysis", "cr1", "cr2"); + _wp.UpdateOrdinal("cr1", 2); + _wp.UpdateOrdinal("cr2", 1); + + var json = GetJson(WorkStep.Root); + + const string expected = "[{workstep:\"analysis\",workitemList:[{id:\"cr2\"},{id:\"cr1\"}]}]"; + Assert.AreEqual(expected, json); + + } + private string GetJson(WorkStep workStep) { diff --git a/WhiskWork.Web.UnitTest/Resources/FullFeatureTest.html b/WhiskWork.Web.UnitTest/Resources/FullFeatureTest.html index f765ed4..8b7a9b5 100644 --- a/WhiskWork.Web.UnitTest/Resources/FullFeatureTest.html +++ b/WhiskWork.Web.UnitTest/Resources/FullFeatureTest.html @@ -2,9 +2,9 @@ Analysis
  • Development -

        1. +

              1. Tasks -

              1. +

              1. Tasks

              1. Tasks diff --git a/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs b/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs index 6c908a4..d782127 100644 --- a/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs +++ b/WhiskWork.Web.UnitTest/WorkflowHttpHandlerTest.cs @@ -35,7 +35,7 @@ public void ShouldCreateWorkItemWhenPostingNewId() } using (_mocks.Playback()) { - var request = CreateCsvRequest("post", "/scheduled", "id=cr1"); + var request = CreateCsvPostRequest("/scheduled", "id=cr1"); Assert.AreEqual(HttpStatusCode.Created, _httpHandler.HandleRequest(request).HttpStatusCode); } } @@ -49,7 +49,7 @@ public void ShouldCreateWorkStepWhenPostingNewStep() } using (_mocks.Playback()) { - var request = CreateCsvRequest("post", "/analysis", "step=inprocess,class=cr"); + var request = CreateCsvPostRequest("/analysis", "step=inprocess,class=cr"); Assert.AreEqual(HttpStatusCode.Created, _httpHandler.HandleRequest(request).HttpStatusCode); } } @@ -62,7 +62,7 @@ public void ShouldReturnHttpStatusCode405ForUnknownHttpMethod() } using (_mocks.Playback()) { - var request = CreateRequest("wrong", "/analysis", null, "text/html"); + var request = CreateRequest("wrong", "/analysis", null, "text/html",null); Assert.AreEqual(HttpStatusCode.MethodNotAllowed, _httpHandler.HandleRequest(request).HttpStatusCode); } } @@ -75,7 +75,7 @@ public void ShouldReturnHttpStatusCode415ForUnknownContentTypeForPost() } using (_mocks.Playback()) { - var request = CreateRequest("post", "/analysis", null, "text/wrong"); + var request = CreateRequest("post", "/analysis", null, "text/wrong",null); Assert.AreEqual(HttpStatusCode.UnsupportedMediaType, _httpHandler.HandleRequest(request).HttpStatusCode); } } @@ -92,13 +92,13 @@ public void ShouldMoveWorkItemWhenPostingExistingWorkItemToDifferentPath() } using (_mocks.Playback()) { - var request = CreateCsvRequest("post", "/analysis", "id=cr1"); + var request = CreateCsvPostRequest("/analysis", "id=cr1"); Assert.AreEqual(HttpStatusCode.OK, _httpHandler.HandleRequest(request).HttpStatusCode); } } [TestMethod] - public void ShouldRequestHtmlRendererWhenUsingGetWithHtmlContentType() + public void ShouldGetRendererWhenReceivingGetRequest() { var renderer = _mocks.Stub(); @@ -108,7 +108,7 @@ public void ShouldRequestHtmlRendererWhenUsingGetWithHtmlContentType() } using (_mocks.Playback()) { - var request = CreateHtmlRequest("get", "/", null); + var request = CreateHtmlGetRequest("/"); Assert.AreEqual(HttpStatusCode.OK, _httpHandler.HandleRequest(request).HttpStatusCode); } } @@ -124,7 +124,7 @@ public void ShouldDeleteWorkItemWhenItemExistsInTheRightPath() } using (_mocks.Playback()) { - var request = CreateCsvRequest("delete", "/scheduled/cr1",null); + var request = CreateDeleteRequest("/scheduled/cr1"); Assert.AreEqual(HttpStatusCode.OK, _httpHandler.HandleRequest(request).HttpStatusCode); } @@ -140,7 +140,7 @@ public void ShouldReturnNotFoundWhenAttemptingToDeleteExistingWorkItemInTheWrong } using (_mocks.Playback()) { - var request = CreateCsvRequest("delete", "/scheduled/cr1", null); + var request = CreateDeleteRequest("/scheduled/cr1"); Assert.AreEqual(HttpStatusCode.NotFound, _httpHandler.HandleRequest(request).HttpStatusCode); } @@ -155,7 +155,7 @@ public void ShouldReturnNotFoundWhenAttemptingToDeleteNonExistingWorkItem() } using (_mocks.Playback()) { - var request = CreateCsvRequest("delete", "/scheduled/cr1", null); + var request = CreateDeleteRequest("/scheduled/cr1"); Assert.AreEqual(HttpStatusCode.NotFound, _httpHandler.HandleRequest(request).HttpStatusCode); } @@ -170,7 +170,7 @@ public void ShouldReturnNotFoundWhenAttemptingToDeleteNonExistingWorkStep() } using (_mocks.Playback()) { - var request = CreateCsvRequest("delete", "/scheduled", null); + var request = CreateDeleteRequest("/scheduled"); Assert.AreEqual(HttpStatusCode.NotFound, _httpHandler.HandleRequest(request).HttpStatusCode); } @@ -222,7 +222,7 @@ private void AssertExceptionIsCaughtAndHttpStatusCodeReturnedForCreateWorkStep(E } using (_mocks.Playback()) { - var request = CreateCsvRequest("post", "/", "step=/scheduled,class=cr"); + var request = CreateCsvPostRequest("/", "step=/scheduled,class=cr"); Assert.AreEqual(httpStatusCode, _httpHandler.HandleRequest(request).HttpStatusCode); } } @@ -236,28 +236,33 @@ private void AssertExceptionIsCaughtAndHttpStatusCodeReturnedForCreateWorkItem(E } using (_mocks.Playback()) { - var request = CreateCsvRequest("post", "/", "id=id1"); + var request = CreateCsvPostRequest("/", "id=id1"); Assert.AreEqual(httpStatusCode, _httpHandler.HandleRequest(request).HttpStatusCode); } } + private static WorkflowHttpRequest CreateHtmlGetRequest(string url) + { + return CreateRequest("get", url, null, null, "text/html"); + } - private static WorkflowHttpRequest CreateHtmlRequest(string httpMethod, string url, string httpMessage) + private static WorkflowHttpRequest CreateDeleteRequest(string url) { - return CreateRequest(httpMethod, url, httpMessage, "text/html"); + return CreateRequest("delete", url, null, null, null); } - private static WorkflowHttpRequest CreateCsvRequest(string httpMethod, string url, string httpMessage) + private static WorkflowHttpRequest CreateCsvPostRequest(string url, string httpMessage) { - return CreateRequest(httpMethod, url, httpMessage, "text/csv"); + return CreateRequest("post", url, httpMessage, "text/csv",null); } - private static WorkflowHttpRequest CreateRequest(string httpMethod, string url, string httpMessage, string contentType) + private static WorkflowHttpRequest CreateRequest(string httpMethod, string url, string httpMessage, string contentType, string accept) { var request = new WorkflowHttpRequest { + Accept = accept, ContentType = contentType, HttpMethod = httpMethod, RawUrl = url, diff --git a/WhiskWork.Web/HtmlRenderer.cs b/WhiskWork.Web/HtmlRenderer.cs index 86406c8..0650e49 100644 --- a/WhiskWork.Web/HtmlRenderer.cs +++ b/WhiskWork.Web/HtmlRenderer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -38,6 +39,11 @@ public void Render(Stream stream, string path) } } + public string ContentType + { + get { return "text/html"; } + } + public void Render(Stream stream, WorkStep workStep) { @@ -59,7 +65,6 @@ public void Render(Stream stream, WorkStep workStep) private void RenderWorkStepsRecursively(HtmlTextWriter writer, WorkStep workStep) { - RenderWorkItemList(writer, workStep); if (_workStepRepository.IsParallelStep(workStep)) { @@ -71,6 +76,7 @@ private void RenderWorkStepsRecursively(HtmlTextWriter writer, WorkStep workStep } else { + RenderWorkItemList(writer, workStep); RenderNormalStep(writer, workStep); } } @@ -138,17 +144,21 @@ private void RenderExpandStep(HtmlTextWriter writer, WorkStep workStep) writer.RenderEndTag(); //ol } - private void RenderTransientListItems(HtmlTextWriter writer, WorkStep step) + private void RenderTransientListItems(HtmlTextWriter writer, WorkStep expandStep) { - var transientSteps = _workStepRepository.GetChildWorkSteps(step.Path).Where(ws => ws.Type == WorkStepType.Transient); + var expandedWorkItems = _workItemRepository.GetWorkItems(expandStep.Path).OrderBy(wi=>wi.Ordinal); - foreach (var transientStep in transientSteps) + foreach (var expandedWorkItem in expandedWorkItems) { - RenderTransientListItem(writer,transientStep); + var transientPath = ExpandedWorkStep.GetTransientPath(expandStep, expandedWorkItem); + var transientStep = _workStepRepository.GetWorkStep(transientPath); + + RenderTransientListItem(writer, transientStep, expandedWorkItem); } + } - private void RenderTransientListItem(HtmlTextWriter writer, WorkStep transientStep) + private void RenderTransientListItem(HtmlTextWriter writer, WorkStep transientStep, WorkItem expandedWorkItem) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "transient"); writer.RenderBeginTag(HtmlTextWriterTag.Li); @@ -159,7 +169,9 @@ private void RenderTransientListItem(HtmlTextWriter writer, WorkStep transientSt writer.AddAttribute(HtmlTextWriterAttribute.Class, GetLeafStepClasses(transientStep).Join(' ')); writer.RenderBeginTag(HtmlTextWriterTag.Li); - RenderWorkItemList(writer, transientStep); + writer.RenderBeginTag(HtmlTextWriterTag.Ol); + RenderWorkItem(writer, expandedWorkItem); + writer.RenderEndTag(); //ol writer.RenderEndTag(); //li @@ -217,17 +229,23 @@ private void RenderWorkItemList(HtmlTextWriter writer, WorkStep workStep) writer.RenderBeginTag(HtmlTextWriterTag.Ol); foreach (var workItem in workItems.OrderBy(wi => wi.Ordinal)) { - writer.AddAttribute(HtmlTextWriterAttribute.Id, workItem.Id); + RenderWorkItem(writer, workItem); + } + writer.RenderEndTag(); //ol + } - var workItemClass = GenerateWorkItemClass(workItem); - writer.AddAttribute(HtmlTextWriterAttribute.Class, workItemClass); - writer.RenderBeginTag(HtmlTextWriterTag.Li); + private static void RenderWorkItem(HtmlTextWriter writer, WorkItem workItem) + { + writer.AddAttribute(HtmlTextWriterAttribute.Id, workItem.Id); - RenderProperties(writer, workItem); + var workItemClass = GenerateWorkItemClass(workItem); + writer.AddAttribute(HtmlTextWriterAttribute.Class, workItemClass); + writer.RenderBeginTag(HtmlTextWriterTag.Li); - writer.RenderEndTag(); //li - } - writer.RenderEndTag(); //ol + RenderProperties(writer, workItem); + + writer.RenderEndTag(); //li + } private static void RenderProperties(HtmlTextWriter writer, WorkItem item) diff --git a/WhiskWork.Web/HtmlWorkStepRendererFactory.cs b/WhiskWork.Web/HtmlWorkStepRendererFactory.cs index 92f618b..077eb9e 100644 --- a/WhiskWork.Web/HtmlWorkStepRendererFactory.cs +++ b/WhiskWork.Web/HtmlWorkStepRendererFactory.cs @@ -1,3 +1,4 @@ +using System; using WhiskWork.Core; namespace WhiskWork.Web diff --git a/WhiskWork.Web/IWorkStepRenderer.cs b/WhiskWork.Web/IWorkStepRenderer.cs index 27f5a35..91e25cb 100644 --- a/WhiskWork.Web/IWorkStepRenderer.cs +++ b/WhiskWork.Web/IWorkStepRenderer.cs @@ -5,5 +5,6 @@ namespace WhiskWork.Web public interface IWorkStepRenderer { void Render(Stream stream, string path); + string ContentType { get; } } } \ No newline at end of file diff --git a/WhiskWork.Web/JsonRenderer.cs b/WhiskWork.Web/JsonRenderer.cs index c9fa9a9..4fbcae2 100644 --- a/WhiskWork.Web/JsonRenderer.cs +++ b/WhiskWork.Web/JsonRenderer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using WhiskWork.Core; using System.Linq; @@ -16,26 +17,39 @@ public JsonRenderer(IWorkStepRepository workStepRepository, IWorkItemRepository _workItemRepository = workItemRepository; } + public string ContentType + { + get { return "application/json"; } + } + public void Render(Stream stream, string path) { - var workStep =_workStepRepository.GetWorkStep(path); - Render(stream, workStep); + if (string.IsNullOrEmpty(path) || WorkStep.Root.Path == path) + { + Render(stream, WorkStep.Root); + } + else + { + var workStep = _workStepRepository.GetWorkStep(path); + Render(stream, workStep); + } } + public void Render(Stream stream, WorkStep workStep) { using(var writer = new StreamWriter(stream)) { writer.Write("["); - WriteWorkStepsRecursively(writer, workStep, true); + RenderWorkStepsRecursively(writer, workStep, true); writer.Write("]"); } } - private void WriteWorkStepsRecursively(TextWriter writer, WorkStep workStep, bool first) + private void RenderWorkStepsRecursively(TextWriter writer, WorkStep workStep, bool first) { foreach (var childWorkStep in _workStepRepository.GetChildWorkSteps(workStep.Path)) { @@ -63,115 +77,100 @@ private void WriteWorkStepsRecursively(TextWriter writer, WorkStep workStep, boo private void RenderParallelStep(TextWriter writer, WorkStep workStep) { - WriteWorkStepsRecursively(writer,workStep,true); + RenderWorkStepsRecursively(writer,workStep,true); } private void RenderNormalStep(TextWriter writer, WorkStep childWorkStep) { - writer.Write("{workstep:"); - - writer.Write(CreateWorkStepName(childWorkStep)); - writer.Write(","); - - writer.Write("workitemList:"); - - writer.Write("["); - - WriteWorkItems(writer, childWorkStep, false); - - writer.Write("]"); - + RenderWorkStep(writer, childWorkStep); - writer.Write("}"); - - WriteWorkStepsRecursively(writer, childWorkStep, false); + RenderWorkStepsRecursively(writer, childWorkStep, false); } - private void RenderExpandStep(TextWriter writer, WorkStep workStep) { - writer.Write("{workstep:"); - - writer.Write(CreateWorkStepName(workStep)); - writer.Write(","); - RenderTransientStepsAsWorkItems(writer, workStep); - writer.Write("}"); + RenderWorkStep(writer, workStep); } - private void RenderTransientStepsAsWorkItems(TextWriter writer, WorkStep step) + private void RenderWorkStep(TextWriter writer, WorkStep childWorkStep) { - var transientSteps = _workStepRepository.GetChildWorkSteps(step.Path).Where(ws => ws.Type == WorkStepType.Transient); + writer.Write("{workstep:"); + + writer.Write(CreateWorkStepName(childWorkStep)); + writer.Write(","); writer.Write("workitemList:"); writer.Write("["); - bool isFirst = true; - - foreach (var transientStep in transientSteps) - { - if(!isFirst) - { - writer.Write(","); - } - WriteWorkItems(writer,transientStep,true); - - isFirst = false; - } + RenderWorkItems(writer, childWorkStep); writer.Write("]"); - } - private string CreateWorkStepName(WorkStep childWorkStep) - { - return "\""+childWorkStep.Path.Replace('/','-').Remove(0,1)+"\""; + + writer.Write("}"); } - private void WriteWorkItems(TextWriter writer, WorkStep step, bool renderChildWorkSteps) + private void RenderWorkItems(TextWriter writer, WorkStep step) { - //writer.Write(step.WorkItemClass); var first = true; - foreach (var workItem in _workItemRepository.GetWorkItems(step.Path)) + foreach (var workItem in _workItemRepository.GetWorkItems(step.Path).OrderBy(wi=>wi.Ordinal)) { if (!first) { writer.Write(","); } - writer.Write("{"); - - writer.Write("id:\"{0}\"",workItem.Id); - - RenderProperties(writer, workItem); + RenderWorkItem(step, writer, workItem); - if(renderChildWorkSteps) - { - writer.Write(",worksteps:["); - - //throw new NotImplementedException(_workStepRepository.GetChildWorkSteps(workItem.Path).Count().ToString()); + first = false; + } + } - var currentStep = _workStepRepository.GetWorkStep(workItem.Path); + private void RenderWorkItem(WorkStep step, TextWriter writer, WorkItem workItem) + { + writer.Write("{"); - WriteWorkStepsRecursively(writer,currentStep,true); + writer.Write("id:\"{0}\"",workItem.Id); - writer.Write("]"); - - } + RenderProperties(writer, workItem); - writer.Write("}"); + RenderTransientWorkSteps(step, writer, workItem); - first = false; - } - + writer.Write("}"); } - private void RenderProperties(TextWriter writer, WorkItem item) + + private static void RenderProperties(TextWriter writer, WorkItem item) { foreach (var keyValue in item.Properties) { writer.Write(",{0}:\"{1}\"",keyValue.Key, keyValue.Value); } } + + private void RenderTransientWorkSteps(WorkStep step, TextWriter writer, WorkItem workItem) + { + var childStepPath = ExpandedWorkStep.GetTransientPath(step, workItem); + + if (_workStepRepository.ExistsWorkStep(childStepPath)) + { + var childStep = _workStepRepository.GetWorkStep(childStepPath); + writer.Write(",worksteps:["); + + RenderWorkStepsRecursively(writer, childStep, true); + + writer.Write("]"); + + } + } + + private static string CreateWorkStepName(WorkStep childWorkStep) + { + return "\"" + childWorkStep.Path.Replace('/', '-').Remove(0, 1) + "\""; + } + + } } \ No newline at end of file diff --git a/WhiskWork.Web/WorkflowHttpHandler.cs b/WhiskWork.Web/WorkflowHttpHandler.cs index 3640f0d..b4591ab 100644 --- a/WhiskWork.Web/WorkflowHttpHandler.cs +++ b/WhiskWork.Web/WorkflowHttpHandler.cs @@ -2,6 +2,7 @@ using System.Collections.Specialized; using System.Linq; using WhiskWork.Core; +using System.Net; namespace WhiskWork.Web { @@ -49,7 +50,7 @@ private WorkflowHttpResponse RespondToPost(WorkflowHttpRequest request) private WorkflowHttpResponse RespondToGet(WorkflowHttpRequest request) { - var renderer = _rendererFactory.CreateRenderer(request.ContentType); + var renderer = _rendererFactory.CreateRenderer(request.Accept); return Render(renderer, request.RawUrl); } @@ -70,6 +71,7 @@ private static WorkflowHttpResponse Render(IWorkStepRenderer renderer, string pa try { var response = WorkflowHttpResponse.Ok; + response.Headers.Add(HttpRequestHeader.ContentType,renderer.ContentType); renderer.Render(response.OutputStream, path); return response; diff --git a/WhiskWork.Web/WorkflowHttpRequest.cs b/WhiskWork.Web/WorkflowHttpRequest.cs index f7a9349..29ba42f 100644 --- a/WhiskWork.Web/WorkflowHttpRequest.cs +++ b/WhiskWork.Web/WorkflowHttpRequest.cs @@ -1,10 +1,12 @@ using System.IO; +using System.Linq; using System.Net; namespace WhiskWork.Web { public class WorkflowHttpRequest { + public string Accept { get; set; } public string ContentType { get; set; } public string HttpMethod { get; set; } @@ -21,7 +23,8 @@ public static WorkflowHttpRequest Create(HttpListenerRequest listenerRequest) InputStream = listenerRequest.InputStream, RawUrl = listenerRequest.RawUrl, HttpMethod = listenerRequest.HttpMethod, - ContentType = listenerRequest.ContentType + ContentType = listenerRequest.ContentType, + Accept = listenerRequest.AcceptTypes != null ? listenerRequest.AcceptTypes.FirstOrDefault() : null }; return request;