Jira
The Jira integration plugin for Soundcheck supports the extraction of the following facts:
Prerequisites
Configure Jira integration in Backstage
Integrations are configured at the root level of app-config.yaml
. Here's an example configuration for Jira:
soundcheck:
collectors:
jira:
baseUrl: https://jira-instance.atlassian.net
username: username # This could be your email or username that you use to log in to jira
apiToken: token # Generate new token https://id.atlassian.com/manage-profile/security/api-tokens
Add the JiraFactCollector to Soundcheck
First, add the @spotify/backstage-plugin-soundcheck-backend-module-jira
package:
yarn workspace backend add @spotify/backstage-plugin-soundcheck-backend-module-jira
Then add the following to your packages/backend/src/index.ts
file:
const backend = createBackend();
backend.add(import('@spotify/backstage-plugin-soundcheck-backend'));
backend.add(import('@spotify/backstage-plugin-soundcheck-backend-module-jira'));
// ...
backend.start();
Consult the Soundcheck Backend documentation for additional details on setting up the Soundcheck backend.
Legacy Backend
If you are still using the Legacy Backend you can follow these instructions but we highly recommend migrating to the New Backend System.
First add the package: yarn workspace backend add @spotify/backstage-plugin-soundcheck-backend-module-jira
Then in packages/backend/src/plugins/soundcheck.ts
, add the JiraFactCollector
:
import { SoundcheckBuilder } from '@spotify/backstage-plugin-soundcheck-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
+ import { JiraFactCollector } from '@spotify/backstage-plugin-soundcheck-backend-module-jira';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return SoundcheckBuilder.create({ ...env })
.addFactCollectors(
+ JiraFactCollector.create(env.logger)
)
.build();
}
Plugin Configuration
The collection of Jira facts is driven by configuration. To learn more about the configuration, consult the Defining Jira Fact Collectors section.
Jira Fact Collector can be configured via YAML or No-Code UI. If you configure it via both YAML and No-Code UI, the configurations will be merged. It's preferable to choose a single source for the Fact Collectors configuration (either No-Code UI or YAML) to avoid confusing merge results.
No-Code UI Configuration Option
-
Make sure the prerequisite Configure Jira integration in Backstage is completed and Jira instance details are configured.
-
To enable the Jira Integration, go to
Soundcheck > Integrations > Jira
and click theConfigure
button. To learn more about the No-Code UI config, see the Configuring a fact collector (integration) via the no-code UI.
YAML Configuration Option
- Create a
jira-facts-collectors.yaml
file in the root of your Backstage repository and fill in all your Jira Fact Collectors. A simple example Jira fact collector is listed below.
---
baseUrl: https://jira-instance.atlassian.net
username: username # This could be your email or username that you use to log in to jira
apiToken: token # Generate new token https://id.atlassian.com/manage-profile/security/api-tokens
collects:
- type: issues-search
filter:
- spec.lifecycle: 'production'
spec.type: 'website'
cache: false
Note: this file will be loaded at runtime along with the rest of your Backstage configuration files, so make sure it's available in deployed environments in the same way as your app-config.yaml
files.
- Add a Soundcheck collectors field to the
app-config.yaml
and reference the newly createdjira-facts-collectors.yaml
file.
# app-config.yaml
soundcheck:
collectors:
jira:
$include: ./jira-facts-collectors.yaml
Defining Jira Fact Collectors
This section describes the data shape and semantics of Jira Fact Collectors.
Overall Shape Of A Jira Fact Collector
The following is an example of a descriptor file for a Jira Fact Collector:
---
baseUrl: https://jira-instance.atlassian.net
username: username # This could be your email or username that you use to log in to jira
apiToken: token # Generate new token https://id.atlassian.com/manage-profile/security/api-tokens
collects:
- type: issues-search
filter:
- spec.lifecycle: 'production'
spec.type: 'website'
cache: false
Below are the details for each field.
baseUrl
[required]
The base URL of the Jira instance to use.
apiToken
[optional]
The Jira api token to use for authentication. If not provided the plugin will attempt to make anonymous requests which most likely will be rejected.
username
[optional]
The Jira username or email to use for authentication. If not provided the plugin will attempt to make anonymous requests which most likely will be rejected.
collects
[required]
An array describing which facts to collect.
Overall Shape Of A Fact Collector
Each collector supports the fields described below.
type
[required]
The type of the extractor (e.g. issues-search).
frequency
[optional]
The frequency at which the fact collection should be executed. Possible values are either a cron expression { cron: ... }
or HumanDuration.
If provided, it overrides the default frequency provided at the top level. If not provided, it defaults to the frequency provided at the top level. If neither extractor's frequency nor default frequency is provided, the fact will only be collected on demand.
Example:
frequency:
minutes: 10
initialDelay
[optional]
The amount of time that should pass before the first invocation happens. Possible values are either a cron expression { cron: ... }
or HumanDuration.
Example:
initialDelay:
seconds: 30
filter
[optional]
A filter specifying which entities to collect the specified facts for. Matches the filter format used by the Catalog API. If provided, it overrides the default filter provided at the top level. If not provided, it defaults to the filter provided at the top level. If neither extractor's filter nor default filter is provided, the fact will be collected for all entities.
cache
[optional]
If the collected facts should be cached, and if so for how long. Possible values are either true
or false
or a nested { duration:
HumanDuration }
field.
If provided it, overrides the default cache config provided at the top level. If not provided, it defaults to the cache config provided at the top level. If neither extractor's cache nor default cache config is provided, the fact will not be cached.
Example:
cache:
duration:
hours: 24
Rate Limiting (Optional)
This fact collector can be rate limited in Soundcheck using the following configuration:
soundcheck:
job:
workers:
jira:
limiter:
max: 400
duration: 60000
Jira API has a rate limiting policy in place. We recommend setting your rate limit to something that will avoid exceeding your quota, i.e. in the example above, we set the rate limit to 400 executions every minute.
This fact collector handles 429 rate limit errors from Jira. Soundcheck will automatically wait and retry requests that are rate limited.
Entity configuration
In your catalog-info.yaml
file, add the following metadata annotation to allow the plugin to map an entity to a project in Jira.
metadata:
annotations:
atlassian.net/jql: status = "To Do" AND created >= -30d
Check out JQL docs for more examples.
Collecting Issues Search Fact
An Issues Search Fact contains information about issues from Jira Issues Search API.
If JQL is defined in both places: soundcheck.collectors.jira.collects[*].jql
and metadata.annotations["atlassian.net/jql"]
, the plugin will concatenate them with an &
. Plugin will not remove any duplicate params.
This is useful if you want to create reusable facts, for example you can create fact that will look at issues with the status To Do
like this:
soundcheck:
collectors:
jira:
collects:
- factName: todoIssues
type: issues-search
jql: status="To Do"
In the next step you may want to restrict the jql
to only look for issues with status To Do
AND where project key is PROJECT
. In your entity you would add following annotation:
metadata:
annotations:
atlassian.net/jql: project = "PROJECT"
The next step is to create a check that could look like this:
soundcheck:
checks:
- id: has_no_todo_tickets
rule:
factRef: jira:default/todoIssues
path: $.total
operator: equal
value: 0
schedule:
frequency:
minutes: 5
filter:
metadata.tags: someTagValue
And finally add it to a track:
soundcheck:
programs:
- id: product-certified
name: Product certified
ownerEntityRef: group:default/example-owner
description: >
Improve quality of product backlog
documentationURL: https://www.backstage.io
filter:
catalog:
metadata.tags: someTagValue
levels:
- ordinal: 1
checks:
- id: has_no_todo_tickets
name: checks if there are any todo tickets
description: >
This checks if there are any todo tickets for the project
The plugin will call the JIRA endpoint with following jql value status="To Do" & project = "PROJECT"
every 5 minutes for all entities with tag someTagValue
.
If there are 0 issues with status To Do
for project PROJECT
the check will be green, otherwise it will fail.
Shape of A Issues Search Fact Collector
The shape of an Issues Search Fact Collector matches the Overall Shape Of A Jira Fact Collector (restriction: type: issues-search
).
Here's an example of an Issues Search Fact Collector configuration:
- type: issues-search
cache: true
frequency:
cron: '0 * * * *'
Shape of A Issues Search Fact Check
The shape of a Issues Search Fact Check matches the Shape of a Fact Check.
The following is an example of a Issues Search Fact checks:
soundcheck:
checks:
- id: has_no_issues_search
rule:
factRef: jira:default/issuesSearch // match the factName
path: $.total
operator: equal
value: 0
schedule:
frequency:
minutes: 5
filter:
metadata.tags: someTagValue
The following is an example response from Jira Issues Search API.
{
"expand": "schema,names",
"startAt": 0,
"maxResults": 50,
"total": 6,
"issues": [
{
"expand": "html",
"id": "10230",
"self": "http://localhost:7007/api/soundcheck/issue/BULK-62",
"key": "BULK-62",
"fields": {
"summary": "testing",
"timetracking": null,
"issuetype": {
"self": "http://localhost:7007/api/soundcheck/issuetype/5",
"id": "5",
"description": "The sub-task of the issue",
"iconUrl": "http://localhost:7007/images/icons/issue_subtask.gif",
"name": "Sub-task",
"subtask": true
},
"transitions": "http://localhost:7007/api/soundcheck/issue/BULK-62/transitions"
},
"customfield_10071": null
},
{
"expand": "html",
"id": "10004",
"self": "http://localhost:7007/api/soundcheck/issue/BULK-47",
"key": "BULK-47",
"fields": {
"summary": "Cheese v1 2.0 issue",
"timetracking": null,
"issuetype": {
"self": "http://localhost:7007/api/soundcheck/issuetype/3",
"id": "3",
"description": "A task that needs to be done.",
"iconUrl": "http://localhost:7007/images/icons/task.gif",
"name": "Task",
"subtask": false
},
"transitions": "http://localhost:7007/api/soundcheck/issue/BULK-47/transitions"
}
}
]
}