Versioner sammenlignet

Nøgle

  • Linjen blev tilføjet.
  • Denne linje blev fjernet.
  • Formatering blev ændret.

...

  • 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
languageshell
titleautomationpasswordpassword.txt
PASSWORD=*******************

...

Kodeblok
languageshell
titleUpdateShadowFieldsForIssue.sh
#!/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:

This script is for field from the Elements App - hence the line:

Extracts the value. This can be different, or just:

Info
Kodeblok
languageshell
elementsFieldValue = extractValues(theIssue.getCustomFieldValue(elementsField),type)
Kodeblok
languageshell
elementsFieldValue = theIssue.getCustomFieldValue(elementsField)

For normal copy

Kodeblok
languageshell
titleUpdateShadowFieldsForIssue.groovy
titleUpdateShadowFieldsForIssue.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()
    }   
}

...