Blog from November, 2021

Some alerts are more important than others and there are times when it is very important that the message is received. When I was a Jira admin (way back when) we had a problem with our server and it would randomly restart. I wanted to know when this happened because sometimes Jira would start correctly, sometimes it wouldn’t. This was way back in the days of Jira 5 (I think). I don’t remember exactly what the issue was with the server but I do know what I did to solve the problem of getting notified of when it went down, I sent myself a text message.

With the use of Start and Stop scripts you can configure Power Scripts to run a script whenever Jira starts or stops. Note that this is assuming that Jira starts/stops correctly. If the server crashes the procedures to shut JVM instance down get skipped. So, if you configure a script to run when Jira starts it tends to be more reliable. If you install Jira to run as a service it will automatically start when the server reboots and the script will run whenever the server crashes and restarts. That is what I used it for anyway, perhaps you have something else in mind. Either way the steps to send the message will be the same.

For a long time SIL had SMS capabilities built directly within it. However, these SMS gateways had to be configured with each service provider. That way has become obsolete now that (just about) every carrier has an email to SMS service that will automatically convert an email to a text message if the email is sent to the right address. Each provider has a different email address but they all basically work the same way. The phone number servers as the username and then each carrier has a unique domain. See a list of some of the most common carriers below.

But, before you run off and start creating a script to do something similar there are some considerations to be made. Like, how to associate the phone number and carrier to a user. You could just write a script like this:

sendEmail("0123456789@phonecompany.com", {}, "Jira has restarted!", "The Jira server has been restarted. Maybe you should check it out?");

But, I wouldn’t recommend it. Maybe you don’t want your phone number out there for everyone to see. Plus, what happens when another admin wants this super cool feature? You would need to put there info in the script as well. What happens when it becomes 20? Suddenly you make everyone's personal information available for everyone to see.

One option would be to use persistent variables. That way the personal information would be a little more out of sight and would not be stored in the script.

persistent string [] textUsers;

JEmailMessage email;
email.to = textUsers;
email.subject = "Jira has restarted!";
email.message = "The Jira server has been restarted. Maybe you should check it out?";
 
sendEmail(email);

This method is okay, however, personally I like to build solutions that don’t require the script to me modified in order to maintain them. I also like to keep user data with the user. The way I prefer to do, which admittedly, is a little more involved, is to store the information as a user property.

Then the users who whish to receive the text messages can be added to a group. The script will loop through the users in the group and get the emails from the users properties.

string [] smsEmails;

for(string user in usersInGroups("SMS Recipients", true)) {
    string email = getUserProperty(user, "SMS Email");
    if(isNotNull(email)) {
        smsEmails += email;
    }
}

JEmailMessage email;
email.to = smsEmails;
email.subject = "Jira has restarted!";
email.message = "The Jira server has been restarted. Maybe you should check it out?";
 
sendEmail(email);

Yes, it is a little more code but in this way a users email can easily be updated in the Jira admin UI and not the script. And, users can easily be added or removed from the user group.

Here are 10 tips (for now) to help you along your scripting journey. These tips are in no particular order.

Use Aliases

There are currently 3 methods for accessing data from a custom field:

  • By Name

  • By ID

  • By SIL Alias

However, only one of these methods is recommended, see below for more details.

Name – While generally readable, the names of custom fields can be changed by admins who may not be aware that they are being used in a script. This could cause the scripts to not be able to access the data for the custom field.

ID – While less breakable than using the field name, custom field id’s can make a script hard to read. Also, the script can’t easily be migrated to another instance since the id’s could be different.

SIL Alias – While an extra setup step is required, aliases make the scripts readable, protect against name changes, and allow scripts to be migrated without requiring updates.

Comments

It’s pretty much guaranteed that your code will be modified or updated over time. It is also true that almost all developers will come across someone else’s code at one time or another. A bad habit among inexperienced programmers is to include little or no comments while coding.

RunnerLog

It is hard to debug scripts when you don’t know what is happening inside of it. Using the runnerLog() routine allows for information to be printed out to the script console. This information can include the value of variables of custom fields which can greatly aid in debugging. And, since the console only exists in two place, SIL Manager, and the SIL Runner dashboard gadget, the debug messages can be left in the code since they will not be visible when used with standard triggers like listeners and workflow functions.

Avoid Code Repetition

The purpose for most applications (or computers in general) is to automate repetitive tasks. This principle should be maintained in all code, even web applications. The same piece of code should not be repeated over and over again.

No Passwords

Since the most performant way to store SIL scripts is on the file system, it is not recommended to leave passwords inside them since the scripts can be opened by other users who may have access to those files. While a 100% secure method of storing the password does not yet exist, there are some strategies to make it much harder for any users with malicious intent. These include:

  1. Using the encrypt/decrypt routines to obfuscate the password

  2. Storing the password in a persistent variable

  3. Storing the password in an external database

  4. Storing the password in the Java Key Store and using the system() routine to retrieve it

Generally, using a combination of #1 and #2 is an effective enough solution. It would require the malicious user to be a Jira administrator in order to be able to run the scripts through the SIL Manager.

CSV/JSON Data Files

A simple script is a good script. Therefore, scripts should not contain information that can be considered data in them. Instead, SIL has a hidden gem of a feature that allows CSV or JSON files to be automatically converted into an array of structs. It is much cleaner to store the data separately and import it using this feature. It will also be easier to maintain or update the data in the future.

Breakup Large Files

The include function is very helpful for creating scripts that can be used by other scripts. If a script becomes too large it becomes difficult to read and harder to maintain. The include function can also come in handy in these situations since chunks of common logic can be broken out into separate files. This makes it much easier to consume each portion of the overall solution. And, it also helps keep the scripts maintainable and you may also find that one of the broken out scripts can become reusable as well.

For example, a general guideline on how to split up a large script could be this:

  1. Separate any struct definitions into a separate file. These files that only contain structs are usually highly reusable.

  2. Separate user defined functions into a separate file. Normally, the main body of the script can be understood without including the code for the function (as long as the function is properly named). Also, it creates a file that can easily be reused by other scripts that could benefit from the same functions.

  3. Any required data should be stored in a separate CSV or JSON file.

  4. Any other bits of code that could be considered “self contained” or otherwise logically grouped together can be broken out to another file.

As a general guideline, any script over 100 lines of code should be reviewed for possible ways to split it up and therefore more readable.

Code Versioning

Code versioning does not just include using tools like Git or Subversion (but it could). It can also be as simple as copying a script and adding a version number to its name. It is a good practice to adapt since it allows the code to be easily reverted should a problem be found with the new changes.

Logging

Again, you can’t troubleshoot a script while blind. You need to know information about the script or how/when it was triggered. Logging is a great solution for this. However, make sure the logs are actually valuable and you are don’t just spamming them since too much noise in the log file could make it harder to detect other issues that may be occurring in the system. Also, be aware that each log message requires an update from the file system. Putting to much stress on the file system by spamming the logs can actually dramatically slow down your system.

File Extensions

Use them… While not required it is possible to different types of files in the silprograms folder other than SIL scripts. The recommended file extensions are:

  • For SIL scripts: .sil

  • Text files: .txt

  • CSV data files: .csv

  • JavaScript files: .js

Contents

Organizing scripts in the SIL Manager is one of those things that everyone does wrong when they first start using the tools. Assume that you will end up with hundreds of script because you are so fond of all the magical things they can do to save you time that you keep creating them because you can’t get enough of them (usually happens). Realizing that you should have been more organized only after you gave hundreds of script can be too late since various settings are pointed to the script in it’s current location. Use these simple tips from the start and you could save yourself a lot of headache in the future.

Naming Conventions

Do

  • Name the files something meaningful. The number of scripts in the silprograms folder will build up over time. It is not recommended to name files something ambiguous like "script1.sil". Once you have dozens of scripts you will not be able to identify what the purpose of the script is. Instead, name the file something meaningful like "userstoryPostFunction.sil".

  • Use folders. Keeping scripts organized in folders makes finding them later much easier. See below for recommended organization methods.

  • Use a file extension. While not required it is possible to different types of files in the silprograms folder other than SIL scripts. The recommended file extensions are:

    • For SIL scripts: .sil

    • Text files: .txt

    • CSV data files: .csv

    • JavaScript files: .js

  • Use Camel Case. Using camelCase is a good way to keep file names short yet readable.

Don't

  • Don't add a version number to the "good" or working copy of the script. Adding a version number to the working copy of the script requires you to modify the workflow, listener, etc., to use the script with the latest version. Instead, use version numbers on the old versions of the script. The working copy can have something like "latest" in the name to indicate that it is the most recent version. Example:

    • userstoryPostFunction_latest.sil

    • userstoryPostFunction_v1.sil

    • userstoryPostFunction_v2.sil

    • userstoryPostFunction_v3.sil

  • Do not use spaces in the file or folder names. While spaces are allowed it can cause difficulty when using file paths with spaces in them. Spaces can be substituted with underscores.

Folders

Before creating scripts in the SIL manager you should have a general plan on how to organize the files in folders. In general, there are two main schools of thought into organizing files in the SIL Manager:

  1. Organize scripts by script type

  2. Organize scripts by use or by project

Script Type

Most scripts are designed to be applied in a specific way, as part of a workflow for example. By organizing scripts by type it is an easy way to locate the script. This would be preferred way since scripts can be applied multiple projects but are typically applied using a single method.

Example:

  • Calls

  • Custom_Fields

  • Includes

  • Javascript

  • JQL

  • Listeners

  • Live_Fields

  • Runner_Gadget

    • Param_Scripts

  • Scheduled_Services

  • Workflows

    • Conditions

    • Validators

    • Postfunctions

Project

You are not limited to a single method. In this example the scripts are organized by project at the highest level then type in the next level of the hierarchy.

Example:

  • Project_A

    • Workflows

      • Conditions

      • Validators

      • Postfunctions

  • Project_B

    • Listeners

    • Live_Fields

  • Project_C

When writing large and complicated scripts it can become hard to debug the script. Either it becomes difficult to determine where in the large script the point of failure is contained or it is just difficult to identify why the code is not working. A simple solution to this is to add logic to your script that allows you to break it into smaller sections for easier debugging. Or, perhaps you do not wish to perform certain actions while debugging, like sending emails to users. A "test mode" concept will allow you to execute the script without performing this specific actions.

Constructing the flag

The basic idea behind a testing mode is to use flags to identify if the whole script should be run in test mode, or perhaps which sections of the script should be run in test mode. This is done with simple if/then statements. However, it is a good idea to use a struct to create a single flag that has multiple attributes. Whenever I create a test mode flag I usually start with a structure like this:

struct debug {
	boolean debug;
	boolean useLogging;
	boolean sendEmails;
	boolean codeAction1;
	boolean codeAction2;
	boolean codeAction3;
}

This allows me to create a single flag that controls multiple actions.

Initializing the flag

To start using the structure you must initialize it like the code below. When I create the actual flag (instead of the struct) I do not call it "debug" or "test" because that becomes redundant with the "debug" or "test" property. For example, I don't want the flag to look like "debug.debug" or "test.test". For this reason I call the main flag something else. And, for whatever reason, I typically use "goldBug" from the Richard Scarry children books.

debug goldBug;
goldBug.debug = true;

Using the flag

Now that the flag has been initialized it can be used as a wrapper around your actual code like this:

if(goldBug.debug != true) {
	//do some live, non-test action
}
else {
	//do some test/debug related action
}

Setting up the modes

I typically use the "debug" or "test" property to determine if the script is indeed running in test mode. If it is running in test mode you may have specific settings or sections of code that should or should not run. I use the main flag to help set the test mode as a whole. Also, the flag can be used to set the script to live or production mode as well. In the example below logging will be used in test mode and emails will not be sent to the users. However, in production mode logging will not be used but emails will be sent.

if(goldBug.debug == true) {
	//test mode
	goldBug.useLogging = true;
	goldBug.sendEmails = false;
	goldBug.codeAction1 = true;
	goldBug.codeAction2 = true;
	goldBug.codeAction3 = false;
}
else {
	//live mode
	goldBug.useLogging = false;
	goldBug.sendEmails = true;
	goldBug.codeAction1 = true;
	goldBug.codeAction2 = true;
	goldBug.codeAction3 = true;
}

Automatically trigger test mode

The potential of forgetting to switch test mode off is very high. However, there are some ways to automatically trigger test mode. Generally, the logical place to test the execution of scripts is directly from the SIL Manager in order to take advantage of the output console and viewing the logs. The concept behind the automatic triggering of test mode is that when the script is run from the SIL Manager itself it will be run in test mode but when the script is triggered by other methods it will be run in live or production mode.

There are two methods of automatically triggering test mode and they are based on the method of execution:

  1. Method 1 - Issue context; This is used for the following types of scripts:

    • Conditions

    • Validators

    • Post functions

    • Listeners

    • Custom fields

  2. Method 2 - Arguments; This is used for the following types of scripts:

    • SIL Runner dashboard gadget

    • SIL Scheduler

Issue Context

The general idea behind this method is that when running in the SIL Manager the script will not be in the context of an issue. This, of course, assumes that an issue has not been added to the Run Configuration setting. If the script is run by executing an issue action (transitions the issue, updating the issue, etc.) it will be in the context of that issue. By using the isIssueContext() routine we can determine if the script is running in the context of an issue.

if(isIssueContext() == true) {
	goldBug.debug = false; //production mode
}
else {
	goldBug.debug = true; //test mode
}

Arguments

When running scripts from the SIL Runner dashboard gadget or through the SIL Scheduler the script can be passed arguments. By detecting if the arguments are present we can trigger test mode. Of course, there are times when the script does not require arguments. For these instances we can just pass a flag as a bogus parameter that indicates the script is running in production mode.

if(size(argv) > 0) {
	goldBug.debug = false; //production mode
}
else {
	goldBug.debug = true; //test mode
}

What is different about test mode?

Issue context

One valuable use of test mode is switching into the context of a test issue or test project. This way, if the action does not perform correctly it will not update a live issue or potentially notify other end users.

string _key;


if(goldBug.debug == false) {
	_key = key; //live issue key
}
else {
	_key = "TST-1234"; //test issue
}


//example use of using conditional issue context
%_key%.summary = "New summary text";

Output to the console

Often times you may want more information displayed in the output console when debugging then other times. This is helpful for getting the value of custom fields while the script is running.

if(goldBug.debug == false) { //test mode
	//write output to console
	runnerLog(key + ": " + summary);
}

Logging

Just like displaying information to the console you might want to write information to the logs when running in test mode.

if(goldBug.debug == false) {
	//write output to the log
	logPrint(currentUser() + " performed some action on " + currentDate(), "INFO");
}

Restricting actions

There may be times were you don't want an action to occur when testing, like sending an email to a user or writing data to a database. And, there may also be times when you do want this action to occur so you can test that it is behaving correctly. In these instances the action can be controlled using separate flags.

JEmailMessage email;
email.to = {"testJiraUser1", "testEmail@cprime.com", "testJiraUser2"};
email.subject = "Email to Santa";
email.message = "Dear Santa, I want a train.";
 
for(string a in attach) {  
    JEmailAttachment att;
    att.name = a;
    att.file = getAttachmentPath(key, a);
    email.attachments += att;
}

if(goldBug.sendEmail == false) { //live mode
	sendEmail(email); //send email
}
else { //test mode
	//don't spam me bro!
}

Compound flag conditions

Like the previous example, there may be times where you want to toggle an action on or off while testing. However, can you be sure that the flag is set back to the proper setting before going live with the script changes. By using compound conditions with the flags we can ensure that the action is only getting toggled on or off in test mode but will always be on in live mode.

if(goldBug.debug == false) { //live mode
	sendEmail(email);
}
else if(goldBug.debug == true && goldBug.sendEmail == true) { //test mode
	sendEmail(email);
}
else {
	//don't send email
}

This example will always send the email in live mode but will only send the email in test mode when the sendEmail flag is set to true.

The example above can be consolidated into a single statement:

if(goldBug.debug == false || (goldBug.debug == true && goldBug.sendEmail == true)) {
	sendEmail(email);
}

Complete test mode script template

Here is a complete test script template for you to get started with:

struct debug { //define the flag
	boolean debug;
	boolean useLogging;
	boolean sendEmails;
	boolean codeAction1;
	boolean codeAction2;
	boolean codeAction3;
}


debug goldBug; //initialize

//automatically set the flag
if(isIssueContext() == true) {
	goldBug.debug = false; //production mode
}
else {
	goldBug.debug = true; //test mode
}


//build out the modes
if(goldBug.debug == true) {
	//test mode
	goldBug.useLogging = true;
	goldBug.sendEmails = false;
	goldBug.codeAction1 = true;
	goldBug.codeAction2 = true;
	goldBug.codeAction3 = false;
}
else {
	//live mode
	goldBug.useLogging = false;
	goldBug.sendEmails = true;
	goldBug.codeAction1 = true;
	goldBug.codeAction2 = true;
	goldBug.codeAction3 = true;
}


/*----------------------------------------
-----------End Include File---------------
-----------------------------------------*/


//use the flag in the script
if(goldBug.debug == false || (goldBug.debug == true && goldBug.codeAction1 == true)) {
	
	//do something
	
	//test output
	if(goldBug.debug == true) { //test mode only
		runnerLog("See something");
	}


	//write to log
	if(goldBug,useLogging == true) { //test mode only
		logPrint(currentUser() + " performed some action on " + currentDate(), "INFO");
	}
}


if(goldBug.debug == false || (goldBug.debug == true && goldBug.codeAction2 == true)) {
	//do something else
}


if(goldBug.debug == false || (goldBug.debug == true && goldBug.codeAction3 == true)) {

	//do something else
}

Or, use an include file

In order to make the script smaller and more readable the bulk of the test mode script can be contained in a separate include file and reused in other script as well. In this example the script from the beginning up to the line that says "End Include File" is stored off as a separate file called testMode.incl.

include "testMode.incl";


if(goldBug.debug == false || (goldBug.debug == true && goldBug.codeAction1 == true)) {
	
	//do something
	
	//test output
	if(goldBug.debug == true) { //test mode only
		runnerLog("See something");
	}

	//write to log
	if(goldBug,useLogging == true) { //test mode only
		logPrint(currentUser() + " performed some action on " + currentDate(), "INFO");
	}
}


if(goldBug.debug == false || (goldBug.debug == true && goldBug.codeAction2 == true)) {
	//do something else
}


if(goldBug.debug == false || (goldBug.debug == true && goldBug.codeAction3 == true)) {

	//do something else
}

Contents

Today’s mashup come to us from a company in a highly regulated industry that needed to remove users who had not used Jira in the past 90 days. Users would come back from a leave of absence (or just start using Jira again) and flood the support team with requests to restore their access to Jira.

Since this Data Center instance did not have Service Management they didn’t have a way for non-licensed users (or customers) to create a ticket easily. Yes, they could have sent an email but they wanted an easy way for the users to solve the problem immediately. They wanted a way to send the user to a form (of some sort) from the announcement banner so they could request their access to be restored.

Since this was on a corporate intranet only users who would already be allowed to access Jira could get to the instance. And, all users already existed in Jira because of LDAP integration.

Since I didn’t have access to their intranet and the form they were planning on using I made a simplified version that I shared with them to get started with.

Part 1

The first part of this mashup is the announcement banner JavaScript and HTML.

HTML

The HTML is a simple panel with a shaded border. The text provides a link that would go to the form that the company would need to build. I will use the <div> tags id and the link id in the JavaScript.

<div id="restore_message" style="border: 1px solid #27798d; background-color: #fff; padding: 10px;">
<b>Restore Jira License!</b><br />You can restore the Jira license <a id="restore_link">Click here</a> to know more!</div>

Check User Function

The check user function has two main purposes:

  1. Get the users identity from the session.

  2. Check to see if the user is an active/licensed user.

We need to know if the user is currently licensed or not so we can hide the message from users who don’t need to see it in the first place. Assuming we are even able to determine the user that is. If the user can not be identified automatically the form the user would (ultimately) get redirected to would need to collect that information from the user. For this example we will assume we can collect it.

var isUser = false;
var userKey;

function checkUser() {
    AJS.$.ajax({
        dataType: "json",
        url: "/rest/api/2/myself",
        type: "GET",
        data: {
            expand: "groups"
        },
        async: false,
        success: function(data) {
            userKey = data.key;
            for (var i = 0; i < data.groups.size; i++) {
                if (data.groups.items[i].name == "jira-software-users") {
                    isUser = true;
                    break;
                }
            }
        }
    });
}

The code above is using the group called jira-software-users to determine if the user is already licensed or not. This may need to be changed according to your Jira’s settings.

Page Load Script Part 1

The next bit of code checks to see of the user is on the login page. If not, then the message does not need to be displayed. Next, the code calls the check user function (above) and if the user is already licensed it hides the message as well.

var url = window.location.href;
if (url.includes("Dashboard") == true) {
	checkUser();
	if(isUser == true) {
		AJS.$("#restore_message").hide();
	}
} else {
	AJS.$("#restore_message").hide();
}

Page Load Script Part 2

This next bit of code is the function that gets triggered when the user clicks the link. Ideally, this is the part that the form would have taken care of. However, in my example I included it here but it has some inherent flaws like the fact that I needed to provide authentication to call the webhook that completes the task.

Basically, all this code does is call a REST endpoint that I created and passes it some data. If there are any errors it updates the text in the message box with information regarding those errors and different instructions if necessary.

AJS.$("#restore_link").click(function() {
	var data = {};
	data["key"] = userKey;
	
	var settings = {
	  "url": "http://localhost:8080/rest/keplerrominfo/refapp/latest/webhooks/RestoreUser/run",
	  "method": "POST",
	  "timeout": 0,
	  "data": JSON.stringify(data),
	  "headers": {
	  "Content-Type": "application/json",
		"Authorization": "Basic am11ckjhjhgdfhgfd8wNTE4"
	  },
	   success: function (response) {
		$('#restore_message').html('Success! Please try logging in again.');
	  },
	  error: function (jqXHR, exception) {
		var msg = '';
		if (jqXHR.status === 0) {
			msg = 'Not connected.\n Verify Network.';
		} else if (jqXHR.status == 404) {
			msg = 'Your account was not found. Please contact support.';
		} else if (exception === 'timeout') {
			msg = 'Time out error.';
		} else {
			msg = 'Uncaught Error.\n' + jqXHR.responseText;
		}
		$('#restore_message').html(msg);
	}
	};

	$.ajax(settings).done(function (response) {
	  console.log(response);
	}); 
});

Complete Script

 Entire announcement banner code
<script>
var isUser = false;
var userKey;

function checkUser() {
    AJS.$.ajax({
        dataType: "json",
        url: "/rest/api/2/myself",
        type: "GET",
        data: {
            expand: "groups"
        },
        async: false,
        success: function(data) {
            userKey = data.key;
            for (var i = 0; i < data.groups.size; i++) {
                if (data.groups.items[i].name == "jira-software-users") {
                    isUser = true;
                    break;
                }
            }
        }
    });
}

$(document).ready(function() {
    
    var url = window.location.href;
    if (url.includes("Dashboard") == true) {
        checkUser();
        if(isUser == true) {
            AJS.$("#restore_message").hide();
        }
    } else {
		AJS.$("#restore_message").hide();
	}
	
	AJS.$("#restore_link").click(function() {
        var data = {};
        data["key"] = userKey;
        
    	var settings = {
    	  "url": "http://localhost:8080/rest/keplerrominfo/refapp/latest/webhooks/RestoreUser/run",
    	  "method": "POST",
    	  "timeout": 0,
    	  "data": JSON.stringify(data),
    	  "headers": {
		  "Content-Type": "application/json",
    		"Authorization": "Basic am11ckjhjhgdfhgfd8wNTE4"
    	  },
		   success: function (response) {
			$('#restore_message').html('Success! Please try logging in again.');
		  },
		  error: function (jqXHR, exception) {
			var msg = '';
			if (jqXHR.status === 0) {
				msg = 'Not connected.\n Verify Network.';
			} else if (jqXHR.status == 404) {
				msg = 'Your account was not found. Please contact support.';
			} else if (exception === 'timeout') {
				msg = 'Time out error.';
			} else {
				msg = 'Uncaught Error.\n' + jqXHR.responseText;
			}
			$('#restore_message').html(msg);
		}
    	};

		$.ajax(settings).done(function (response) {
		  console.log(response);
		});  
    });
});
</script>

<div id="restore_message" style="border: 1px solid #27798d; background-color: #fff; padding: 10px;">
<b>Restore Jira License!</b><br />You can restore the Jira license <a id="restore_link">Click here</a> to know more!</div>

Warning

When adding JavaScript in the announcement banner it is important to be very careful. Any mistake in the code could make Jira completely unusable… like all of it.

I have done it a couple times so I should know. They only way to dig yourself out of this mess is to remove the code from the announcement banner. However, you will no longer be able to do this from the UI since you broke it. You will need to fix it in the database. I suggest having a terminal or database app running while doing this so you can implement the fix immediately before anyone finds out that you didn’t have enough sense to know that you shouldn’t listen to me.

Here is the SQL code I needed to use a couple times to fix my mistakes:

DELETE from propertytext WHERE ID = (select id from propertyentry where property_key='jira.alertheader');
DELETE from propertystring where ID = (select id from propertyentry where property_key='jira.alertheader.visibility');
DELETE from propertyentry where property_key in ('jira.alertheader','jira.alertheader.visibility');

See this post for more information about clearing the announcement banner from the database.

Part 2

Webhook

The next part of this mashup is a SIL Webhook. However, this could have been done some other ways and I wonder if they may have actually been better. To be far it is the webhooks fault that authentication is required, however, security is generally a good thing. I also considered using:

  • Jira issue collector - I think this would have worked pretty well. The issue could have gone to an issue type with a specific workflow that had code similar to what is posted below as a post function. When approved the user could have been automatically restored.

  • SIL Mail Handler - The users could have emailed their request in with specific key words in the subject line. The user could have been identified from the email address contained in the email. This info could have been directed to a ticket (like with the issue collector above) or the user could be restored right there in the mail handler code.

  • Jira Service Management - Since external users can create requests a portal can be used just for internal users as well. Problems like this and many others can be created as request types.

However, that's not what I did. What I did was make a webhook. So lets look at that:

First Half of the Script

The fist thing we do in the script is define a struct to be used to parse the incoming JSON data. See more about the HTTP routines are parsing JSON by checking out the fromJSON() routine page.

Next, we use some structures and functions specific to webhooks to get the data that is being passed to it. See more about webhhook routines.

After that, we check to see if the call made to this REST endpoint was a POST. If not we return an error message and error code.

struct _payload {
	string key;
}

WebhookPayload httpRequestPayload = getWebhookPayload();
string httpMethod = httpRequestPayload.httpMethod;
string httpPayload = httpRequestPayload.payload;

if(httpMethod != "POST") {
	appendToWebhookResponse("Unknown method");
	return false, 405;
}

How do we know it should be a POST call? Because we created the endpoint so we can decided what type it should be! Since we are receiving some data from this call it really should be a POST or maybe a PUT, but really a POST. See more about configuring SIL Webhooks.

Second Half of the Script

This part of the script takes the JSON payload and tries to read the data from it. If the user key is null it will return an error message and code.

After that, the script impersonates an admin user to its permissions can be increased. This is done as a (perhaps lame) attempt to compensate for the authentication within the JavaScript. The thought being that a low privileged user could be used just so the webhook can be triggered. That way, if the user information is compromised the malicious user will not have enough privileges to cause any real harm. Ideally, the form would have been created in a more secure fashion so that the authentication information would not be exposed to the end user in this way.

After switching to an admin user the script checks to see if the data provided does in fact represent a Jira user. If so, the user will be reactivated, if not, another error will be sent.

_payload p = fromJson(httpPayload);
if(isNull(p.key)) {
    appendToWebhookResponse("Key can not be null");
    return false, 400;
} else {
    runAs("admin");
    string user = userKeyToUsername(p.key);
    if(userExists(user)) {
        admActivateUser(user);
    } else {
        appendToWebhookResponse("User does not exist");
        return false, 404;
    }
}

return true, 200;

At the end of the script a code representing a successful execution is returned. These are the same codes used by the announcement banner JavaScript to update the messages displayed to the user.

Complete Script

 Complete SIL Webhook script
struct _payload {
	string key;
}

WebhookPayload httpRequestPayload = getWebhookPayload();
string httpMethod = httpRequestPayload.httpMethod;
string httpPayload = httpRequestPayload.payload;

if(httpMethod != "POST") {
	appendToWebhookResponse("Unknown method");
	return false, 405;
}

_payload p = fromJson(httpPayload);
if(isNull(p.key)) {
    appendToWebhookResponse("Key can not be null");
    return false, 400;
} else {
    runAs("admin");
    string user = userKeyToUsername(p.key);
    if(userExists(user)) {
        admActivateUser(user);
    } else {
        appendToWebhookResponse("User does not exist");
        return false, 404;
    }
}

return true, 200;

Contents

Be advised: The scripts in this example are not perfect and assume that additional work will be done to make them more acceptable, like creating a new form page that collects additional information and hides the authentication information. It is not the intent of this author to offer up a complete solution but to inspire and educate the reader to consider new possibilities when looking to solve problems similar to their own.

Having files that do not contain SIL code is very common. Advanced scripters use a wide variety of file types to build their automation and integration solutions. Here are a few common types:

  • Text files

  • HTML files

  • JSON data

  • JavaScript files

  • Images

  • Many more…

But why would you have non-code files in the SIL Manager, what is the purpose of them? The answer to that depends on what you are using the file for. Lets explore a little deeper.

Data

My #1 use of non-SIL code files would be data. This is both data that can be read into a SIL script and data that is actually produced by a SIL script.

When I transitioned from someone who wrote SIL scripts for myself to someone who wrote SIL scripts for others I became a little self conscience about the quality of my code and looked for ways to keep things simple. As time passed I realized that I was using CSV data files in conjunction with my code. Not just some of the time, almost every time I had to write a complicated script. I soon realized that a lot of “logic” that would normally get added as code in the script could also be added to the data to simplify things even further. However, that topic is to large to cover here and will need to be another blog post.

Data isn’t just for importing into a script but for exporting as well. I have found many different examples of why it is helpful or even necessary to export data with a script. For example, how nice would it be to export a list of all the users in the system and the date that they last logged in to the system?

I will be creating more examples of importing/exporting data to help illustrate the value inherent in this ability.

Resources

Resources is such a generic term but I called them this on purpose because these could include anything that us needed to help your script work. Resources could include things like:

  • JavaScript

  • Zip file

  • Images

  • HTML or CSS

When using Live Fields it is very common to come across a use case that extends beyond what the Live Fields routines were originally created for. That is why Live Fields comes with a routine that allows it to call JavaScript whenever it needs a little extra power. I have used JavaScript to gain a high degree of control over issue screens when needed. However, this JavaScript can also be used in SIL panels and with Power Dashboards & Reports.

I have used Zip files when exporting the SIL code off the sever or perhaps to push data to an external FTP server.

HTML, CSS, and images are very common resources to use when sending email messages or creating new pages in Confluence. Usually, I use the SIL Template Language to dynamically generate the HTML, however, there are times where I externalize the CSS or when the email is basically static that I do not need the dynamic capability.

Logs or Reports

You absolutely have the ability to use the logPrint() routine to write to the Jira system log. However, there may be reasons why you wouldn’t want to do this. For example, searching through all those log messages can be time consuming. One option would be to create your own log using the printInFile() routine. This way, the log could be created in the silprograms folder and viewed from within the SIL Manager. I like to do this when testing SIL Listeners sometimes to be sure they are firing at the right times with the right information. However, you should be careful doing this because the file could grow extremely large so I only do this while debugging.

However, I have used this method to create reports for companies in highly regulated industries. I have created reports based on users login activity, when things like issues and projects get deleted, etc. SIL Listeners create a variety of opportunity to add additional tracking to Jira. A simple solution to the file size problem is dynamically name files based on the date so a single file can only be written to for a day. Then you can create a SIL scheduler to go in and clean up files in that folder that are older than 6 months (for example).

External Code

Some types of code files I have used in the SIL Manager:

  • DOS scripts

  • Shell script

  • Python

  • Power Shell

Back in the day, before the HTTP routines were created, I used the system() routine quite often to user curl to sent REST calls. The HTTP routines have proven to be quite robust and I have not needed to use curl in a long time. However, once in a blue moon I find a REST API that is so bizarre and obviously hacked together that I need to revert to curl in order to use its extensive capabilities. I used to have the curl code in a .bat file that took parameters and had that file living out in the SIL Manager. It worked pretty well. Its is nice to know that that's still an option.

I have also used shell scripts to call other Java apps living on the system to create some really extraordinary integrations. For example, I have used Apache Tika to parse MS Word docs or PDFs into text files so I could read the contents using SIL. Sometimes I would try to integrate with systems that didn’t really have open API’s or that may have already had an integration created in another language like python. I found it easier just to call that python integration rather than reproducing it. There are many reasons why I would call other systems living on the Jira server from SIL.

There might be security and performance implications to calling other applications on the Jira server. However, if done properly some next-level integrations can be created this way.

Contents