Foreman›Docs›Stories›Stories from Salesforce

Stories from Salesforce

Stories · page 3 of 5

Salesforce Cases as the story source: the org, the filter, the mapping, what Foreman writes back.

Foreman 0.11.08 sections

A project that uses the Salesforce pack can take its stories from Salesforce Cases instead of markdown files or Jira. Each Case that a filter finds in one sandbox is a story. This page is for the admin who sets that up: the org, how a Case becomes a story, the filter, dependencies and owner tasks, what Foreman writes back, the switches, what happens when the org does not answer, and how to check it. The Salesforce pack is a preview: its release paths pass their own tests, but no story has yet shipped through them on a live org, and the Gearset path has not run against a live Gearset pipeline. Put this in foreman.config.json (Set up › This project writes a starter version of it):

"pack": "salesforce",
"salesforce": {
  "orgs": { "acme-qa": { "orgId": "00D000000000002AAA" } },
  "build": "acme-qa",
  "stages": [{ "name": "qa", "org": "acme-qa" }]
},
"stories": {
  "adapter": "salesforce",
  "org": "acme-qa",
  "where": "IsClosed = false AND Type = 'Foreman'",
  "orderBy": "CreatedDate ASC",
  "doneWhenField": "Done_When__c",
  "dependsOnField": "Depends_On__c",
  "ownerField": "Type",
  "ownerValue": "Owner task",
  "statuses": { "started": "Working", "review": "Escalated", "blocked": "On Hold", "shipped": "Closed" }
}

Every option of the block:

Name What it does Default
org The alias in salesforce.orgs of the sandbox that holds the Cases. Required. none
where A SOQL condition on Case, at most 4,000 characters. Give this or listView. none
listView The API name of a Case list view. Give this or where. none
orderBy With where only: field names with ASC or DESC, the pick order. CreatedDate ASC
pollSeconds How long the last list is reused before the org is asked again; 0 asks every time. 60
idPattern What a Case number looks like. \d{8,10}
doneWhenField A Case field holding “Done when”. unset: the heading in the Description
dependsOnField A Case field holding the numbers of the Cases this one waits for. unset
ownerField A picklist or multi-select field that marks owner tasks. Needs ownerValue. unset
ownerValue The value of ownerField that means an owner task. unset
statuses { event: Case Status } for started, review, blocked, failed and shipped. Each name at most 255 characters, with no quote, backslash or line break. {}: comments only
briefsDir Where the briefs are written, relative to the project. .foreman/stories

The org

The Cases come from stories.org, the alias of an org in salesforce.orgs (the pack is required: "pack": "salesforce"). It must be a sandbox, never a production org. Foreman refuses a production org in three places: foreman doctor and the config check refuse an org marked "production": true or named prod or production; the story source refuses to open on one; and every poll asks the org itself whether it is a sandbox, with the org ID salesforce.orgs names, before it reads a Case. An org that answers “not a sandbox” or “another org ID” is an error, never served from the cache. The org can be one a stage or the build already uses, or a sandbox that only holds the Cases. Foreman reaches it through its own sign-in (Set up › Salesforce), in an sf home of its own, never the sf logins you use yourself. Each sf call is given 60 seconds.

The mapping

The Case number is the story id (00001026), the Subject is the title and the Description is the brief. A “Done when” heading in the Description, at any level, or the Case field named in doneWhenField, says when it is finished; a field with HTML is turned into plain text. idPattern says what a Case number looks like (default \d{8,10}); change it when your org numbers Cases another way. The Case’s position in the query’s answer is its order; groups are not used, and the story’s link is the Case in Lightning.

A story is done when the Case is closed. Otherwise its status is the one Foreman last recorded (in-progress, blocked, ready-to-promote), else todo. What Foreman records lives in a state overlay, .foreman/stories/<number>.json, never in the Case.

Before a story starts, Foreman reads the Case with its last 10 comments and writes a brief to .foreman/stories/<number>.md (briefsDir moves it): the frontmatter (id, title, status, url), ## What, ## Done when, ## Comments newest first, and ## Progress notes from the overlay. The agents read that file.

There is no way to create a Case from Foreman: a Case is made in Salesforce. So foreman learn proposes no follow-up stories for this source (“follow-ups not proposed: this story source cannot create stories”), and features.autoCreateFollowUps has nothing to create.

The filter

Give exactly one of where or listView. where is a SOQL condition on Case, and orderBy (default CreatedDate ASC) sets the order they are picked in; Foreman runs SELECT … FROM Case WHERE (<where>) ORDER BY <orderBy> LIMIT 200. listView is the API name of a Case list view; Foreman reads the view’s own query from the org and uses its filter, its order and its USING SCOPE, with its limit replaced by 200. orderBy is refused with a list view, which has its own order. Either way Foreman reads at most 200 Cases at a time, and pollSeconds (default 60) is how long it reuses the list before it asks again.

Dependencies and owner tasks

dependsOnField names a Case field, such as a text or long text field, that holds the numbers of the Cases a Case waits for, separated by commas, spaces or semicolons. A number that does not match idPattern is ignored. A Case outside the filter is looked up in one more query: one that is closed counts as done; one that is open holds the story, and foreman story plan names it as “outside the filter, not done” (or “not found” when there is no such Case). Once Foreman has written a depends_on for a story, its list is the one that counts.

An owner task is yours to do and is never picked. Mark one with ownerField and ownerValue (a picklist or multi-select field and the value that means an owner task; in a multi-select, any one of the chosen values matches), or, with neither set, put “(owner task)” in the Subject. ownerValue is required with ownerField, and refused without it. The card the dashboard shows for an owner task reads its headings from the Case’s Description; see Owner tasks.

What Foreman writes back

Every comment starts with “Foreman:” and is private to your team. Foreman posts it with IsPublished off, so a customer never sees it.

When Comment Status
A story starts “Started.” started
Its pull request is open “Pull request ready for review: <link> (commit <short sha>)” review
It is blocked “Blocked: <reason>” blocked
It fails “Failed: <reason>” failed
It ships the release note shipped

The statuses map names the Case Status to set for each event. Leave an event out to only comment for it. A name must be an active value of the Case Status picklist; one the org refuses is dropped, with a warning in the events, and the story carries on. The same event with the same note twice in a row writes nothing new. A depends_on Foreman adds or takes off a story is written to dependsOnField, whatever the switches say.

With salesforceComments on, the story also holds the reasons it took the path it did (the questions Foreman asked you and your answers, the architect’s design, each round of review, how a session stopped, a release that stopped), each as its own private comment of at most 4,000 characters; a longer one is split into numbered parts, “Foreman: <title> (part i of n)”. features.jiraCommentDetail chooses whether a round’s findings are counted or listed, for this source too. The notes are described in full on Stories from Jira; foreman story note <id> --kind design --file <plan> records a design by hand here as well.

The two switches

foreman features off salesforceComments stops the comments and the story notes, and foreman features off salesforceStatuses stops the Status changes. Both are on by default and apply from the next story. They are listed on Settings and switches and appear only while stories.adapter is salesforce.

When Salesforce does not answer

The supervisor never waits on sf. A refresher of its own polls the filter every pollSeconds (at least every 5 seconds) in a child process that is given 3 minutes, and writes the answer to .foreman/salesforce/list.json. Commands and the supervisor share that cache, so one poll serves everyone. The answer is reused only while the org, the filter, the order and the fields named are the same.

A write the org does not take waits in .foreman/salesforce-outbox/, one small file per write, and is sent again, in order, before the next write or after the next poll that reaches the org. Before any write, Foreman makes the same sandbox check a poll makes (once per pollSeconds); when it fails the writes stay queued, with a warning, and nothing is sent. Each queue, replay and drop is an event in .foreman/events.jsonl, prefixed “Salesforce:”.

While the org cannot be reached, the last answer of any age stands in, so a story under way can still record its progress, and Foreman picks no new story: the run shows “Salesforce not reachable since HH:MM: <why>; picking paused, stories in progress carry on” while the last refresh failed or the cache is older than 5 × pollSeconds. Stories already under way carry on. If the sign-in to the org lapses, Foreman sends the paused notification: “Foreman’s sign-in to the Salesforce org that holds the stories has lapsed: connect it again on Set up › Salesforce, then resume.”

Checking it

foreman doctor adds three checks to the Salesforce group:

Check What it says
salesforce stories options The options are valid, the org is in salesforce.orgs and is not production: “Cases in acme-qa”.
salesforce stories filter The filter runs in the org and finds a number of Cases (“finds 12 Cases”; past 200, “Foreman reads the first 200”). Skipped when the options fail or the org did not pass its own check.
salesforce stories statuses Each statuses value is an active Case Status, with a near match named (“did you mean “Working”?“), and each field you named (doneWhenField, dependsOnField, ownerField) exists on Case. “none set” when there is nothing to check.

Set up › This project has a Story source choice, Salesforce Cases, for a project that already has the pack on: Foreman saves a starter filter of the open Cases of Type Foreman, in the first sandbox of salesforce.orgs, and you edit the filter in foreman.config.json. The pack cannot be turned off while the stories come from Cases: “The stories come from Salesforce Cases: pick another story source before turning the pack off.” The rest of the pack’s setup is on Salesforce set-up and the config block on salesforce config.