Move Insight Object from one Object Type to another in post function

Deleted user March 25, 2019

Hello,

I'm trying to move an Insight object from one object type to another. Of course, if you are using the interface, this is no problem. But we want to move an object in a post function. The background of this is that we have a JIRA process that handles Hardware exchanges. At a certain status the user is asked, if the "old" Hardware should be scraped or put back in the pool for further usage.

If the user chooses "Scrape" the Hardware will be moved to the object type "Deprecated Hardware". With this, we can still access the old hardware in the Insight Backend, but remove the object from being chosen by a user during a "New User" Workflow.

I'm using the code below, and it does actually move the object (I'm using JMWE Groovy Script), but unfortunately without any values, but I can't figure out, where the problem in my code is. Help is much appreciated.

Thanks a lot, Michael

import com.atlassian.jira.component.ComponentAccessor;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectMapping;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectBean;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping;

/* Get Insight Object Facade from plugin accessor */
Class objectFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade");
def objectFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectFacadeClass);

Class objectTypeAttFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade");
def objectTypeAttFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeAttFacadeClass);

def moveMap = new MoveObjectMapping();
def fromAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(826);
def toAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(832);
def index = 0;

toAtts?.each { item ->
MoveAttributeMapping temp = MoveAttributeMapping.create(item,fromAtts[index]);
moveMap.map(fromAtts[index].id,temp);
index++;
}

def mObject = new MoveObjectBean();

mObject.setFromObjectTypeId(826);
mObject.setObjectSchemaId(47);
mObject.setToObjectTypeId(832);
mObject.setMapping(moveMap);
mObject.setReferences(mObject.getReferences().valueOf("KEEP_REFERENCES_TO_OBJECT"));

def insightObjects = issue.get("Old asset");

if (insightObjects != null) {
insightObjects.each{insightObject ->
/* Move object/s */
try {
mObject.setIql("Key = " + insightObject.objectKey);
objectFacade.moveObjects(mObject);
} catch (Exception vie) {
log.warn("Could not update object attribute due to validation exception:" + vie.getMessage());
}
}
}

return true

4 answers

1 vote
Carlos Tichy May 5, 2023

Hello everyone on the forum, first of all, I want to thank you for the support and collaboration in this community; as a token of appreciation, I want to contribute my grain of sand by sharing with you this script (which was achieved thanks to the contributions from this and other threads). This script moves any object from one object type to another, mapping the attributes according to the structure of the destination object type.

 

import com.atlassian.jira.component.ComponentAccessor;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectMapping;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectBean;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping;

Class objectFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade");
def objectFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectFacadeClass);
Class objectTypeFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeFacade");
def objectTypeFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeFacadeClass);
Class objectTypeAttributeFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade");
def objectTypeAttributeFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeAttributeFacadeClass);

/*
* Define some variables
*/

// set schemaId and typeId of source and destination objectTypes (secure/InsightConfigure.jspa?objecttypeid="SOURCE_AND_DEST_OBJECT_TYPES")
int schemaId = 22; // <- Replace here with your own value
int sourceObjectTypeId = 12463; // <- Replace here with your own value
int destObjectTypeId = 12562; // <- Replace here with your own value

// set verificationAttributeId which is the ID of "Status" in source objectType (/secure/ObjectSchema.jspa?id="YOUR_SCHEMA_ID"&typeId="SOURCE_OBJECT_TYPE_ID"&view=list&mode=attribute)
int verificationAttributeId = 250332; // <- Replace here with your own value

// set verificationAttributeValue Id based on Jira Insight ID (/secure/InsightConfigure.jspa?id="YOUR_SCHEMA_ID"&tab=statuses)
int verificationAttributeValue = 234; // <- Replace here with your own value

// actual object ID and KEY from current request
int objectId = object.getId();
String objectKey = object.getObjectKey();

// action to take if the object to move has inboundReferences (options: "REMOVE_REFERENCES_TO_OBJECT" || "KEEP_REFERENCES_TO_OBJECT" || "DO_NOT_MOVE_OBJECT")
String ifInboundReferences = "REMOVE_REFERENCES_TO_OBJECT" // <- Replace here with your own value ("ADVICE: Do the exercise on the GUI beforehand to see the real behavior.")

/*
* Gets the actual values of current object
*/

// get the current objectType to verify
def currentObjectTypeId = object.getObjectTypeId();
if (currentObjectTypeId != sourceObjectTypeId) {
log.info("currentObjectTypeId is ${currentObjectTypeId} and it was expected ${sourceObjectTypeId}. Mistmatch of source objectTypes; no action taken on object ${objectKey}");
return false;
}

// get the current verification value of the "Status" objectAttribute to verify
def currentVerificationAttributeValue = objectFacade.loadObjectAttributeBean(objectId, verificationAttributeId).getObjectAttributeValueBeans()[0].getValue();
if (currentVerificationAttributeValue != verificationAttributeValue) {
log.info("currentVerificationAttributeValue is ${currentVerificationAttributeValue} and it was expected ${verificationAttributeValue}. Mistmatch of value in the Status objectAttribute; no action taken on object ${objectKey}");
return false;
}

/*
* Define move Map - for mapping attributes between two objectTypes (source objectType -> destination objectType)
*/
MoveObjectMapping moveMap = new MoveObjectMapping();
def sourceAttributes = objectTypeAttributeFacade.findObjectTypeAttributeBeans(sourceObjectTypeId);
def destAttributes = objectTypeAttributeFacade.findObjectTypeAttributeBeans(destObjectTypeId);

// Map attributes based on destination map structure to properly adjust source object attributes with their corresponding destination attributes.
sourceAttributes?.each {
sourceAttribute->
def destAttribute = destAttributes.find {
it.name == sourceAttribute.name && it.type == sourceAttribute.type
}
def attributesMap
if (!destAttribute) {
log.warn("Nothing to map to ${sourceAttribute.name}")
attributesMap = MoveAttributeMapping.create(sourceAttribute)
} else {
attributesMap = MoveAttributeMapping.create(sourceAttribute, destAttribute)
}
log.info("Adding mapping of ${sourceAttribute.name} from ${sourceAttribute.id} to ${destAttribute?.id}")
moveMap.map(sourceAttribute.id, attributesMap)
}

/**

Defines the moveObject and performs it.
*/
// definition
def moveObject = new MoveObjectBean()
moveObject.objectSchemaId = schemaId
moveObject.fromObjectTypeId = sourceObjectTypeId
moveObject.toObjectTypeId = destObjectTypeId
moveObject.mapping = moveMap
moveObject.references = moveObject.references.valueOf(ifInboundReferences)

// performing the actual move.
try {
if (object != null) {
moveObject.iql = "Key == ${objectKey}"
objectFacade.moveObjects(moveObject)
log.info("Object ${objectKey} moved from objectType ${sourceObjectTypeId} to objectType ${destObjectTypeId}")
}
} catch (Exception vie) {
log.warn("Could not move object ${objectKey} due to Exception: ${vie.message}")
}
return true;
1 vote
attilasalanki1983 June 17, 2021

Hi Guys,

 

I tried this script with automation, but it do not work. Do you have any idea what may wrong with it?

 

BR,

Attila

 

import com.atlassian.jira.component.ComponentAccessor;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectMapping;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectBean;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger log = LoggerFactory.getLogger("1. log");

/* Get Insight Object Facade from plugin accessor */
Class objectFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade");
def objectFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectFacadeClass);

Class objectTypeAttFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade");
def objectTypeAttFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeAttFacadeClass);

def moveMap = new MoveObjectMapping();
def fromAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(object.getObjectTypeId());
def toAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(144);
def index = 0;

toAtts?.each { item ->
MoveAttributeMapping temp = MoveAttributeMapping.create(item,fromAtts[index]);
moveMap.map(fromAtts[index].id,temp);
index++;
}
def mObject = new MoveObjectBean();

mObject.setFromObjectTypeId(object.getObjectTypeId());
mObject.setObjectSchemaId(1);
mObject.setToObjectTypeId(144);
mObject.setMapping(moveMap);
mObject.setReferences(mObject.getReferences().valueOf("KEEP_REFERENCES_TO_OBJECT"));
def insightObjects = object;

if (insightObjects != null)
{
/* Move object */
try
{
mObject.setIql("Key = " + insightObjects.objectKey);
objectFacade.moveObjects(mObject);
objectFacade.storeObjectBean(insightObjects);
} catch (Exception vie)
{
log.warn("Could not update object attribute due to validation exception:" + vie.getMessage());
}
}
vahem2lu October 19, 2022

@attilasalanki1983old topic, but did you resolve your problem? Was your problem with missing attributes?

I am using quite same logic and object gets moved, but all the attributes are empty.

0 votes
vahem2lu October 19, 2022

I thought I'd not do a new topic, but rather update this old one as the problem is still the same and there are some few topics here as well, but they all end up with the same result - no attributes are being updated.

 

As this does not let me to submit my code highlihted I've used Pastebin. See my reply with working example.

 

The idea is the same - when objectType "Domains" attribute "STATUS" value gets changed to "NOT USED" I would like to move this Insight object to a child objectType ("Domains not used"). Child objectType holds the same attributes and in the same order as parent. Schema is the same.

I am going to use automation which is simplifed like this: when object is updated and IQL (insert your necessary condition here) execute (this) groovy script.

I have done some extra checks to script, but this is not definitely necessary, because the idea is the  same as previously posted here.

@Peter-Dave Sheehanmention here, as you have done the same in another topic: https://community.atlassian.com/t5/Jira-Software-questions/Moving-Insight-objects-via-Groovy-fails/qaq-p/1897498

When I'm doing mapping manually like in this quoted thread (see link before) I will get all attributes in the KEY value.

When I'm doing mapping automatically (with each), all attributes are "lost", but the object gets moved.

I have tried "sleep", to wait for object to move, it does not change the result.

I have tried to change the order (attribute map before and actually move later), also the result is the same.

 

I'm going to post some log.info output here:

2022-10-20 08:43:10,414 [http-nio-8080-exec-4] | fromAtts: [
ObjectTypeAttributeBean [id=789, name=Key, type=DEFAULT, defaultType=TEXT],
ObjectTypeAttributeBean [id=791, name=Created, type=DEFAULT, defaultType=DATE_TIME],
ObjectTypeAttributeBean [id=792, name=Updated, type=DEFAULT, defaultType=DATE_TIME],
ObjectTypeAttributeBean [id=841, name=Domeeninimi, type=DEFAULT, defaultType=TEXT],
ObjectTypeAttributeBean [id=842, name=Kontakt, type=REFERENCED_OBJECT, defaultType=NONE],
ObjectTypeAttributeBean [id=843, name=Kontakt_, type=DEFAULT, defaultType=TEXT],
ObjectTypeAttributeBean [id=844, name=Kus registris, type=DEFAULT, defaultType=SELECT],
ObjectTypeAttributeBean [id=845, name=Aegub, type=DEFAULT, defaultType=DATE],
ObjectTypeAttributeBean [id=846, name=Staatus, type=STATUS, defaultType=NONE],
ObjectTypeAttributeBean [id=847, name=Kommentaar, type=DEFAULT, defaultType=TEXT]
]


2022-10-20 08:43:10,414 [http-nio-8080-exec-4] | toAtts: [
ObjectTypeAttributeBean [id=1223, name=Key, type=DEFAULT, defaultType=TEXT],
ObjectTypeAttributeBean [id=1225, name=Created, type=DEFAULT, defaultType=DATE_TIME],
ObjectTypeAttributeBean [id=1226, name=Updated, type=DEFAULT, defaultType=DATE_TIME],
ObjectTypeAttributeBean [id=1227, name=Domeeninimi, type=DEFAULT, defaultType=TEXT],
ObjectTypeAttributeBean [id=1228, name=Kontakt, type=REFERENCED_OBJECT, defaultType=NONE],
ObjectTypeAttributeBean [id=1229, name=Kontakt_, type=DEFAULT, defaultType=TEXT],
ObjectTypeAttributeBean [id=1230, name=Kus registris, type=DEFAULT, defaultType=SELECT],
ObjectTypeAttributeBean [id=1231, name=Aegub, type=DEFAULT, defaultType=DATE],
ObjectTypeAttributeBean [id=1232, name=Staatus, type=STATUS, defaultType=NONE],
ObjectTypeAttributeBean [id=1233, name=Kommentaar, type=DEFAULT, defaultType=TEXT]
]

2022-10-20 08:43:10,425 [http-nio-8080-exec-4] | moveObject:
MoveObjectBean{
fromObjectTypeId=69,
toObjectTypeId=100,
mapping=MoveObjectMapping{attributeMappingMap={789=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@5ab84c21,
791=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@c695d39,
792=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@22c08628,
841=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@156ab381,
842=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@dab3c49,
843=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@4b6d3850,
844=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@6d3a16d3,
845=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@2d0c564e,
846=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@79917ed2,
847=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@5b9c6608}},
iql=Key = KAS-132347,
objectSchemaId=3,
references=KEEP_REFERENCES_TO_OBJECT}

 

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 21, 2022

One question before I dig into it. .. when you say "child objecttype" are you leveraging the attribute inheritance (this means that the attribute id is the same for the parent and the child) or do the child and parent have the same attribute names but with different ids?

Like Antoine Berry likes this
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 21, 2022

The first error I see is here:

MoveAttributeMapping.create(item, fromAtts[index]);

You have your 2 arguments reversed. Is't from then to.

But also, how can you be certain your 2 lists of attributes will be in the same sequence? 

And are you 100% sure they are of the same type?

Maybe try something like this:

toAtts?.each { toAttribute ->
def fromAttribute = fromAtts.find{it.name == toAttribute.name && it.type == toAttribute.type}
if(!fromAttribute) {
log.warn "Nothing to map to $toAttribute.name"
return
}
MoveAttributeMapping attMap = MoveAttributeMapping.create(fromAttribute, toAttribute);

log.info "Adding mapping of $fromAttribute.name from $fromAttribute.id to $toAttribute.id"
moveMap.map(fromAttribute.id, attMap);
}

Also, don't create a mutable of your object. That's only for editing attributes.

Like vahem2lu likes this
vahem2lu October 24, 2022

Thank you @Peter-Dave Sheehan, such a rookie mistake for mapping attributes.

For my case right now this object is "tree" child

- CMDB
-- Infra
--- Domain names
---- domains not used

And they always use the same attribute names and types in the same order. IDs of course are different.

Regarding to not/being mutable, if it's not mutable, I will get this error:

Could not update object attribute due to error: No signature of method: com.riadalabs.jira.plugins.insight.channel.external.api.facade.impl.ObjectFacadeImpl.storeObjectBean() is applicable for argument types: (com.riadalabs.jira.plugins.insight.services.model.ObjectBean) values: [domain.tld (KAS-132375)]

Working as expected, I am going to post a working example in some time.

vahem2lu October 24, 2022

So my working example of moving an Insight object from one type to another.

I have used some extra checks (especially IDs), but they are not necessary when you "trust" your automation workflow. 😃

import com.atlassian.jira.component.ComponentAccessor;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectMapping;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectBean;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping;

Class objectFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade");
def objectFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectFacadeClass);
Class objectTypeFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeFacade");
def objectTypeFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeFacadeClass);
Class objectTypeAttFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade");
def objectTypeAttFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeAttFacadeClass);

// Define some variables

// set schemaId and typeId for active and inactive domains (from Jira Insight)
int schemaId = 3;
int activeDomainTypeId = 69;
int inactiveDomainTypeId = 100;

// set statusActiveAttributeId which is the ID of "Staatus" in active Object Type (secure/ObjectSchema.jspa?id=3&typeId=YOUR_ID&view=list&mode=attribute)
// set statusActive and statusNotActive IDs based on Jira Insight ID (secure/InsightConfigure.jspa?id=3&tab=statuses)
int statusActiveAttributeId = 846;
int statusActive = 70;
int statusNotActive = 72;

// object needs to be mutable and initiator gives us the object (which triggers this script)
// current object ID and KEY from actual request
object = object.createMutable();
int objectId = object.getId();
String objectKey = object.getObjectKey();

def objectTypeCurrentId = object.getObjectTypeId();
if ( objectTypeCurrentId != activeDomainTypeId ){
log.info ("objectTypeCurrentId is: " + objectTypeCurrentId + " and we are looking for: " + activeDomainTypeId + ". So, it's a mismatch! Exiting.");
// This object is not in correct typeId, exiting
return false;
}

def objectAttributeValue = objectFacade.loadObjectAttributeBean(objectId, statusActiveAttributeId).getObjectAttributeValueBeans()[0].getValue();

if (objectAttributeValue == statusNotActive){
/**
* Define move map - for mapping attributes between two typeId (old type -> new type)
* and make map with current attributes
* and to map them with new attributes
*/
MoveObjectMapping moveMap = new MoveObjectMapping();
def fromAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(activeDomainTypeId);
def toAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(inactiveDomainTypeId);
def index = 0;

// do the actual mapping with both arrays
// NB! This assumes that both attributes are same type and same name and in exact same order.
toAtts?.each { item ->
MoveAttributeMapping attMap = MoveAttributeMapping.create(fromAtts[index], item);
moveMap.map(fromAtts[index].id, attMap);
index++;
}

/**
* Define moveObject and store necessary elements
* for new object
*/
MoveObjectBean moveObject = new MoveObjectBean();
moveObject.setObjectSchemaId(schemaId);
moveObject.setFromObjectTypeId(activeDomainTypeId);
moveObject.setToObjectTypeId(inactiveDomainTypeId);
moveObject.setMapping(moveMap);
moveObject.setReferences(moveObject.getReferences().valueOf("KEEP_REFERENCES_TO_OBJECT"));

// Actually moving the object
// and storing attributes to the new object
try{
if (object != null){
moveObject.setIql("Key = " + objectKey);
objectFacade.storeObjectBean(object);
objectFacade.moveObjects(moveObject);
log.info("domain-status-move-to_unused for object: " + objectKey );
}
} catch (Exception e){
log.warn("Could not update object attribute due to error: " + e.getMessage());
}

return true;
} // if (objectAttributeValue == statusNotActive)
else {
log.info ("objectAttributeValue is: " + objectAttributeValue + " and we are looking for: " + statusNotActive + ". So, it's a mismatch! Exiting.");
return false;
}

return false;
Like Olesja Hozjaitšikova likes this
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 24, 2022

You don't need to store the object (objectFacade.storeObjectBean(object)).

Remove that line and you won't need to make it mutable.

vahem2lu October 31, 2022

@Peter-Dave SheehanI've replaced my attribute mapping with your suggestion. The problem is that now when I'm moving within two objectTypeId's, which are the same attributes (order, name and type), the object gets moved.

But when I'm moving within two quite different objectTypeId's, then return is "true", so no errors, but the object is not being moved actually.

The idea should be that when a mapping is found, then this attribute is moved, others should be discarded.

Also moveObject seems OK to me as well.

2022-10-31 16:50:36,875 [http-nio-8080-exec-6] |
moveObject: MoveObjectBean{fromObjectTypeId=14,
toObjectTypeId=5,
mapping=MoveObjectMapping{attributeMappingMap={880=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@3064a8e,
144=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@4e7951d1,
83=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@590b1081,
85=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@2b84a08a,
86=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@1a67dd4e,
1306=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@1e14f00a,
667=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@1f9a7451,
141=com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping@119417d8}},
iql=Key = KAS-18177,
objectSchemaId=3,
references=KEEP_REFERENCES_TO_OBJECT}

Anything I'm missing with "discarding" attributes?

Like Antoine Berry likes this
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 31, 2022

Sorry, I don't have a lot more to add. I've only ever really moved within 2 objects that largely share the same attributes. 

I'm not sure how else to discard an attribute other than not including it in the mapping.

Based on the MoveAttributeMapping documentation I can see that there is a possibility of creating a mapping using From only, which sets the "ShouldMap" to false.

You could try to reverse the logic (process all the "fromAtts" and find a matching "toAtt" instead of processing all the "toAtts" and finding a matching "fromAtt") and add a no-map mapping.

fromAtts?.each { fromAttribute ->
def toAttribute = toAtts.find{it.name == fromAttribute.name && it.type == fromAttribute.type}
MoveAttributeMapping attMap
if(!toAttribute) {
log.warn "Nothing to map to $fromAttribute.name"
attMap = MoveAttributeMapping.create(fromAttribute)
} else {
attMap = MoveAttributeMapping.create(fromAttribute, toAttribute);
}

log.info "Adding mapping of $fromAttribute.name from $fromAttribute.id to ${toAttribute?.id}"
moveMap.map(fromAttribute.id, attMap);
}
Like # people like this
vahem2lu November 1, 2022

Actually I guess it's the GUI behaviour too, that you find "fromAttributes" and map them to "toAttributes".

And this worked like a charm! Thank you for your support and I believe whoever finds this topic, they will get it done as well. :)

0 votes
Yinon Negev
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 3, 2019

Hi Michael.

You have to map every attribute from the source to the destination - and the type must match between source and target attributes...

The best way to understand the Move Object is to follow the Bulk-Move UI - the same should be done in your script.

Missing attributes won't be moved if not specified.

Kind regards,

Yinon

Team Riada

Hubert Sękowski October 16, 2019

Hi @Yinon Negev ,

I faced pretty the same challenge @[deleted]  was dealing with. Unfortunately, I'm not sure I did follow your hint exactly the way you were describing. Let me describe the issue, what is it current status and ask you for piece of code if possible :)

We are storing important kind of data in CRC object schema.
Because of the importance, we don't want anyone to operate manually.
Operations like new object creation or update are fully automated. Unfortunately, objects "removal" has to be done manually. We'd like to automate it.

There are 3 main object types:

  • Register Objects
  • Attribute Objects
  • Removed Objects
    Attribute Objects are only reference objects.
    Register Objects are our main objects. Once object is removed from Register Objects, it is moved to Removed Objects.
    Register Objects and Removed Objects hierarchy is the same. The difference is only in child type names that are prefixed with "Removed" in Removed Objects. Attribute list, their types and type values are the same, so manual action is just few clicks.

Here is one of my try, mainly based on this post.
Object is moved but without any attribute. It's searchable by it's label, but none attribute displayed and none attribute is saved when object edited manually.
Wondering if anything should be done with Progress object returned by moveObjects method (https://insight-javadoc.riada.io/insight-javadoc-5.4.2/com/riadalabs/jira/plugins/insight/channel/external/api/facade/ObjectFacade.html#moveObjects-com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectBean-) but was not able any clue for that :/

 

/*
skipping imports and issue load
*/
def object = (issue.getCustomFieldValue(cfObject)[0]).createMutable()

Class objectSchemaFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectSchemaFacade");
ObjectSchemaFacade objectSchemaFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectSchemaFacadeClass);

Class objectTypeFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeFacade");
ObjectTypeFacade objectTypeFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeFacadeClass);

ObjectSchemaBean crc = objectSchemaFacade.findObjectSchemaBeans().find

{ o -> o.objectSchemaKey == 'CRC'}

List<ObjectTypeBean> crcAllObjectTypes = objectTypeFacade.findObjectTypeBeansFlat(crc.id)
ObjectTypeBean srcObjectType = crcAllObjectTypes.find

{ o -> o.id == object.objectTypeId}

ObjectTypeBean tgtObjectType = crcAllObjectTypes.find

{ o -> o.name == ('Removed ' + srcObjectType.name)}

Class objectFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade");
ObjectFacade objectFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectFacadeClass);

Class objectTypeAttFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade");
ObjectTypeAttributeFacade objectTypeAttFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeAttFacadeClass);

MoveObjectMapping moveMap = new MoveObjectMapping();
List<ObjectTypeAttributeBean> fromAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(srcObjectType.id);
List<ObjectTypeAttributeBean> toAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(tgtObjectType.id);

def index = 0;
toAtts?.each

{ item -> MoveAttributeMapping temp = MoveAttributeMapping.create(item,fromAtts[index]); moveMap.map(fromAtts[index].id,temp); index++; }

MoveObjectBean mObject = new MoveObjectBean();
mObject.setObjectSchemaId(crc.id);
mObject.setFromObjectTypeId(srcObjectType.id);
mObject.setToObjectTypeId(tgtObjectType.id);
mObject.setMapping(moveMap);
// mObject.setReferences(mObject.getReferences().valueOf("REMOVE_REFERENCES_TO_OBJECT"));

try

{ mObject.setIql("Key = " + object.objectKey); objectFacade.moveObjects(mObject); // objectFacade.storeObjectBean(object) }

catch (Exception vie)

{ log.warn("Could not update object attribute due to validation exception:" + vie.getMessage()); }

Like Fredrik Elmqvist likes this
Swami Dridapada November 28, 2023

I am able to move the object ID when i use the script in the insight script console below provided in this thread.


import com.atlassian.jira.component.ComponentAccessor;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectMapping;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveObjectBean;
import com.riadalabs.jira.plugins.insight.services.model.move.MoveAttributeMapping;

Class objectFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade");
def objectFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectFacadeClass);
Class objectTypeFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeFacade");
def objectTypeFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeFacadeClass);
Class objectTypeAttFacadeClass = ComponentAccessor.getPluginAccessor().getClassLoader().findClass("com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade");
def objectTypeAttFacade = ComponentAccessor.getOSGiComponentInstanceOfType(objectTypeAttFacadeClass);

// Define some variables

// set schemaId and typeId for active and inactive domains (from Jira Insight)
int schemaId = 3;
int activeDomainTypeId = 69;
int inactiveDomainTypeId = 100;

// set statusActiveAttributeId which is the ID of "Staatus" in active Object Type (secure/ObjectSchema.jspa?id=3&typeId=YOUR_ID&view=list&mode=attribute)
// set statusActive and statusNotActive IDs based on Jira Insight ID (secure/InsightConfigure.jspa?id=3&tab=statuses)
int statusActiveAttributeId = 846;
int statusActive = 70;
int statusNotActive = 72;

// object needs to be mutable and initiator gives us the object (which triggers this script)
// current object ID and KEY from actual request
object = object.createMutable();
int objectId = object.getId();
String objectKey = object.getObjectKey();

def objectTypeCurrentId = object.getObjectTypeId();
if ( objectTypeCurrentId != activeDomainTypeId ){
log.info ("objectTypeCurrentId is: " + objectTypeCurrentId + " and we are looking for: " + activeDomainTypeId + ". So, it's a mismatch! Exiting.");
// This object is not in correct typeId, exiting
return false;
}

def objectAttributeValue = objectFacade.loadObjectAttributeBean(objectId, statusActiveAttributeId).getObjectAttributeValueBeans()[0].getValue();

if (objectAttributeValue == statusNotActive){
/**
* Define move map - for mapping attributes between two typeId (old type -> new type)
* and make map with current attributes
* and to map them with new attributes
*/
MoveObjectMapping moveMap = new MoveObjectMapping();
def fromAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(activeDomainTypeId);
def toAtts = objectTypeAttFacade.findObjectTypeAttributeBeans(inactiveDomainTypeId);
def index = 0;

// do the actual mapping with both arrays
// NB! This assumes that both attributes are same type and same name and in exact same order.
toAtts?.each { item ->
MoveAttributeMapping attMap = MoveAttributeMapping.create(fromAtts[index], item);
moveMap.map(fromAtts[index].id, attMap);
index++;
}

/**
* Define moveObject and store necessary elements
* for new object
*/
MoveObjectBean moveObject = new MoveObjectBean();
moveObject.setObjectSchemaId(schemaId);
moveObject.setFromObjectTypeId(activeDomainTypeId);
moveObject.setToObjectTypeId(inactiveDomainTypeId);
moveObject.setMapping(moveMap);
moveObject.setReferences(moveObject.getReferences().valueOf("KEEP_REFERENCES_TO_OBJECT"));

// Actually moving the object
// and storing attributes to the new object
try{
if (object != null){
moveObject.setIql("Key = " + objectKey);
objectFacade.storeObjectBean(object);
objectFacade.moveObjects(moveObject);
log.info("domain-status-move-to_unused for object: " + objectKey );
}
} catch (Exception e){
log.warn("Could not update object attribute due to error: " + e.getMessage());
}

return true;
} // if (objectAttributeValue == statusNotActive)
else {
log.info ("objectAttributeValue is: " + objectAttributeValue + " and we are looking for: " + statusNotActive + ". So, it's a mismatch! Exiting.");
return false;
}

return false;

However, when i saved the script as a file and tried to initiate a post function - i m unable to get the result. Can anyone help me with this code?   I m trying to load the object from customfield_ID and move that object from sourcetypeID to desinationtypeID to another where both are childobjecttypes of same parentobjecttype

Thanks

Devops November 29, 2023

Hi

Have you added this script to allowed list?

Administration -> Manage apps -> Insight Whitelist

JIRAURL/secure/admin/InsightPluginWhiteList.jspa

Swami Dridapada November 29, 2023

Yes. I have whitelisted it.

In the Insight post function, there is an option to test with a issue key. The code executes but without a desired result. 

In the Insight script console, I am able to get TRUE/ false result in the result tab and comment in the log tab 

Carlos Tichy November 29, 2023

Try my code (above) and adapt it. It works as post-function 100%! Nevertheless, I moved it with the same logic as a REST Endpoint with ScriptRunner and have it in the production environment.

The reason I moved to REST Endpoint was precisely that... as post-function, from time to time it stopped working. Something related to the browser or cache in the server... or who knows why.

Since I moved the script to REST ... NO failures with tons of objects.

 

Carlos Tichy November 29, 2023

You need to keep in mind that during the mapping process, you must adjust the object's attributes to match the structure of the destination object type; otherwise, it will fail.

Like Swami Dridapada likes this
Carlos Tichy November 29, 2023

REST Endpoint to run with ScriptRunner:  (I'll really appreciate it if you keep my credits on it!)

/*

* Script written by Carlos E. Tichy

* Last updated on: May 05, 2023

* Contact:

*   - Email: carlos.tichy@gmail.com

*   - Phone: +43 1 26030 6438

* Affiliation: Comprehensive Nuclear-Test-Ban Treaty Organization (CTBTO)

*/

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate

import com.riadalabs.jira.plugins.insight.services.model.ObjectBean

import customRiadaLibraries.insightmanager.InsightManagerForScriptrunner

import org.apache.log4j.Level

import groovy.json.JsonSlurper

import groovy.transform.BaseScript

import javax.ws.rs.core.MediaType

import javax.ws.rs.core.MultivaluedMap

import javax.ws.rs.core.Response

import com.onresolve.scriptrunner.runner.customisers.WithPlugin

@WithPlugin("com.riadalabs.jira.plugins.insight")

@BaseScript CustomEndpointDelegate delegate

def im = new InsightManagerForScriptrunner()

//im.readOnly = true //false is default  

im.dispatchEvents(true)//After this, this specific instance of InsightManager (im) wont dispatch events.

// Set log level to INFO (default for REST endpoints is WARN)

log.setLevel(Level.WARN)

def allowedGroups = ["jira-administrators"]

moveObjectFromOneObjectTypeToAnother(httpMethod: "PUT", groups: allowedGroups) { MultivaluedMap queryParams, String body ->

    def jsonSlurper = new JsonSlurper()

    def request = jsonSlurper.parseText(body) as Map<String, List>

    String insightObjectKey = request.insightObjectKey

    // get schemaId and typeId of source and destination objectTypes (secure/InsightConfigure.jspa?objecttypeid="SOURCE_AND_DEST_OBJECT_TYPES")

    int schemaId = Integer.parseInt(request.schemaId as String)

    int sourceObjectTypeId = Integer.parseInt(request.sourceObjectTypeId as String);

    int destObjectTypeId = Integer.parseInt(request.destObjectTypeId as String);

    // set verificationAttributeId which is the ID of "Status" in source objectType (/secure/ObjectSchema.jspa?id="YOUR_SCHEMA_ID"&typeId="SOURCE_OBJECT_TYPE_ID"&view=list&mode=attribute)

    int verificationAttributeId = Integer.parseInt(request.verificationAttributeId as String);

    // set verificationAttributeValue Id based on Jira Insight ID (/secure/InsightConfigure.jspa?id="YOUR_SCHEMA_ID"&tab=statuses)

    Object verificationAttributeValue

    try {

        verificationAttributeValue = Integer.parseInt(request.verificationAttributeValue as String);

        // First try to get the integer value

    } catch (NumberFormatException e) {

        verificationAttributeValue = request.verificationAttributeValue as String

        // If not, then the value is a simple string

    }

    // action to take if the object to move has inboundReferences (options: "REMOVE_REFERENCES_TO_OBJECT" || "KEEP_REFERENCES_TO_OBJECT" || "DO_NOT_MOVE_OBJECT")

    String ifInboundReferences = request.ifInboundReferences //   ("ADVICE: Do the exercise on the GUI beforehand to see the real behavior.")

    /*

     * Gets the actual values of current object

     */

    ObjectBean insightObjectBean = im.getObjectBean(insightObjectKey)

    if (!insightObjectBean) {

        return Response.status(Response.Status.BAD_REQUEST).entity("Insight object not found").type(MediaType.TEXT_PLAIN).build()

    }

    if(im.moveObject(insightObjectKey, schemaId, sourceObjectTypeId, destObjectTypeId, verificationAttributeId, verificationAttributeValue, ifInboundReferences)) {

        return Response.ok("Object ${insightObjectKey} moved from objectType ${sourceObjectTypeId} to objectType ${destObjectTypeId}.").type(MediaType.TEXT_PLAIN).build()

    } else {

        return Response.status(Response.Status.BAD_REQUEST).entity("Object ${insightObjectKey} not moved").type(MediaType.TEXT_PLAIN).build()

    }

}

 

 

And here is my modification to the InsightManagerForScriptRunner:
Look at this URL for the InsightManager =>  https://github.com/Riada-AB/InsightManager


/*

* Script modification written by Carlos E. Tichy

* Last updated on: May 05, 2023

* Contact:

*   - Email: carlos.tichy@gmail.com

*   - Phone: +43 1 26030 6438

* Affiliation: Comprehensive Nuclear-Test-Ban Treaty Organization (CTBTO)

*/



/**


    * Moves an object

    * @param object Can be object ID, Object Key or ObjectBean

    * @param schemaId What scheme to do the move on

    * @param sourceObjectTypeId ID of the type of object to be moved from

    * @param destObjectTypeId ID of the object type you want to move to

    * @param verificationAttributeId ID of "Status" in source objectType (/secure/ObjectSchema.jspa?id="YOUR_SCHEMA_ID"&typeId="SOURCE_OBJECT_TYPE_ID"&view=list&mode=attribute)

    * @param verificationAttributeValue Id based on Jira Insight ID (/secure/InsightConfigure.jspa?id="YOUR_SCHEMA_ID"&tab=statuses)

    * @param ifInboundReferences action to take if the object to move has inboundReferences (options: "REMOVE_REFERENCES_TO_OBJECT" || "KEEP_REFERENCES_TO_OBJECT" || "DO_NOT_MOVE_OBJECT")

    * @return boolean representing success or failure

    */

    boolean moveObject(def object, int schemaId, int sourceObjectTypeId, int destObjectTypeId, int verificationAttributeId, Object verificationAttributeValue, String ifInboundReferences) {

        log.debug("Moving object:" + object)

        ObjectBean objectBean = getObjectBean(object)

        int objectId = objectBean.getId();

        String objectKey = objectBean.getObjectKey();

        log.trace("\tObject id:" + objectBean.id)

        if (readOnly) {

            log.info("\tCurrently in readOnly mode, not moving object $objectBean")

            return false

        } else {

            // get the current objectType to verify

            def currentObjectTypeId = objectBean.getObjectTypeId();

            if (currentObjectTypeId != sourceObjectTypeId) {

                log.info("currentObjectTypeId is ${currentObjectTypeId} and it was expected ${sourceObjectTypeId}. Mistmatch of source objectTypes; no action taken on object ${objectKey}");

                return false;

            }

            // get the current verification value of the "Status" objectAttribute to verify

            def currentVerificationAttributeValue = objectFacade.loadObjectAttributeBean(objectId, verificationAttributeId).getObjectAttributeValueBeans()[0].getValue();

            if (currentVerificationAttributeValue != verificationAttributeValue) {

                log.info("currentVerificationAttributeValue is ${currentVerificationAttributeValue} and it was expected ${verificationAttributeValue}. Mistmatch of value in the Status objectAttribute; no action taken on object ${objectKey}");

                return false;

            }

            /*

             * Define move Map - for mapping attributes between two objectTypes (source objectType -> destination objectType)

             */

            MoveObjectMapping moveMap = new MoveObjectMapping();

            def sourceAttributes = objectTypeAttributeFacade.findObjectTypeAttributeBeans(sourceObjectTypeId);

            def destAttributes = objectTypeAttributeFacade.findObjectTypeAttributeBeans(destObjectTypeId);

            // Map attributes based on destination map structure to properly adjust source object attributes with their corresponding destination attributes.

            sourceAttributes?.each {

                sourceAttribute->

                def destAttribute = destAttributes.find {

                    it.name == sourceAttribute.name && it.type == sourceAttribute.type

                }

                def attributesMap

                if (!destAttribute) {

                    log.info("Nothing to map to ${destAttribute.name}")

                    attributesMap = MoveAttributeMapping.create(sourceAttribute)

                } else {

                    attributesMap = MoveAttributeMapping.create(sourceAttribute, destAttribute)

                }

                log.info("Adding mapping of ${sourceAttribute.name} from ${sourceAttribute.id} to ${destAttribute?.id}")

                moveMap.map(sourceAttribute.id, attributesMap)

            }

            /**

            * Defines the moveObject and moveObjectProgress to performs the move.

            */

            def moveObject = new MoveObjectBean()

            def Progress moveObjectProgress

            moveObject.objectSchemaId = schemaId

            moveObject.fromObjectTypeId = sourceObjectTypeId

            moveObject.toObjectTypeId = destObjectTypeId

            moveObject.mapping = moveMap

            moveObject.references = moveObject.references.valueOf(ifInboundReferences)

            // performing the actual move.

            try {

                moveObject.iql = "Key == ${objectKey}"

                if (moveObject != null) {

                    moveObjectProgress = objectFacade.moveObjects(moveObject)

                }

            } catch (Exception vie) {

                log.warn("Could not move object ${objectKey} due to Exception: ${vie.message}")

            }

            int curWait = 0

            int maxWaitMillis = 60000

            log.warn("Waiting for move to finish: ${curWait} (${moveObjectProgress.status})}")

            while (!moveObjectProgress.isFinished() && curWait < maxWaitMillis) {

                Thread.sleep(100)

                curWait = curWait + 100

                log.warn("Waiting for move to finish: ${curWait} (${moveObjectProgress.status})")

            }

            if (curWait >= maxWaitMillis){

                log.warn("Unknown final status moving object ${objectKey} from objectType ${sourceObjectTypeId} to objectType ${destObjectTypeId}; maxWaitMillis reached while waiting for move to finish.")

                return false;

            }

            if (!moveObjectProgress.isError()) {

                log.warn("Object ${objectKey} moved from objectType ${sourceObjectTypeId} to objectType ${destObjectTypeId}")

                return true;

            } else {

                log.warn("Object ${objectKey} failed to move from objectType ${sourceObjectTypeId} to objectType ${destObjectTypeId}")

                return false;

            }

        }

    }

}
Like Antoine Berry likes this
Swami Dridapada November 29, 2023

Namaskaram 

With regards to attributed mapping , in one of my use case, both the object types are child of the same parent as I has mentioned earlier, so it's ensured by default I think

Thanks a lot for REST code. I will keep the credits 🙂

Like Carlos Tichy likes this
Carlos Tichy November 29, 2023

If you encounter any difficulties, just let me know. I walked the same challenging path some time back and know how frustrating it can be. It works 100%.

Carlos Tichy November 29, 2023

This is the logic for the automation done by a functional user who has Jira Administrator rights.

In this example, decommissioned Hosts are moved to another branch called "Removed Hosts".


image.png

Like Swami Dridapada likes this

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events