How to detect duplicate screens in Jira

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?

 

Ideas and preparations

How do we know screens are duplicate?

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.

How do we know tabs are duplicate?

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.

What data do we need?

Based on the previous information, we need to know how all the tabs look like. There's REST API endpoint for:

Step One: Get all the fields for every tab

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"
    }
]

Step Two: Determine for each tab, whether this combination of fields already exist or not, and add it to the results list

How do we determine, whether the list of fields (= tab) is unique?

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.

How do we prepare the results list?

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.

Step Three: Export the results

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

Python Script

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")

Questions & Answers

What do I need to change, if I want to use the script?

You need to set URL of your Jira, username and password in the beginning of the script. Scipt uses standard Basic Auth.

Can the script be used with Jira cloud?

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).

Is it safe to use the script with my Jira DC?

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.

Is the script good?

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.

0 comments

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events