hamacs/ha-org-sprint.org

425 lines
16 KiB
Org Mode
Raw Normal View History

#+TITLE: My Sprint Calculations and Support
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-25
A literate program for configuring org files for work-related notes.
#+begin_src emacs-lisp :exports none
;;; org-sprint --- Configuring org files for work-related notes. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; Licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams
;; Created: September 25, 2020
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/other/hamacs/org-sprint.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
* Introduction
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand little ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
I want a single keybinding that always displays the current Sprint note file, regardless of the Sprint. This means, I need to have functions that can calculate what this is.
To have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:snippets/org-mode/__sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
This template needs the following functions:
- =sprint-current-name= to be both the numeric label as well as the nickname
- =sprint-date-range= to include a org-formatted date range beginning and ending the sprint
- =sprint-date-from-start= return a date for pre-scheduled and recurring meetings
* Naming Sprints
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both… for myself.
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
#+begin_src emacs-lisp :noweb yes
(defvar sprint-nicknames
(--map (replace-regexp-in-string " *[:#].*" "" (first it))
'<<sprint-names-2022()>>)
"List of 26 Sprint Nicknames from A to Z.")
#+end_src
** 2022
2022-04-01 18:43:24 +00:00
Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://list.fandom.com/wiki/List_of_monsters][this list of monsters]] from mythology and other sources:
#+NAME: sprint-names-2022
- ankheg
- beholder
- centaur
- dragon
- elf
- fetch
- goblin
- hydra
- illythid
- jackalwere
- kobold
- lich
- mimic
- nymph
- owlbear
- pegasus
- quasit
- remorhaz
- satyr
- troll
- unicorn
- vampire
- warg
- xorn
- yuan-ti
- zombie
** 2021
Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-words-in-the-english-language/][Funny or Silly Words]]:
#+NAME: sprint-names-2021
- abibliophobia :: The fear of running out of reading materials to read
- bamboozled :: To trick or confuse someone
- catawampus :: Something positioned diagonally
- dweeb :: A boring and uninteresting person
- eep :: Another expression of surprise or fear.
- formication :: The feeling that ants are crawling on your skin.
- goombah :: An older friend who protects you.
- hootenanny :: A country music party or get-together.
- Izzat :: This relates to your personal respect and dignity.
- jabberwock :: Something that is complete nonsense or gibberish
- kebbie :: A Scottish term relating to a walking stick with a hooked end.
- lollygagger :: Someone who walks around with no aim or goal.
- mollycoddle :: To be extra nice to someone or to overprotect them.
- nacket :: A light lunch or snack.
- obi :: A sash worn around the waist of a kimono
- panjandrum :: Someone who thinks that they are superior to others.
- quoz :: Something that is strange.
- ratoon :: The small root that sprouts from a plant, especially during the springtime.
- sialoquent :: Someone who splits while talking.
- taradiddle :: this is a small lie or when someone is speaking nonsense.
- urubu :: A blank vulture found in South American.
- vamp :: To make something brand-new.
- wabbit :: A Scottish word referring to feeling exhausted or a little unwell.
- xanthoderm :: A person with yellowish skin.
- yerk :: Pull or push something with a sudden movement.
- zazzy :: Something that is shiny and flashy
** 2020
New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list of dinosaurs]].
#+NAME: sprint-names-2020
- ankylosaurus
- brontosaurus
- coelophysis
- diplodocus
- eoraptor
- fruitadens
- gobiceratops
- harpymimus
- iguanodozn
- jinfengopteryx
- kentrosaurus
- lambeosaurus
- maiasaura
- neimongosaurus
- oviraptor
- pachycephalosaurus
- quetzalcoatlus
- rioarribasaurus
- stegosaurus
- tyrannosaurus
- utahraptor
- velociraptor
- wannanosaurus
- xiaotingia
- yi
- zuul
** 2019
Came up with a list of somewhat well-known cities throughout the world (at least, they had to have a population of 100,000 or more), but I didn't want any real obvious ones.
#+NAME: sprint-names-2019
- achy-aachen
- bare-bacabal
- candid-cannes
- darling-dadu
- easy-edmonton
- fancy-fargo
- gray-gaya
- handsome-hanoi
- itchy-incheon
- jumpy-juba
- kind-kindia
- less-liling
- mad-madrid
- natural-naga
- octarine-oakland
- painful-paris
- quirky-qufu
- rabid-rabat
- slow-slough
- typing-taipei
- ugly-ufa
- vibrant-vienna
- wacky-waco
- xenophobic-xichang
- yellow-yamaguchi
- zippy-zinder
* Sprint Boundaries
Function to help in calculating dates and other features of a two-week sprint that starts on Thursday and ends on a Wednesday… how we work at my job.
Emacs have an internal rep of a time.
#+begin_src emacs-lisp
(defun get-date-time (date)
"My functions can't deal with dates as string, so this will
parse DATE as a string, or return the value given otherwise."
(if (and date (stringp date))
(->> date ; Shame that encode-time
parse-time-string ; can't take a string, as
(-take 6) ; this seems excessive...
(--map (if (null it) 0 it))
(apply 'encode-time))
date))
#+end_src
** Sprint Numbering
My Sprint starts on Thursday, but this sometimes changed, so let's make this a variable:
#+begin_src emacs-lisp
(defvar sprint-starting-day 2 "The day of the week the sprint begins, where 0 is Sunday.")
#+end_src
We label our sprint based on the week number that it starts. Note that on a Monday, I want to consider that we are still numbering from last week.
#+begin_src emacs-lisp
(defun sprint-week-num (&optional date)
"Return the week of the current year (or DATE), but starting
the week at Thursday to Wednesday."
(let* ((d (get-date-time date))
(dow (nth 6 (decode-time d))) ; Day of the week 0=Sunday
(week (->> d ; Week number in the year
(format-time-string "%U")
string-to-number)))
(if (>= dow sprint-starting-day)
(1+ week)
week)))
#+end_src
Let's have these tests to make sure, and yeah, perhaps we update this at the beginning of each year.
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-week-num-test ()
(should (= (sprint-week-num "2021-03-15") 11)) ;; Monday previous week
(should (= (sprint-week-num "2021-03-16") 12)) ;; Tuesday current week
(should (= (sprint-week-num "2021-03-19") 12)))
#+end_src
My company has sprints two weeks long, we could be see that on even week numbers, the /sprint/ is actually the previous week's number.
And it appears that my PM for this year, is a week number behind.
#+begin_src emacs-lisp
(defun sprint-number (&optional date)
"Return the current sprint number, with some assumptions that
each sprint is two weeks long, starting on Tuesday."
(let ((num (sprint-week-num date)))
(if (cl-oddp num)
(1- num)
num)))
#+end_src
And some tests to verify that:
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-number-test ()
(should (= (sprint-number "2021-03-15") 10))
(should (= (sprint-number "2021-03-16") 12))
(should (= (sprint-number "2021-03-22") 12))
(should (= (sprint-number "2021-03-23") 12))
(should (= (sprint-number "2021-03-29") 12))
(should (= (sprint-number "2021-03-30") 14)))
#+end_src
** Sprint File Name
I create my org-file notes based on the Sprint number.
#+begin_src emacs-lisp
(defun sprint-current-file (&optional date)
"Return the absolute pathname to the current sprint file."
(let ((d (get-date-time date)))
(expand-file-name
(format "~/Notes/Sprint-%s-%02d.org"
(format-time-string "%Y" d)
(sprint-number d)))))
#+end_src
So given a particular date, I should expect to be able to find the correct Sprint file name:
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-current-file-test ()
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-07")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-09")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-10")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-13")))
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-14")))
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-17"))))
#+end_src
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
#+begin_src emacs-lisp
2021-11-10 01:28:58 +00:00
(defun sprint-current-find-file (&optional date)
"Load the `org-mode' note associated with my current sprint."
(interactive)
(let ((filename (sprint-current-file date)))
(setq org-main-file filename
org-annotate-file-storage-file filename)
(add-to-list 'org-agenda-files filename)
(find-file filename)))
#+end_src
The /name/ and /nickname/ of the sprint will be used in the =#+TITLE= section, and it looks something like: =Sprint 2019-07 (darling-dadu)=
#+begin_src emacs-lisp
(defun sprint-current-name (&optional date)
"Return the default name of the current sprint (based on DATE)."
(let* ((d (get-date-time date))
(sprint-order (/ (1- (sprint-number d)) 2))
(nickname (nth sprint-order sprint-nicknames)))
(format "Sprint %s-%02d %s"
(format-time-string "%Y" d)
(sprint-number d)
nickname)))
#+end_src
These test won't pass any more, as the nickname of the sprint changes from year to year.
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-current-name-test ()
(should (equal "Sprint 2019-05 (candid-cannes)" (sprint-current-name "2019-02-13")))
(should (equal "Sprint 2019-07 (darling-dadu)" (sprint-current-name "2019-02-14"))))
#+end_src
** Sprint Start and End
I want to print the beginning and ending of the sprint, where we have a sprint number or a data, and we can give the dates that bound the sprint. This odd function calculates this based on knowing the date of the /first thursday/ of the year, so I need to begin the year changing this value. I should fix this.
#+begin_src emacs-lisp
(defun sprint-range (&optional number-or-date)
"Return a list of three entries, start of the current sprint,
end of the current sprint, and the start of the next sprint.
Each date value should be formatted with `format-time-string'."
(let* ((num (if (or (null number-or-date) (stringp number-or-date))
(sprint-number number-or-date)
number-or-date))
(year-start "2020-01-02") ; First Thursday of the year
(time-start (-> year-start ; Converted to time
get-date-time
float-time))
(day-length (* 3600 24)) ; Length of day in seconds
(week-length (* day-length 7))
(sprint-start (time-add time-start (* week-length (1- num))))
(sprint-next (time-add time-start (* week-length (1+ num))))
(sprint-end (time-add sprint-next (- day-length))))
(list sprint-start sprint-end sprint-next)))
#+end_src
Format the start and end so that we can insert this directly in the org file:
#+begin_src emacs-lisp
(defun sprint-date-range (&optional number-or-date)
"Return an `org-mode' formatted date range for a given sprint
number or date, `NUMBER-OR-DATE' or if `nil', the date range of
the current sprint."
(seq-let (sprint-start sprint-end) (sprint-range number-or-date)
(let* ((formatter "%Y-%m-%d %a")
(start (format-time-string formatter sprint-start))
(end (format-time-string formatter sprint-end)))
(format "[%s]--[%s]" start end))))
#+end_src
And validate with a test:
#+begin_src emacs-lisp
(ert-deftest sprint-date-range ()
(should (equal (sprint-date-range 7)
(sprint-date-range "2020-02-17"))))
#+end_src
** Pre-scheduled Dates
Due to the regularity of the sprint cadence, I can pre-schedule meetings and other deadlines by /counting/ the number of days from the start of the sprint:
#+begin_src emacs-lisp
(defun sprint-date-from-start (days &optional formatter)
"Given a number of DAYS from the start of the sprint, return a formatted date string."
(let* ((day-length (* 3600 24))
(start (car (sprint-range)))
(adate (time-add start (* day-length days))))
(if formatter
(format-time-string formatter adate)
(format-time-string "%Y-%m-%d %a" adate))))
#+end_src
* Other Date Functions
The following functions /were/ helpful at times. But I'm not sure I will use them.
#+begin_src emacs-lisp :tangle no
(defun sprint-num-days (time-interval)
"Converts a TIME-INTERVAL to a number of days."
(let ((day-length (* 3600 24)))
(round (/ (float-time time-interval) day-length))))
#+end_src
#+begin_src emacs-lisp :tangle no
(defun sprint-day-range (&optional date)
"Returns a list of two values, the number of days from the
start of the sprint, and the number of days to the end of the
sprint based on DATE if given, or from today if DATE is `nil'."
(seq-let (sprint-start sprint-end) (sprint-range date)
(let* ((now (get-date-time date))
(starting (time-subtract sprint-start now))
(ending (time-subtract sprint-end now)))
(list (sprint-num-days starting) (sprint-num-days ending)))))
#+end_src
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-day-range ()
;; This sprint starts on 2/13 and ends on 2/26
(should (equal '(0 13) (sprint-day-range "2020-02-13")))
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
#+end_src
#+begin_src emacs-lisp :tangle no
(defun sprint-day-start (&optional date)
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Thursday, this would return -1."
(first (sprint-day-range date)))
(defun sprint-day-end (&optional date)
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Wednesday, this would return 3."
(second (sprint-day-range date)))
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-org-sprint)
;;; ha-org-sprint.el ends here
#+end_src
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
#+DESCRIPTION: A literate program for configuring org files for work-related notes.
#+PROPERTY: header-args:sh :tangle no
#+PROPERTY: header-args:emacs-lisp yes
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js