Join the #BuildWithBuildAgent Challenge! Get recognized, earn exclusive swag, and inspire the ServiceNow Community with what you can build using Build Agent.  Join the Challenge.

Widget "My Request" in Service Portal for Watch list

Itsmehiiiiiiii
Tera Contributor

Hi All,

 

Can someone help me how can I achieve this?


To summarize my request, what they want is use the watchlist functionality in portal view so that the users without access on our backend can view it using the portal.


Then there's this request tab or button beside the user's profile and when you click it there's this dropdown called "VIEW" wherein we can see the open request and closed request that we or user created.

The requirement that they gave to me is that can we add another value on that drop down and we can call it "Closed Watch list", wherein when they select that value all of the closed tickets will be visible as long as they're part of the watch list.

As you can see on the screenshots I provided, I already apply it but under the closed watch list the title of the tickets I'm seeing is just the number of the ticket instead of the short description for incident and name of the catalog items for RITM.

I'll attached as well the default view or title display under the open request.

Thank you in advance!

1 ACCEPTED SOLUTION

Hello @Itsmehiiiiiiii, Glad that you are able to see the titles correctly.

Yes, you will get the duplicates and for the same I asked to adjust the function getMyRequestSysIds().

Nevertheless, could you please try replacing with below one. You can make any necessary adjustment in the if and else block ( your filter name or any new add query)

    function getMyRequestSysIds() {		
        var ids = {};
        var rq_filter = new GlideRecord('request_filter');
        rq_filter.addActiveQuery();
        if (rq_filter.isValidField('applies_to')){
			if (localInput && localInput.view === 'closed_watchlist'){
				rq_filter.addQuery('applies_to', 1).addOrCondition('applies_to', 10);
				rq_filter.addQuery('title', 'Watched Closed List');
			}
			else {
				rq_filter.addQuery('applies_to', 1).addOrCondition('applies_to', 10);
				rq_filter.addQuery('title','!=', 'Watched Closed List');
			}
		}
        rq_filter.query();
        while (rq_filter.next()) {
            var tableName = rq_filter.table_name;
            if (rq_filter.isValidField('table'))
                tableName = rq_filter.table;
            var gr = new GlideRecord(tableName);
            if (!gr.isValid())
                continue;

            gr.addQuery(rq_filter.filter);
			gr.enableSecurityFeature('data_filter');
            gr.query();
            if (tableName == 'sc_request')
                alsoRequest = true;
            while (gr.next()) {
                var portalSettings = {};
                portalSettings.page = rq_filter.portal_page.nil() ? '' : rq_filter.portal_page.getDisplayValue() + '';
                portalSettings.primary_display = rq_filter.primary_display.nil() ? '' : rq_filter.primary_display + '';
                portalSettings.secondary_displays = rq_filter.secondary_display.nil() ? '' : rq_filter.secondary_display + '';
                ids[gr.sys_id + ''] = portalSettings;
            }
        }
        return ids;
    }

 

Please note that I'm sharing above code without any validation, make necessary adjustment, if required.

You can create duplicate titles too in request filter like one for incident and one for Request, but if you wish to keep them different, then change the addQuery() accordingly.

 

P.S: surely, there might be better ways to filter in/out the Filter Request, but I can write above without any validation right now.

 

Regards,

Nishant 

View solution in original post

12 REPLIES 12

@Itsmehiiiiiiii 

Sorry not very sure on that and you will have to debug from your side.

Regards,
Ankur
Certified Technical Architect  ||  9x ServiceNow MVP  ||  ServiceNow Community Leader

Hello @Itsmehiiiiiiii , I assume that you are able to fetch right information under Closed Watch List. Since I'm not sure how you have done this customization, an ideal way is to define the My Request Filter ( request_filter table).

If you have done in this way then probably you can verify the 'Secondary fields to display' under the Portal Settings of your new Filter as below. 

 

Nishant8_0-1761117635062.png

 

If you have defined everything in the same way and still you see this problem, then you can share your complete Widget code to understand the gap, if any.

 

Regards,

Nishant

Hi @Nishant8 ,

I'm okay to share the script that I have, please the code below

(function() {
 
    var localInput = input; //to safeguard pullution of "input" via BR or other scripts
    var itemsObj;
    if (localInput && localInput.action === "remove_item") {
        var cartRecord = new sn_sc.CartJS('draft_items');
        cartRecord.remove(localInput.removeItemID);
        itemsObj = getDraftItems(localInput.prevLimit);
        data.draftItems = itemsObj.items;
        data.hasMoreDrafts = itemsObj.hasMore;
        if (!gs.nil(localInput.attachmentTable))
          new global.GlobalServiceCatalogUtil().deleteAttachments(localInput.attachmentTable, localInput.removeItemID);
 
        return;
    }
 
    if (localInput && localInput.action == "fetch_more_draft_items") {
        itemsObj = getDraftItems(localInput.prevLimit + 100);
        data.draftItems = itemsObj.items;
        data.hasMoreDrafts = itemsObj.hasMore;
        return;
    }
 
    var alsoRequest = false;
 
    var msg = data.messages = {};
    msg.myRequestsTitle = options.title ? gs.getMessage(options.title) : gs.getMessage('My Requests');
    msg.openRequests = gs.getMessage('Open requests');
    msg.closedRequests = gs.getMessage('Closed requests');
    msg.closedWatchlist = gs.getMessage('Closed watch list');
    msg.showMoreRequests = gs.getMessage('Show More Requests');
    msg.requestsTabLabel = gs.getMessage("Submitted requests");
    msg.draftsTabLabel = gs.getMessage("Drafts");
    data.filterMsg = gs.getMessage("Search open requests");
    data.draftFilterMsg = gs.getMessage("Search draft items");
    data.draftItemsMsg = gs.getMessage("Draft Items");
    data.deleteDraftItemMsg = gs.getMessage("Are you sure you want to delete the draft item?");
    data.dialogCancel = gs.getMessage('Cancel');
    data.dialogDelete = gs.getMessage('Delete draft');
    data.draftSearchText = $sp.getParameter("draftSearchText");
    var selectDraftTab = $sp.getParameter('selectDraftTab');
    //localInput will be undefined only on the first load.
    if (gs.nil(localInput)) {
        data.isRequestsTabActive = !data.draftSearchText && !selectDraftTab;
        data.isDraftsTabActive = !!data.draftSearchText || selectDraftTab;
    } else {
        data.isRequestsTabActive = true;
        data.isDraftsTabActive = false;
    }
    if (gs.nil(data.draftSearchText))
        data.draftSearchText = "";
    else
        data.draftSearchText = decodeURIComponent(data.draftSearchText);
 
    data.hide_draft_tab = (gs.getProperty('glide.sc.disable.save_as_draft') == 'true') || (gs.getProperty('glide.sc.enable.save_as_draft.portal.' + $sp.getPortalRecord().getValue("url_suffix")) != 'true');
 
    var recordTable = options.record_table || $sp.getParameter("table");
    var recordId = options.record_id || $sp.getParameter("sys_id");
 
    data.is_associated_ticket_tab = options.is_associated_ticket_tab;
 
    if (localInput && localInput.view === 'open')
        data.filterMsg = gs.getMessage("Search open requests");
    else if (localInput && localInput.view === 'close')
        data.filterMsg = gs.getMessage("Search closed requests");
else if (localInput && localInput.view === 'closed_watchlist'){
data.filterMsg = gs.getMessage("Search closed watch list requests");
}
 
    data.is_new_order = (($sp.getParameter("is_new_order") + '') === "true");
    data.requestSubmitMsg = gs.getMessage('Thank You. Your request has been submitted');
    var draftItemsObj = getDraftItems(100);
    data.draftItems = draftItemsObj.items;
    data.hasMoreDrafts = draftItemsObj.hasMore;
 
    function getDraftItems(limit) {
        var userID = gs.getUser().getID();
        var cart = new SPCart("draft_items", userID);
 
        if (!gs.nil(cart) && typeof cart.getItemsWithPagination === "function")
            itemsObj = cart.getItemsWithPagination('sys_updated_on', limit);
        else {
            gs.info("Drafts tab is hidden as we are either unable to fetch the draft cart or the SPCart script include is customized.");
            data.hide_draft_tab = true;
            itemsObj = {};
        }
 
        return itemsObj;
    }
 
    function getField(gr, name) {
        var f = {};
        var id = gr.getUniqueValue();
        gr = new GlideRecord(gr.getRecordClassName());
        gr.get(id);
        f.display_value = gr.getDisplayValue(name);
        f.value = gr.getValue(name);
        var ge = gr.getElement(name);
        if (ge) {
            var ed = ge.getED();
            if (ed)
                f.type = ed.getInternalType();
            f.label = ge.getLabel();
        }
        return f;
    }
 
    function getMyRequestSysIds() {
        var ids = {};
        var rq_filter = new GlideRecord('request_filter');
        rq_filter.addActiveQuery();
        if (rq_filter.isValidField('applies_to'))
            rq_filter.addQuery('applies_to', 1).addOrCondition('applies_to', 10);
        rq_filter.query();
        while (rq_filter.next()) {
            var tableName = rq_filter.table_name;
            if (rq_filter.isValidField('table'))
                tableName = rq_filter.table;
            var gr = new GlideRecord(tableName);
            if (!gr.isValid())
                continue;
 
            gr.addQuery(rq_filter.filter);
            gr.enableSecurityFeature('data_filter');
            gr.query();
            if (tableName == 'sc_request')
                alsoRequest = true;
            while (gr.next()) {
                var portalSettings = {};
                portalSettings.page = rq_filter.portal_page.nil() ? '' : rq_filter.portal_page.getDisplayValue() + '';
                portalSettings.primary_display = rq_filter.primary_display.nil() ? '' : rq_filter.primary_display + '';
                portalSettings.secondary_displays = rq_filter.secondary_display.nil() ? '' : rq_filter.secondary_display + '';
                ids[gr.sys_id + ''] = portalSettings;
            }
        }
        return ids;
    }
 
    // retrieve the request's
    var myRequestMap = getMyRequestSysIds();
    var taskIDs = Object.keys(myRequestMap);
//new script added for closed watch list this is to get the tickets that are not created by the current login user but part of the watch list
if (localInput && localInput.view === 'closed_watchlist'){
    // separate arrays for task sys_ids and request sys_ids (avoid mixing them)
    var watchedTaskIds = [];
    var watchedRequestIds = [];
 
    var watchListGR = new GlideRecord('task');
    watchListGR.addQuery('active', 0);
    watchListGR.addQuery('watch_list', 'CONTAINS', gs.getUserID());
    watchListGR.query();
    while (watchListGR.next()){
        var tId = watchListGR.getUniqueValue();
        if (watchedTaskIds.indexOf(tId) === -1)
            watchedTaskIds.push(tId);
 
        // if this task is part of a request (parent request), capture the request sys_id separately
        if (watchListGR.request && !watchListGR.request.nil()) {
            var rId = watchListGR.request.toString();
            if (watchedRequestIds.indexOf(rId) === -1)
                watchedRequestIds.push(rId);
        }
    }
 
    // integrate watchedTaskIds into taskIDs (these are the task-level records the widget expects)
    // taskIDs originally comes from getMyRequestSysIds() and contains primary mapped record sys_ids
    watchedTaskIds.forEach(function(id){
        if (!taskIDs.includes(id))
            taskIDs.push(id);
    });
 
    // expose watchedRequestIds for RITM lookup later (attach to a local variable so later code can use it)
    data._watched_request_ids = watchedRequestIds;
}
 
    var gr = new GlideRecordSecure('task');
 
    if (!data.is_associated_ticket_tab) {
        if (localInput && localInput.view === 'open')
            gr.addActiveQuery();
        else if (localInput && localInput.view === 'close')
            gr.addQuery('active', 0);
else if (localInput && localInput.view === 'closed_watchlist')
//gr.addQuery('active', 0);
//gr.addEncodedQuery('watch_list', 'CONTAINS', gs.getUserID());
gr.addEncodedQuery('active=false^watch_listLIKE'+ gs.getUserID());
        else
            gr.addActiveQuery();
    } else {
        if (recordTable != 'universal_request') {
            //Check if universal_request field is present and it is a universal request
            var taskRecord = new GlideRecordSecure('task');
            taskRecord.get(recordId);
            if (taskRecord.isValid() && !taskRecord.universal_request.nil()) {
                var qc = gr.addQuery('parent', taskRecord.universal_request);
                qc.addOrCondition('parent', 'IN', new sn_uni_req.UniversalRequestUtilsSNC().getChildRequests(taskRecord.universal_request));
            } else
                gr.addQuery('parent', recordId);
        } else {
            var qc = gr.addQuery('parent', recordId);
            qc.addOrCondition('parent', 'IN', new sn_uni_req.UniversalRequestUtilsSNC().getChildRequests(recordId));
        }
    }
 
    gr.orderByDesc('sys_updated_on');
    if (localInput && localInput.search_text) {
        var req = [];
        var task = new GlideRecordSecure('task');
        task.addQuery('123TEXTQUERY321', localInput.search_text);
        if (localInput && localInput.view === 'open')
            task.addQuery('active', 1);
        else if (localInput && localInput.view === 'close')
            task.addQuery('active', 0);
else if (localInput && localInput.view === 'closed_watchlist')
            task.addEncodedQuery('active=false^watch_listLIKE'+ gs.getUserID());
        else
            task.addQuery('active', 1);
        task.addQuery('sys_id', taskIDs);
        task.query();
 
        while (task.next())
            req.push(task.getUniqueValue());
 
        var ritmGR = new GlideRecord('sc_req_item');
        if (alsoRequest && ritmGR.isValid()) {
            if (localInput && localInput.view === 'open')
                ritmGR.addQuery('request.active', 1);
            else if (localInput && localInput.view === 'close')
                ritmGR.addQuery('request.active', 0);
else if (localInput && localInput.view === 'closed_watchlist')
                ritmGR.addQuery('request.active', 0);
            else
                ritmGR.addQuery('request.active', 1);
            ritmGR.addQuery('123TEXTQUERY321', localInput.search_text);
            ritmGR.addQuery('request.sys_id', taskIDs);
            ritmGR.query();
            while (ritmGR.next())
                req.push(ritmGR.getValue('request'));
        }
        gr.addQuery('sys_id', req);
    } else
        gr.addQuery('sys_id', taskIDs);
 
    gr.enableSecurityFeature('data_filter');
    gr.query();
 
    data.request = {};
 
    data.request.req_list = [];
    var recordIdx = 0;
    var limit = options.items_per_page ? options.items_per_page : 15;
    if (localInput && localInput.action == 'fetch_more')
        data.lastLimit = localInput.lastLimit + limit;
    else
        data.lastLimit = limit;
 
    data.hasMore = false;
    while (recordIdx != data.lastLimit && gr.next()) {
        var portalSettings = myRequestMap[gr.getUniqueValue()];
        if (typeof portalSettings == 'undefined')
            portalSettings = {};
 
        var record = {};
        record.sys_id = gr.getValue('sys_id');
 
if (gr.getRecordClassName() == 'sc_request') {
            var ritm = new GlideRecord("sc_req_item");
            if (!ritm.isValid())
                continue;
 
            ritm.addQuery("request", gr.getUniqueValue());
            ritm.query();
            if (ritm.getRowCount() == 0)
                continue;
            if (ritm.getRowCount() > 1)
                record.display_field = gs.getMessage("{0} requested items", ritm.getRowCount());
            else {
                ritm.next();
                record.display_field = ritm.cat_item.getDisplayValue() || ritm.getDisplayValue("short_description");
            }
            record.url = {
                id: portalSettings.page ? portalSettings.page : 'sc_request',
                table: 'sc_request',
                sys_id: record.sys_id
            }
} else {
            record.display_field = portalSettings.primary_display ? getField(gr, portalSettings.primary_display).display_value : getField(gr, 'number').display_value;
            record.url = {
                id: portalSettings.page ? portalSettings.page : 'ticket',
                table: gr.getRecordClassName(),
                sys_id: record.sys_id
            };
        }
 
        record.display_number = getField(gr, 'number').display_value || '';
        if (portalSettings.secondary_displays) {
            record.secondary_displays = [];
            portalSettings.secondary_displays.split(",").forEach(function(sDisplay) {
                record.secondary_displays.push(getField(gr, sDisplay));
            });
        } else
            record.secondary_displays = getField(gr, 'short_description');
 
        record.updated_on = gr.getValue('sys_updated_on');
        record.state = gr.getDisplayValue('state');
        if ((recordIdx !== 0) && (data.lastLimit - limit === recordIdx))
            record.highlight = true;
 
        data.request.req_list.push(record);
        recordIdx++;
    }
 
    if (gr.next())
        data.hasMore = true;
 
})();

Hello @Itsmehiiiiiiii, Thanks for sharing the server side code. Like as I mentioned, the ideal way would be to create a request filter and OOB functionality takes care of displaying right fields on the portal. However, I notice that you are making your own Watched List in the code and storing in _watched_request_ids in the data object.

I'm wondering whether your watched list filter is displaying the right Requests/INCs, please verify this part once more. 

Although, there is no CS and HTML shared, I think you are passing the right input and comparing at the server side, you can make a few modifications as below

- Try to create my request filter as I suggested ( you can refer existing ones )

- stop populating data._watched_request_ids

- as you are already passing localInput.view === 'closed_watchlist', basis on the same you can decide in the following function getMyRequestSysIds() whether to look for watched list or not for e.g. OOB can continue as it is without watched list and for your new code, you can include your filter (surely you can decide somewhere else too, but on the fly I feel this place better)

- keep other checks enabled localInput.view === 'closed_watchlist'

 

As I'm sharing my thoughts without validating, you might have to make a few adjustment, but this approach is much easier and will allow you to enable/disable functionalities at later stage.

should you need any further assistance, please feel free to respond back, I can try at my end at my free time as you know this might take some time.

 

Regards,

Nishant

Hi @Nishant8 ,

I tried creating a request filter for closed watchlist items, but I noticed it’s also affecting the existing open and closed requests. Records that are part of the watchlist are being included in both the open and closed request lists.

 

I created the request filter for both Incident and Requested Item and added a condition where the Watchlist contains javascript:gs.getUserID() and the State is Closed.

 
Can you help me with this? When I create that request filter its displaying the correct title. Thank you for that