Dynamic Status History App: Code guide

 

Annotated Code

In this section, we’ll describe how each part of the code works and provide notes on tweaking it.

 

["jira" "cache" "status-history" "atlaskit"] USE-MODULES

We begin by importing the words we need from other modules.

See Forthix Modules for details.

"config" LOAD-SCREEN

This loads the Forthic code from the app’s config file

# ----- A little more config ------------------------------------------------------------------ ["parent_key"] VARIABLES # A convenience variable to help with JQL construction : EXTRA-FIELDS ["Assignee"]; : PARENT-KEY>CHILD-JQL (parent_key !) ["parent = " parent_key @] CONCAT; : EXTRA-CHILD-FIELDS [];

This section defines some additional configuration that you can use to further tweak the report. For instance, you may want have particular JQL for selecting child tickets for your report.

The EXTRA-FIELDS and EXTRA-CHILD-FIELDS are used to pull more data for the parent and child tickets. This can be used to render additional columns in the table or present more information in other ways.

["parent" "child" "ticket" "date" "key" "summary" "comment" "status" "field_key"] VARIABLES

These are variables used throughout the app. They’re often used to set the context for other words.

# ----- Utils ------------------------------------------------------------------------------- : >STATUS "Status" REC@ UPPERCASE; : >KEY "key" REC@; : TICKET-LINK (summary ! key !) A ["href" key @ jira.ISSUE-URL] <<PROP [ [ key @ ": " summary @ ] CONCAT ] . ; : |NON-NULL ">BOOL" SELECT; : PMAP 20 !INTERPS MAP; : STATUS-KEY>APPEARANCE [ ["new" "new"] ["indeterminate" "inprogress"] ["done" "success"] ] REC SWAP REC@ "unknown" DEFAULT; : date-DAYS-AGO TODAY date @ SUBTRACT-DATES; : STATUS-KEY>COLOR [ ["new" "color.background.discovery.bold"] ["indeterminate" "color.background.information.bold"] ["done" "color.background.success.bold"] ] REC SWAP REC@ "color.background.neutral" DEFAULT; : DATE>SHORT DATE>STR "-" SPLIT 1 DROP "/" JOIN; : POPUP-XCSS [ ["maxWidth" "800px"]] REC atlaskit.XCSS; : SORT-BY-STATUS ">STATUS STATUS>ORDER" !COMPARATOR SORT; : JIRA-SEARCH MAX-TICKETS jira.!MAX-RESULTS jira.SEARCH MAX-TICKETS TAKE;

Utility words

These words are used throughout the app to provide reusable functionality.

Word

Description

>STATUS

Extracts an uppercase Status from a ticket

>KEY

Extracts a key from a ticket

TICKET-LINK

Given a ticket key and a summary, returns a ticket link React node.

|NON-NULL

Selects all non-null elements

PMAP

A wrapper that allows MAP to run concurrently using 20 Forthic interpreters at a time

STATUS-KEY>APPEARANCE

Used to set the color of the status badges. See Atlassian Lozenge Examples

date-DAYS-AGO

Returns the number of days since today.

STATUS-KEY>COLOR

Used to set the color of the status history chips. See Atlassian Design Colors for more info.

DATE>SHORT

This converts a date into a short string like “01/15”

POPUP-XCSS

This sets the style of the popup that shows when hovering over a status chip.

SORT-BY-STATUS

This is used to sort tickets by status

JIRA-SEARCH

Performs Jira search returning MAX-TICKETS

# ----- Data ------------------------------------------------------------------------------- : DATES [END-DATE "DATE-INTERVAL -1 * ADD-DAYS" NUM-DATES 1 - <REPEAT] REVERSE; : TICKET>CHANGELOG >KEY ["Status"] jira.CHANGELOG; : PARENT>CHILDREN >KEY PARENT-KEY>CHILD-JQL ["Summary" "Due date" "Resolved" EXTRA-CHILD-FIELDS] FLATTEN UNIQUE JIRA-SEARCH; : TICKET>COMMENTS >KEY jira.COMMENTS; : <ADD-CHANGELOGS DUP "TICKET>CHANGELOG" PMAP "'changelog' <REC!" ZIP-WITH; : <ADD-CHILDREN DUP "PARENT>CHILDREN" PMAP "'children' <REC!" ZIP-WITH; : <ADD-COMMENTS DUP "TICKET>COMMENTS" PMAP "'comments' <REC!" ZIP-WITH; : PARENT-TICKETS PARENT-JQL ["Summary" "Status" EXTRA-FIELDS] FLATTEN UNIQUE JIRA-SEARCH; : MAIN-REFRESH PARENT-TICKETS <ADD-CHANGELOGS <ADD-CHILDREN <ADD-COMMENTS CONSOLE.LOG "parents" cache.CACHE!; @: PARENTS 'parents' cache.CACHE@ MAX-TICKETS TAKE SORT-BY-STATUS; @: STATUS-BY-NAME jira.PROJECT "project_statuses" REC@ "'statuses' REC@" MAP FLATTEN "name" BY-FIELD "'statusCategory' REC@" MAP;

Data Words

Word

Description

DATES

Creates an array of dates using END-DATE and DATE-INTERVAL

TICKET>CHANGELOG

Returns the Status history changelog

PARENT>CHILDREN

Returns a parent’s child tickets

TICKET>COMMENTS

Returns the comments for a ticket

<ADD-CHANGELOGS

Adds changelogs to each ticket in an array of tickets

MAIN-REFRESH

This is a special word that’s called automatically when data is more than a day old or when the refresh control is clicked.

This stores the parent tickets in the cache under the “parents” key.

PARENTS

This returns the parent tickets from the cache.

STATUS-BY-NAME

Returns all statuses for all issue types in the current project by name. This is used to look up metainfo for a Status

# ----- ticket Words ------------------------------------------------------------------------------- : STATUS>KEY STATUS-BY-NAME SWAP REC@ 'key' REC@ "unknown" DEFAULT; : status-APPEARANCE status @ STATUS>KEY STATUS-KEY>APPEARANCE; : STATUS>BADGE (status !) atlaskit.Lozenge [["appearance" status-APPEARANCE] ["isBold" TRUE]] <<PROPS status @ .; : ticket-SUMMARY ticket @ "Summary" REC@; : ticket-KEY ticket @ "key" REC@; : ticket-STATUS ticket @ "Status" REC@; : ticket-STATUS-BADGE ticket-STATUS STATUS>BADGE; : ticket-BADGED-SUMMARY atlaskit.Inline ["space" "space.100"] <<PROP [ticket-STATUS-BADGE ticket-KEY ticket-SUMMARY TICKET-LINK] .;

Ticket Words

These are words we can use to construct various things for a ticket. For instance, the ticket-BADGED-SUMMARY is what’s used to render the first column in the report:

image-20240110-194615.png

 

# ----- parent Words ------------------------------------------------------------------------------- : parent-KEY parent @ "key" REC@; : parent-SUMMARY parent @ "Summary" REC@; : parent-STATUS parent @ "Status" REC@; : parent-CHILDREN parent @ "children" REC@; : parent-BADGED-SUMMARY (parent @ ticket !) ticket-BADGED-SUMMARY; : parent-COMMENTS parent @ "comments" REC@; : parent-COMMENTS-BY-DATE parent-COMMENTS DATES "DATE>INT" MAP "'for_date' REC@ '-' '' REPLACE >INT" RANGE-BUCKETS; : parent/date-COMMENTS parent-COMMENTS-BY-DATE date @ DATE>INT REC@ [] DEFAULT; : parent-CHANGELOG parent @ 'changelog' REC@; : parent-STATUS-AS-OF parent-CHANGELOG "Status" jira.FIELD-AS-OF; # (date -- status) : parent/date-COLOR-AS-OF date @ parent-STATUS-AS-OF STATUS>KEY STATUS-KEY>COLOR; : parent-DATE>STATUS-CHIP (date !@) parent/date-COLOR-AS-OF parent @ status-history.STATUS-CHIP [ ["fcontent" "(date ! parent !) parent/date-COMMENTS LENGTH"] ["fmouseOver" "(date ! parent !) parent/date-POPUP-CONTENT"] ["fclick" "(date ! parent !) parent/date-MODAL-CONTENT atlaskit.MODAL-CONTENT! atlaskit.SHOW-MODAL"] ] <<PROPS; : parent/date-STATUS-BADGE date @ parent-STATUS-AS-OF STATUS>BADGE; : parent/date-POPUP-ANNOTATIONS atlaskit.Box [["padding" "space.100"]] <<PROPS [ H6 "Annotations" . atlaskit.Stack ["space" "space.150"] <<PROP [parent/date-COMMENTS "COMMENT>ANNOTATION" MAP] . ] .; : parent/date-ANNOTATIONS-SECTION [ [TRUE parent/date-POPUP-ANNOTATIONS] ] REC parent/date-COMMENTS LENGTH 0 > REC@; : parent/date-POPUP-CONTENT atlaskit.Stack ["xcss" POPUP-XCSS] <<PROP [ atlaskit.Box [["padding" "space.100"] ["backgroundColor" "color.background.neutral"]] <<PROPS [ H5 [date @ DATE>SHORT " " MDASH " " parent/date-STATUS-BADGE " " date-DAYS-AGO DAYS-AGO ]. ] . atlaskit.Box [["padding" "space.100"]] <<PROPS [ P [(parent @ ticket !) ticket-KEY ticket-SUMMARY TICKET-LINK] . ] . parent/date-ANNOTATIONS-SECTION ] .; : parent/date-MODAL-CONTENT [ atlaskit.ModalHeader [ atlaskit.ModalTitle ["Annotations for " date @ DATE>SHORT " " MDASH " " (parent @ ticket !) ticket-KEY ticket-SUMMARY TICKET-LINK] . ]. atlaskit.ModalBody [ atlaskit.AnnotationEditor [["ticketKey" parent-KEY] ["forDate" date @] ["annotations" parent-COMMENTS] ["annotationTitle" "Annotation for " date @ DATE>SHORT CONCAT]] <<PROPS atlaskit.Stack ["space" "space.150"] <<PROP [parent/date-COMMENTS "COMMENT>ANNOTATION" MAP] . ] . atlaskit.ModalFooter [P " " .] . ];

Parent Words

These are all words related to the parent tickets. The first set of these just return fields from the parent

The parent-COMMENTS-BY-DATE groups comments on a ticket into the DATES computed above. We avoid timezone weirdness by converting dates into integers (e.g., 2023-12-20 to 20231220)

Popup Support

The parent/date-POPUP-CONTENT is used to render the popup that’s displayed on hover. If you want to change anything about the hover popup, this is where you can do it. See Forthix React UI Documentation for explanation about using React in Forthix.

Modal Dialog Support

The parent/date-MODAL-CONTENT is used to render the modal dialog that’s displayed when clicking on a status history chip. See the atlaskit module for documentation on the relevant components.

# ----- child Words ------------------------------------------------------------------------------- : child-DUE child @ "Due date" REC@; : child-RESOLVED child @ "Resolved" REC@; : child-DUE-INFO [ [TRUE ["Due: " child-DUE >DATE DATE>SHORT] CONCAT] [FALSE "No due date"] ] REC child-DUE >BOOL REC@; : child-RESOLVED-LOZENGE [ [TRUE atlaskit.Lozenge ["appearance" "success"] <<PROP ["Resolved: " child-RESOLVED >DATE DATE>SHORT] CONCAT .] ] REC child-RESOLVED >BOOL REC@; : child-DUE-LOZENGE [ [TRUE atlaskit.Lozenge [["appearance" "removed"] ["isBold" FALSE]] <<PROPS child-DUE-INFO .] [FALSE atlaskit.Lozenge child-DUE-INFO .] ] REC child-DUE >DATE TODAY < child-RESOLVED NOT AND REC@; : child-STATUS-INFO atlaskit.Inline ["space" "space.100"] <<PROP [child-DUE-LOZENGE child-RESOLVED-LOZENGE] |NON-NULL .; : child-KEY child @ "key" REC@; : child-SUMMARY child @ "Summary" REC@; : child-BADGED-SUMMARY (child @ ticket !) ticket-BADGED-SUMMARY;

Child Words

These words are used to render information about the child tickets for each parent.

# ----- comment Words ------------------------------------------------------------------------------- : comment-BODY comment @ "renderedBody" REC@ RAW-HTML; : comment-TIME comment @ "created" REC@ "." SPLIT 0 NTH "T" " @ " REPLACE; : comment-AUTHOR comment @ ["author" "displayName"] REC@; : comment-AUTHOR-IMAGE comment @ ["author" "avatarUrls" "32x32"] REC@; : comment-AVATAR atlaskit.Avatar [ ["appearance" "circle"] ["src" comment-AUTHOR-IMAGE] ["size" "small"] ["name" comment-AUTHOR] ] <<PROPS; : comment-AUTHOR-HEADLINE atlaskit.Inline [["alignBlock" "center"] ["space" "space.025"]] <<PROPS [comment-AVATAR comment-AUTHOR ", " comment-TIME] . ; : COMMENT>ANNOTATION (comment !) atlaskit.Box [comment-AUTHOR-HEADLINE comment-BODY] .;

Comment Words

These are used to render comments in the popup and modal dialog.

# ----- Tickets Table ------------------------------------------------------------------------------- : COLUMN-KEYS ["Ticket" "Assignee" "history"]; : HISTORY-HEADER ["Status from " DATES 0 NTH DATE>SHORT " to " DATES LAST DATE>SHORT " (" DATE-INTERVAL " day intervals)"] CONCAT; : KEY>HEADER (key !) [ ["history" HISTORY-HEADER] ] REC key @ REC@ key @ DEFAULT; : KEY>WIDTHS [ ["Ticket" "700px"] ["history" "700px"] ] REC SWAP REC@ "150px" DEFAULT; : parent-KEY>CONTENT (field_key !) [ ["Ticket" parent-BADGED-SUMMARY RENDER] ["history" atlaskit.Inline ["space" "space.025"] <<PROP [DATES "parent-DATE>STATUS-CHIP" MAP] .] ] REC field_key @ REC@ parent @ field_key @ REC@ DEFAULT; : child-KEY>CONTENT (field_key !) [ ["Ticket" atlaskit.Stack ["space" "space.100"] <<PROP [child-BADGED-SUMMARY child-STATUS-INFO ] . ] ] REC field_key @ REC@ child @ field_key @ REC@ DEFAULT; : PARENT>ITEM (parent !) [ ["id" parent-KEY] ["hasChildren" parent-CHILDREN LENGTH 0 >] ["content" COLUMN-KEYS "[SWAP DUP parent-KEY>CONTENT]" MAP REC] ["children" parent-CHILDREN "(child !) child-ITEM" MAP] ] REC; : child-ITEM [ ["id" child-KEY] ["hasChildren" FALSE] ["content" COLUMN-KEYS "[SWAP DUP child-KEY>CONTENT]" MAP REC] ] REC; : TABLE-ITEMS PARENTS "PARENT>ITEM" MAP; : TICKETS-TABLE atlaskit.TableTree [ ["headers" COLUMN-KEYS "KEY>HEADER" MAP] ["columns" COLUMN-KEYS "atlaskit.TABLE-TREE-COLUMN" MAP] ["columnWidths" COLUMN-KEYS "KEY>WIDTHS" MAP] ["items" TABLE-ITEMS] ] <<PROPS;

Words for Tickets Table

These next set of words are used to render the main ticket table view. We use an Atlaskit TableTree component for this. Many of the words here are used to support the TableTree.

Tweak: Add columns

You can update COLUMN-KEYS and EXTRA-FIELDS to pull additional info and render it in the table. The “Assignee” column is an example of how you can do this.

# ----- Main page ------------------------------------------------------------------------------- : MAIN-PAGE [ H2 [jira.PROJECT 'name' REC@ " Status History"] CONCAT . TICKETS-TABLE ];

Main Page

The MAIN-PAGE word is special and is used to render the app. As anticlimactic as this looks, this word is where it all happens. It’s basically a title and a table.

As a rule, it’s good to start at the bottom of a Forthic file to get a bird’s eye view of what the app does and then move upwards to understand the pieces.

Tweak: Change the title

You can change Line 3 pretty easily to update your report’s title.

Tweak: Add a summary table

If you wanted to add another table with a summary of the child tickets, this is where you’d call that word. We may give an example of this in the future.