Changing the way ticket conversation aligns messages

James Roberts
Giga Contributor

Hi all

 

I would like to change the way the ticket conversation places comments in the ticket conversation.

as far as i can tell it currently puts comments from the creator to the right of the timeline and all other comments to the left.

I would like it change it so all comments made by users that do not have the itil role to be on the right of the time line and all the other comments to be on the left

I have already duplicated the widget and modified it with some other settings but i still cant get my head around how it determines which side of the time line comments go.

i think its to do with "ng-class="::{'timeline-inverted': e.user_sys_id == data.stream.user_sys_id}" but not sure how to go about checking the roles.

1 ACCEPTED SOLUTION

SatheeshKumar
Kilo Sage

try the below solution:

 

update your server code with below:

(function() {
	data.maxAttachmentSize = parseInt(gs.getProperty("com.glide.attachment.max_size", 1024));
	if (isNaN(data.maxAttachmentSize))
		data.maxAttachmentSize = 24;
	data.uploadingAttachmentMsg = gs.getMessage("Uploading attachment...");
	data.sharingLocMsg = gs.getMessage("Sharing location...");
	data.scanBarcodeMsg = gs.getMessage("Scan barcode");
	data.checkInLocMsg = gs.getMessage("Check in location");
	data.messagePostedMsg = gs.getMessage("Message has been sent");
	data.viewMsg = gs.getMessage("View");
	data.attachAddedMsg = gs.getMessage("Attachment added");
	data.attachFailMsg = gs.getMessage("Failed to add attachment");
	data.scanFailedMsg = gs.getMessage("File failed security scan");
	data.sys_id = input.sys_id || options.sys_id || $sp.getParameter("sys_id");
	data.table = input.table || options.table || $sp.getParameter("table");
	// don't use options.title unless sys_id and table also come from options
	if (options && options.sys_id && options.table)
		data.ticketTitle = options.title;
	data.placeholder = options.placeholder || gs.getMessage("Type your message here...");
	data.placeholderNoEntries = options.placeholderNoEntries || gs.getMessage("Type your message here...");
	data.btnLabel = options.btnLabel || gs.getMessage("Send");
	data.includeExtended = options.includeExtended || false;
	data.use_dynamic_placeholder = options.use_dynamic_placeholder;
	data.isNewRecord = data.sys_id == -1 || gr.isNewRecord();
	data.hideAttachmentBtn = options.hideAttachmentBtn;

	var gr = new GlideRecord(data.table);
	if (!gr.isValid())
		return;

	gr.get(data.sys_id);
	if (!gr.canRead())
		return;

	data.table = gr.getRecordClassName(); // use actual table for the record
	options.no_readable_journal_field_message = options.no_readable_journal_field_message || gs.getMessage("No readable comment field");
	data.number = gr.getDisplayValue('number');
	data.created_on = gr.getValue('sys_created_on');

	if (input) { // if we have input then we're saving
		if (input.journalEntry && input.journalEntryField){
			if (gr.canWrite(input.journalEntryField)){
				gr.setDisplayValue(input.journalEntryField, input.journalEntry);
				gr.update();
				$sp.logStat('Comments', data.table, data.sys_id, input.journalEntry);
			}
		}
		data.ticketTitle = input.ticketTitle;
		data.placeholder = input.placeholder;
		data.btnLabel = input.btnLabel;
		data.includeExtended = input.includeExtended;
	} else {
		if (!data.ticketTitle) {
			if (gr.short_description.canRead())
				data.ticketTitle = gr.getDisplayValue("short_description");
			if (!data.ticketTitle)
				data.ticketTitle = data.number;
		}

		$sp.logStat('Task View', data.table, data.sys_id);
	}

	data.canWrite = gr.canWrite();
	data.canAttach = gs.hasRole(gs.getProperty("glide.attachment.role")) && GlideTableDescriptor.get(data.table).getED().getAttribute("no_attachment") != "true";
	data.canRead = gr.canRead();
	data.hasWritableJournalField = false;
	data.hasReadableJournalField = false;
	if (data.canRead && !data.isNewRecord) {
		data.stream = $sp.getStream(data.table, data.sys_id);
		
		
		if ('entries' in data.stream) {
			
			var ent = data.stream.entries;
			
				for(var k=0; k < ent.length; k++){
			
			console.log("entries" + ent[k].user_sys_id);
					
					var role = new GlideRecord('sys_user_has_role');
            role.addQuery('user', ent[k].user_sys_id);
            role.addQuery('role.name','itil');
             role.setLimit(1);
             role.query();

if(role.hasNext()){
  ent[k].hasItil = true;
}
else{
		ent[k].hasItil = false;
	}				
				}
				
			}
		
		
		
		
		//gs.addInfoMessage(data.stream);
		// Journal fields come in correct order already
		// so grab the first 2 writeable fields
		if ('journal_fields' in data.stream) {
			var jf = data.stream.journal_fields;
			for(var i=0; i < jf.length; i++){
				if (jf[i].can_read === true)
					data.hasReadableJournalField = true;
				if (jf[i].can_write === true){
					data.hasWritableJournalField = true;
					if (!data.primaryJournalField)
						data.primaryJournalField = jf[i];
					else if (data.includeExtended && !data.secondaryJournalField)
						data.secondaryJournalField = jf[i];
					else
						break;
				}
			}
		}

	}
	
	function checkItil(id)
		{
			
		}

	data.tableLabel = gr.getLabel();

})()

 

 

html

 

<div>
  <div ng-if="!data.canRead && !data.isNewRecord">
    ${Requested record not found}
  </div>
  <div ng-if="data.canRead && !data.isNewRecord" class="panel panel-{{options.color}} b ticket_conversation" >
    <div class="panel-heading">

      <div class="h2 panel-title panel-title-container">
        <h2 class="h4 panel-title" aria-label="{{::data.ticketTitle}} ${Ticket history}" >{{::data.ticketTitle}}</h2>

        <div class="pull-right panel-title-icons">
          <ul>
            <li>
              <button href ng-show="data.showLocationIcon && data.canWrite" class="panel-button btn btn-link" ng-click="checkInLocation()" title="{{data.checkInLocMsg}}">
                <span class="glyphicon glyphicon-globe"></span>
              </button>
            </li>
            <li>
              <button href class="panel-button btn btn-link" ng-show="isNative" ng-click="scanBarcode()" title="{{data.scanBarcodeMsg}}">
                <span class="glyphicon glyphicon-barcode"></span>
              </button>
            </li>
            <li ng-if="::(!data.hideAttachmentBtn)"><sp-attachment-button ng-if="::data.canWrite && data.canAttach"></sp-attachment-button></li>
          </ul>
        </div>
      </div>

    </div>

    <div class="panel-body">
      <div ng-if="data.hasReadableJournalField">
        <form ng-submit="postEntry(data.journalEntry)" id="sand">
          <div ng-show="data.hasWritableJournalField" class="input-group">
            <textarea ng-keypress="keyPress($event)"
                      sn-resize-height="trim"
                      rows="1" id="post-input"
                      class="form-control no-resize overflow-hidden"
                      ng-model='data.journalEntry'
                      ng-model-options='{debounce: 250}'
                      ng-attr-placeholder="{{getPlaceholder()}}"
                      aria-label="{{getPlaceholder()}}"
                      autocomplete="off"
                      ng-change="userTyping(data.journalEntry)"/>
            <span class="journal-field-indicator" ng-style="({'background-color': data.useSecondaryJournalField ? data.secondaryJournalField.color : data.primaryJournalField.color})"></span>
            <span class="input-group-btn" style="vertical-align: top">
              <input type="submit" class="btn btn-primary" value="{{data.btnLabel}}" ng-disabled="data.isPosting"/>
            </span>
          </div>
          <div ng-if="::(data.secondaryJournalField && data.secondaryJournalField.can_write)">
            <label class="pull-right">
              <input type="checkbox" ng-model="::data.useSecondaryJournalField" ng-change="updateFormWithJournalFields()"/>
              <span> {{::data.secondaryJournalField.label}}</span>
            </label>
          </div>
        </form>
        <ul class="list-group m-b-none" ng-if="typing.length > 0">
          <li class="list-group-item user-typing m-t" ng-repeat="u in typing">${{{::u.user_display_name}} is typing}</li>
        </ul>
        <ul class="list-group m-b-none m-t" ng-if="msg">
          <li class="list-group-item user-typing">{{msg}}</li>
        </ul>
        <div class="timeline-container">
          <ul role="list" class="timeline" aria-label="${Ticket history}">
            <li class="timeline-item" ng-class="::{'timeline-inverted': !e.hasItil} " ng-repeat="e in data.mergedEntries">
              <div class="timeline-badge">
                <sn-avatar-once
                           ng-if="hasLiveProfile(e.user_sys_id)"
                           primary="getLiveProfileByUserId(e.user_sys_id)"
                           class="avatar-large"
                           show-presence="false"
                           enable-context-menu="false">
                </sn-avatar-once>
              </div>
              <div class="timeline-panel">
                <div class="timeline-panel-inner" ng-style="::{'border-color': getFieldColor(e.element)}">
                  <div class="timeline-heading">
                    <div class="timeline-title h4">{{::e.name}}</div>
                    <p class = "time-text">
                      <small class="text-muted" >
                        <span class="glyphicon glyphicon-time" aria-hidden="true" tabindex="-1" />
                        <sn-time-ago  timestamp="::e.sys_created_on"  /> 
                      </small>
                      <i ng-if="::e.field_label" class="fa fa-circle text-muted" aria-hidden="true"></i>
                      <small class = "text-muted journal-type">{{::e.field_label}}</small>
                    </p>
                  </div>
                  <div class="timeline-body">
                    <p ng-if="::(e.element != 'attachment')" ng-bind-html="::e.value"></p>
                    <div ng-if="::(e.element == 'attachment')">
                      <a ng-if="(e.attachment.state == 'available')" target="_blank" href="/sys_attachment.do?view=true&sys_id={{::e.attachment.sys_id}}" title="${View}">
                        <img ng-if="e.attachment.thumbnail_path" alt="" ng-src="/{{::e.attachment.path}}?t=medium" class="img-responsive"/>
                      </a>
                      <a ng-if="(e.attachment.state == '' || e.attachment.state == 'pending' || e.attachment.state == 'available_conditionally')" ng-click="scanAttachment(e.attachment)" href="javascript:void(0)" title="${View}">
                        <img ng-if="e.attachment.thumbnail_path" alt="" ng-src="/{{::e.attachment.path}}?t=medium" class="img-responsive"/>
                      </a>
                      <div>
                        <div ng-if="(e.attachment.state == 'available')">
                        <a href="/sys_attachment.do?sys_id={{::e.attachment.sys_id}}" target="_blank" title="{{dataViewMsg}}"><strong>{{e.attachment.file_name}}</strong></a><br/>
                        {{::e.attachment.size}}
                      </div>
                      <div ng-if="(e.attachment.state == 'not_available')">
                        <span title="{{dataViewMsg}}" class="not_available">{{e.attachment.file_name}}</span><br/>
                        <span class="error">{{::data.scanFailedMsg}}</span>
                      </div>
                      <div ng-if="(e.attachment.state == '' || e.attachment.state == 'pending' || e.attachment.state == 'available_conditionally')">
                        <a href="javascript:void(0)" ng-click="scanAttachment(e.attachment)" title="{{dataViewMsg}}"><strong>{{e.attachment.file_name}}</strong></a><br/>
                        {{::e.attachment.size}}
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </li>
            <li role="listitem" class="timeline-item timeline-inverted" aria-label="{{data.stream.user_full_name}}">
              <div class="timeline-badge">
                <sn-avatar-once
                           ng-if="hasLiveProfile(data.stream.user_sys_id)"
                           primary="getLiveProfileByUserId(data.stream.user_sys_id)"
                           class="avatar-large"
                           show-presence="false"
                           enable-context-menu="false">
                </sn-avatar-once>
              </div>
              <div class="timeline-panel timeline-panel-first-item">
                <div class="timeline-heading">
                  <div class="timeline-title h4">{{data.stream.user_full_name}}</div>
                  <p>
                    <small class="text-muted">
                      <span class="glyphicon glyphicon-time" aria-hidden="true" tabindex="-1" />
                      <sn-time-ago timestamp="data.created_on" />
                    </small>
                  </p>
                </div>
                <div class="timeline-body">
                  <p>{{data.number}} ${Created}</p>
                </div>
              </div>
            </li>
            <li role="presentation" aria-hidden="true">
              <div class="timeline-badge-wrap">
              <div class="timeline-badge success">
                  <span>${Start}</span>
              </div>
              </div>            	              
            </li>            
          </ul>
        </div>

      </div>
      <div ng-if="!data.hasReadableJournalField">
        {{options.no_readable_journal_field_message}}
      </div>
    </div>
  </div>
</div>

 

this will  do what you are looking for.

 

let me know if you have any questions!!!

 

-satheesh

View solution in original post

6 REPLIES 6

This looks promising. I will give it a go. I have already customized the code a fair bit (well backwards engineered someone elses code) so i will see if i can merge what you've added. 

Does this not require anything doing within the controller?

 

Thanks really appreciate the help.

Managed to get this incorporate this into the custom data stream we are using (which is based on the sys_audit table to allow us to post extra information like when a ticket moves to another team or technician as well as the resolution notes). It works perfectly.

Thank you so much for the help, I've been bashing my head against the wall for days now.