Create asset with stock conditionally

In this example, we will define a post function to create asset and set it to "Assets" asset custom field.

Post function performs following operations:

  • Checks if the asset custom field is empty, if it is not empty, does nothing
  • Checks if "In Stock" is not Yes
  • Creates asset with 
    • "Asset Name" field value will be name of asset
    • Order Amount will be "Number of items in stock" asset value if asset has this attribute
  • Sets new asset to "Assets" custom field

Fields of Screen and Asset

  • Assets - Asset custom field 
    • maps to assetCustomFieldId in groovy script

Other Fields

  • Asset Type - ListBox - Values are "Asset Type Name - ID". 
    • maps to assetTypeCustomFieldId in groovy script
    • Examples:
      • Laptop - 3
      • Keyboard - 2
      • Computer - 27
  • Asset Name - Text Field: Name of the asset. If asset names are set to unique and asset name is not unique for assets, it will not be created. 
    • maps to assetNameCustomFieldId in groovy script
  • Order Amount - Number Field - number of items to be purchased
      • maps to orderAmountCustomFieldId in groovy script
  • In Stock - Radio (Yes / No) - This must be No for this operation as we will create new asset.
    • maps to inStockCustomFieldId in groovy script

Asset Attribute

  • Number of items in stock - Number - This will hold inventory of assets


Add Post Function

Add "[AIP] - Asset generic groovy script" to a specific transition. Put the post function after issue updates and before "Update change history for an issue and store the issue in the database", GenerateChangeHistoryFunction and Re-index post functions.

Post function parameters

"Asset custom fields (with values) to inject as script parameter":  Select the target asset custom field

"Groovy script": 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.customfields.option.Option
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import groovy.json.JsonSlurper
import org.apache.commons.lang3.StringUtils
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils
import org.apache.log4j.Level
import org.apache.log4j.Logger

// To test with script runner
//import com.atlassian.jira.issue.Issue
//def issueManager = ComponentAccessor.getIssueManager()
//def Issue issue = issueManager.getIssueObject("KTP-25")

Logger logger = Logger.getLogger("inventoryplugin.groovy.script")
logger.setLevel(Level.DEBUG)


/*** variables to configure **************************/
def assetCustomFieldId = 'customfield_10227';
def assetTypeCustomFieldId = 'customfield_10314';
def inStockCustomFieldId = 'customfield_10317';
def orderAmountCustomFieldId = 'customfield_10316';
def assetNameCustomFieldId = 'customfield_10318';
def inStockNoValue = 'No'
def AUTH_HASH = 'Basic YWRtaW46YWRtaW4='; // auth hash for authorization. this sample is for user=admin, and password=admin
def stockAttributeId = 67; // asset "Number of items in stock" attribute id
/*****************************************************/

boolean isNotInStockCase(issueObj, inStockCustomFieldId, inStockNoValue) {
    def customFieldManager = ComponentAccessor.getCustomFieldManager()
    def cField = customFieldManager.getCustomFieldObject(inStockCustomFieldId)
    def option = (Option) issueObj.getCustomFieldValue(cField);
    return option != null && option.getValue() != null && option.getValue().equals(inStockNoValue)
}


def updateAssetCustomFieldOfTheIssue(issueObj, assetCustomFieldId, issueKey, assetCustomFieldValue) {
    // get issue
    def issueToUpdate = ComponentAccessor.getIssueManager().getIssueObject(issueKey);
    // get asset custom field
    CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager()
    def assetCf = customFieldManager.getCustomFieldObject(assetCustomFieldId) // asset cf id
    // execute update
    assetCf.updateValue(null, issueToUpdate, new ModifiedValue(issueToUpdate.getCustomFieldValue(assetCf), assetCustomFieldValue), new DefaultIssueChangeHolder())
}

int getAssetTypeId(issueObj, assetTypeCustomFieldId) {
    def customFieldManager = ComponentAccessor.getCustomFieldManager()
    def cField = customFieldManager.getCustomFieldObject(assetTypeCustomFieldId)
    def assetTypeValue = issueObj.getCustomFieldValue(cField).getValue() as String
    return StringUtils.trim(StringUtils.substring(assetTypeValue, StringUtils.lastIndexOf(assetTypeValue, "- ") + 2)) as Integer;
}

/**
 * Checks if Asset custom field value is empty
 */
boolean isCustomFieldEmpty(issueObj, assetCustomFieldId) {
    def customFieldManager = ComponentAccessor.getCustomFieldManager()
    def cField = customFieldManager.getCustomFieldObject(assetCustomFieldId)
    return StringUtils.isBlank(issueObj.getCustomFieldValue(cField));
}

/**
 * converts stream to json
 */
def streamToJson(stream) {
    BufferedReader br = new BufferedReader(new InputStreamReader(stream));
    StringBuilder sb = new StringBuilder();
    String line;
    while ((line = br.readLine()) != null) {
        sb.append(line + "\n");
    }
    br.close();

    def jsonSlurper = new JsonSlurper()
    return jsonSlurper.parseText(sb.toString())
}

/**
 * Calls GET service
 */
def callGetRestServiceAndReturnJson(serviceUrl, authHash) {
    Logger logger = Logger.getLogger("inventoryplugin.groovy.script")
    try {
        // get asset ID(s)
        def baseurl = ComponentAccessor.getApplicationProperties().getString("jira.baseurl")
        CloseableHttpClient httpclient = HttpClients.createDefault();
        def httpGet = new HttpGet(baseurl + serviceUrl);
        httpGet.setHeader("Accept", "application/json");
        httpGet.setHeader("Content-type", "application/json");
        httpGet.setHeader("Authorization", authHash);
        CloseableHttpResponse response = httpclient.execute(httpGet);
        try {
            if (response.getStatusLine().getStatusCode() == 200) {
                return streamToJson(response.getEntity().getContent());
            } else {
                return null;
            }
        } finally {
            EntityUtils.consume(response.getEntity());
            httpclient.close();
        }
    } catch (Exception e) {
        logger.error("Error while calling service: " + e.getMessage() + serviceUrl);
        return null;
    }
}

/**
 * Calls POST service
 */
def callPostRestServiceAndReturnJson(serviceUrl, queryToPostJson, authHash) {
    Logger logger = Logger.getLogger("inventoryplugin.groovy.script")
    try {
        // get asset ID(s)
        def baseurl = ComponentAccessor.getApplicationProperties().getString("jira.baseurl")
        CloseableHttpClient httpclient = HttpClients.createDefault();
        def httpPost = new HttpPost(baseurl + serviceUrl);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("Content-type", "application/json");
        httpPost.setHeader("Authorization", authHash);
        def entity = new StringEntity(queryToPostJson);
        httpPost.setEntity(entity);
        CloseableHttpResponse response = httpclient.execute(httpPost);
        try {
            if (response.getStatusLine().getStatusCode() == 200) {
                return streamToJson(response.getEntity().getContent())
            } else {
                return null;
            }
        } finally {
            EntityUtils.consume(response.getEntity());
            httpclient.close();
        }
    } catch (Exception e) {
        logger.error("Error while calling service:" + serviceUrl, e);
        return null;
    }
}

String getAssetName(issueObj, assetNameCustomFieldId) {
    def customFieldManager = ComponentAccessor.getCustomFieldManager()
    def cField = customFieldManager.getCustomFieldObject(assetNameCustomFieldId)
    return issueObj.getCustomFieldValue(cField);
}

int getOrderAmount(issueObj, orderAmountCustomFieldId) {
    def customFieldManager = ComponentAccessor.getCustomFieldManager()
    def cField = customFieldManager.getCustomFieldObject(orderAmountCustomFieldId)
    def orderAmount = issueObj.getCustomFieldValue(cField);
    return (orderAmount == null) ? 0 : orderAmount as Integer;
}

def createAsset(issueObj, assetTypeCustomFieldId, assetNameCustomFieldId, stockAttributeId, orderAmountCustomFieldId, authHash) {
    Logger logger = Logger.getLogger("inventoryplugin.groovy.script")

    int assetTypeId = getAssetTypeId(issueObj, assetTypeCustomFieldId);
    if (assetTypeId > 0) {
        def addStockAttr = false;
        def assetTypeJson = callGetRestServiceAndReturnJson("/rest/jip-api/1.0/form/" + assetTypeId + ".json", authHash);
        logger.debug('assetTypeJson')
        logger.debug(assetTypeJson)
        if (assetTypeJson == null || assetTypeJson.attributes == null) {
            return null;
        }
        // loop JsonSlurper
        assetTypeJson.attributes.each {
            if (it.attribute.id == stockAttributeId) {
                addStockAttr = true;
            }
        }

        // get asset ID(s)
        def assetName = getAssetName(issueObj, assetNameCustomFieldId)
        // this is the search query parameters. When you search assets on Asset Navigator,
        // same parameters appear on Developer tools-> Network Tab. You can make different searches and get the parameters from there when you need.
        def queryToPostJson = "{\n" +
                "  \"formId\": " + assetTypeId + ",\n" +
                "  \"name\": \"" + assetName + "\",\n";

        // if asset type has stock attribute set it
        if (addStockAttr) {
            queryToPostJson += "  \"attributes\": [ {\"attributeId\": " + stockAttributeId +
                    ",  \"attributeValue\": \"" + getOrderAmount(issueObj, orderAmountCustomFieldId) + "\"}] ";
        } else {
            queryToPostJson += "  \"attributes\": [ ] ";
        }
        queryToPostJson += " }"
        logger.debug(queryToPostJson);


        def assetJson = callPostRestServiceAndReturnJson("/rest/jip-api/1.0/inventory.json", queryToPostJson, authHash)
        if (assetJson != null) {
            return assetJson.id;
        } else {
            return null;
        }
    }
}

if (isCustomFieldEmpty(issue, assetCustomFieldId)) {
    if (isNotInStockCase(issue, inStockCustomFieldId, inStockNoValue)) {
        Integer assetId = createAsset(issue, assetTypeCustomFieldId, assetNameCustomFieldId, stockAttributeId, orderAmountCustomFieldId, AUTH_HASH);
        if (assetId != null && assetId > 0) {
            updateAssetCustomFieldOfTheIssue(issue, assetCustomFieldId, issue.getKey(), "," + assetId + ",")
            return true;
        }
    }
}
return false;



Variables to Set

Inspect issue edit screen and located the Ids of the fields and then update the script.

def assetCustomFieldId = 'customfield_10227';
def assetTypeCustomFieldId = 'customfield_10314';
def inStockCustomFieldId = 'customfield_10317';
def orderAmountCustomFieldId = 'customfield_10316';
def assetNameCustomFieldId = 'customfield_10318';
def inStockNoValue = 'No'
def AUTH_HASH = 'Basic YWRtaW46YWRtaW4='; // auth hash for authorization. this sample is for user=admin, and password=admin
def stockAttributeId = 67; // asset "Number of items in stock" attribute id


Example of Post function location



Test

After Done transition, new asset created with inventory and assigned to Issue.

BeforeAfter