planning-day
Version:
TypeScript Playground plugin for planning an agile sprint or iteration by combining static type verification of configuration with a React Gantt chart visualization.
1,251 lines (1,113 loc) • 240 kB
JavaScript
define(function () { 'use strict';
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _taggedTemplateLiteral(strings, raw) {
if (!raw) {
raw = strings.slice(0);
}
return Object.freeze(Object.defineProperties(strings, {
raw: {
value: Object.freeze(raw)
}
}));
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it;
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = o[Symbol.iterator]();
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
var r={data:""},t=function(t){try{var e=t?t.querySelector("#_goober"):self._goober;return e||((e=(t||document.head).appendChild(document.createElement("style"))).innerHTML=" ",e.id="_goober"),e.firstChild}catch(r){}return r},n=/(?:([a-z0-9-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(})/gi,a=/\/\*.*?\*\/|\s{2,}|\n/gm,o=function(r,t,e){var n="",a="",c="";for(var i in r){var u=r[i];if("object"==typeof u){var s=t+" "+i;/&/g.test(i)&&(s=i.replace(/&/g,t)),"@"==i[0]&&(s=t,"f"==i[1]&&(s=i)),/@k/.test(i)?a+=i+"{"+o(u,"","")+"}":a+=o(u,s,s==t?i:e||"");}else /^@i/.test(i)?c=i+" "+u+";":n+=i.replace(/[A-Z]/g,"-$&").toLowerCase()+":"+u+";";}if(n.charCodeAt(0)){var f=t+"{"+n+"}";return e?a+e+"{"+f+"}":c+f+a}return c+a},c={},i=function(r,t,e,i){var u=JSON.stringify(r),s=c[u]||(c[u]=".go"+u.split("").reduce(function(r,t){return 101*r+t.charCodeAt(0)>>>0},11));return function(r,t,e){t.data.indexOf(r)<0&&(t.data=e?r+t.data:t.data+r);}(c[s]||(c[s]=o(r[0]?function(r){for(var t,e=[{}];t=n.exec(r.replace(a,""));)t[4]&&e.shift(),t[3]?e.unshift(e[0][t[3]]=e[0][t[3]]||{}):t[4]||(e[0][t[1]]=t[2]);return e[0]}(r):r,e?"":s)),t,i),s.slice(1)},u=function(r,t,e){return r.reduce(function(r,n,a){var o=t[a];if(o&&o.call){var c=o(e),i=c&&c.props&&c.props.className||/^go/.test(c)&&c;o=i?"."+i:c&&c.props?"":c;}return r+n+(null==o?"":o)},"")};function s(r){var e=this||{},n=r.call?r(e.p):r;return i(n.map?u(n,[].slice.call(arguments,1),e.p):n,t(e.target),e.g,e.o)}var l=s.bind({g:1});
const _global_window_react = window.react;
var n$1={display:"block",opacity:0,position:"absolute",top:0,left:0,height:"100%",width:"100%",overflow:"hidden",pointerEvents:"none",zIndex:-1},t$1=function(t){var r=t.onResize,u=_global_window_react.useRef();return function(n,t){var r=function(){return n.current&&n.current.contentDocument&&n.current.contentDocument.defaultView};function u(){t();var e=r();e&&e.addEventListener("resize",t);}_global_window_react.useEffect((function(){return r()?u():n.current&&n.current.addEventListener&&n.current.addEventListener("load",u),function(){var e=r();e&&"function"==typeof e.removeEventListener&&e.removeEventListener("resize",t);}}),[]);}(u,(function(){return r(u)})),_global_window_react.createElement("iframe",{style:n$1,src:"about:blank",ref:u,"aria-hidden":!0,tabIndex:-1,frameBorder:0})},r$1=function(e){return {width:null!=e?e.offsetWidth:null,height:null!=e?e.offsetHeight:null}};var dist=function(n){void 0===n&&(n=r$1);var u=_global_window_react.useState(n(null)),o=u[0],i=u[1],c=_global_window_react.useCallback((function(e){return i(n(e.current))}),[n]);return [_global_window_react.useMemo((function(){return _global_window_react.createElement(t$1,{onResize:c})}),[c]),o]};
function dispatchCustomEvent(name, model) {
window.dispatchEvent(new CustomEvent(name, {
detail: {
model: model
}
}));
}
function bindCustomListener(_ref) {
var debounce = _ref.debounce,
listenerFn = _ref.listenerFn;
var eventName = debounce ? "modelChangedDebounce" : "modelChanged";
window.addEventListener(eventName, listenerFn);
var otherEventName = debounce ? "modelChanged" : "modelChangedDebounce";
window.removeEventListener(otherEventName, listenerFn, false);
return function () {
return window.removeEventListener(eventName, listenerFn, false);
};
}
var useState = window.react.useState,
useEffect = window.react.useEffect,
createContext = window.react.createContext,
useCallback = window.react.useCallback;
var PluginContext = createContext({});
var Provider = function Provider(_ref) {
var sandbox = _ref.sandbox,
container = _ref.container,
utils = _ref.utils,
children = _ref.children;
var _useState = useState(),
_useState2 = _slicedToArray(_useState, 2),
model = _useState2[0],
setModel = _useState2[1];
var _useState3 = useState(sandbox.getText()),
_useState4 = _slicedToArray(_useState3, 2),
code = _useState4[0],
_setCode = _useState4[1];
var _useState5 = useState([]),
_useState6 = _slicedToArray(_useState5, 2),
markers = _useState6[0],
setMarkers = _useState6[1];
var _useState7 = useState(true),
_useState8 = _slicedToArray(_useState7, 2),
debounce = _useState8[0],
setDebounce = _useState8[1];
var _useResizeAware = dist(),
_useResizeAware2 = _slicedToArray(_useResizeAware, 2),
resizeListener = _useResizeAware2[0],
sizes = _useResizeAware2[1];
var listenerFn = useCallback(function (evt) {
setModel(_objectSpread2({}, evt.detail.model));
_setCode(sandbox.getText());
}, [sandbox]);
useEffect(function () {
var disposable = sandbox.editor.onDidChangeModelDecorations(function () {
var allMarkers = sandbox.monaco.editor.getModelMarkers({}).map(function (marker, index) {
return _objectSpread2(_objectSpread2({}, marker), {}, {
key: index.toString()
});
});
var noMarkersBeforeNoMarkersNow = markers.length === 0 && allMarkers.length === 0;
if (!noMarkersBeforeNoMarkersNow) setMarkers(allMarkers);
});
return function () {
return disposable.dispose();
};
}, [sandbox, markers.length]);
useEffect(function () {
return bindCustomListener({
debounce: debounce,
listenerFn: listenerFn
});
}, [debounce, listenerFn]);
var setCode = useCallback(function (value, options) {
if (options && options.format) {
sandbox.setText(value);
sandbox.editor.getAction("editor.action.formatDocument").run();
} else {
sandbox.setText(value);
}
}, [sandbox]);
var formatCode = useCallback(function () {
return sandbox.editor.getAction("editor.action.formatDocument").run();
}, [sandbox.editor]);
var _window$playground$ui = window.playground.ui,
showModal = _window$playground$ui.showModal,
flashInfo = _window$playground$ui.flashInfo;
return /*#__PURE__*/window.react.createElement(PluginContext.Provider, {
value: {
model: model,
code: code,
container: _objectSpread2({
ref: container
}, sizes),
flashInfo: flashInfo,
formatCode: formatCode,
markers: markers,
sandbox: sandbox,
setCode: setCode,
setDebounce: setDebounce,
showModal: showModal,
utils: utils
}
}, resizeListener, children);
};
var useContext = window.react.useContext;
function usePlugin() {
return useContext(PluginContext);
}
var checkFields = function checkFields(obj, fields) {
return _typeof(obj) === "object" && fields.every(function (field) {
return typeof obj[field] !== "undefined";
});
};
var formatDate = function formatDate(date) {
return date.toLocaleDateString();
};
var convertToDate = function convertToDate(str) {
var today = removeTime(new Date());
var parts = str.split("/");
var date = parts.length === 1 ? new Date(today.getFullYear(), today.getMonth(), Number(parts[0])) : parts.length === 2 ? new Date(today.getFullYear(), Number(parts[0]) - 1, Number(parts[1])) : new Date(str);
return isNaN(date.getTime()) ? new Error("Error converting '".concat(str, "' to date")) : removeTime(date);
};
var secInMs = 1000;
var minInMs = 60 * secInMs;
var hourInMs = 60 * minInMs;
var dayInMs = 24 * hourInMs;
var addDays = function addDays(date, days) {
return new Date(date.getTime() + days * dayInMs);
};
var getDaysOffset = function getDaysOffset(startDate) {
return function (date) {
return Math.round((date.getTime() - startDate.getTime()) / dayInMs);
};
};
/** Return just the date portion of the date without any time */
var removeTime = function removeTime(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
};
var fractionOfOne = function fractionOfOne(num) {
// This should return a Try for type safety but this is just a tinker project and I'm being incredibly lazy.
if (num > 1.0) console.warn("Number not expected to exceed one");
return num;
};
var zeroBasedIndex = function zeroBasedIndex(num) {
return num;
};
var range = function range(start, stop) {
var result = [];
var current = start;
while (current <= stop) {
result.push(current);
current++;
}
return result;
};
var fractionOfNumber = function fractionOfNumber(num) {
return fractionOfOne(num - Math.floor(num));
};
var createMap = function createMap(values, keyFn) {
return values.reduce(function (map, value, index) {
map[keyFn(value, index)] = value;
return map;
}, {});
};
var aggregate = function aggregate(values, keyFn) {
return values.reduce(function (map, value, index) {
var key = keyFn(value, index);
var entries = getCreateEntry(map, key, function () {
return [];
});
entries.push(value);
return map;
}, {});
};
var getCreateEntry = function getCreateEntry(map, id, create) {
var existing = map[id];
if (!existing) {
var newEntry = create();
map[id] = newEntry;
return newEntry;
} else return existing;
};
var forEach = function forEach(map, fn) {
Object.keys(map).forEach(function (key) {
fn(key, map[key]);
});
};
var mapToArray = function mapToArray(map, fn) {
var result = [];
for (var _i = 0, _Object$keys = Object.keys(map); _i < _Object$keys.length; _i++) {
var _key = _Object$keys[_i];
result.push(fn(_key, map[_key]));
}
return result;
};
var mapMap = function mapMap(map, fn) {
var outMap = {};
for (var _i2 = 0, _Object$keys2 = Object.keys(map); _i2 < _Object$keys2.length; _i2++) {
var _key2 = _Object$keys2[_i2];
outMap[_key2] = fn(_key2, map[_key2]);
}
return outMap;
};
/**
* - When the arguments are valid, returns a new array with the item at the fromIndex moved to the toIndex
* - Otherwise returns an Error.
**/
var moveItem = function moveItem(_ref) {
var values = _ref.values,
fromIndex = _ref.fromIndex,
toIndex = _ref.toIndex;
var lastIndex = values.length - 1;
if (fromIndex < 0 || fromIndex > lastIndex) return new Error("Expected fromIndex to be in the range 0..".concat(lastIndex));
if (toIndex < 0 || toIndex > lastIndex) return new Error("Expected toIndex to be in the range 0..".concat(lastIndex));
var newValues = values.slice(0);
var element = newValues[fromIndex];
newValues.splice(fromIndex, 1);
newValues.splice(toIndex, 0, element);
return newValues;
};
var flatten = function flatten(it) {
var _ref2;
return (_ref2 = []).concat.apply(_ref2, _toConsumableArray(it));
};
var flatMap = function flatMap(it, map) {
return flatten(it.map(map));
};
var lastItemThrow = function lastItemThrow(arr) {
var len = arr.length;
if (len < 0) throw new Error("Cannot fetch last item of empty array.");
return arr[len - 1];
};
var partition = function partition(arr, predicate) {
var trueList = [];
var falseList = [];
arr.forEach(function (item) {
(predicate(item) ? trueList : falseList).push(item);
});
return [trueList, falseList];
};
var getValue = function getValue(v) {
if (v instanceof Error) throw v;else return v;
};
var mapErrorMessage = function mapErrorMessage(v, map) {
return v instanceof Error ? new Error(map(v.message)) : v;
};
var map = function map(v, _map) {
return v instanceof Error ? v : _map(v);
};
var isIteration = function isIteration(json) {
return checkFields(json, ["dates", "teamSchedule", "stories"]) && checkFields(json.dates, ["firstDayOfIteration", "lastDayOfIteration", "lastDayOfCoding"]);
};
/** The number of days past the end of the iteration that we will continue to calculate weekend days. */
var pastIterationEndBuffer = 14;
var parseIterationJson = function parseIterationJson(json) {
if (isIteration(json)) {
try {
var startDate = getValue(convertToDate(json.dates.firstDayOfIteration));
var startDayOfWeek = startDate.getDay();
var dayOffset = getDaysOffset(startDate);
var convertToDateNumber = function convertToDateNumber(str, fieldName) {
return getValue(mapErrorMessage(map(convertToDate(str), dayOffset), function (msg) {
return "Error converting ".concat(fieldName, ": ").concat(msg);
}));
};
var userDates = {
firstDayOfIteration: convertToDateNumber(json.dates.firstDayOfIteration, "firstDayOfIteration"),
lastDayOfCoding: convertToDateNumber(json.dates.lastDayOfCoding, "lastDayOfCoding"),
lastDayOfIteration: convertToDateNumber(json.dates.lastDayOfIteration, "lastDayOfIteration"),
teamPTODays: json.dates.teamPTODays.map(function (day, index) {
return convertToDateNumber(day, "teamPTODays[".concat(index, "]"));
})
};
if (userDates.lastDayOfIteration < userDates.firstDayOfIteration) return new Error("The last day of the iteration must be on or after the first day.");
if (userDates.lastDayOfCoding > userDates.lastDayOfIteration) return new Error("Last day of coding must be on or prior to last day of iteration.");
if (userDates.lastDayOfCoding <= userDates.firstDayOfIteration) return new Error("Last day of coding must follow the first day of the iteration. You have to give your devs a little time!");
if (userDates.lastDayOfIteration > 60) return new Error("Cannot plan iterations more than 60 days with this tool.");
var result = {
teamSchedule: mapMap(json.teamSchedule, function (_teamMember, _ref) {
var hoursPerDay = _ref.hoursPerDay,
ptoDays = _ref.ptoDays;
return {
hoursPerDay: hoursPerDay,
ptoDays: ptoDays.map(function (day) {
return dayOffset(getValue(convertToDate(day)));
})
};
}),
stories: json.stories,
userDates: _objectSpread2(_objectSpread2({}, userDates), {}, {
teamPTODays: [].concat(_toConsumableArray(userDates.teamPTODays), _toConsumableArray(range(0, userDates.lastDayOfIteration + pastIterationEndBuffer).filter(function (it) {
var adjustedDay = (it + startDayOfWeek) % 7;
return adjustedDay === 0 || adjustedDay === 6;
})))
})
};
var errors = [].concat(_toConsumableArray(flatten(mapToArray(result.teamSchedule, function (teamMember, teamMemberSchedule) {
var daysBeforeIteration = teamMemberSchedule.ptoDays.filter(function (day) {
return day < 0;
});
var daysAfterIteration = teamMemberSchedule.ptoDays.filter(function (day) {
return day > result.userDates.lastDayOfIteration;
});
return [].concat(_toConsumableArray(daysBeforeIteration.length > 0 ? ["".concat(teamMember, " has PTO days listed which are prior to the start of the iteration.")] : []), _toConsumableArray(daysAfterIteration.length > 0 ? ["".concat(teamMember, " has PTO days listed which follow the last day of the iteration.")] : []));
}))), _toConsumableArray(flatten(mapToArray(result.stories, function (storyId, story) {
if (!!story.dependsOn) {
var badReferences = story.dependsOn.filter(function (otherStoryReference) {
return !result.stories[otherStoryReference];
});
if (badReferences.length > 0) return ["Story ".concat(storyId, " depends on the following story identifiers which don't exist ").concat(badReferences)];
}
return [];
}))));
return errors.length > 0 ? new Error(errors.join(", ")) : _objectSpread2(_objectSpread2({}, result), {}, {
startDate: startDate,
lastDayToConsider: result.userDates.lastDayOfIteration + pastIterationEndBuffer
});
} catch (ex) {
return ex instanceof Error ? ex : new Error(ex + "");
}
} else return null;
};
var initialParseState = {
rawInput: "",
result: null
};
var updateParsedField = function updateParsedField(current, rawInput) {
return _objectSpread2(_objectSpread2({}, current), {}, {
value: {
rawInput: rawInput,
result: current.parser(rawInput)
}
});
};
/**
* A tri-state computation result which can be
* - null means never attempted
* - Error indicates an error producing the result
* - result
**/
var isOK = function isOK(value) {
return !(value instanceof Error) && value !== null && value !== undefined;
};
var flatMap$1 = function flatMap(value, map) {
return isOK(value) ? map(value) : value;
};
var map2 = function map2(in1, in2, map) {
return isOK(in1) && isOK(in2) ? map(in1, in2) : in1 instanceof Error ? in1 : in2 instanceof Error ? in2 : null;
};
var aggregateResultCases = function aggregateResultCases(givenResults) {
var errors = [];
var results = [];
var numberOfNull = 0;
givenResults.forEach(function (result) {
if (result instanceof Error) errors.push(result);else if (result === null) numberOfNull++;else results.push(result);
});
return {
errors: errors,
numberOfNull: numberOfNull,
results: results
};
};
var aggregateResults = function aggregateResults(results) {
var cases = aggregateResultCases(results);
return cases.errors.length > 0 ? new Error(cases.errors.map(function (err) {
return err.message;
}).join(", ")) : cases.numberOfNull > 0 ? null : cases.results;
};
var teamParser = function teamParser(str) {
return (str || "").trim().length === 0 ? null : {
teamMembers: (str || "").trim().split(",").map(function (it) {
return it.trim();
}).filter(function (it) {
return it.length > 0;
})
};
};
var storiesParser = function storiesParser(str) {
var cleaned = (str || "").trim();
if (cleaned.length === 0) return null;
return flatMap$1(aggregateResults(cleaned.split("\n").map(function (it) {
return it.trim();
}).filter(function (it) {
return it.length > 0;
}).map(function (it, index) {
var storyColumns = it.split(",").map(function (it) {
return it.trim();
}).filter(function (it) {
return it.length > 0;
});
return storyColumns.length === 1 || storyColumns.length === 2 ? storyColumns : new Error("Story # ".concat(index + 1, ": \"").concat(it, "\": Expected to have one or two fields separated by commas but found ").concat(storyColumns.length, " fields."));
})), function (stories) {
return stories.map(function (storyParts) {
return storyParts.length === 1 ? {
name: storyParts[0],
description: storyParts[0]
} : {
name: storyParts[0],
description: storyParts[1]
};
});
});
};
var quote = function quote(str) {
return "\"".concat(str, "\"");
};
var teamMemberCode = function teamMemberCode(teamMember) {
return "\n \"".concat(teamMember, "\": {\n ptoDays: [],\n hoursPerDay: 6\n },");
};
var exampleTeamMemberHour = function exampleTeamMemberHour(teamMember, index) {
return " // ".concat(index > 0 ? ", " : "", "\"").concat(teamMember, "\": 0");
};
var exampleTeamMembersHours = function exampleTeamMembersHours(teamMembers, index) {
return index === 0 ? teamMembers.map(exampleTeamMemberHour).join("\n") : "";
};
var storyCode = function storyCode(teamMembers) {
return function (story, index) {
var _story$description;
return "\n \"".concat(story.name, "\": { \n description: \"").concat((_story$description = story.description) !== null && _story$description !== void 0 ? _story$description : story.name, "\",\n devHourEstimates: { \n").concat(exampleTeamMembersHours(teamMembers, index), "\n },\n qaHourEstimates: {\n").concat(exampleTeamMembersHours(teamMembers, index), "\n },\n // Uncomment \"dependsOn\" below and provide story identifiers of other stories this story depends upon. \n // dependsOn: [] \n },");
};
};
var createCode = function createCode(stories, _ref) {
var teamMembers = _ref.teamMembers;
return "type HoursPerDay = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;\ntype HourEstimates<teamMembers extends string> = { [teamMember in teamMembers]?: number; };\ntype Story<storyNames extends string, storyName extends string, teamMembers extends string> = {\n description: string;\n devHourEstimates: HourEstimates<teamMembers>;\n qaHourEstimates: HourEstimates<teamMembers>;\n dependsOn?: Exclude<storyNames, storyName>[];\n};\ntype TeamSchedule<teamMembers extends string> = { [teamMember in teamMembers]: TeamMemberSchedule; };\ntype TeamMemberSchedule = { ptoDays: string[]; hoursPerDay: HoursPerDay; };\ntype Stories<storyNames extends string, teamMembers extends string> = {\n [storyName in storyNames]: Story<storyNames, storyName, teamMembers>;\n};\ntype Iteration<teamMembers extends string, storyNames extends string> = {\n dates: {\n firstDayOfIteration: string;\n lastDayOfCoding: string;\n lastDayOfIteration: string;\n teamPTODays: readonly string[];\n };\n teamSchedule: TeamSchedule<teamMembers>;\n stories: Stories<storyNames, teamMembers>;\n};\n//------------------ Disregard Everything Above This Line --------------------\n\n/** \uD83D\uDCA5 Modify team members by first changing this type and then updating the iteration specification below. */\ntype TeamMembers = ".concat(teamMembers.map(quote).join(" | "), ";\n/** \uD83D\uDCA5 Modify the list of stories by first changing this type and then updating the iteration specification below. */\ntype StoryNames = ").concat(stories.map(function (it) {
return quote(it.name);
}).join(" | "), ";\nconst defineIteration = (iteration: Iteration<TeamMembers, StoryNames>) => iteration;\n\n/**\n * # Iteration Parameters.\n * Configure your iteration below\n * - Meaningful dates of the iteration\n * - Individual team member PTO days and average work capacity\n * - Individual story task assignment and estimation.\n * \n * When the configuration passes a minimum of validation then a planning visualization\n * will display on the right.\n */\ndefineIteration({\n dates: {\n firstDayOfIteration: \"").concat(formatDate(new Date()), "\",\n lastDayOfIteration: \"").concat(formatDate(addDays(new Date(), 13)), "\",\n lastDayOfCoding: \"").concat(formatDate(addDays(new Date(), 10)), "\",\n teamPTODays: []\n },\n teamSchedule: {").concat(teamMembers.map(teamMemberCode).join(""), "\n },\n stories: {").concat(stories.map(storyCode(teamMembers)).join(""), "\n }\n});\n");
};
var initial = {
teamMembers: {
name: "Team Members",
value: initialParseState,
parser: teamParser,
instructions: "Enter team members in a comma delimited list"
},
stories: {
name: "Stories",
value: initialParseState,
parser: storiesParser,
instructions: "Enter one story per line. If you want to provide a description enter the identifier comma description ex. \"S123, Build the the thing\""
},
typeScriptCode: "",
generateTypeScriptCodeResult: null,
iterationResult: null,
iterationPlanResult: null,
planView: "GanttChart",
storyOrdering: []
};
var createSet = function createSet(values) {
return values.reduce(function (set, value) {
set[value] = true;
return set;
}, {});
};
var taskId = function taskId(_ref) {
var story = _ref.story,
kind = _ref.kind,
assigned = _ref.assigned;
return "".concat(story, "::").concat(kind, "::").concat(assigned);
};
var createResources = function createResources(stories, teamSchedule, teamPTODays) {
var storyMap = createMap(stories, function (story) {
return story.id;
});
var resourceMap = {};
stories.forEach(function (story) {
var _story$dependsOn;
var dependsOnStories = (_story$dependsOn = story.dependsOn) !== null && _story$dependsOn !== void 0 ? _story$dependsOn : [];
var dependsOnDevTasks = flatMap(dependsOnStories, function (story) {
var dependsOnStory = storyMap[story];
if (!dependsOnStory) throw new Error("Unable to find ".concat(story, " in story map"));
return Object.keys(dependsOnStory.devHourEstimates).map(function (devOnOtherStory) {
return taskId({
story: story,
kind: "dev",
assigned: devOnOtherStory
});
});
});
var devTasks = mapToArray(story.devHourEstimates, function (assigned, hoursEstimate) {
var schedule = teamSchedule[assigned];
if (!schedule) throw new Error("Unable to find schedule for ".concat(assigned));
return {
story: story.id,
assigned: assigned,
dependsOn: dependsOnDevTasks,
durationInDays: hoursEstimate / schedule.hoursPerDay,
kind: "dev"
};
});
devTasks.forEach(function (it) {
var tasks = getCreateEntry(resourceMap, it.assigned, function () {
return [];
});
tasks.push(it);
});
forEach(story.qaHourEstimates, function (assigned, hoursEstimate) {
var tasks = getCreateEntry(resourceMap, assigned, function () {
return [];
});
var schedule = teamSchedule[assigned];
if (!schedule) throw new Error("Unable to find schedule for ".concat(assigned));
tasks.push({
story: story.id,
assigned: assigned,
dependsOn: devTasks.map(taskId),
durationInDays: hoursEstimate / schedule.hoursPerDay,
kind: "qa"
});
});
});
return mapToArray(resourceMap, function (name, tasks) {
var capacityAvailableOnDay = new Map();
var teamMemberSchedule = teamSchedule[name];
for (var dayNumber = 0; dayNumber < 60; dayNumber++) {
capacityAvailableOnDay.set(dayNumber, 1);
}
teamMemberSchedule.ptoDays.forEach(function (day) {
return capacityAvailableOnDay.set(day, 0);
});
teamPTODays.forEach(function (day) {
return capacityAvailableOnDay.set(day, 0);
});
return {
name: name,
tasks: tasks,
capacityAvailableOnDay: capacityAvailableOnDay
};
});
};
var fractionOf = function fractionOf(num) {
return num - Math.floor(num);
};
var scheduleResources = function scheduleResources(unscheduledResources, pastEndOfIterationBuffer) {
var resourceNameToCapacityAvailable = mapMap(createMap(unscheduledResources, function (it) {
return it.name;
}), function (_, val) {
return val.capacityAvailableOnDay;
});
var resources = unscheduledResources.map(function (_ref2) {
var name = _ref2.name,
tasks = _ref2.tasks,
capacityAvailableOnDay = _ref2.capacityAvailableOnDay;
return {
name: name,
capacityAvailableOnDay: capacityAvailableOnDay,
remainingTasks: tasks.slice(),
scheduledTasks: []
};
});
var missedCycleCount = 0;
var scheduledMap = {};
while (resources.some(function (resource) {
return resource.remainingTasks.length > 0;
}) && missedCycleCount < 2) {
var scheduled = false;
resources.filter(function (resource) {
return resource.remainingTasks.length > 0;
}).forEach(function (resource) {
while (didScheduleResource(resource)) {
scheduled = true;
}
});
missedCycleCount = scheduled ? 0 : missedCycleCount + 1;
}
return resources;
function didScheduleResource(resource) {
var pendingTask = resource.remainingTasks[0];
if (!pendingTask) return false;
var dependenciesTry = getScheduledDependencies(pendingTask.dependsOn);
if (dependenciesTry instanceof Error) return false;
resource.remainingTasks.shift();
resource.scheduledTasks.push(scheduleTask(resourceNameToCapacityAvailable[resource.name], pendingTask, dependenciesTry, pastEndOfIterationBuffer));
return true;
}
function getScheduledDependencies(taskIdentifiers) {
var scheduledTasks = [];
var _iterator = _createForOfIteratorHelper(taskIdentifiers),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var identifier = _step.value;
var scheduledTask = scheduledMap[identifier];
if (scheduledTask === undefined) return new Error("Unable to find identifier ".concat(identifier));else scheduledTasks.push(scheduledTask);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return scheduledTasks;
}
function scheduleTask(capacityAvailableOnDayMap, task, dependencies, pastEndOfIterationBuffer) {
var startOn = dependencies.reduce(function (maxDay, scheduledTask) {
var lastDayWorked = lastItemThrow(scheduledTask.daysWorked);
return Math.max(lastDayWorked.startOfDay + lastDayWorked.fractionOfDay, maxDay);
}, 0);
var daysWorked = [];
var remainingDays = task.durationInDays;
var capacityAvailableOnDay = capacityAvailableOnDayMap.get(Math.floor(startOn));
if (capacityAvailableOnDay !== undefined && capacityAvailableOnDay > 0) {
startOn = startOn + (1 - capacityAvailableOnDay);
var fractionOfDay = Math.min(remainingDays, 1 - fractionOf(startOn));
remainingDays -= fractionOfDay;
capacityAvailableOnDayMap.set(Math.floor(startOn), capacityAvailableOnDay - fractionOfDay);
daysWorked.push({
startOfDay: startOn,
fractionOfDay: fractionOfOne(fractionOfDay)
});
}
var currentDay = Math.floor(startOn) + 1;
while (remainingDays > 0 && currentDay < pastEndOfIterationBuffer) {
var _capacityAvailableOnDay = capacityAvailableOnDayMap.get(Math.floor(currentDay));
if (_capacityAvailableOnDay !== undefined && _capacityAvailableOnDay > 0) {
var _fractionOfDay = Math.min(remainingDays, _capacityAvailableOnDay);
remainingDays -= _fractionOfDay;
capacityAvailableOnDayMap.set(Math.floor(currentDay), _capacityAvailableOnDay - _fractionOfDay);
daysWorked.push({
startOfDay: currentDay + (1 - _capacityAvailableOnDay),
fractionOfDay: fractionOfOne(_fractionOfDay)
});
}
currentDay++;
}
var scheduledTask = _objectSpread2(_objectSpread2({}, task), {}, {
daysWorked: daysWorked
});
scheduledMap[taskId(task)] = scheduledTask;
return scheduledTask;
}
};
/**
* Create the iteration plan from the iteration (parsed from the TypeScript Playground) and the story ordering.
*
* **⛔ATTENTION:** Do **NOT** look to this file for algorithm elegance!!! This was thrown together brute force.
* I will clean it up in the future. For now I am concentrating on proving the concept of using the
* TypeScript Playground and type-checker as a _complement_ to a classic GUI.
*/
var createIterationPlan = function createIterationPlan(_ref3) {
var iteration = _ref3.iteration,
storyOrdering = _ref3.storyOrdering;
var storyMap = iteration.stories;
var stories = storyOrdering.map(function (id) {
return _objectSpread2({
id: id
}, storyMap[id]);
});
var resources = createResources(stories, iteration.teamSchedule, iteration.userDates.teamPTODays);
var scheduledResources = scheduleResources(resources, iteration.lastDayToConsider);
var dates = {
lastDayOfCoding: zeroBasedIndex(iteration.userDates.lastDayOfCoding),
endOfIteration: zeroBasedIndex(iteration.userDates.lastDayOfIteration),
startDate: iteration.startDate
};
return {
getStoriesPlan: function getStoriesPlan() {
var scheduledTasksByStory = aggregate(flatMap(scheduledResources, function (resource) {
return resource.scheduledTasks;
}), function (task) {
return task.story;
});
var remainingTasksByStory = aggregate(flatMap(scheduledResources, function (resource) {
return resource.remainingTasks;
}), function (task) {
return task.story;
});
var plannedStories = storyOrdering.map(createPlannedStory);
return {
dates: _objectSpread2(_objectSpread2({}, dates), {}, {
lastStoryCompleted: zeroBasedIndex(plannedStories.reduce(function (lastDay, current) {
return current.completedDayNumberMaybe === null ? lastDay : Math.max(lastDay, current.completedDayNumberMaybe);
}, 0))
}),
stories: plannedStories
};
function createPlannedStory(storyName) {
var story = storyMap[storyName];
var scheduledTasks = scheduledTasksByStory[storyName] || [];
var remainingTasks = remainingTasksByStory[storyName] || [];
return {
story: storyName,
description: story.description,
tasks: [].concat(_toConsumableArray(scheduledTasks.map(function (scheduledTask) {
return {
assigned: scheduledTask.assigned,
kind: scheduledTask.kind,
daysWorkedTry: scheduledTask.daysWorked
};
})), _toConsumableArray(remainingTasks.map(function (remainingTask) {
return {
assigned: remainingTask.assigned,
kind: remainingTask.kind,
daysWorkedTry: new Error("Unable to schedule task of ".concat(remainingTask.durationInDays, " days"))
};
}))),
completedDayNumberMaybe: remainingTasks.length > 0 || scheduledTasks.length < 1 ? null : scheduledTasks.reduce(function (max, current) {
return current.daysWorked.length === 0 ? max : Math.max(max, current.daysWorked[current.daysWorked.length - 1].startOfDay);
}, 0)
};
}
},
getResourcesView: function getResourcesView() {
return {
resources: scheduledResources.map(createResourceView),
dates: dates
};
}
};
function createResourceView(_ref4) {
var name = _ref4.name,
capacityAvailableOnDay = _ref4.capacityAvailableOnDay,
scheduledTasks = _ref4.scheduledTasks;
var dayUsageMap = new Map();
capacityAvailableOnDay.forEach(function (availableCapacity, dayNumber) {
dayUsageMap.set(dayNumber, {
fractionIdle: fractionOfOne(availableCapacity),
fractionWorking: fractionOfOne(0)
});
});
scheduledTasks.forEach(function (_ref5) {
var daysWorked = _ref5.daysWorked;
daysWorked.forEach(function (_ref6) {
var startOfDay = _ref6.startOfDay,
fractionOfDay = _ref6.fractionOfDay;
var dayNumber = Math.floor(startOfDay);
var existingDayUsage = dayUsageMap.get(dayNumber);
dayUsageMap.set(dayNumber, existingDayUsage === undefined ? {
fractionIdle: fractionOfOne(0),
fractionWorking: fractionOfOne(fractionOfDay)
} : _objectSpread2(_objectSpread2({}, existingDayUsage), {}, {
fractionWorking: fractionOfOne(existingDayUsage.fractionWorking + fractionOfDay)
}));
});
});
return {
name: name,
dayUsageMap: dayUsageMap
};
}
};
/* global Map:readonly, Set:readonly, ArrayBuffer:readonly */
var hasElementType = typeof Element !== 'undefined';
var hasMap = typeof Map === 'function';
var hasSet = typeof Set === 'function';
var hasArrayBuffer = typeof ArrayBuffer === 'function' && !!ArrayBuffer.isView;
// Note: We **don't** need `envHasBigInt64Array` in fde es6/index.js
function equal(a, b) {
// START: fast-deep-equal es6/index.js 3.1.1
if (a === b) return true;
if (a && b && typeof a == 'object' && typeof b == 'object') {
if (a.constructor !== b.constructor) return false;
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (!equal(a[i], b[i])) return false;
return true;
}
// START: Modifications:
// 1. Extra `has<Type> &&` helpers in initial condition allow es6 code
// to co-exist with es5.
// 2. Replace `for of` with es5 compliant iteration using `for`.
// Basically, take:
//
// ```js
// for (i of a.entries())
// if (!b.has(i[0])) return false;
// ```
//
// ... and convert to:
//
// ```js
// it = a.entries();
// while (!(i = it.next()).done)
// if (!b.has(i.value[0])) return false;
// ```
//
// **Note**: `i` access switches to `i.value`.
var it;
if (hasMap && (a instanceof Map) && (b instanceof Map)) {
if (a.size !== b.size) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0])) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!equal(i.value[1], b.get(i.value[0]))) return false;
return true;
}
if (hasSet && (a instanceof Set) && (b instanceof Set)) {
if (a.size !== b.size) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0])) return false;
return true;
}
// END: Modifications
if (hasArrayBuffer && ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (a[i] !== b[i]) return false;
return true;
}
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) return false;
for (i = length; i-- !== 0;)
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
// END: fast-deep-equal
// START: react-fast-compare
// custom handling for DOM elements
if (hasElementType && a instanceof Element) return false;
// custom handling for React/Preact
for (i = length; i-- !== 0;) {
if ((keys[i] === '_owner' || keys[i] === '__v' || keys[i] === '__o') && a.$$typeof) {
// React-specific: avoid traversing React elements' _owner
// Preact-specific: avoid traversing Preact elements' __v and __o
// __v = $_original / $_vnode
// __o = $_owner
// These properties contain circular references and are not needed when
// comparing the actual elements (and not their owners)
// .$$typeof and ._store on just reasonable markers of elements
continue;
}
// all other properties should be traversed as usual
if (!equal(a[keys[i]], b[keys[i]])) return false;
}
// END: react-fast-compare
// START: fast-deep-equal
return true;
}
return a !== a && b !== b;
}
// end fast-deep-equal
var reactFastCompare = function isEqual(a, b) {
try {
return equal(a, b);
} catch (error) {
if (((error.message || '').match(/stack|recursion/i))) {
// warn on circular references, don't crash
// browsers give this different errors name and messages:
// chrome/safari: "RangeError", "Maximum call stack size exceeded"
// firefox: "InternalError", too much recursion"
// edge: "Error", "Out of stack space"
console.warn('react-fast-compare cannot handle circular refs');
return false;
}
// some other error. we should definitely know about these
throw error;
}
};
var createGenerateCode = function createGenerateCode(_ref) {
var stories = _ref.stories,
teamMembers = _ref.teamMembers;
return map2(stories.value.result, teamMembers.value.result, function (stories, teamMembers) {
return function () {
return createCode(stories, teamMembers);
};
});
};
var reducer = function reducer(state, action) {
switch (action.kind) {
case "ChangePlanView":
return _objectSpread2(_objectSpread2({}, state), {}, {
planView: action.planView
});
case "GenerateTypeScriptCode":
return _objectSpread2(_objectSpread2({}, state), {}, {
typeScriptCode: action.generateCode()
});
case "MoveStory":
var storyOrderingTry = moveItem(_objectSpread2({
values: state.storyOrdering
}, action));
if (storyOrderingTry instanceof Error) {
console.error(storyOrderingTry);
return state;
} else return _objectSpread2(_objectSpread2({}, state), {}, {
storyOrdering: storyOrderingTry,
iterationPlanResult: isOK(state.iterationResult) ? createIterationPlan({
iteration: state.iterationResult,
storyOrdering: storyOrderingTry
}) : null
});
case "UpdateIterationResult":
var existingStorySet = createSet(state.storyOrdering);
var storiesFromIteration = isOK(action.iterationResult) ? Object.keys(action.iterationResult.stories) : [];
var storiesFromIterationSet = createSet(storiesFromIteration);
var storyOrdering = [].concat(_toConsumableArray(state.storyOrdering.filter(function (it) {
return storiesFromIterationSet[it];
})), _toConsumableArray(storiesFromIteration.filter(function (it) {
return !existingStorySet[it];
})));
return _objectSpread2(_objectSpread2({}, state), {}, {
storyOrdering: storyOrdering,
iterationResult: action.iterationResult,
iterationPlanResult: isOK(action.iterationResult) ? reactFastCompare(action.iterationResult, state.iterationResult) ? state.iterationPlanResult : createIterationPlan({
iteration: action.iterationResult,
storyOrdering: storyOrdering
}) : null
});
case "UpdateStories":
var stories = updateParsedField(state.stories, action.value);
return _objectSpread2(_objectSpread2({}, state), {}, {
stories: stories,
generateTypeScriptCodeResult: createGenerateCode({
stories: stories,
teamMembers: state.teamMembers
})
});
case "UpdateTeamMembers":
var teamMembers = updateParsedField(state.teamMembers, action.value);
return _objectSpread2(_objectSpread2({}, state), {}, {
teamMembers: teamMembers,
generateTypeScriptCodeResult: createGenerateCode({
teamMembers: teamMembers,
stories: state.stories
})
});
}
};
var ApplicationStateContext = /*#__PURE__*/window.react.createContext({});
var myEval = window.eval;
var Provider$1 = function Provider(_ref) {
var children = _ref.children;
var _usePlugin = usePlugin(),
setCode = _usePlugin.setCode,
code = _usePlugin.code,
markers = _usePlugin.markers,
sandbox = _usePlugin.sandbox,
setDebounce = _usePlugin.setDebounce;
var _useReducer = window.react.useReducer(reducer, initial),
_useReducer