Skip to main content
Skip table of contents

Bulk Bidirectional Link Deleter

In many OSLC systems we have bidirectional links. We can write scripts that can delete on both ends by using the Sodius APIs.

Following is an example that deletes both locally and the links in the remote tooling. It serves as an example of what you might be interested in doing.

It uses the local link type to determine the remote link and then searches based on the link and URL of the Jira issue.

Try it in test mode, and refine the behavior you want.

GROOVY
import com.onresolve.scriptrunner.runner.customisers.WithPlugin;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.link.RemoteIssueLinkManager;
import com.atlassian.jira.issue.link.RemoteIssueLink;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import com.sodius.oslc.core.process.model.LinkType;
import com.sodius.oslc.core.process.links.model.DirectedLink;
import com.sodius.oslc.core.process.links.requests.AddLink;
import com.sodius.oslc.core.process.links.requests.RemoveLink;
import org.eclipse.lyo.oslc4j.core.model.Link;
import com.sodius.oslc.client.OslcClient;
import com.sodius.oslc.client.OslcClients;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.auth.Credentials;

@WithPlugin("com.sodius.oslc.app.jira")

def log = Logger.getLogger("com.onresolve.scriptrunner.runner.ScriptRunnerImpl")

def rootUrl = ComponentAccessor.getApplicationProperties().getString('jira.baseurl')

// Set the log level.  
// Test mode disables the creation but runs all the checks
log.setLevel(Level.INFO);
boolean testMode = true;
boolean removebacklinks = true;
boolean jazz = true;
// Set the local username to be logged as the action performing the work
def userName = "bob"
// Set the remote user name and password
def remoteUserName = "patricia"
def remoteUserPassword = "patricia"

// Metrics counters
int deletedLinks = 0;
int issueCount = 0;

log.info("Starting Sodius Link Bidirectional Deletion")

// Get the local Jira user
def user = ComponentAccessor.getUserManager().getUserByName(userName)

// Get the remote Client (assuming all links are to the same respository) - note jazz/IBM have different client connections
def userCredentials = new UsernamePasswordCredentials(remoteUserName, remoteUserPassword);
def OslcClient client;
if (jazz) {
	client = OslcClients.jazzForm( userCredentials ).create();
} else {
	client = OslcClients.basic( userCredentials ).create();
}



// Search parameter can be set to '' to get all issues from all projects or add 'AND issuekey = JPG-3' to filter on a specific issue
Issues.search('project = AMRPORTLAND AND issuekey = AMRPRT-2').findAll { issue ->

	log.info "Looking at issue " + issue.getKey()
	issueCount++;
	def remoteIssueLinkManager = ComponentAccessor.getComponent(RemoteIssueLinkManager.class)

	// Iterate over links (OSLC Links are stored as Remote Links tied to tha Application Type com.sodius.oslc.app.jira)
	for (RemoteIssueLink existingLink in remoteIssueLinkManager.getRemoteIssueLinksForIssue( issue ) ) {
		// Need to check for the application owner to get all of the OSLC Links
		//  It is assumed that backlinks exist and must be deleted.  If using GC, just set removebacklinks to false
		if ( existingLink.getApplicationType() == "com.sodius.oslc.app.jira" ) {
			// found a OSLC link, we should consider deleting it
			log.info ( 'Evaluting ' + issue.getKey() + ' -> ' + existingLink.getUrl() + ' of type ' + existingLink.getRelationship() )
			if ( !testMode ) {

			    // Remove the remote link
				if (removebacklinks) {
			    	DirectedLink directedLink = new DirectedLink()
					def URI backlinkType = getBacklinkType( existingLink.getRelationship() )
					if ( backlinkType != null ) { 
    					directedLink.setPropertyDefinition( backlinkType )
    					directedLink.setSource(URI.create( existingLink.getUrl() ))
						directedLink.setTarget(new Link(URI.create( rootUrl + '/rest/oslc/1.0/cm/issue/' + issue.getKey() )))
						try {
		    				def removeLinkResponse = new RemoveLink(client, directedLink).call()
							log.info ('Deleted backlink')
						} catch (Exception ex) {
							log.warn("Caught exception ->" + ex.getMessage())
						}
					} else {
						log.info("No defined backlink for " + existingLink.getRelationship() + " skipping backlink deletion")
					}
				}
				
				// Remove the local link
			    remoteIssueLinkManager.removeRemoteIssueLink( existingLink.getId(), user )
				log.info('Deleted local link')
				

			} else {
				log.info( 'Test Mode (skipped deletion)')
			}
			deletedLinks++;
		}
	}

}

log.info("Reviewed  " + issueCount + " issues")
if ( testMode ) {
	log.info("[TESTMODE] Planned to delete " + deletedLinks + " links ")
} else {
		log.info("Deleted " + deletedLinks + " links ")
}

def URI getBacklinkType(String relationship) {
	switch (relationship) {
		case "related change request":
			return LinkType.RELATED_CHANGE_REQUEST.getPropertyDefinition()
		case "implements requirement":
			return LinkType.IMPLEMENTED_BY.getPropertyDefinition()
		case "tracks requirement":
			return LinkType.TRACKED_BY.getPropertyDefinition()
		case "affects requirement":
			return LinkType.AFFECTED_BY.getPropertyDefinition()
		case "affected by defect":
			return LinkType.AFFECTS_PLAN_ITEM.getPropertyDefinition()
		case "affects plan item":
			return LinkType.AFFECTED_BY_DEFECT.getPropertyDefinition()
		case "contributes to":
			return LinkType.TRACKS_WORK_ITEM.getPropertyDefinition()
		case "tracks":
			return LinkType.TRACKED_WORK_ITEM.getPropertyDefinition()
		default:
			return null
	}
}

For a simple, local only, deletion see Bulk OSLC Local Link Deletion

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.