Sidehistorik
...
- Disable mail
- Limit access
- Install Apps - Notice this is not like Data Center - You cant disable and enable them as You please....
Clean up
I do recommend to clean up first:
...
A test - Migrate a project - Delete it - Migrate it again... Gave a lot of (migrated) fields, screens etc etc
| Advarsel |
|---|
Current recommendation seems to be a sire Reset (for Jira and Jira SErvice Management, and then a Big Bang migrate. |
Dark Features
Disable Workflow migration errors
...
Copy App/Speciel Fields to Text/Text Multiline field
This As it is ofthen often nessesary to get copy values to shadow fields, if the App has no migration or the App will not be uysed used in cloud.
| Tip |
|---|
This script does not send mail to users! |
...
| Kodeblok | ||||
|---|---|---|---|---|
| ||||
PASSWORD=******************* |
...
| Kodeblok | ||||
|---|---|---|---|---|
| ||||
#!/bin/bash source automationpasswordpassword.txt IFS=$(echo -en ",") BASEURL="https://jira.server.dk" REST="/rest/scriptrunner/latest/custom/UpdateShadowFieldsForIssue" USERNAME=automation DRYRUN="1" EMPTYONLY="1" SKIPINDEX="" DAYSBACK= #Loop projects curl -s -u "$USERNAME:$PASSWORD" -X GET -H 'Content-Type: application/json' https://jira.server.dk/$BASEURL/rest/api/2/project?maxResults=1000 > projects.json cat projects.json | jq '.[].key' | while read -r PROJECTKEY; do PROJECTKEY=$(echo $PROJECTKEY | sed 's/\"//g') #echo "$PROJECTKEY" #Loop Issuetypes for the Project curl -s -u "$USERNAME:$PASSWORD" -X GET -H 'Content-Type: application/json' https:$BASEURL/rest/api/jira.server.dk/rest/api/2/issuetype2/issue/createmeta/$PROJECTKEY/issuetypes?maxResults=1000 > issuetypes.json cat issuetypes.json | jq '.values[].name' | while read -r ISSUETYPE; do ISSUETYPEISSUETYPE=$(echo $ISSUETYPE | sed 's/\"//g') #echo $ISSUETYPE #Loop Fields declare -a FIELDMAP=("23321|29033|M" "23523|29020|S" "24523|29021|S" "23522|29022|S" "24623|29026|S" "24422|29023|S" "23726|29024|S" "23728|29034|M" "23729|29035|M" "23727|29025|S" "24920|29036|S" "24921|29037|S" "24624|29027|S" "24423|29028|S" "23320|29032|S" "24821|29031|S" "24625|29030|S" "24424|29029|S" "24922|29039|S" "24923|29038|S" "24924|29040|S" "24925|29041|S" "24926|29042|S" "25028|29043|S" "25027|29044|S") for FIELD in "${FIELDMAP[@]}" do #echo "Field $FIELD" ELEMENTSFIELD SOURCEFIELDID=$(echo $FIELD | cut -d '|' -f 1) SHADOWFIELD SHADOWFIELDID=$(echo $FIELD | cut -d '|' -f 2) TYPE=$(echo $FIELD | cut -d '|' -f 3) ISSUETYPE=$(echo $ISSUETYPE | sed 's/ /%20/g') #Sample: https://jira.server.dk/rest/scriptrunner/latest/custom/UpdateShadowFieldsForIssue?projectkey=NIS&issuetype=Repeatable%20Task&elementsfield=23321&shadowfield=29033&type=M&emptyonly=1&dryrun=0&skipindex=&daysback=10 QUERYSTRING= QUERYSTRING="projectkey=$PROJECTKEY&issuetype=$ISSUETYPE&elementsfieldsourcefieldid=$ELEMENTSFIELD$SOURCEFIELDID&shadowfieldshadowfieldid=$SHADOWFIELD$SHADOWFIELDID&type=$TYPE&emptyonly=$EMPTYONLY&dryrun=$DRYRUN&skipindex=$SKIPINDEX&daysback=$DAYSBACK" echo "curling.... $QUERYSTRING" curl -u "$USERNAME:$PASSWORD" -X GET -H 'Content-Type: application/json' "$BASEURL$REST?$QUERYSTRING" done done done |
The scriptrunner REST code:
| Info | ||||
|---|---|---|---|---|
| Kodeblok | ||||
| ||||
elementsFieldValue = extractValues(theIssue.getCustomFieldValue(elementsField),type) |
| Kodeblok | ||
|---|---|---|
| ||
elementsFieldValue = theIssue.getCustomFieldValue(elementsField) |
For normal copy
| language | shell |
|---|---|
| title | UpdateShadowFieldsForIssue.groovy |
| title | UpdateShadowFieldsForIssue.groovy |
|---|
import com.atlassian.jira.component.ComponentAccessor import com.import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.IssueManager import com.atlassian.jira.user.util.UserUtil import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.issue.fields.CustomField import com.atlassian.jira.bc.issue.search.SearchService import com.atlassian.jira.issue.search.SearchProvider import com.atlassian.jira.issue.search.SearchResults import com.atlassian.jira.web.bean.PagerFilter import com.atlassian.jira.user.ApplicationUser import com.atlassian.jira.event.type.EventDispatchOption import groovy.json.JsonSlurper import com.atlassian.jira.issue.index.IssueIndexingService import com.atlassian.jira.issue.MutableIssue import com.atlassian.jira.issue.IssueManager import com.atlassian.jira.util.ImportUtils import groovy.transform.BaseScript import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import javax.ws.rs.core.MultivaluedMap import javax.servlet.http.HttpServletRequest import javax.ws.rs.core.Response @BaseScript CustomEndpointDelegate delegate UpdateShadowFieldsForIssue(httpMethod: "GET", groups: ["automaticservices","jira-administrators"]) { MultivaluedMap queryParams -> Random random = new Random() String scriptRunIdent = Math.abs(random.nextInt() % 99999) + 1 String scriptName = "UpdateShadowFieldsForIssue.groovy" String projectKey = queryParams.getFirst("projectkey") as String String issueType = queryParams.getFirst("issuetype") as String String elementsFieldId = queryParams.getFirst("elementsfield") as String String shadowFieldId = queryParams.getFirst("shadowfield") as String String type = queryParams.getFirst("type") as String String emptyOnly = queryParams.getFirst("emptyonly") as String String dryRun = queryParams.getFirst("dryrun") as String String skipIndex = queryParams.getFirst("skipindex") as String Integer currentNumber = 0 def builder = new groovy.json.JsonBuilder() String jql="Project=" + projectKey + " and issuetype='" + issueType + "' and cf[" + elementsFieldId + "] is not empty" if (emptyOnly == "1") { jql=jql + " and cf[" + shadowFieldId + "] is empty" } if (daysBack != ".http.HttpServletRequest import javax.ws.rs.core.Response @BaseScript CustomEndpointDelegate delegate UpdateShadowFieldsForIssue(httpMethod: "GET", groups: ["automaticservices","jira-administrators"]) { MultivaluedMap queryParams -> Random random = new Random() String scriptRunIdent = Math.abs(random.nextInt() % 99999) + 1 String scriptName = "UpdateShadowFieldsForIssue.groovy" String projectKey = queryParams.getFirst("projectkey") as String String issueType = queryParams.getFirst("issuetype") as String String sourceFieldId = queryParams.getFirst("sourcefieldid") as String String shadowFieldId = queryParams.getFirst("shadowfieldid") as String String type = queryParams.getFirst("type") as String String emptyOnly = queryParams.getFirst("emptyonly") as String String dryRun = queryParams.getFirst("dryrun") as String String skipIndex = queryParams.getFirst("skipindex") as String String daysBack = queryParams.getFirst("daysback") as String Integer currentNumber = 0 def builder = new groovy.json.JsonBuilder() String jql="Project=" + projectKey + " and issuetype='" + issueType + "' and cf[" + sourceFieldId + "] is not empty" if (emptyOnly == "1") { jql=jql + " and cf[" + shadowFieldId + "] is empty" } if (daysBack != "") { jql=jql + " and updated > -" + daysBack + "d" } log.info "Script=" + scriptName + " ScriptRunIdent=" + scriptRunIdent + " Message='JQL: " + jql + "'" ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser() CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager() IssueManager issueManager = ComponentAccessor.getIssueManager() def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService) CustomField sourceField = customFieldManager.getCustomFieldObject("customfield_" + sourceFieldId) String jiraSourceFieldType =sourceField.getCustomFieldType().getName() CustomField shadowField = customFieldManager.getCustomFieldObject("customfield_" + shadowFieldId) String jiraShadowFieldType = shadowField.getCustomFieldType().getName() log.info "Script=" + scriptName + " ScriptRunIdent=" + scriptRunIdent + " Message='Source field Jira type: " + jiraSourceFieldType + "'" log.info "Script=" + scriptName + " ScriptRunIdent=" + scriptRunIdent + " Message='Shadow field Jira type: " + jiraShadowFieldType + "'" if (jiraShadowFieldType == "Text Field (single line)" || jiraShadowFieldType == "Text Field (multi-line)") { jqlSearchService searchService =jql + " and updated > -" + daysBack + "d" } log.info "Script=" + scriptName + " ScriptRunIdent=" + scriptRunIdent + " Message='JQL: " + jql + "'" ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser() CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager() IssueManager issueManager = ComponentAccessor.getIssueManager() def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService ComponentAccessor.getComponent(SearchService.class) SearchService.ParseResult parseResult = searchService.parseQuery(currentUser, jql) if (parseResult.isValid()) { SearchResults results = searchService.search(currentUser, parseResult.getQuery(), PagerFilter.getUnlimitedFilter()) final List issues = results?.results totalIssues = issues.size() CustomField elementsField = customFieldManager.getCustomFieldObject("customfield_" + elementsFieldId) issues.each CustomField{ shadowField = customFieldManager.getCustomFieldObject("customfield_" + shadowFieldId) theIssue -> String jiraFieldType = shadowField.getCustomFieldType().getName() loglog.info "Script=" + scriptName + " ScriptRunIdent=" + scriptRunIdent + " Message='Value " + theIssue.getCustomFieldValue(sourceField) + scriptRunIdent + " Message='Shadow field Jira type: " + jiraFieldType + "'" //Make sure we copy to a text field. "'" //Extract value(s) here from Source field. Its possible to expand for another 'jiraSourceFieldType' if (jiraFieldType == "Text Field (single line)" || jiraFieldType == "Text Field (multi-line)"jiraSourceFieldType.contains("Elements Connect")) { SearchService searchService = ComponentAccessor.getComponent(SearchService.class) { SearchService.ParseResult parseResult = searchService.parseQuery(currentUser, jql) if (parseResult.isValid()) { sourceFieldValue = extractElementsValues(theIssue.getCustomFieldValue(sourceField),type) SearchResults results = searchService.search(currentUser, parseResult.getQuery(), PagerFilter.getUnlimitedFilter()) } final List issues = results?.results totalIssues = issues.size() else issues.each { theIssue -> { log.info "Script=" + scriptName + " ScriptRunIdent=" + scriptRunIdent + " Message='Value " + sourceFieldValue = theIssue.getCustomFieldValue(elementsFieldsourceField) + "'" elementsFieldValue = extractValues(theIssue.getCustomFieldValue(elementsField),type) } shadowFieldsValue = theIssue.getCustomFieldValue(shadowField) if (elementsFieldValuesourceFieldValue != shadowFieldsValue) { currentNumber = currentNumber + 1 if (dryRun != "1") { log.info "Script=" + scriptName + " IssueKey=" + theIssue.getKey() + " ScriptRunIdent=" + scriptRunIdent + " Message='Copying " + elementsFieldIdsourceFieldId + " to " + shadowFieldId + ", value: " + elementsFieldValuesourceFieldValue + "'" MutableIssue mIssue = issueManager.getIssueByCurrentKey(theIssue.getKey()) try { mIssue.setCustomFieldValue(shadowField,elementsFieldValuesourceFieldValue) ComponentAccessor.getIssueManager().updateIssue(currentUser, mIssue, EventDispatchOption.DO_NOT_DISPATCH, false) } catch (Exception ex) { log.info "Script=" + scriptName + " IssueKey=" + theIssue.getKey() + " ScriptRunIdent=" + scriptRunIdent + " Error='Error Updating: " + ex.getMessage() + "'" } //Reindex Issue if (skipIndex != "1") { boolean wasIndexing = ImportUtils.isIndexIssues() ImportUtils.setIndexIssues(true) issueIndexingService.reIndex(mIssue) ImportUtils.setIndexIssues(wasIndexing) } } else { log.info "Script=" + scriptName + " IssueKey=" + theIssue.getKey() + " ScriptRunIdent=" + scriptRunIdent + " Message='DryRun - Copying " + elementsFieldIdsourceFieldId + " to " + shadowFieldId + ", value: " + elementsFieldValuesourceFieldValue + "'" } } else { log.info "Script=" + scriptName + " IssueKey=" + theIssue.getKey() + " ScriptRunIdent=" + scriptRunIdent + " Message='Skipped copy due to identical values'" } } if (dryRun != "1") { if (currentNumber > 0) { builder {success "Fields copied successfully. Copied " + currentNumber + " field(s)."} } else { builder {success "No Fields copied."} } } else { builder {success "Dry Run. Woulf have copied " + currentNumber + " field(s)."} } Response.status(200).entity(builder.toString()).build() } else { builder {error "Not valid JQL"} Response.status(500).entity(builder.toString()).build() } } else { builder {error "Shadow field is not a Text Field."} Response.status(500).entity(builder.toString()).build() } } def extractValuesextractElementsValues(theValue,fieldType) { if (theValue == null) { return null } def slurper = new JsonSlurper().parseText(theValue) if (fieldType == "M") { slurper.keys.join("\n").toString() } else { slurper.keys[0].toString() } }
...