Bulk deleting unused screens in Jira with JMeter

Hi folks!

 

Yet another "heavy mouse-clicking labor" in Jira Administration that I worked around with JMeter. :-)

In .../secure/admin/ViewFieldScreens.jspa you can see all Screens available in the instance. Search for "Delete" and you'll see how many screens are unused. The first "delete" occurence is in the very upper text, all others point to actual unused screens.

 

Here we had over 200 unused screens and I setup JMeter to work it out.

Other alternatives are:

- Bear with the mouse-clicking and screen-loading...

- Code in Script Runner or other groovy script App (plugin) in Jira.

- Temper with the database. If you considered this one, please use this JMeter solution instead. ;-)

 

The following solution has worked on Jira Server 7 and JMeter 5.

Should work on Cloud too, since all requests are against Jira's web interface.

Some texts are in Portuguese (pt_BR) but nevermind. It shouldn't affect any functionality.

 

1) Access your Jira's .../secure/admin/ViewFieldScreens.jspa and load the sourcecode.

 

2) Extract all screen IDs from the file. You may want to "grep" the file to only contain the words with ">Delete</a>" and then maintain only the screen ID from the "a href" URL.
Work the regexps on your terminal or file editor of choice.

You may exchange steps 1 & 2 for some other way of listing all the screen IDs you want to delete. They shouldn't have any screen scheme or workflow step associated with them.

 

3) Save a new text file with all the Screen IDs one on each line. No header and nothing except the IDs line-by-line.

Eg:
16203
16204
17802
17803
17804

 

4) Copy the XML content in the bottom of this post to a new file and save it with JMX extension.

 

5) Edit the file and replace the following variables with their actual values:

Line 33: VAR_SCREENS_TO_DELETE_COUNT
Total number os screens to be deleted. It's how many "Delete" words appear on .../secure/admin/ViewFieldScreens.jspa. It's also the line count of the file you just created.

Line 57: VAR_JIRA_URL
Your Jira instance URL, like jira.company.com

Line 73: VAR_ADMINUSERNAME
An active admin username. As JMeter will perform a new login with this username, avoid using one that's currenly using Jira.

Line 80: VAR_ADMINPASSWORD
The above user's password. Yeah, plaintext. You may want to edit the file (or do it through JMeter) to erase the password after using it.

123: VAR_FILENAME
The fullpath of the file you created in step 3.

 

6) Start JMeter and load the newly created and editer JMX file.

 

7) Start the thread on the "play" button and watch it tirelessly do all the requests for you!

You may need some tweaks to the JMX depending on you Jira environment...

 

 

Happy housekeeping!

 

JMX:

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Hide Fields in Field Configuration" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="Variáveis Definidas Pelo Usuário" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">stopthread</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Controlador de Iteração" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<longProp name="ThreadGroup.start_time">1441750292000</longProp>
<longProp name="ThreadGroup.end_time">1441750292000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="VARIAVEIS" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="loop_count" elementType="Argument">
<stringProp name="Argument.name">loop_count</stringProp>
<stringProp name="Argument.value">VAR_SCREENS_TO_DELETE_COUNT</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">Amount of looping requests</stringProp>
</elementProp>
</collectionProp>
</Arguments>
<hashTree/>
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="Cookie" enabled="true">
<collectionProp name="CookieManager.cookies"/>
<boolProp name="CookieManager.clearEachIteration">false</boolProp>
</CookieManager>
<hashTree/>
<AuthManager guiclass="AuthPanel" testclass="AuthManager" testname="Auth" enabled="true">
<collectionProp name="AuthManager.auth_list"/>
</AuthManager>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="Gerenciador de Cabeçalhos HTTP" enabled="true">
<collectionProp name="HeaderManager.headers"/>
</HeaderManager>
<hashTree/>
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">VAR_JIRA_URL</stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.concurrentPool">4</stringProp>
<stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
<stringProp name="HTTPSampler.connect_timeout">60000</stringProp>
<stringProp name="HTTPSampler.response_timeout">60000</stringProp>
</ConfigTestElement>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Login" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variáveis Definidas Pelo Usuário" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="os_username" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">VAR_ADMINUSERNAME</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">os_username</stringProp>
</elementProp>
<elementProp name="os_password" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">VAR_ADMINPASSWORD</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">os_password</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/rest/gadget/1.0/login</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">false</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="TestPlan.comments">Faz login com um usuário admin no Jira</stringProp>
</HTTPSamplerProxy>
<hashTree>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extractor de Expressão Regular" enabled="true">
<stringProp name="RegexExtractor.useHeaders">true</stringProp>
<stringProp name="RegexExtractor.refname">atl_token</stringProp>
<stringProp name="RegexExtractor.regex">atlassian\.xsrf\.token=(.*)?;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
<LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Repetição" enabled="true">
<boolProp name="LoopController.continue_forever">true</boolProp>
<stringProp name="LoopController.loops">${loop_count}</stringProp>
<stringProp name="TestPlan.comments">Repete a chamada da URL &quot;loop_count&quot; vezes</stringProp>
</LoopController>
<hashTree>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="Configuração dos dados CSV" enabled="true">
<stringProp name="delimiter">,</stringProp>
<stringProp name="fileEncoding">UTF-8</stringProp>
<stringProp name="filename">VAR_FILENAME</stringProp>
<boolProp name="ignoreFirstLine">false</boolProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">false</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames">screenid</stringProp>
</CSVDataSet>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete Screen" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variáveis Definidas Pelo Usuário" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="atl_token" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">true</boolProp>
<stringProp name="Argument.value">${atl_token}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">atl_token</stringProp>
</elementProp>
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${screenid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
<elementProp name="confirm" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">confirm</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/secure/admin/DeleteFieldScreen.jspa</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">false</boolProp>
<boolProp name="HTTPSampler.auto_redirects">true</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
</hashTree>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<threadCounts>true</threadCounts>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</jmeterTestPlan>

 

2 comments

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 13, 2018

> Tamper with the database. If you considered this one, please use this JMeter solution instead. ;-)

Yes.  Always.

Tampering with a Jira database is an awful thing to do, and cleaning up the disastrous mess people usually make when they do it is even less fun.

Thank you for giving us another way to do this!

Nicketa July 31, 2021

Can you also add steps for sso authorization in jmeter for beginners ?

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events