How to Save the History of Changes in Task Parameters

Starting with version 4.0.9 ,TrackStudio now has an in-built mechanism for auditing the changes. So as to enable it, you just need to create an operation with the name* in the required process. Thereafter, all the changes, taking place in the task, will be recorded with the help of this operation, though without considering the user permissions (if the user can edit the task, or execute any operation on it – a system message will also be generated). You can set the permissions of viewing this operation, in the same way as for remaining operations.

In TrackStudio from version 4.0 to 4.0.8 this feature is not in-built, but it can be managed using triggers. This solution can be treated simply as an example of using triggers.

Task properties may change when the task itself is edited as well as when some operation is performed. This means that we will require not one but two triggers of different types, working on same principle. As for tracking the changes we must know the state of task before incorporating these changes, trigger of type After will not work – then the changes have already been recorded. Trigger of type Before will not work for us as the recording about changes takes place before the changes, which, at first place, can once again change the properties of task, and secondly, can return the false information, if the change itself didn’t take place because of error.

Consequently, we require two triggers of type Instead Of.

For editing the task, we will write the trigger Instead Of Edit Task. It must correspond to the interface com.trackstudio.external.TaskTrigger and stored in the folder

./etc/plugins/scripts/instead_of_edit_task/
package scripts.instead_of_edit_task;

import com.trackstudio.app.adapter.AdapterManager;
import com.trackstudio.app.csv.CSVImport;
import com.trackstudio.exception.GranException;
import com.trackstudio.external.TaskTrigger;
import com.trackstudio.secured.*;
import com.trackstudio.startup.I18n;

import java.util.Calendar;

/**
 * Script saves all the changes of the task
 */
public class LogChanges implements TaskTrigger {
    public SecuredTaskTriggerBean execute(SecuredTaskTriggerBean task) throws GranException {
        StringBuffer sb2 = new StringBuffer();
        StringBuffer sb = new StringBuffer();
        sb2.append("<table class=\"general\" cellpadding=4>");
        String budget = task.getBudgetAsString();

        Calendar deadline = task.getDeadline();
        SecuredPrstatusBean group = task.getHandlerGroup();
        SecuredUserBean user = task.getHandlerUser();
        SecuredPriorityBean priority = task.getPriority();
        SecuredResolutionBean resolution = task.getResolution();
        String description = task.getDescription();
        String name = task.getName();
        String alias = task.getShortname();

        SecuredTaskBean oldTask = new SecuredTaskBean(task.getId(), task.getSecure());
        String _name = oldTask.getName();
        String _alias = oldTask.getShortname();
        String _budget = oldTask.getBudgetAsString();
        Calendar _deadline = oldTask.getDeadline();
        SecuredPrstatusBean _group = oldTask.getHandlerGroup();
        SecuredUserBean _user = oldTask.getHandlerUser();
        SecuredPriorityBean _priority = oldTask.getPriority();
        SecuredResolutionBean _resolution = oldTask.getResolution();
        String _description = oldTask.getDescription();
        if ((_name != null && !_name.equals(name)) || (_name == null && name != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "NAME"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_name);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(name);
            sb.append("</td>");
            sb.append("</tr>\n");

        }

        if ((_alias != null && !_alias.equals(alias)) || (_alias == null && alias != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "ALIAS"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_alias);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(alias);
            sb.append("</td>");
            sb.append("</tr>\n");

        }

        if ((_budget != null && !_budget.equals(budget)) || (_budget == null && budget != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "BUDGET"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_budget);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(budget);
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_deadline != null && !_deadline.equals(deadline)) || (_deadline == null && deadline != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "DEADLINE"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_deadline != null)
                sb.append(task.getSecure().getUser().getDateFormatter().parse(_deadline));
            sb.append("</strike></td>");
            sb.append("<td>");
            if (deadline != null)
                sb.append(task.getSecure().getUser().getDateFormatter().parse(deadline));
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_priority != null && !_priority.equals(priority)) || (_priority == null && priority != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "PRIORITY"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_priority != null)
                sb.append(_priority.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (priority != null)
                sb.append(priority.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_resolution != null && !_resolution.equals(resolution)) || (_resolution == null && resolution != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "RESOLUTION"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_resolution != null)
                sb.append(_resolution.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (resolution != null)
                sb.append(resolution.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_user != null && !_user.equals(user)) || (_user == null && user != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_user != null)
                sb.append(_user.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (user != null)
                sb.append(user.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_group != null && !_group.equals(group)) || (_group == null && group != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_group != null)
                sb.append(_group.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (group != null)
                sb.append(group.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_description != null && !_description.equals(description)) || (_description == null && description != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "DESCRIPTION"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_description);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(description);
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if (task.getUdfValues() != null && !task.getUdfValues().isEmpty()) {

            for (Object okey : task.getUdfValues().keySet()) {
                String key = okey.toString();
                String value = task.getUdfValues().get(key).toString();
                String oldValue = AdapterManager.getInstance().getSecuredUDFAdapterManager()
                        .getTaskUDFValue(task.getSecure(), task.getId(), key);
                if ((oldValue != null && !oldValue.equals(value))
                        || (oldValue == null && value != null)) {
                    sb.append("<tr>");
                    sb.append("<th align=\"right\">");
                    sb.append(key);
                    sb.append("</th>");
                    sb.append("<td><strike>");
                    sb.append(oldValue);
                    sb.append("</strike></td>");
                    sb.append("<td>");
                    sb.append(value);
                    sb.append("</td>");
                    sb.append("</tr>\n");

                }
            }
        }
        SecuredMessageTriggerBean createMessage = null;
        if (sb.length() > 0) {
            sb2.append(sb);
            sb2.append("</table>\n");
            String mstatusId = CSVImport.findMessageTypeIdByName("Log", task.getCategory().getName());
            /**
             * Создаем SecuredMessageTriggerBean
             */
            createMessage = new SecuredMessageTriggerBean(
                    null /* identifier */,
                    sb2.toString() /* text of the comment */,
                    Calendar.getInstance() /* time of operation execution */,
                    null /* spent time */,
                    task.getDeadline() /* deadline */,
                    task.getBudget() /* budget */,
                    task.getId() /* task */,
                    task.getSecure().getUserId() /* operation submitter и */,
                    null /* resolution */,
                    task.getPriorityId() /* priority */,
                    task.getHandlerId() /* assignees */,
                    task.getHandlerUserId() /* assignee */,
                    task.getHandlerGroupId() /* assignee, if group is to be set as assignee */,
                    mstatusId /* type of operation */,
                    null /* Map with custom fields */,
                    task.getSecure() /* SessionContext */,
                    null /* attachments */);
            /**
             * execute             */

        }
        task.update(true);
        if (createMessage != null) createMessage.create(false);
        return task;
    }
}

This trigger must be included in the category settings of those tasks, changes in which are to be tracked . Thus in the processes of tasks, there must be the operation “Log”, which does not change the state of task. Name of the operation can be, of course, changed. In that case, do not forget to change it in both the triggers.

Further, for tracking the changes in the tasks, carried out with the help of operations (add notes/messages), we will require the trigger Instead Of Add Message. It must correspond to the interface

com.trackstudio.external.OperationTrigger and stored in the folder

./etc/plugins/scripts/instead_of_add_message/
package scripts.instead_of_add_message;

import com.trackstudio.app.adapter.AdapterManager;
import com.trackstudio.app.csv.CSVImport;
import com.trackstudio.exception.GranException;
import com.trackstudio.external.OperationTrigger;
import com.trackstudio.secured.*;
import com.trackstudio.startup.I18n;


import java.util.Calendar;

public class LogChanges implements OperationTrigger {
    public SecuredMessageTriggerBean execute(SecuredMessageTriggerBean message) throws GranException {
        StringBuffer sb2 = new StringBuffer();
        StringBuffer sb = new StringBuffer();
        sb2.append("<table class=\"general\" cellpadding=4>");
        String budget = message.getBudgetAsString();
        Calendar deadline = message.getDeadline();
        SecuredPrstatusBean group = message.getHandlerGroup();
        SecuredUserBean user = message.getHandlerUser();
        SecuredPriorityBean priority = message.getPriority();
        SecuredResolutionBean resolution = message.getResolution();
        SecuredStatusBean state = message.getTask().getStatus();

        String _budget = message.getTask().getBudgetAsString();
        Calendar _deadline = message.getTask().getDeadline();
        SecuredPrstatusBean _group = message.getTask().getHandlerGroup();
        SecuredUserBean _user = message.getTask().getHandlerUser();
        SecuredPriorityBean _priority = message.getTask().getPriority();
        SecuredResolutionBean _resolution = message.getTask().getResolution();
        SecuredStatusBean _state = null;
        for (SecuredTransitionBean t : message.getMstatus().getTransitions()) {
            if (t.getStart().equals(state)) _state = t.getFinish();
            break;
        }

        if ((_budget != null && !_budget.equals(budget)) || (_budget == null && budget != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "BUDGET"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_budget);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(budget);
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_deadline != null && !_deadline.equals(deadline)) || (_deadline == null && deadline != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "DEADLINE"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_deadline != null)
                sb.append(message.getSecure().getUser().getDateFormatter().parse(_deadline));
            sb.append("</strike></td>");
            sb.append("<td>");
            if (deadline != null)
                sb.append(message.getSecure().getUser().getDateFormatter().parse(deadline));
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_priority != null && !_priority.equals(priority)) || (_priority == null && priority != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "PRIORITY"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_priority != null)
                sb.append(_priority.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (priority != null)
                sb.append(priority.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_resolution != null && !_resolution.equals(resolution)) || (_resolution == null && resolution != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "RESOLUTION"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_resolution != null)
                sb.append(_resolution.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (resolution != null)
                sb.append(resolution.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_user != null && !_user.equals(user)) || (_user == null && user != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_user != null)
                sb.append(_user.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (user != null)
                sb.append(user.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_group != null && !_group.equals(group)) || (_group == null && group != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_group != null)
                sb.append(_group.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (group != null)
                sb.append(group.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_state != null && !_state.equals(state)) || (_state == null && state != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "TASK_STATE"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_state.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(state.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if (message.getUdfValues() != null && !message.getUdfValues().isEmpty()) {

            for (Object okey : message.getUdfValues().keySet()) {
                String key = okey.toString();
                String value = message.getUdfValues().get(key).toString();
                String oldValue = AdapterManager.getInstance().getSecuredUDFAdapterManager()
                        .getTaskUDFValue(message.getSecure(), message.getTaskId(), key);
                if ((oldValue != null && !oldValue.equals(value))
                        || (oldValue == null && value != null)) {
                    sb.append("<tr>");
                    sb.append("<th align=\"right\">");
                    sb.append(key);
                    sb.append("</th>");
                    sb.append("<td><strike>");
                    sb.append(oldValue);
                    sb.append("</strike></td>");
                    sb.append("<td>");
                    sb.append(value);
                    sb.append("</td>");
                    sb.append("</tr>\n");

                }
            }
        }
        SecuredMessageTriggerBean createMessage = null;
        if (sb.length() > 0) {
            sb2.append(sb);
            sb2.append("</table>\n");
            String mstatusId = CSVImport.findMessageTypeIdByName("Log", message.getTask().getCategory().getName());
            /**
             * Создаем SecuredMessageTriggerBean
             */
            createMessage = new SecuredMessageTriggerBean(
                    null /* identifier */,
                    sb2.toString() /* text of the comment */,
                    Calendar.getInstance() /* time of execution of operation */,
                    null /* spent time */,
                    message.getDeadline() /* deadlines */,
                    message.getBudget() /* budget */,
                    message.getTaskId() /* task */,
                    message.getSecure().getUserId() /* submitter of operation */,
                    null /* resolution */,
                    message.getPriorityId() /* priority */,
                    message.getHandlerId() /* assignees */,
                    message.getHandlerUserId() /* assignee */,
                    message.getHandlerGroupId() /* assignee, if group is to be set as assignee */,
                    mstatusId /* type of operation */,
                    null /* Map with custom fields */,
                    message.getSecure() /* SessionContext */,
                    null /* attachments */);
            /**
             * execute             */

        }
        message.create(true);
        if (createMessage != null) createMessage.create(false);
        return message;
    }
}

This trigger must be attached in the parameters of those operations, which you want to track. Thus in the process of tasks, there must be the operation “Log”, which does not change the state of task. Do not connect the trigger to the operation “Log”. Refer "Recursion" . Name of the operation can be, of course, changed. In that case, do not forget to change it in both the triggers.