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 |
---|---|
| Extracts an uppercase Status from a ticket |
| Extracts a key from a ticket |
| Given a ticket key and a summary, returns a ticket link React node. |
| Selects all non-null elements |
| A wrapper that allows |
| Used to set the color of the status badges. See Atlassian Lozenge Examples |
| Returns the number of days since today. |
| Used to set the color of the status history chips. See Atlassian Design Colors for more info. |
| This converts a date into a short string like “01/15” |
| This sets the style of the popup that shows when hovering over a status chip. |
| This is used to sort tickets by status |
| Performs Jira search returning |
# ----- 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 |
---|---|
| Creates an array of dates using |
| Returns the Status history changelog |
| Returns a parent’s child tickets |
| Returns the comments for a ticket |
| Adds changelogs to each ticket in an array of tickets |
| 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. |
| This returns the parent tickets from the cache. |
| 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:
# ----- 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.