- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-05-2019 09:37 AM
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.
Solved! Go to Solution.
- Labels:
-
Service Portal Development

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-06-2019 07:05 AM
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-09-2019 02:08 AM
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-09-2019 03:53 AM
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.