fbpx

For a digital health company, risk management is one of the cornerstones of compliance. If you’re already using Jira for other design activities, from user requirements to tests, it makes sense to also manage risk for end-to-end visibility and accessibility. And you’ll also make it easier for your team members to participate and engage in the process.

There are three steps for setting up Jira for risk management:

  1. Define how your risk management report will look — the report content dictates how you configure Jira.
  2. Setup and configure Jira cloud. For example, configure a dedicated issue type to capture each hazardous situation.
  3. Map all the data elements from your risk management report into this new Jira issue type while also collecting data from related processes (linked issues).

By the time you finish reading this article, you’ll have a blueprint for each of these three steps. This guide to set up a Jira Cloud instance will help you fulfill your risk management process inside the platform.

1. Define how your risk management report will look

As a compliance-team member in a digital health company, you might relate to the following example: We need to submit the risk management for a given product in a report that contains a table or list of “Hazardous situations”.

For each Hazardous situation, we need to list:

  1. Title
  2. Unique identifier
  3. Hazard — Select one of the following:
    • Data discrepancy
    • UX
    • Security
    • Privacy
    • Performance
    • Technical interfaces
    • Wrong use
    • Wrong patient
    • Off-label use
  4. Harm — Select one of the following:
    • Safety: Wrong treatment
    • Safety: Delay in treatment
    • Safety: Misdiagnosis
    • Safety: Delay in diagnosis
    • Cybersecurity: Sensitive data exposed
  5. Description
  6. Probability — Select one of the following:
    • Very unlikely
    • Unlikely
    • Likely
    • Very likely
    • Almost certain
  7. Impact — Select one of the following:
    • Negligible
    • Low
    • Medium
    • High
    • Severe
  8. Inherent risk: A calculated level resulting from the probability and the impact, based on our risk model (full model below)
  9. Risk control strategy — Select one of the following:
    • Accept
    • Reduce
    • Avoid
    • Transfer
  10. Information for safety
  11. Inherent safety by design: This is a list of risk mitigation measures, which fall under “inherent safety by design.”
  12. Protective measures: This is a list of risk mitigation measures, which are acting as protective measures.
  13. Residual probability
  14. Residual impact
  15. Residual risk
  16. Residual risk acceptability— Select one of the following:
    • As low as possible
    • Intolerable

The Risk Register app provides a risk model in Jira cloud. Digital Health

The Risk Register app provides a risk model in Jira cloud.

 

2. Basic Jira Cloud setup and configuration; for digital health companies

Configure a new Jira issue type: “Hazardous Situation.” Add it to the project where you define your other traceability items, such as User Requirements or Software Specification.

In our traceability system, items of a type of Software Specification are the ones that can also play a role of “risk mitigations.” This will be indicated as a dedicated Link Type:

A Jira issue of the type “Hazardous Situation” is connected via a link called “Is mitigated by” to one or more issues of the type “Functional Requirement”.

Risk mitigation link in Jira Cloud

Risk mitigation link in Jira

You’ll need the following two apps installed on your instance:

  1. Risk Register: This app provides an easy way to configure the Risk Model into Jira and assign the values: probability, impact, risk level, residual probability, residual impact, and residual risk level.
  2. ScriptRunner for Jira: We’ll use ScriptRunner to save us some manual data entry and ensure consistency between the risk data and the traceability links in Jira.

3. Map all the data elements into the new Jira issue type

Now it’s time to map all the data items we want to have in our risk management table into Jira fields.

Each Hazardous Situation is configured with these fields (at minimum):

  1. Title: This is simply the “Summary” field in Jira.
  2. Unique identifier: This is the Jira IssueKey.
  3. Hazard: This is a new custom field of type single select.
  4. Harm: This is a new custom field of type single select.
  5. Description: This is simply the “Summary” field in Jira.
  6. Probability: This is a custom field provided by the Risk Register application. You can customize the risk model and get the list of values that you need.
  7. Impact: This is a custom field provided by the Risk Register application. You can customize the risk model and get the list of values that you need.
  8. Inherent risk: This is a custom field provided by the Risk Register application. You can customize the risk model and get the list of values that you need.
  9. Risk control strategy: This is a new custom field of type single select.
  10. Information for safety: This is a new custom field of type “long text field.”
  11. Inherent safety by design: This field is a scripted field. (See more about this below.)
  12. Protective measures: This field is a scripted field. (See more about this below.)
  13. Residual probability: Provided by the Risk Register application.
  14. Residual impact: Provided by the Risk Register application.
  15. Residual risk: Provided by the Risk Register application.
  16. Residual risk acceptability: This is a new custom field of type single select.

Also, for our risk mitigation issues, we will add this field into the issue type “Functional Requirement”:

  1. Risk control role: This is a single select field type, where the value is one of:
    • Not a risk control
    • Inherent safety by design
    • Protective measure

Automate the values in “Inherent Safety by Design” and “Protective Measure”

Both of these fields must reflect what you, as a digital health company, actually implement in your product: the Software Specifications. So, to avoid having to once again manually enter the information from your Software Specifications into the “Hazardous Situation” issue type, we will apply the following engineering magic:

Each Software Specification is assigned (through its own field) to a single “risk control role”:

  1. Not a risk control
  2. Inherent safety by design
  3. Protective measure

The field Risk control role in the Software Specification issue. Digital Health

The field Risk control role in the Software Specification issue

In the Hazardous Situation issue, the fields “inherent safety by design” and “Protective measures” will be set automatically, relying on the data in the Software Specification.

The “inherent safety by design” field will work like this:

  1. Look into all linked Software Specifications (through the link type: “is mitigated by”).
  2. Collect all the ones for which the risk control role is “inherent safety by design.”
  3. Put the list of [issuekey, issue summary] in the field “inherent safety by design.”

Do the same for  the field “Protective measures”.

Inherit safety by design field is set automatically, Digital Health

Inherit safety by design field is set automatically

This way, these two fields always reflect the actual data in your Software Specifications. This makes it effortless for you to ensure the data is always consistent across the two sides of the traceability link.

Here is the actual code to make this automation happen:

ScriptRunner listener: setting the field "Inherit safety by design"

RISK_ISSUE_TYPE = "Hazardous Situation"
RISK_MITIGATION_ISSUE_TYPE = "Functional Requirement"
RISK_TO_MITIGATION_LINKNAME = "Is mitigated by"
MITIGATION_TO_RISK_LINKNAME = "Mitigates"
FIELD_TO_UPDATE = "Inherit safety by design"

class IssueResponse {
    String key
    String type
    String summary
}

def customFieldId = Unirest.get("/rest/api/2/field")
        .asObject(List).body
        .findAll { (it as Map).custom }
        .find { (it as Map).name == FIELD_TO_UPDATE }
        ?.id
logger.info("customFieldId is ${customFieldId}")

switch (webhookEvent) {
    case "issuelink_deleted":
    case "issuelink_created":
        def sourceIssue = getIssue(issueLink.sourceIssueId)
        def destinationIssue = getIssue(issueLink.destinationIssueId)
        def outwardLinkName = issueLink.issueLinkType.outwardName
        logger.info("Link event of type {}: Issue {} {} issue {}",
                webhookEvent, sourceIssue.key, outwardLinkName, destinationIssue.key)
        if (outwardLinkName == RISK_TO_MITIGATION_LINKNAME &&
                sourceIssue.type == RISK_ISSUE_TYPE &&
                destinationIssue.type == RISK_MITIGATION_ISSUE_TYPE
        ) {
            updateTextField(sourceIssue.key, customFieldId, calculateFieldValue(sourceIssue.key))
        } else if (outwardLinkName == MITIGATION_TO_RISK_LINKNAME &&
                sourceIssue.type == RISK_MITIGATION_ISSUE_TYPE &&
                destinationIssue.type == RISK_ISSUE_TYPE
        ) {
            updateTextField(destinationIssue.key, customFieldId, calculateFieldValue(destinationIssue.key))
        }
        break
    case "jira:issue_updated":
        if (issue.fields.issuetype.name != RISK_MITIGATION_ISSUE_TYPE) return
        if (!changelog?.items?.find { it['field'] == 'summary' }) return // continue only if the "summary" field is updated

        jqlSearch("issue in linkedIssues(${issue.key}, 'Mitigates') ")
                .each { updateTextField(it.key, customFieldId, calculateFieldValue(it.key as String)) }
        break
    case "jira:issue_created":
        if (issue.fields.issuetype.name == RISK_ISSUE_TYPE) {
            updateTextField(issue.key, customFieldId, calculateFieldValue(issue.key))
        } else if (issue.fields.issuetype.name == RISK_MITIGATION_ISSUE_TYPE) {
            jqlSearch("issue in linkedIssues(${issue.key}, 'Mitigates') ")
                    .each { updateTextField(it.key, customFieldId, calculateFieldValue(it.key as String)) }
        }
        break
}

void updateTextField(issueKey, customfieldId, value) {
    logger.info("Updating issue: ${issueKey}")
    Unirest.put("/rest/api/2/issue/${issueKey}")
            .queryString("notifyUsers", Boolean.FALSE)
            .header("Content-Type", "application/json")
            .body([
                    fields: [
                            (customfieldId): value
                    ]
            ]).asString()
}

List<Map> jqlSearch(jql) {
    logger.info("jql is ${jql}")
    return Unirest.get("/rest/api/2/search")
            .queryString('jql', jql as String)
            .header('Content-Type', 'application/json')
            .asObject(Map)
            .body
            .issues as List<Map>
}

IssueResponse getIssue(issueIdOrKey) {
    def issueResponse = Unirest.get("/rest/api/2/issue/${issueIdOrKey}").asObject(Map)
    assert issueResponse.status == 200
    logger.info("issue found: key=${issueResponse.body.key}")
    return new IssueResponse(
            key: issueResponse.body.key,
            type: issueResponse.body.fields.issuetype.name,
            summary: issueResponse.body.fields.summary
    )
}

String calculateFieldValue(String issueKey) {
    return jqlSearch("issue in linkedIssues(" + issueKey + ", 'Is mitigated by') ")
            .collect { "${it.key}: ${getIssue(it.key).summary}" }
            .join("\n")
}

And voilà! Risk management is now set up for success in your Jira Cloud. Keep in mind, that we’re always here if you have any questions. It’s no risk to contact us even if you are not a digital health company 🙂

 

Blog posts in this series:

  1. Why you should manage requirements in Jira
  2. Advice for selecting traceability matrices
  3. How to manage requirements and risk analysis in Jira
  4. Requirement specifications