Cleaning up Field Configurations

The Field Configuration tells Jira which fields are available to each Project (and Issuetype). Even if the fields aren't present in any screen, they can still be set by bulk editions and are considered in reindex (degrading it's performance).

The native behaviour of Jira is to add every newly created Custom Field to all Field Configurations available. If you happen to have hundreds of Custom Fields in your instance and would like a hint on how trimming the Field Configurations, this article is for you! :-)

 

You should have an "empty" Field Configuration (FC) with no fields associated with it. You may copy this FC and add the desired Fields when creating new Projects. Create a new FC called "Empty" and leave the Default Field Configuration as it is.

The problem is arriving at this empty FC. If you have a brand new Instance, just create it right away and "hide" all fields. The more Custom Fields you have, the worse it will be to empty a FC.

 

The bad news is that you have to hide every Field one-by-one. Hundreds of fields. One. By. One.

You can spend a whole day or two clicking the mouse and waiting the reload, temper with the database (a big nope) or use JMeter.

1) Create a new Field Configuration and call it "Empty" or something similar.

2) Click to configure it and check the URL. It should end (or contain) the FC's ID like "..;&id=12601"

3) Copy the link of the first "hide" link. It might end with "...&hide=0". 0 is then the first index we'll setup in JMeter.

4) Repeat for the last "hide" link on the entire FC. Let's exemplify with "...&hide=487". 487 is the last index, then.

5) Copy the XML below into a new file and save it with ".jmx" extension. Edit a few lines and save again:

Line 58: Replace XYZ with your Jira's URL (like "jira.company.com")
Line 74: Replace XYZ with an Admin's username
Line 81: Replace XYZ with Admin's password
Line 134: Replace XYZ with the FC's ID from step 2). 12601 in the example.
Line 167: Replace XYZ with the first index from step 3). 0 in the example.
Line 168: Replace XYZ with the last index from step 4). 487 in the example.
Line 33: Replace XYZ with (last index - first index). If first index is 0, then just repeat the last index here. (487 - 0 = 487)

6) Open JMeter and load the file you just saved.

7) Press "play" and wait a few seconds for all requests to complete. Log into Jira again and check if all fields are hidden in the FC. (press ctrl+f in the browser and search for "hide")

 

There you have it! An emtpy Field Configuration from wich you may copy and enable only the fields you want to use for your Projects (or Issuetypes).

Everytime you create a new Custom Field you'll have to remove it from all optimized Field Configurations, or just run these JMeter requests adjusting the parameters for each FC.

Either way, just disabling hundreds of Custom fields once will grant you a better reindex performance right off the bat.

 

CAUTION:

There are some native fields that you very, very likely won't want to disable in your Field Configuration:

Assignee, Attachment, Description, Due Date, Labels, Log Work, Priority, Reporter, Resolution and Time Tracking.

Been there, done that... You may even want to enable these fields in the "empty" FC just for sake.

 

Happy housekeeping!

 

XML (tested with JMeter 2 and 5. Some comment's are in pt_BR, nevermind):

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="2.8" jmeter="2.13 r1665067">
<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">XYZ</stringProp> <!-- REPLACE WITH (MAX ID - MIN ID) -->
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">Número de vezes pra subir ou descer o status</stringProp>
</elementProp>
</collectionProp>
</Arguments>
<hashTree/>
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="Cookie" enabled="true">
<collectionProp name="CookieManager.cookies"/>
<boolProp name="CookieManager.clearEachIteration">false</boolProp>
<stringProp name="CookieManager.implementation">org.apache.jmeter.protocol.http.control.HC4CookieHandler</stringProp>
</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">XYZ</stringProp> <!-- REPLACE WITH YOUT JIRA INSTANCE -->
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout">60000</stringProp>
<stringProp name="HTTPSampler.response_timeout">60000</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
<stringProp name="HTTPSampler.concurrentPool">4</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">XYZ</stringProp> <!-- REPLACE WITH ADMIN USERNAME -->
<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">XYZ</stringProp> <!-- REPLACE WITH ADMIN PASSWORD -->
<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.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></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>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></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>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Hide fields" 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">XYZ</stringProp> <!-- REPLACE WITH FIELD CONFIGURATION ID -->
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
<elementProp name="hide" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${hide}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">hide</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/secure/admin/EditFieldLayoutHide.jspa</stringProp>
<stringProp name="HTTPSampler.method">GET</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>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="TestPlan.comments">Chama a URL parametrizada pra mover a ordem de um Status pra cima ou pra baixo</stringProp>
</HTTPSamplerProxy>
<hashTree/>
<CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Contador Hide" enabled="true">
<stringProp name="CounterConfig.start">XYZ</stringProp> <!-- REPLACE WITH FIRST FIELD "HIDE" PARAM -->
<stringProp name="CounterConfig.end">XYZ</stringProp> <!-- REPLACE WITH LAST FIELD "HIDE" PARAM -->
<stringProp name="CounterConfig.incr">1</stringProp>
<stringProp name="CounterConfig.name">hide</stringProp>
<stringProp name="CounterConfig.format"></stringProp>
<boolProp name="CounterConfig.per_user">false</boolProp>
</CounterConfig>
<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

Steven F Behnke
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 13, 2018

Awesome! I use Scriptrunner to accomplish a similar thing: 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutManager
import com.atlassian.jira.issue.fields.layout.field.EditableFieldLayout
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem

FieldLayoutManager fieldConfigurationManager = ComponentAccessor.getComponent(FieldLayoutManager)
EditableFieldLayout fieldConfiguration = fieldConfigurationManager.getEditableFieldLayout(18000L)
List<FieldLayoutItem> items = fieldConfiguration.getFieldLayoutItems()

items.each {
try {
fieldConfiguration.hide(it)
} catch (ignored) {}
}

fieldConfigurationManager.storeEditableFieldLayout(fieldConfiguration)
Like # people like this
Steven F Behnke
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 24, 2023

I wrote this script a long time ago. I'd be very careful about using this in Production. It has serious issues and doesn't indicate failure modes.

  • You DON'T want to hide plugin fields like Epic Link, Epic Name, Epic Status, Customer Request Type, etc -- This will break Jira features such as Boards and Queues.
  • You DON'T want to hide Affects Versions/Fix Versions -- This will break reports and Jira Roadmaps.
  • If the ID you give to #getEditableFieldLayout(Long id) doesn't exist, you get back the DEFAULT FIELD LAYOUT!!!!!! This can be DISASTEROUS! 

Be very careful with this script. If you don't understand Jira Field Layouts and how their data is stored, DO NOT USE THIS SCRIPT.

TAGS
AUG Leaders

Atlassian Community Events