Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan D. Brooks2012-12-21 18:06:10 -0500
committerRyan D. Brooks2015-06-18 22:38:19 -0400
commitd2dfac16fdf059929319bf10e055e85c9f0acfb5 (patch)
tree0f790423e4f5baf4daaab3cfeb792d38c85031d3
parentca2f3257f44b41dfd0c14d9676b18ecf4cd40e6b (diff)
downloadorg.eclipse.osee-roadmap.tar.gz
org.eclipse.osee-roadmap.tar.xz
org.eclipse.osee-roadmap.zip
feature: Agile roadmap generationroadmap
-rw-r--r--plugins/org.eclipse.osee.ats.reports.test/.classpath7
-rw-r--r--plugins/org.eclipse.osee.ats.reports.test/.project28
-rw-r--r--plugins/org.eclipse.osee.ats.reports.test/META-INF/MANIFEST.MF18
-rw-r--r--plugins/org.eclipse.osee.ats.reports.test/build.properties5
-rw-r--r--plugins/org.eclipse.osee.ats.reports.test/fragment.xml11
-rw-r--r--plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapBlam.java45
-rw-r--r--plugins/org.eclipse.osee.ats.reports.test/src/org/eclipse/osee/ats/reports/RoadmapTest.java182
-rw-r--r--plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Roadmap.java171
-rw-r--r--plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Scrum.java43
-rw-r--r--plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/Sprint.java226
-rw-r--r--plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/SprintDay.java161
-rw-r--r--plugins/org.eclipse.osee.ats.reports/src/org/eclipse/osee/ats/reports/agile/UserStory.java111
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 0000000000..ad32c83a78
--- /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 0000000000..6699c66536
--- /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 0000000000..b66b7aa577
--- /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 0000000000..e3023e14e9
--- /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 0000000000..f437ce3343
--- /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 0000000000..344e99708d
--- /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 0000000000..d16149b007
--- /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 0000000000..79b73b3658
--- /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 0000000000..5e0cc6c692
--- /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 0000000000..8b9878802c
--- /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 0000000000..f4159ea978
--- /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 0000000000..9f2449a3bd
--- /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

Back to the top