Advertisement
Guest User

calculate-timecards.coffee

a guest
Jan 16th, 2020
133
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. log = console.log.bind console
  2.  
  3. splitHeaderContents = (snippetText) ->
  4.   headerEnd = snippetText.indexOf '\n\n'
  5.   header = snippetText.slice 0, headerEnd
  6.   contents = snippetText.slice headerEnd+2
  7.   { header, contents }
  8.  
  9.  
  10. # get all worklogs
  11. # build a hash
  12. # sum time
  13. # sort it
  14. # print it above timeWorkedTable
  15. # should also make a dictionary... or it should be possible to group
  16.  
  17. # code words in worklog. these in-outs should be merged with date line timestamps
  18. # in
  19. # out
  20.  
  21. # duration break  should create an out-in of that duration
  22. # eg. 12m break
  23.  
  24.  
  25. calculateTimecards = ->
  26.   textarea = document.querySelector('.note textarea#txtarea')
  27.   snippetText = textarea.value
  28.   { sum, timeWorkedTable, projectWorklog } = tools.processSnippet snippetText
  29.   totalTimestamp = tools.minutesToTimestamp sum
  30.   { header, contents } = splitHeaderContents snippetText
  31.   newFileContens = [
  32.     header,
  33.     '',
  34.     projectWorklog,
  35.     '',
  36.     "total time: #{totalTimestamp}  (as minutes: #{sum})",
  37.     timeWorkedTable,
  38.     '',
  39.     contents,
  40.     ].join '\n'
  41.  
  42.   textarea.value = newFileContens
  43.  
  44.  
  45. assert = (proposition) ->
  46.   if not proposition
  47.     assertion_failed
  48.  
  49.  
  50. assertListEquals = (list, other) ->
  51.   [
  52.     list.every (element) -> element in other,
  53.     other.every (element) -> element in list,
  54.   ].every Boolean
  55.  
  56.  
  57. tools =
  58.   isWorklogLine: (line) ->
  59.     (tools.isTimestampWorklogLine line) or (tools.isDurationWorklogLine line)
  60.   isTimestampWorklogLine: (line) ->
  61.     line.match /^(\d{2}:\d{2})\s+(.*)/
  62.   isDurationWorklogLine: (line) ->
  63.     line.match /^(\d+)[mh]\s+(.*)/
  64.   isTimestampLine: (line) ->
  65.     line.match /^\s*[12][09][0-9]{2}-?[0-9]{2}-?[0-9]{2}/
  66.   splitIntoDateAndInfo: (line) ->
  67.     m = line.match /^\s*([12][09][0-9]{2}-?[0-9]{2}-?[0-9]{2})\s+(.*)/
  68.     throw 'error 2' if not m
  69.     date = m[1]
  70.     info = m[2] # the rest
  71.     [date, info]
  72.   getInfoParts: (info) ->
  73.     parts = info.split ','
  74.     part.trim() for part in parts
  75.   partIsText: (part) ->
  76.     part.match /[a-zA-Z]/
  77.   partIsTimestampPair: (part) ->
  78.     part.match /\d{2}:\d{2}  ?\d{2}:\d{2}/
  79.   timestampLineHasError: (line) ->
  80.     try
  81.       r = tools.lineToTimeWorking line
  82.       not r
  83.     catch
  84.       true
  85.   hhmmToMinutes: (hhmm) ->
  86.     [hh, mm] = hhmm.split ':'
  87.     if hh.length isnt 2 or mm.length isnt 2
  88.       throw "error 8: timestamp was not of format HH:MM, was #{hhmm}"
  89.     parseInt(hh) * 60 + parseInt(mm)
  90.   timestampPairToMinutePair: (part) ->
  91.     parts = part.split(/  ?/).map (p) -> p.trim()
  92.     if parts.length isnt 2
  93.       throw "error 5: must have two (one in- and one out-) timestamp. you had #{parts}"
  94.     minutesPair = parts.map tools.hhmmToMinutes
  95.     minutesPair.map (minutes) ->
  96.       if !minutes? or minutes < 0
  97.         throw "error 6: timestamp with invalid value in minutes: #{minutes}"
  98.     minutesPair
  99.   lineToTimeWorking: (line) ->
  100.     [date, info] = tools.splitIntoDateAndInfo line
  101.     parts = tools.getInfoParts info
  102.     throw "error 3: there were no timestamps for date #{date}" if parts.length < 1
  103.     parts.map (part) ->
  104.       if not tools.partIsTimestampPair part
  105.         throw "error 4: part '#{part}' at date #{date} is not a timestamp pair"
  106.     try
  107.       minutePairs = parts.map (part) -> tools.timestampPairToMinutePair part
  108.     catch e
  109.       throw "error 7: error when processing date #{date}: #{e}"
  110.     minutesThisDay = minutePairs.reduce (sum, pair) ->
  111.       sum + pair[1] - pair[0]
  112.     , 0
  113.     { date, minutesThisDay }
  114.   minutesToTimestamp: (minutes) ->
  115.     h = Math.floor(minutes/60).toString()
  116.     if h.length is 1 then hh = '0' + h else hh = h
  117.     m = (minutes%60).toString()
  118.     if m.length is 1 then mm = '0' + m else mm = m
  119.     hh + ':' + mm
  120.   makeTimeWorkedTable: (datesAndWorkingTimes) ->
  121.     lines = datesAndWorkingTimes.map (item) ->
  122.       "#{item.date} #{tools.minutesToTimestamp(item.minutesThisDay)}"
  123.     lines.join '\n'
  124.   dayInfoReducer: (days, line) ->
  125.     if line is ''
  126.       days.push {}
  127.     else
  128.       a_day = days[days.length - 1]
  129.       isTimestampLine = tools.isTimestampLine line
  130.       if isTimestampLine
  131.         a_day['date'] = tools.lineToTimeWorking line
  132.       else
  133.         a_day['worklog'] = 'heh'
  134.     days
  135.   processSnippet: (snippetText) ->
  136.     lines = snippetText.split '\n'
  137.     dayInfo = lines.reduce tools.dayInfoReducer, [{}]
  138.     projectWorklog = dayInfo # TODO implement
  139.     timestampLines = lines.filter (line, idx) ->
  140.       emptyLineAbove = idx is 0 or lines[idx-1] is ''
  141.       isTimestampLine = tools.isTimestampLine line
  142.       if isTimestampLine and not emptyLineAbove
  143.         throw "error 1: missing empty line above timestamp line #{line}"
  144.       isTimestampLine
  145.     datesAndWorkingTimes = timestampLines.map tools.lineToTimeWorking
  146.     sum = 0
  147.     sum += w.minutesThisDay for w in datesAndWorkingTimes
  148.     timeWorkedTable = tools.makeTimeWorkedTable datesAndWorkingTimes
  149.     { sum, timeWorkedTable, projectWorklog }
  150.  
  151. tests =
  152.   test_makeTimeWorkedTable: ->
  153.     expectedTable = [
  154.       '2018-01-06 01:40',
  155.       '2018-01-07 11:41',
  156.       ].join '\n'
  157.     actual = tools.makeTimeWorkedTable [
  158.       {date: '2018-01-06', minutesThisDay: 100},
  159.       {date: '2018-01-07', minutesThisDay: 701},
  160.       ]
  161.     assert expectedTable.trim() is actual.trim()
  162.   test_minutesToTimestamp: ->
  163.     assert '10:40' is tools.minutesToTimestamp 640
  164.     assert '00:10' is tools.minutesToTimestamp 10
  165.     assert '00:00' is tools.minutesToTimestamp 0
  166.     assert '26:30' is tools.minutesToTimestamp 1590
  167.   test_processSnippet_withInvalidWorklogEntry: ->
  168.     try
  169.       tools.processSnippet [
  170.         '2018-02-02 10:06 10:41, 12:29 16:10'
  171.         '11:11 min side refactoring'
  172.       ].join '\n'
  173.       assert false
  174.     catch
  175.       # was not working at 11 am, so must crash
  176.       assert true
  177.   test_processSnippet_hmm: ->
  178.     # TODO In the process of implementing here, yes.
  179.     processed = tools.processSnippet [
  180.         '2018-06-04'
  181.         '10m standup'
  182.         '11:20 chats, meeting agenda, work logging'
  183.         '5m break'
  184.         '12:10 out'
  185.         '12:16 in'
  186.         '1h weekly dev sync (logged in jira)'
  187.         '2h jira issue creation meeting with eugen and roy'
  188.         '1h work logging'
  189.         '1h email with brandmaster supplier for category editing API'
  190.         '30m documentation for block watne (in confluence)'
  191.         '15:54 out'
  192.     ].join '\n'
  193.   test_processSnippet_withWorklogEntries: ->
  194.     processed = tools.processSnippet [
  195.         '2018-02-02 10:06 10:41, 12:29 16:10'
  196.         '10:10 min side refactoring'
  197.         '12:29 min side cleanup'
  198.     ].join '\n'
  199.     expected = [
  200.       '2018-02-02'
  201.       '4.0h 3h41m min side cleanup'
  202.       '0.5h 0h30m min side refactoring'
  203.       '0.0h 0h4m untracked'
  204.     ].join '\n'
  205.     assert processed.projectWorklog is expected
  206.     # result must be
  207.     # 4m untracked / unknown
  208.     # 41 - 10 = 31 minutes of min side refactoring
  209.     #
  210.   test_processSnippet_missingEmptyLine: ->
  211.     try
  212.       tools.processSnippet [
  213.         '2018-02-02 10:06 10:41, 12:29 16:10',
  214.         'discussed naming with sigrid',
  215.         'read about react',
  216.         'posted naming question on stackoverflow',
  217.         'learned design thinking',
  218.         'missing empty line',
  219.         '2018-02-01 11:10 11:41, 12:15 17:00, 17:32 17:48',
  220.         ].join '\n'
  221.       assert false
  222.     catch
  223.       assert true
  224.   test_processSnippet_textInTimestampIsNotSupported: ->
  225.     try
  226.       tools.processSnippet [
  227.         '2018-01-30 09:55 16:45, minus 30 min lunsj not implemented',
  228.         '',
  229.         '2018-01-29 09:35 16:54, minus 30 min lunsj',
  230.         '',
  231.         '2018-01-26 09:10 13:21, 13:55 17:40 eller 18:25, minus 30 min lunsj',
  232.         ].join '\n'
  233.       assert false
  234.     catch e
  235.       assert true
  236.   test_processSnippet_assertCorrectSum: ->
  237.     processed = tools.processSnippet [
  238.         '2018-02-02 10:06 10:41, 12:29 16:10',
  239.         'discussed naming with sigrid',
  240.         'read about react',
  241.         '',
  242.         '2018-02-01 11:10 11:41, 12:15 17:00, 17:32 17:48',
  243.         'posted naming question on stackoverflow',
  244.         'learned design thinking',
  245.         'spoke to kommunikasjonsrÃ¥dgiverjente',
  246.         'no description for previous day, thats okay',
  247.         '',
  248.         '2018-01-29 09:35 16:54',
  249.         '',
  250.         '2018-01-26 09:10 13:21, 13:55 17:40',
  251.         'what the person did the first day',
  252.         ].join '\n'
  253.     assert 1503 is processed.sum
  254.   test_timestampPairToMinutePair: ->
  255.     assertListEquals [606, 641], tools.timestampPairToMinutePair '10:06 10:41'
  256.     assertListEquals [749, 970], tools.timestampPairToMinutePair '12:29 16:10'
  257.     try
  258.       tools.timestampPairToMinutePair '12:29 16:1'
  259.       assert false
  260.     catch
  261.       assert true
  262.     try
  263.       tools.timestampPairToMinutePair '12:29 16:'
  264.       assert false
  265.     catch
  266.       assert true
  267.     try
  268.       tools.timestampPairToMinutePair '12:2916:12'
  269.       assert false
  270.     catch
  271.       assert true
  272.     try
  273.       tools.timestampPairToMinutePair '12:29'
  274.       assert false
  275.     catch
  276.       assert true
  277.     try
  278.       tools.timestampPairToMinutePair ''
  279.       assert false
  280.     catch
  281.       assert true
  282.   test_lineToTimeWorking: ->
  283.     actual = tools.lineToTimeWorking '2018-02-02 10:06 10:41, 12:29 16:10, 17:01 18:00'
  284.     assert 315 is actual.minutesThisDay
  285.     actual = tools.lineToTimeWorking '2018-01-30 09:55 16:45'
  286.     assert 410 is actual.minutesThisDay
  287.     try
  288.       tools.lineToTimeWorking '2018-01-30 09:55 16:45, minus 30 min lunsj'
  289.       assert false
  290.     catch
  291.       assert true
  292.   test_isTimestampLine: ->
  293.     assert tools.isTimestampLine '2018-02-02 10:06 10:41, 12:29 16:10, 17:01 18:00'
  294.     assert tools.isTimestampLine '2018-02-02 10:06 10:41, 12:29 16:10'
  295.     assert tools.isTimestampLine '2018-02-02 10:06 10:41'
  296.     assert tools.isTimestampLine '2018-02-02'
  297.     assert tools.isTimestampLine '2018-02-02'
  298.     assert tools.isTimestampLine '1996-12-02'
  299.     assert tools.isTimestampLine '1997-02-02 1000000000'
  300.     assert tools.isTimestampLine '2018-01-30 09:55 16:45, minus 30 min lunsj'
  301.     assert tools.isTimestampLine ' 2018-02-02 10:06 10:41, 12:29 16:10'
  302.     assert tools.isTimestampLine '  2018-02-02 10:06 10:41, 12:29 16:10'
  303.     assert not tools.isTimestampLine '1814-02-02'
  304.   test_timestampLineHasError: ->
  305.     assert tools.timestampLineHasError '2018-02-02 10:06 10:41, 12:29 16:'
  306.     assert tools.timestampLineHasError '2018-02-02 10:06 10:41, 12:29'
  307.     assert tools.timestampLineHasError '2018-02-02 10:06 10:41, 12:2'
  308.     assert tools.timestampLineHasError '2018-02-02'
  309.     assert tools.timestampLineHasError ' 2018-02-02' # err on leading whitespace
  310.     assert tools.timestampLineHasError '2018-02-02 10:06 10:41, '
  311.     assert not tools.timestampLineHasError '2018-02-02 10:06 10:41'
  312.     assert not tools.timestampLineHasError '2018-02-02 10:06 10:41, 11:40 20:01'
  313.   test_isTimestampWorklogLine: ->
  314.     assert tools.isTimestampWorklogLine '10:10 break'
  315.     assert tools.isTimestampWorklogLine '12:30 break'
  316.     assert tools.isTimestampWorklogLine '18:00 started another project'
  317.   test_isDurationWorklogLine: ->
  318.     assert tools.isDurationWorklogLine '10m break'
  319.     assert tools.isDurationWorklogLine '12m TT status meeting'
  320.     assert tools.isDurationWorklogLine '2h  TT status meeting'
  321.     assert tools.isDurationWorklogLine '2h TT status meeting'
  322.   test_isDurationWorklogLine_acceptOnlyStrictFormat: ->
  323.     assert not tools.isDurationWorklogLine '2 hours important meeting'
  324.     assert not tools.isDurationWorklogLine '12 min TT status meeting'
  325.     assert not tools.isDurationWorklogLine '12 minutes TT status meeting'
  326.   test_dayInfoReducer: ->
  327.     lines = [
  328.       '2018-05-30'
  329.       '10:00 in'
  330.       '12m break'
  331.       '12:00 out'
  332.       ''
  333.       '2018-05-31'
  334.       '10:00 in'
  335.       '12m break'
  336.       '12:00 out'
  337.     ]
  338.     dayInfo = lines.reduce tools.dayInfoReducer, []
  339.     day_0 =
  340.       date: '2018-05-30'
  341.       worklog: 'heh'
  342.     day_1 =
  343.       date: '2018-05-31'
  344.       worklog: 'heh'
  345.     assert dayInfo[0].date is day_0.date
  346.     assert dayInfo[0].worklog is day_0.worklog
  347.     assert dayInfo[1].date is day_1.date
  348.     assert dayInfo[1].worklog is day_1.worklog
  349.  
  350.  
  351.  
  352. do tests[testName] for testName of tests
  353.  
  354. module.exports = calculateTimecards
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement