Maik Skoddow
Tera Patron
Tera Patron

find_real_file.png

 

It seems that more and more users are working with Agent Workspace because there are more and more questions asked about this topic here in the community.

And of course it is also about enhancing the existing configuration options and features with own customizations so that the daily routines are supported even better.

For example there are questions about how a modal displaying list of records can be opened by clicking on a button.

 

find_real_file.png

 

The button is implemented easily, because UI Actions have a section for workspaces, but opening and configuring a modal is the challenge.

On the documention page there is a manual for that, but it is rather sparse and in practice there are unexpected difficulties that are not mentioned there. 

And so I started some extensive investigations to find out how to create a modal with a predefined list of records. The use case is getting a list of Incidents for the same caller as for the one in the currently opened Incident record.

The following step-by-step guide describes how to do that, but you also can download the attached update set, which contains all necessary artifacts.

 

(1) Create a "Registered Scripting Modal"

According to the documentation, you first have to register a component in the table sys_aw_registered_scripting_modal, which then can be referenced in the UI Action.

I don't know why, but access to that table is currently restricted due to underlying ACLs. Therefore, the first task is to modify the ACLs. I just checked the option "Admin overrides":

 

find_real_file.png

 

After that you can create a new record with the following configuration values:

 

find_real_file.png

 

Hint:
The "Public API" value is needed for the UI Action script.

 

(2) Create and configure a UI Action

Create a UI Action "Related Incidents" with the following configurations:

 

Activetrue
Updatetrue
Clienttrue
Condition!gs.nil(current.caller_id)
Workspace Form Buttontrue
Workspace Client Script
function onClick(g_form) {
	g_modal.global.showRecordList({
		title: 'Related Incidents for ' + g_form.getDisplayValue('caller_id'),
		confirmTitle:'Close',
		size: "xl",
		params: {
			"listTitle" : "Related Incidents",
			"table" : "incident",
			"query" : "caller_id=" + g_form.getValue("caller_id"),
			"columns" : "number,short_description,state,category",
			"limit" : "20",
			"maxColumns" : "5",
			"page" : "1",
			"hideHeader" : true,
			"hideTitle" : false,
			"hideTitleRowCount" : false,
			"hideRefreshButton" : false,
			"hideLastRefreshedText" : false,
			"hideLinks" : false,
			"hideViewAll" : false,
			"hideHighlightedValues" : false,
			"hideCellFilter" : false,
			"hideCheckboxHover" : true,
			"hideColumnFiltering" : false,
			"hideColumnGrouping" : true,
			"hideColumnSorting" : false,
			"hideFilterPanel" : false,
			"hideFirstPage" : false,
			"hideLastPage" : false,
			"hideLimitSelector" : false,
			"hideMenuButton" : false,
			"hideMultiEdit" : false,
			"hideDeclarativeActions" : false,
			"hideNextPage" : false,
			"hidePages" : false,
			"hidePagination" : false,
			"hidePanel" : false,
			"hidePanelAdvanced" : false,
			"hidePanelConditionDelete" : false,
			"hidePanelFooter" : false,
			"hidePanelRestore" : false,
			"hidePreviousPage" : false,
			"hideQuickEdit" : true,
			"hideRange" : false,
			"hideRowCount" : false,
			"hideRowSelector" : true,
			"hideSelectAll" : true,
			"hideEmptyStateImage" : false,
			"hideDBViews" : false,
			"hideUnnecessaryRowSelectors" : true
		}
	}).then(function(result) {}, function(error) {});	
}

 

Hints:

  • On line 2 you can find the previously configured Public API Call g_modal.global.showRecordList
  • On line 3 and 9 display name and Sys ID of the caller are handed over to the modal.
  • There are a lot of parameters you can play with.

 

You may wonder where I got all the configuration parameters from, and I want to give the answer here. If you are not interested in, you can skip the rest of the article.

As there is no reference documentation for UX components you have to do a kind of reverse engineering. As described on the documentation page create an empty landing page and then drop a list component on that page:

 

find_real_file.png

 

Next, open the developer console of your browser and inspect the HTML code. You have to find the component of the page itself:

 

find_real_file.png

 

Take the Sys ID from the tag <now-uxf-page> and enter it in my dashboard widget "Open record page by Sys ID" to perform a search for the corresponding record.  

For the above Sys ID a record at table sys_ux_macroponent was found. It has two fields with JSON based data:

  • Layout Model: Describes the page layout and the placement of components in it.
  • Composition: Contains the configuration parameters of each component.

 

And at field "composition" you can find all the configuration parameters from the above Workspace Client Script:

 

find_real_file.png

Comments
Sushma5
Kilo Explorer

How to get the selected incidents from modal back to UI action from where it was triggered

Daniel Biesiada
Mega Guru

Have you found any solution? 

alvaroruiz
Tera Contributor

Really good article. It is really helping me to understand the Now Experience capabilities 🙂

 

One question though, where does the value on the API field came from? I was looking on the Developer site for a list of API, but no luck

 

find_real_file.png

Thanks for your help!

Maik Skoddow
Tera Patron
Tera Patron

The value at field "Public API" is generated automatically by concatenating of "g_modal.global." & value from field "API".

Regarding the g_modal object please see https://www.ashleysn.com/post/workspace-ui-actions

Kind regards
Maik

alvaroruiz
Tera Contributor

Thanks a lot for the link and the details. Have a great day!!

Pedro Capit_o
Tera Contributor

Hi Maik,

 

First of all I would like to thank you for this article. I used it for a similar implementation - to have a list with the CIs Assigned to the Caller.

However, I'm currently blocked in a situation: to have the selected CI from the list to populate the cmbd_ci field on the Incident form.

Do you have any suggestions on this can be achieved? I tried to do this with the select box on the left hand side of the this together with the "Confirm" button on the modal, but with no success. Also, after clicking the CI, it opens the record on a new tab on the Agent Workspace.

 

Thanks a lot for your help!

Daniel Biesiada
Mega Guru

Hi Pedro,

I've had simillar requirments, this is how I managed to solve this:

find_real_file.png

After selection and click ok the field is populated. 

I know it doesn't look good but it was first aproach to componnents. 

UI action code from Workspace Client Script: 

function onClick(g_form) {
var table = "cmdb_ci";
var user = g_form.getValue("caller_id");
var query = "u_active=true^assigned_to=" + user;


g_modal.global.showCIList({
title: 'Caller CI',
confirmTitle: 'OK',
cancelTitle: "Cancel",
size: "xl",
params:{
dataQuery:query,
}
}).then(function(result) {
g_form.setValue("cmdb_ci", result.data.selectedValue);
});

}


additionally I have created custom component index.js of this component:

import { createCustomElement,actionTypes  } from '@servicenow/ui-core';
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';
import { createHttpEffect } from '@servicenow/ui-effect-http';

const { COMPONENT_BOOTSTRAPPED } = actionTypes;

const view = (state, { updateState, dispatch}) => {

	const { dataColumns, dataTable } = state.properties;
	var {dataRows} = state.properties
    var mainRow = Object.keys(dataColumns[0])[0];


	if(state.dataRows)
		var {dataRows} = state;

	return (
		<div class="container">
			<div className="table-container">
                <table>
                    <thead>
                        
                        <tr>
                            <th>
                                Selected
                            </th>
                            {dataColumns.map((col) => {
                                if(col.name == 'sys_id')
                                    return("")
                                
                                return (
                                    <th>
                                        {col.label}
                                    </th>
                                );
                            })}
                        </tr>
                    </thead>
                    <tbody>
						
                        {dataRows.map((row) => {
							
                            return (
                                <tr >
                                    <td>
                                        <input onclick={() => {dispatch("ROW_choosen", row)}} name="selection" id="selection" class="btn btn-primary" type="checkbox" value={row}></input>    
                                    </td>
                                    {dataColumns.map((col) => {
                                        if(col.name == 'sys_id'){
                                            return("")
                                        }
										 if(col.name == mainRow){
                                               
                                            return (
                                                <td>
                                                    {row[col.name]}
												</td>
											)}
                                        return (
                                            <td>
                                                {row[col.name]}
                                            </td>
                                        )
                                    })}
                                    
                                </tr>
                            );
                        })}
                    </tbody>
                </table>
            </div>
		</div>
	);
};

createCustomElement('ELEMENT_NAME', {
	renderer: { type: snabbdom },
	view,
	styles,
	actionHandlers: {
		'FETCH_DATA': createHttpEffect('api/now/table/cmdb_ci', {
            method: 'GET',
            queryParams: [
                'sysparm_fields',
                'sysparm_query',
                'sysparm_display_value',
                'sysparm_exclude_reference_link'
            ],
            successActionType: 'FETCH_DATA_SUCCEEDED'
        }),
		[COMPONENT_BOOTSTRAPPED]: (coeffects) => {
            const { dispatch } = coeffects;
			const {dataTable, dataColumns, dataQuery} = coeffects.properties;
            const query = dataQuery;
			
            const fields = dataColumns.map((col) => {
                return col.name;
            }).join(',');
			
            dispatch('FETCH_DATA', {
                sysparm_query: query,
                sysparm_display_value: 'all',
                sysparm_exclude_reference_link: true,
                sysparm_fields: fields
            });
        },
		'FETCH_DATA_SUCCEEDED': (coeffects) => {
            const { action, updateState } = coeffects;
            const { result } = action.payload;
			
            const dataRows = result.map((row) => {
                return Object.keys(row).reduce((acc, val) => {
                    if (val === 'sys_class_name') {
                        acc[val] = row[val].value;
                    } else {
                        acc[val] = row[val].display_value;
                    }

                    return acc;
                }, {});
            });

            updateState({ dataRows });
        },
		'ROW_choosen':(coeffects) =>{
			const { dispatch } = coeffects;
            var data =  coeffects.action.payload.sys_id
            console.log(data);
			dispatch('SN_SCRIPTED_MODAL#DATA_SET', {
				selectedValue:data
			});  

		}
	},
	properties: {
		dataTable: {
			default: "cmdb_ci"
		},
		dataQuery: {
			default: 'manufacturerISNOTEMPTY^locationISNOTEMPTY^short_descriptionISNOTEMPTY^assigned_toISNOTEMPTY'
		},
		dataColumns: {
			default: [{ name: 'name', label: 'Name' }, { name:'manufacturer', label: 'Manufacturer' }, { name:'sys_class_name',label: 'Class' }, {name:'location', label: 'Location' }, { name:'short_description',label: 'Description' }, { name: 'assigned_to', label: 'Assigned to' }, {name:'sys_id', label:'Selected'}]
		}, 
		dataRows: {
			default: []
		}
	}
});

One again please see it more like a guide thean premade solution I'm not fully satisfied. 

Hope it helps you

Best Regards, 
Daniel

Bryan3
Tera Expert

Has anyone else been able to get this to work? I have completed the steps, but my UI Action still does not show within Agent Workspace.

Pedro Capit_o
Tera Contributor

Hi Bryan, 

 

Yes, I got it to work.

Did you check the "Workspace Form Button" or Workspace Form Menu" checkboxes? Otherwise, the UI Action won't appear in Agent Workspace.

Hope it helps!

 

Best regards, 

Pedro

jiral
Giga Sage

Is "index.js" something you can view in the instance itself without the need for the CLI?

Daniel Biesiada
Mega Guru

I don't thing so It's probably not kept in the instance in this human readable format

OGeza
Tera Expert

Hey @Daniel Biesiada, did you find a solution?

gcm1
Tera Expert

Hi @Daniel Biesiada please, where exactly you have created this custom component index.js?

Vivek Singla1
Tera Contributor

Hi @Maik Skoddow 

 

I have implemented the same in the past and it helped me in achieving the requirement in Agent Workspace. But in SOW, the links are not working. When click on the record number they are clickable but are not navigating anywhere. I hope you can help me with that. If something additional needs to be done for SOW please let me know.

Sarah15
Tera Expert

Exactly what I was looking for. Since I wanted it available on only a new record so the agent can see the list before saving (related lists show after) I simply took the condition off of the UI action and added a check for a value in the script to throw a message to add a value. Excellent documentation.  

Sarah15_0-1677155849006.png

 

Richard Hine
Tera Guru
Tera Guru

Did any of you manage to figure out how to work out what record in a list had been selected and return that to the form that launched the UI action?

 

I am having the same issue and am wondering if it is something I am not doing or if the component isn't exposing that data...

 

Thanks

 

Richard

Pablo Sanz
Tera Guru

If I want to open the form of the record shown in the list, how can I do it? Is there any way that clicking on it could redirect the user?

Pablo Sanz
Tera Guru

Hi @Vivek Singla1 

 

I have the same problem, in SOW it doesn't do any action, could you solve it somehow?

caro234
Tera Contributor

Hello,

 

Great article, very helpful ! 

Another question, I would like to insert all selected values into another table, how can I achieve that? 

How to get back selected data ou how to add action on submit in modal popup?

 

Thanks

 

 

sravanikinfy
Tera Contributor

Hi All,

Am able to get the related incidents in workspace related to impacted user (caller) but am unable to open the incident from the pop up.

can some one please help me to activate the link for incident.

caro234
Tera Contributor

Hello, 

 

Works fine in workspace. How to achieve the same output in classic ui ? 

 

Thanks

Savitha8
Tera Contributor

Hi @Maik Skoddow ,

Am able to get the related incidents in workspace related to impacted user (caller) but am unable to open the incident from the pop up.

can some one please help me to activate the link for incident.

TahzeenP
Tera Explorer

Hi , Great article. I am able to get the list. How to open the record?

Version history
Last update:
‎12-15-2020 01:20 AM
Updated by: