diff options
author | Ryan D. Brooks | 2012-12-21 23:06:10 +0000 |
---|---|---|
committer | Ryan D. Brooks | 2015-06-19 02:38:19 +0000 |
commit | d2dfac16fdf059929319bf10e055e85c9f0acfb5 (patch) | |
tree | 0f790423e4f5baf4daaab3cfeb792d38c85031d3 | |
parent | ca2f3257f44b41dfd0c14d9676b18ecf4cd40e6b (diff) | |
download | org.eclipse.osee-roadmap.tar.gz org.eclipse.osee-roadmap.tar.xz org.eclipse.osee-roadmap.zip |
feature: Agile roadmap generationroadmap
Change-Id: Ie86e26be1310b92936e1fe1c9102c61d462a9e13
12 files changed, 1008 insertions, 0 deletions
diff --git a/plugins/org.eclipse.osee.ats.reports.test/.classpath b/plugins/org.eclipse.osee.ats.reports.test/.classpath new file mode 100644 index 00000000000..ad32c83a788 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports.test/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/plugins/org.eclipse.osee.ats.reports.test/.project b/plugins/org.eclipse.osee.ats.reports.test/.project new file mode 100644 index 00000000000..6699c665366 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports.test/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.osee.ats.reports.test</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/plugins/org.eclipse.osee.ats.reports.test/META-INF/MANIFEST.MF b/plugins/org.eclipse.osee.ats.reports.test/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..b66b7aa5770 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports.test/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: ATS Reports Test Fragment +Bundle-SymbolicName: org.eclipse.osee.ats.reports.test;singleton:=true +Bundle-Version: 1.0.0.qualifier +Fragment-Host: org.eclipse.osee.ats.reports +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-Vendor: Eclipse Open System Engineering Environment +Import-Package: org.eclipse.osee.ats.api.data, + org.eclipse.osee.framework.core.data, + org.eclipse.osee.framework.core.enums, + org.eclipse.osee.framework.core.model.type, + org.eclipse.osee.framework.skynet.core.artifact, + org.eclipse.osee.framework.skynet.core.artifact.search, + org.eclipse.osee.framework.ui.skynet.blam, + org.eclipse.osee.framework.ui.skynet.widgets.util, + org.eclipse.ui.forms.widgets, + org.junit;version="4.10.0" diff --git a/plugins/org.eclipse.osee.ats.reports.test/build.properties b/plugins/org.eclipse.osee.ats.reports.test/build.properties new file mode 100644 index 00000000000..e3023e14e99 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports.test/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + fragment.xml diff --git a/plugins/org.eclipse.osee.ats.reports.test/fragment.xml b/plugins/org.eclipse.osee.ats.reports.test/fragment.xml new file mode 100644 index 00000000000..f437ce33434 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports.test/fragment.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="3.4"?> +<fragment> + <extension + point="org.eclipse.osee.framework.ui.skynet.BlamOperation"> + <Operation + className="org.eclipse.osee.ats.reports.RoadmapBlam"> + </Operation> + </extension> + +</fragment> diff --git a/plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapBlam.java b/plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapBlam.java new file mode 100644 index 00000000000..344e99708d2 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapBlam.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ +package org.eclipse.osee.ats.reports; + +import java.util.Arrays; +import java.util.Collection; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.osee.framework.ui.skynet.blam.AbstractBlam; +import org.eclipse.osee.framework.ui.skynet.blam.VariableMap; + +/** + * @author Ryan D. Brooks + */ +public class RoadmapBlam extends AbstractBlam { + + @Override + public String getName() { + return "Roadmap Blam"; + } + + @Override + public void runOperation(VariableMap variableMap, IProgressMonitor monitor) throws Exception { + boolean includeClosed = variableMap.getBoolean("Include Closed"); + RoadmapTest roadmap = new RoadmapTest(); + roadmap.testRoadmap(includeClosed); + } + + @Override + public Collection<String> getCategories() { + return Arrays.asList("Admin"); + } + + @Override + public String getXWidgetsXml() { + return "<xWidgets><XWidget xwidgetType=\"XCheckBox\" horizontalLabel=\"true\" labelAfter=\"true\" displayName=\"Include Closed\" /></xWidgets>"; + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapTest.java b/plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapTest.java new file mode 100644 index 00000000000..d16149b0072 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapTest.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2013 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ +package org.eclipse.osee.ats.reports; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.eclipse.osee.ats.api.data.AtsArtifactTypes; +import org.eclipse.osee.ats.api.data.AtsAttributeTypes; +import org.eclipse.osee.ats.api.data.AtsRelationTypes; +import org.eclipse.osee.ats.reports.agile.Roadmap; +import org.eclipse.osee.ats.reports.agile.Scrum; +import org.eclipse.osee.ats.reports.agile.UserStory; +import org.eclipse.osee.framework.core.enums.CoreBranches; +import org.eclipse.osee.framework.core.enums.CoreRelationTypes; +import org.eclipse.osee.framework.core.enums.DeletionFlag; +import org.eclipse.osee.framework.core.exception.AttributeDoesNotExist; +import org.eclipse.osee.framework.core.exception.OseeCoreException; +import org.eclipse.osee.framework.skynet.core.artifact.Artifact; +import org.eclipse.osee.framework.skynet.core.artifact.search.ArtifactQuery; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test Case for {@link Roadmap} + * + * @author Ryan D. Brooks + */ +public class RoadmapTest { + + @BeforeClass + public void setup() { + } + + private Date getCloseDate(Artifact workflow) throws OseeCoreException { + Date date = workflow.getSoleAttributeValue(AtsAttributeTypes.CompletedDate, null); + if (date == null) { + date = workflow.getSoleAttributeValue(AtsAttributeTypes.CancelledDate, null); + } + return date; + } + + private String getTheme(Artifact workflow) throws OseeCoreException { + List<Artifact> groups = workflow.getRelatedArtifacts(CoreRelationTypes.Universal_Grouping__Group); + if (groups.isEmpty()) { + return null; + } + if (groups.size() > 1) { + System.out.println("more than one group from for " + workflow); + } + return groups.get(0).getName(); + } + + private UserStory createStory(Artifact workflow) throws OseeCoreException { + return new UserStory(workflow.getName(), getPoints(workflow), getTheme(workflow), getCloseDate(workflow)); + } + + @Test + public void testRoadmapUsingMockData(boolean includeClosed) throws OseeCoreException, ParseException { + List<UserStory> closedStories; + if (includeClosed) { + List<Artifact> sprintWorkflows = + ArtifactQuery.getArtifactListFromAttributeKeywords(CoreBranches.COMMON, "OSEE Sprint", true, + DeletionFlag.EXCLUDE_DELETED, false, AtsAttributeTypes.SmaNote); + + closedStories = new ArrayList<UserStory>(sprintWorkflows.size()); + for (Artifact workflow : sprintWorkflows) { + UserStory story = createStory(workflow); + if (story.isClosed()) { + closedStories.add(story); + } + } + } else { + closedStories = Collections.emptyList(); + } + + Artifact goal = ArtifactQuery.getArtifactFromTypeAndName(AtsArtifactTypes.Goal, "OSEE", CoreBranches.COMMON); + List<Artifact> members = goal.getRelatedArtifacts(AtsRelationTypes.Goal_Member); + List<UserStory> orderedBacklog = new ArrayList<UserStory>(members.size()); + for (Artifact workflow : members) { + try { + UserStory story = createStory(workflow); + if (!story.isClosed()) { + orderedBacklog.add(story); + } + } catch (AttributeDoesNotExist ex) { + ex.printStackTrace(); + break; + } + } + + Roadmap roadmap = new Roadmap(new Scrum()); + + roadmap.createSprints(new String[][] { + {"2013-01-17", "2013-02-16"}, + {"2013-02-17", "2013-03-16"}, + {"2013-03-17", "2013-04-16"}, + {"2013-04-17", "2013-05-16"}, + {"2013-05-17", "2013-06-16"}, + {"2013-06-17", "2013-07-16"}, + {"2013-07-17", "2013-08-16"}, + {"2013-08-17", "2013-09-16"}, + {"2013-09-17", "2013-10-16"}, + {"2013-10-17", "2013-11-16"}, + {"2013-11-17", "2013-12-16"}}); + roadmap.populateSprints(closedStories, orderedBacklog); + roadmap.printDetailedReport(); + + } + + @Test + public void testRoadmapUsingArtifacts(boolean includeClosed) throws OseeCoreException, ParseException { + List<UserStory> closedStories; + if (includeClosed) { + List<Artifact> sprintWorkflows = + ArtifactQuery.getArtifactListFromAttributeKeywords(CoreBranches.COMMON, "OSEE Sprint", true, + DeletionFlag.EXCLUDE_DELETED, false, AtsAttributeTypes.SmaNote); + + closedStories = new ArrayList<UserStory>(sprintWorkflows.size()); + for (Artifact workflow : sprintWorkflows) { + UserStory story = createStory(workflow); + if (story.isClosed()) { + closedStories.add(story); + } + } + } else { + closedStories = Collections.emptyList(); + } + + Artifact goal = ArtifactQuery.getArtifactFromTypeAndName(AtsArtifactTypes.Goal, "OSEE", CoreBranches.COMMON); + List<Artifact> members = goal.getRelatedArtifacts(AtsRelationTypes.Goal_Member); + List<UserStory> orderedBacklog = new ArrayList<UserStory>(members.size()); + for (Artifact workflow : members) { + try { + UserStory story = createStory(workflow); + if (!story.isClosed()) { + orderedBacklog.add(story); + } + } catch (AttributeDoesNotExist ex) { + ex.printStackTrace(); + break; + } + } + + Roadmap roadmap = new Roadmap(new Scrum()); + + roadmap.createSprints(new String[][] { + {"2013-01-17", "2013-02-16"}, + {"2013-02-17", "2013-03-16"}, + {"2013-03-17", "2013-04-16"}, + {"2013-04-17", "2013-05-16"}, + {"2013-05-17", "2013-06-16"}, + {"2013-06-17", "2013-07-16"}, + {"2013-07-17", "2013-08-16"}, + {"2013-08-17", "2013-09-16"}, + {"2013-09-17", "2013-10-16"}, + {"2013-10-17", "2013-11-16"}, + {"2013-11-17", "2013-12-16"}}); + roadmap.populateSprints(closedStories, orderedBacklog); + roadmap.printDetailedReport(); + + } + + private int getPoints(Artifact workflow) throws OseeCoreException { + String points = workflow.getSoleAttributeValue(AtsAttributeTypes.Points); + try { + return Integer.parseInt(points); + } catch (NumberFormatException ex) { + return 13; + } + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Roadmap.java b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Roadmap.java new file mode 100644 index 00000000000..79b73b36585 --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Roadmap.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2012 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osee.ats.reports.agile; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map.Entry; +import org.eclipse.osee.framework.core.exception.OseeStateException; + +/** + * @author Ryan D. Brooks + */ +public final class Roadmap { + private LinkedHashSet<UserStory> stories; + private final List<Sprint> sprints = new ArrayList<Sprint>(); + private final Scrum scrum; + + public Roadmap(Scrum scrum) { + this.scrum = scrum; + } + + public List<Sprint> createSprints(String[][] sprintDates) throws ParseException { + int sprintNumber = 1; + for (String[] datePair : sprintDates) { + Sprint sprint = new Sprint(this, sprintNumber++); + sprint.createSprintDays(toCal(datePair[0]), toCal(datePair[1])); + sprints.add(sprint); + } + + return sprints; + } + + /** + * createSprints must be called before populateSprints + * + * @param closedStories + * @param orderedBacklog + * @throws OseeStateException + */ + public void populateSprints(List<UserStory> closedStories, List<UserStory> orderedBacklog) throws OseeStateException { + orderStories(closedStories, orderedBacklog); + + for (Sprint sprint : sprints) { + sprint.layoutUserStories(stories); + } + } + + public Calendar getSimulationDate() { + return SprintDay.getDateOnly(Calendar.getInstance()); + } + + private void orderStories(List<UserStory> closedStories, List<UserStory> orderedBacklog) throws OseeStateException { + int setSize = (closedStories.size() + orderedBacklog.size()) * 4 / 3 + 1; + stories = new LinkedHashSet<UserStory>(setSize); + + Iterator<UserStory> iter = closedStories.iterator(); + while (iter.hasNext()) { + UserStory story = iter.next(); + if (!story.isClosed()) { + iter.remove(); + throw new OseeStateException("Story %s was includied in closedStories, but is still open.", story); + } + } + + Collections.sort(closedStories); + stories.addAll(closedStories); + stories.addAll(orderedBacklog); + } + + @Override + public String toString() { + return sprints.toString(); + } + + /** + * @param workPackage + * @return the percent complete of the given workpackage based on the weighted average of the points for all user + * stories in this roadmap with this workpackage + */ + public double getPercentCompleteFor(String workPackage) { + throw new UnsupportedOperationException(); + } + + private static void test() throws ParseException, OseeStateException { + List<UserStory> closedStories = new ArrayList<UserStory>(); + addTestClosedStories(closedStories); + + List<UserStory> orderedBacklog = new ArrayList<UserStory>(); + orderedBacklog.add(createStory("Bug y", 2, "OSEE Fixes", null)); + + Roadmap roadmap = new Roadmap(new Scrum()); + + roadmap.createSprints(new String[][] { + {"2012-01-06", "2012-01-25"}, + {"2012-01-26", "2012-02-22"}, + {"2012-02-23", "2012-03-21"}, + {"2012-03-22", "2012-04-26"}, + {"2012-04-27", "2012-06-03"}, + {"2012-06-04", "2012-07-04"}, + {"2012-07-05", "2012-08-01"}, + {"2012-08-02", "2012-09-04"}, + {"2012-09-05", "2012-10-01"}, + {"2012-10-02", "2012-10-28"}, + {"2012-10-29", "2012-11-14"}, + {"2012-11-15", "2012-12-09"}, + {"2012-12-10", "2012-12-31"}}); + roadmap.populateSprints(closedStories, orderedBacklog); + roadmap.printDetailedReport(); + } + + private static void addTestClosedStories(List<UserStory> closedStories) throws ParseException { + closedStories.add(createStory("Feature a", 4, "OSEE Features", "2012-11-01")); + closedStories.add(createStory("Feature b", 4, "OSEE Features", "2012-11-20")); + } + + public void printSummary() { + for (Sprint sprint : sprints) { + System.out.println(sprint); + } + } + + public void printDetailedReport() { + for (Sprint sprint : sprints) { + for (SprintDay day : sprint.getSprintDays()) { + HashMap<UserStory, Double> stories = day.getStoriesWorked(); + for (Entry<UserStory, Double> entry : stories.entrySet()) { + UserStory story = entry.getKey(); + System.out.printf("Sprint %d,%s,%s,%s,\"%s\"\n", sprint.getSprintNumber(), day, story.getMainTheme(), + entry.getValue(), story.getName()); + } + } + } + } + + public Scrum getScrum() { + return scrum; + } + + private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + + private static Date toDate(String dateStr) throws ParseException { + return dateStr == null ? null : sdf.parse(dateStr); + } + + private static Calendar toCal(String dateStr) throws ParseException { + Calendar cal = Calendar.getInstance(); + cal.setTime(toDate(dateStr)); + return cal; + } + + private static UserStory createStory(String name, int points, String theme, String closeDate) throws ParseException { + return new UserStory(name, points, theme, toDate(closeDate)); + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Scrum.java b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Scrum.java new file mode 100644 index 00000000000..5e0cc6c692e --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Scrum.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2012 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osee.ats.reports.agile; + +/** + * @author Ryan D. Brooks + */ +public final class Scrum { + + public double getAveragePointsPerManDay() { + return 0.5; + } + + public double getAverageDailyVelocity() { + return getAvailableManPower(null) * getAveragePointsPerManDay(); + } + + public double getWalkupPointsPerDay() { + throw new UnsupportedOperationException(); + } + + /** + * @param sprint + * @return the number of average number of heads for the given sprint + */ + public double getAvailableManPower(Sprint sprint) { + return 8.0; + } + + @Override + public String toString() { + return String.format(""); + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Sprint.java b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Sprint.java new file mode 100644 index 00000000000..8b9878802ce --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Sprint.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2012 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osee.ats.reports.agile; + +import java.util.Calendar; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import org.eclipse.osee.framework.core.exception.OseeStateException; + +/** + * @author Ryan D. Brooks + */ +public final class Sprint { + private final LinkedList<SprintDay> sprintDays = new LinkedList<SprintDay>(); + private final Roadmap roadmap; + private double plannedPoints; + private final LinkedList<UserStory> sprintStories = new LinkedList<UserStory>(); + private final int sprintNumber; + private static final double epsilon = 0.0000001; + + public Sprint(Roadmap roadmap, int sprintNumber) { + this.roadmap = roadmap; + plannedPoints = -1; + this.sprintNumber = sprintNumber; + } + + /** + * removes selected stories from the backlog and adds them to this sprint + * + * @param roadmapStories + * @throws OseeStateException + */ + private void selectStories(LinkedHashSet<UserStory> roadmapStories) throws OseeStateException { + int cumulativePoints = 0; + int dayIndex = 0; + + for (UserStory story : roadmapStories) { + cumulativePoints += story.getPoints(); + if (outsideSprint(story, cumulativePoints)) { + break; + } + + dayIndex = assignSprintAndCloseDay(story, dayIndex); + story.getCloseDay().setCumulativePoints(cumulativePoints); + sprintStories.add(story); + } + roadmapStories.removeAll(sprintStories); + } + + private boolean outsideSprint(UserStory story, int cumulativePoints) { + if (story.isClosed()) { + // must use date because closeDay is still null at this point + return getEndDay().isBefore(story.getCloseDate()); + } + return cumulativePoints > getPlannedPoints() || getEndDay().isBefore(roadmap.getSimulationDate()); + } + + private int assignSprintAndCloseDay(UserStory story, int dayIndex) throws OseeStateException { + if (story.isClosed()) { + Calendar closeDate = story.getCloseDate(); + for (; dayIndex < sprintDays.size(); dayIndex++) { + SprintDay day = sprintDays.get(dayIndex); + if (day.isOnOrAfter(closeDate)) { + story.assignSprintAndCloseDay(this, day); + return dayIndex; + } + } + throw new OseeStateException("close date %tF is outside of sprint with end date %s", closeDate, getEndDay()); + } else { + story.assignSprintAndCloseDay(this, getEndDay()); + return dayIndex; + } + } + + public void createSprintDays(Calendar startDate, Calendar endDate) { + sprintDays.clear(); + int sprintDayNum = 1; + + Calendar stopDate = SprintDay.getDateOnly(endDate); + startDate = SprintDay.getDateOnly(startDate); + stopDate.add(Calendar.DAY_OF_YEAR, 1); + + for (Calendar date = startDate; date.before(stopDate); date.add(Calendar.DAY_OF_YEAR, 1)) { + if (isSprintDay(date)) { + sprintDays.add(new SprintDay((Calendar) date.clone(), this, sprintDayNum++)); + } + } + } + + public void layoutUserStories(LinkedHashSet<UserStory> roadmapStories) throws OseeStateException { + selectStories(roadmapStories); + setMissingCumulativePoints(); + + for (UserStory story : sprintStories) { + allocateStoryPoints(story); + } + } + + private void setMissingCumulativePoints() { + int previousDayCumulativePoints = 0; + for (SprintDay day : sprintDays) { + if (day.getCumulativePoints() == 0) { + day.setCumulativePoints(previousDayCumulativePoints); + } + previousDayCumulativePoints = day.getCumulativePoints(); + } + } + + private void allocateStoryPoints(UserStory story) throws OseeStateException { + SprintDay closeDay = story.getCloseDay(); + double avgPointsPerDay = ((double) closeDay.getCumulativePoints()) / closeDay.getDayNumber(); + double allocatedPoints = 0; + + while (lessThan(allocatedPoints, story.getPoints())) { + SprintDay day = getNextAvailableDay(story, avgPointsPerDay); + double remainingPoints = story.getPoints() - allocatedPoints; + double idealPointsPerDay = getIdealPoints(day, story, remainingPoints); + double pointsForThisDay = Math.min(idealPointsPerDay, avgPointsPerDay - day.getAllocatedPoints()); + + day.allocate(story, pointsForThisDay); + allocatedPoints += pointsForThisDay; + } + } + + // TODO: move this method to the proper utility + private static boolean lessThan(double doubleA, double doubleB) { + return doubleA + epsilon < doubleB; + } + + private double getIdealPoints(SprintDay day, UserStory story, double remainingPoints) { + int remainingDays = story.getCloseDay().getDayNumber() - day.getDayNumber() + 1; + return remainingPoints / remainingDays; + } + + /** + * - next day that has not been fully allocated and not allocated to this story + * + * @param story + * @return + * @throws OseeStateException + */ + private SprintDay getNextAvailableDay(UserStory story, double avgPointsPerDay) throws OseeStateException { + for (SprintDay day : sprintDays) { + if (day.getAllocatedPoints() < avgPointsPerDay && !day.isStoryAlreadyAllocated(story)) { + if (!day.isPlanningDay() || story.getCloseDay().isPlanningDay()) { + return day; + } + } + } + throw new OseeStateException("Sprint %s has no more available days for story %s", this, story); + } + + public List<SprintDay> getSprintDays() { + return sprintDays; + } + + private boolean isSprintDay(Calendar date) { + int dayOfWeek = date.get(Calendar.DAY_OF_WEEK); + return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY; + } + + public void setPlannedPoints(double plannedPoints) { + this.plannedPoints = plannedPoints; + } + + public double getPlannedPoints() { + if (isNotPlanned()) { + return sprintDays.size() * roadmap.getScrum().getAverageDailyVelocity(); + } + return plannedPoints; + } + + /** + * @return points set aside for walk-up work for this sprint + */ + public double getWalkupPoints() { + return sprintDays.size() * roadmap.getScrum().getWalkupPointsPerDay(); + } + + public SprintDay getStartDay() { + return sprintDays.getFirst(); + } + + public SprintDay getEndDay() { + return sprintDays.getLast(); + } + + public boolean isNotPlanned() { + return plannedPoints == -1; + } + + public boolean isAfter(Sprint sprint) { + return getStartDay().isAfter(sprint.getStartDay()); + } + + public int getSprintNumber() { + return sprintNumber; + } + + @Override + public String toString() { + StringBuilder strB = new StringBuilder(sprintDays.size() * 20); + strB.append("Sprint "); + strB.append(sprintNumber); + strB.append('\n'); + for (SprintDay day : sprintDays) { + strB.append(day); + strB.append(','); + strB.append(day.getAllocatedPoints()); + strB.append(", "); + strB.append(day.getCumulativePoints()); + strB.append('\n'); + } + return strB.toString(); + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/SprintDay.java b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/SprintDay.java new file mode 100644 index 00000000000..f4159ea978b --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/SprintDay.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2012 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osee.ats.reports.agile; + +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import org.eclipse.osee.framework.core.exception.OseeStateException; + +/** + * @author Ryan D. Brooks + */ +public final class SprintDay { + private final HashMap<UserStory, Double> storiesToPointsWorked = new HashMap<UserStory, Double>(); + private double allocatedPoints; + private final Sprint sprint; + private final int dayNumber; + private final Calendar date; + private int cumulativePoints; + + /** + * @param date must have the time feild smaller than a day set to zero + * @param sprint + * @param dayNumber + */ + public SprintDay(Calendar date, Sprint sprint, int dayNumber) { + this.date = date; + this.sprint = sprint; + this.dayNumber = dayNumber; + } + + public int getDayNumber() { + return dayNumber; + } + + public double getAllocatedPoints() { + return allocatedPoints; + } + + /** + * - points completed by story for this day + * + * @return + */ + public HashMap<UserStory, Double> getStoriesWorked() { + return storiesToPointsWorked; + } + + /** + * - first check if story has already been allocated to this day; check if day can support allocated of this many + * points + * + * @param story + * @param pointsToAllocate + * @throws OseeStateException + */ + + public boolean isStoryAlreadyAllocated(UserStory story) { + return storiesToPointsWorked.containsKey(story); + } + + public void allocate(UserStory story, double pointsToAllocate) throws OseeStateException { + if (isStoryAlreadyAllocated(story)) { + throw new OseeStateException("%s is already allocated to %s", story, this); + } + storiesToPointsWorked.put(story, pointsToAllocate); + allocatedPoints += pointsToAllocate; + } + + public int getCumulativePoints() { + return cumulativePoints; + } + + public void setCumulativePoints(int cumulativePoints) { + this.cumulativePoints = cumulativePoints; + } + + public Sprint getSprint() { + return sprint; + } + + public boolean isPlanningDay() { + return dayNumber == 0; + } + + public boolean isAfter(SprintDay day) { + // if (day.sprint.equals(sprint)) { + // return dayNumber > day.dayNumber; + // } + // return sprint.isAfter(day.sprint); + return date.after(day.date); + } + + public boolean isOnOrAfter(Calendar cal) { + return equals(cal) || date.after(cal); + } + + public boolean isOnOrBefore(SprintDay day) { + if (day.sprint.equals(sprint)) { + return dayNumber <= day.dayNumber; + } + return sprint.equals(day.sprint) || day.sprint.isAfter(sprint); + } + + public boolean isBefore(Calendar cal) { + return date.before(cal); + } + + public static Calendar getDateOnly(Calendar dateAndTime) { + Calendar date = ((Calendar) dateAndTime.clone()); + date.set(Calendar.HOUR, 0); + date.set(Calendar.MINUTE, 0); + date.set(Calendar.SECOND, 0); + date.set(Calendar.MILLISECOND, 0); + return date; + } + + public static Calendar getDateOnly(Date dateAndTime) { + if (dateAndTime == null) { + return null; + } + Calendar cal = Calendar.getInstance(); + cal.setTime(dateAndTime); + return getDateOnly(cal); + } + + @Override + public String toString() { + return String.format("day %d, %tF", dayNumber, date); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = prime + dayNumber; + result = prime * result + ((sprint == null) ? 0 : sprint.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SprintDay) { + SprintDay day = (SprintDay) obj; + return sprint.equals(day.sprint) && dayNumber == day.dayNumber; + } + if (obj instanceof Calendar) { + Calendar cal = getDateOnly((Calendar) obj); + return date.equals(cal); + } + return false; + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/UserStory.java b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/UserStory.java new file mode 100644 index 00000000000..9f2449a3bdf --- /dev/null +++ b/plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/UserStory.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2012 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osee.ats.reports.agile; + +import java.util.Calendar; +import java.util.Date; + +/** + * @author Ryan D. Brooks + */ +public final class UserStory implements Comparable<UserStory> { + private final Calendar closeDate; + private final int points; + private final String theme; + private final String name; + + private Sprint sprint; + private SprintDay closeDay; + private String workpackage; + + public UserStory(String name, int points, String theme, Calendar closeDate) { + this.name = name; + this.points = points; + this.theme = theme == null ? "Uncategorized" : theme; + this.closeDate = closeDate == null ? null : SprintDay.getDateOnly(closeDate); + } + + public UserStory(String name, int points, String theme, Date closeDate) { + this(name, points, theme, SprintDay.getDateOnly(closeDate)); + } + + public UserStory(String name, int points) { + this(name, points, null, (Calendar) null); + } + + public Sprint getSprint() { + return sprint; + } + + public String getName() { + return name; + } + + void assignSprintAndCloseDay(Sprint sprint, SprintDay closeDay) { + this.sprint = sprint; + this.closeDay = closeDay; + } + + /** + * @return if closed returns the sprint day corresponding to the cancelled or completed date, otherwise returns + * sprint end date + */ + public SprintDay getCloseDay() { + return closeDay; + } + + Calendar getCloseDate() { + return closeDate; + } + + public String getMainTheme() { + return theme; + } + + public String getWorkPackage() { + return workpackage; + } + + public void setWorkPackage(String workpackage) { + this.workpackage = workpackage; + } + + public int getPoints() { + return points; + } + + public boolean isClosed() { + return closeDate != null; + } + + public boolean isWalkup() { + return false; + } + + @Override + public int compareTo(UserStory story) { + if (isClosed() == story.isClosed()) { + if (isClosed()) { + return closeDate.compareTo(story.closeDate); + } else { + return 0; + } + } else { + return isClosed() ? 1 : -1; + } + } + + @Override + public String toString() { + return String.format("\"%s\" %d pt, closeDate=%tF", name, points, closeDate); + } +}
\ No newline at end of file |