As a Jira admin I usually want to share as much configurations as possible. I've always been struggling with the screens. I didn't find any tool to help me find out, whether there are some duplicate screens in Jira. So, I decided to create my own tool.
How hard this can be?
As you probably know, screens can have one or more tabs. As this is first version of the tool and usually screen has only one tab I decided to compare the tabs instead of screens.
Tabs are set of fields in the specific order. If the fields are exactly the same, tabs are the same. I decided that I don't care what order the fields are in, and I mark the tabs as duplicate if they only have the same set of fields.
Based on the previous information, we need to know how all the tabs look like. There's REST API endpoint for:
We need to send three nested requests to the REST API.
Send GET request to get all the screens - the response contains list of the screens, for each of them id, name and description is returned.
Example of the response:
... { "id": 14202, "name": "SHARED: App Screen for Bugs", "description": "", "expand": "fieldScreenSchemes,fieldScreenWorkflows,deletable" }, { "id": 14200, "name": "SHARED: App Screen for Epics", "description": "", "expand": "fieldScreenSchemes,fieldScreenWorkflows,deletable" }, ...
Then we iterate through all the screens and send GET request with screen id parameter to get all the tabs - the response contains list of tabs for one particular screen, for each of them id and name is returned.
Example of the response:
[ { "id": 12500, "name": "Field Tab" } ]
Then we iterate through all the tabs and send GET request with screen id and tab id parameter to get all the fields - the response contains list of fields for one particular tab, for each of them id, name and type is returned.
Example of the response:
[ { "id": "customfield_10204", "name": "Epic Name", "type": "Name of Epic" }, { "id": "summary", "name": "Summary", "type": "System field" }, { "id": "issuetype", "name": "Issue Type", "type": "System field" }, { "id": "reporter", "name": "Reporter", "type": "System field" }, { "id": "components", "name": "Component/s", "type": "System field" }, { "id": "description", "name": "Description", "type": "System field" }, { "id": "fixVersions", "name": "Fix Version/s", "type": "System field" }, { "id": "priority", "name": "Priority", "type": "System field" }, { "id": "assignee", "name": "Assignee", "type": "System field" }, { "id": "customfield_10202", "name": "Epic Link", "type": "Epic Link Relationship" }, { "id": "timetracking", "name": "Time Tracking", "type": "System field" }, { "id": "customfield_10206", "name": "Sprint", "type": "Jira Sprint Field" } ]
When we send the third request to get list of fields for one particular screen and tab, the parameter id is returned for each of the field. This parameter id is unique identifier of the field - it is either something like summary, description, reporter... for system fields or customfield_xxxxx (xxxxx = number) for custom fields. Every time we get the information the field is on the particular tab, the same id is returned.
For example if we have tab with fields Summary, Assignee and Sprint, we get summary, assignee and customfield_10206 as identifiers.
As I have previously mentioned position of the fields is not relevant, so what we do is sort the identifiers and proceed with assignee, customfield_10206 and summary.
This sorted list of identifiers give us unique identifier of the tab.
As the list of fields can be very long, which means the identifier of the tab can be very long, we use md5 hash function to get 16 characters long identifiers.
During the execution of the script we create list of identifiers of the tabs. Under each tab identifier list of found tabs will be stored. We add a new tab identifier, if it is a first time we run into the particular fields combination, or just add the tab under the existing tab identifier.
In this first version we just export results in the text file in the format:
Tab identifier 1 Screen Id 1: Screen name 1, Tab Id 1: Tab name 1 Screen Id 2: Screen name 2, Tab Id 2: Tab name 2 Screen Id 3: Screen name 3, Tab Id 3: Tab name 3 Tab identifier 2 Screen Id 4: Screen name 4, Tab Id 4: Tab name 4 Screen Id 5: Screen name 5, Tab Id 5: Tab name 5 Screen Id 6: Screen name 6, Tab Id 6: Tab name 6
import requests
import hashlib
baseUrl = 'https://my.testjira.com'
user = 'username'
password = 'password'
configsMap = {}
screensResponse = requests.get(f"{baseUrl}/rest/api/2/screens",
auth=(user, password))
statusCode = screensResponse.status_code
if statusCode != 200:
print(f'unable to get screens')
else:
screens = screensResponse.json()
for screen in screens:
screenId = screen["id"]
screenName = screen["name"]
# time.sleep(1)
tabsResponse = requests.get(f"{baseUrl}/rest/api/2/screens/{screenId}/tabs",
auth=(user, password))
statusCode = tabsResponse.status_code
if statusCode != 200:
print(f'unable to get tabs')
else:
tabs = tabsResponse.json()
for tab in tabs:
tabId = tab["id"]
tabName = tab["name"]
fieldsResponse = requests.get(f"{baseUrl}/rest/api/2/screens/{screenId}/tabs/{tabId}/fields",
auth=(user, password))
statusCode = fieldsResponse.status_code
if statusCode != 200:
print(f'unable to get fields')
else:
fields = fieldsResponse.json()
fieldsList = []
for field in fields:
fieldId = field["id"]
fieldsList.append(fieldId)
fieldsList.sort()
fieldsListEncoded = hashlib.md5(str(fieldsList).encode()).hexdigest()
configName = str(screenId) + ": " + screenName + ", " + str(tabId) + ": " + tabName
if fieldsListEncoded in configsMap.keys():
configsMap[fieldsListEncoded].append(configName)
else:
configsMap[fieldsListEncoded] = [configName]
filename = "result.txt"
with open(filename, 'a') as f:
for key in configsMap:
f.write(str(key) + "\n")
for config in configsMap[key]:
f.write(str(config) + "\n")
f.write("\n\n")
You need to set URL of your Jira, username and password in the beginning of the script. Scipt uses standard Basic Auth.
I've tested the script with Jira DC 9. 4. It will probably work with a lot of other versions as there were not many changes in REST API. I haven't tested it with Jira cloud, but I believe it will work (maybe with a few small modifications).
The script sends a lot of requests, so maybe it can run into some problems with API limits. I've used time.sleep command between the REST API requests to avoid flooding the API. But in general it only reads the data, which is relatively safe.
No... There are many, many possible improvements, but it works for me. Would you be interested, if I continue with this activity, improve the script and do the comparison of the screens, screen schemes or issue type screen schemes? Please, let me know.
Thank you.
Hana Kučerová
Atlassian Consultant
BiQ Group
Prague, Czech Republic
511 accepted answers
0 comments