UNPKG

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
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