Martin Rudack
Mega Sage

Note: This is part three of a series of articles where we will develop Custom Components with the Now Experience UI Framework. This series started with two articles about Custom GraphQL APIs. We are going to use these APIs to fetch data from the platform for our component. Custom Components - Custom GraphQL API Part1 | Custom Components - Custom GraphQL API Part2

 

In this article, we start with the development of our Custom Component. It should display the Requests of a contact in the Contextual Side Panel in the Agent Workspace. The end result should look something like this:

find_real_file.png

 

 

Note: I will not explain how to install the Now-CLI here. There are already a lot of good articles written about this topic. I assume that you have at least developed and deployed a simple HelloWorld Component.

 

    -. Create a new project

now-cli project --name @<company>/contact-req  

 

    -. Install dependencies

npm install

 

ServiceNow provides with the package “@servicenow/ui-effect-graphql” an effect that creates GraphQL requests.

To use this, we must first install the package using npm.

 

npm install @servicenow/ui-effect-graphql -E

 

 

find_real_file.png

 

The function createGraphQLEffect takes two parameters. The first parameter is our GraphQL query. The second parameter is an object with options for the query. We will use the following options.

options.variableList - to pass the sys_id of the contact into the query

options.successActionType - to specify an action that should be dispatched when the query has successfully completed. We create an action handler for this action, to process the data and display it in our view.

option.errorActionType - to specify an action that should be dispatched in case that the query failed. We create an action handler for this action, to do the error handling.

 

We import actionTypes and define a constant for the lifecycle action COMPONENT_BOOTSTRAPPED.

import {createCustomElement, actionTypes} from '@servicenow/ui-core';
const { COMPONENT_BOOTSTRAPPED } = actionTypes;

 

Import createGraphQLEffect and define constants for the success and failure actions.

import { createGraphQLEffect } from '@servicenow/ui-effect-graphql';
const DATA_FETCH_SUCCEEDED = 'DATA_FETCH_SUCCEEDED';
const DATA_FETCH_FAILED = 'DATA_FETCH_FAILED';

 

Next, we define our GraphQL Query

const requestQuery = `
query($contactID: ID!){
    x413019 {
        agineo {
            contactRequest(contactID: $contactID) {
                number
                state
                items{
                    number
                    stage
                    cat_item
                    variables{
                        question_text
                        value
                    }  
                }
            }
        }
    }
}`;

 

const dataFetchHandler = createGraphQLEffect(requestQuery, {
    variableList: ['contactID'],    
    successActionType: DATA_FETCH_SUCCEEDED, 
    errorActionType: DATA_FETCH_FAILED      
});

 

Now we need a property for the sys_id of the contact. We name it contact. This should take the sys_id of the contact that we are currently viewing in the Agent Workspace. For the development phase, we specify a default value with a valid sys_id. Also, we add an array to the state of the component to store the Requests.

 

    initialState: {
        requests: []
    },
    properties: {
        contact: { default: "9951981c2f996410e1717f572799b6df" }
    }

 

 Now it is time to create the actionHandler for the Component

actionHandlers: {
        "FETCH_DATA_REQUESTED": dataFetchHandler,
        [COMPONENT_BOOTSTRAPPED]: (coeffects) => {
            coeffects.dispatch("FETCH_DATA_REQUESTED", { contact: coeffects.properties.contact });
        },
        [DATA_FETCH_SUCCEEDED]: (coeffects) => { coeffects.updateState({ requests: coeffects.action.payload.data.x413019.agineo.contactRequest }) }
        
    }

 

 

If the action FETCH_DATA_REQUESTED is dispatched, we create the GraphQLEffect.

If the Component is bootstrapped we dispatch the action FETCH_DATA_REQUESTED and add the contact property with the sys_id of the contact to the payload.

If the GraphQL query was successful and the action DATA_FETCH_SUCCEEDED is dispatched, we update the state of the component with the result of the query.

 

 

The only thing that is missing now is the presentation of the data. In this article, we will only do a very basic presentation without any styling, but we will deploy the component to the ServiceNow instance and configure it for the Contextual Side Panel. In the next article, we will have a look at the Now Experience Design System and change the style of the Component so that it integrates seamlessly into the Workspace and creates a great user experience.

In our view, we return the following:

 

<div>
	{state.requests.map(request =>
		<span>
			<p>Request Number: {request.number}</p>
			<p>State: {request.state}</p>
			{request.items.map(item =>
				<span>
					<p>RITM Number: {item.number}</p>
					<p>Catalog Item: {item.cat_item}</p>
					<p>Stage: {item.stage}</p>
					{item.variables.map(variable =>
						<span>
							<p>{variable.question_text}: {variable.value}</p>
						</span>
					)}
				</span>
			)}
		</span>
	)}
</div>

 

 Let's deploy the Component to the ServiceNow instance so that we can place it in the Agent Workspace.

now-cli deploy

 

 

Once it is deployed we will configure the component for the Contextual Side Panel.

Navigate to “Workspace Experience > Actions & Component > Contextual Side Panel” and create a new record.

 find_real_file.png

 

We select the Agent Workspace for Workspace and Contact for Table because this Component should only be available if we display a Contact in the Agent Workspace.

We select the Component that we have developed, and an Icon that fits the content of the Component.

Action Label is the name that appears on the Component we are adding to the Contextual Side Panel. 

Action Name is the name that appears for this record in the list of action assignments. 

Submit the record.

 

After the record is submitted, we look at the related list Action Model Fields.

find_real_file.png

These fields can be used in our Component when we define a property with the same name. That means we need to change the property contact to sysId.

properties: {
        sysId: { default: "" }
    },

 

Don't forget to change it also in the actionHandler for COMPONENT_BOOTSTRAPPED.

        [COMPONENT_BOOTSTRAPPED]: (coeffects) => {
            coeffects.dispatch("FETCH_DATA_REQUESTED", { contactID: coeffects.properties.sysId });
        },

 

To test the Component, open a contact in the Agent Workspace. The Component should look like this: 

find_real_file.png

 

 

The next article focuses on User Experience and how to make this Component look great.

Custom Components - Using Now Experience Components

 

 

Complete index.js

import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';

import { createCustomElement, actionTypes } from '@servicenow/ui-core';
const { COMPONENT_BOOTSTRAPPED } = actionTypes;
import { createGraphQLEffect } from '@servicenow/ui-effect-graphql';
const DATA_FETCH_SUCCEEDED = 'DATA_FETCH_SUCCEEDED';
const DATA_FETCH_FAILED = 'DATA_FETCH_FAILED';

const requestQuery = `
query($contactID: ID!){
    x413019 {
        agineo {
            contactRequest(contactID: $contactID) {
                number
                state
                items{
                    number
                    stage
                    cat_item
                    variables{
                        question_text
                        value
                    }  
                }
            }
        }
    }
}`;

const dataFetchHandler = createGraphQLEffect(requestQuery, {
    variableList: ['contactID'],
    successActionType: DATA_FETCH_SUCCEEDED,
    errorActionType: DATA_FETCH_FAILED
});


const view = (state, { updateState }) => {
    return (
        <div>
            {state.requests.map(request =>
                <span>
                    <p>Request Number: {request.number}</p>
                    <p>State: {request.state}</p>
                    {request.items.map(item =>
                        <span>
                            <p>RITM Number: {item.number}</p>
                            <p>Catalog Item: {item.cat_item}</p>
                            <p>Stage: {item.stage}</p>
                            {item.variables.map(variable =>
                                <span>
                                    <p>{variable.question_text}: {variable.value}</p>
                                </span>
                            )}
                        </span>
                    )}
                </span>
            )}
        </div>
    );
};

createCustomElement('x-413019-contact-req', {
    renderer: { type: snabbdom },
    view,
    styles,
    initialState: {
        requests: []
    },
    properties: {
        sysId: { default: "" }
    },
    actionHandlers: {
        "FETCH_DATA_REQUESTED": dataFetchHandler,
        [COMPONENT_BOOTSTRAPPED]: (coeffects) => {
            coeffects.dispatch("FETCH_DATA_REQUESTED", { contactID: coeffects.properties.sysId });
        },
        [DATA_FETCH_SUCCEEDED]: (coeffects) => { coeffects.updateState({ requests: coeffects.action.payload.data.x413019.agineo.contactRequest }) }
    }
});
Comments
CezaryBasta
Tera Guru

This guide is way better than official documentation! It saved me hours of debugging. Thank you very much. Even though it's the end of 2022... I hope you are doing well! Thank you and enjoy your life @Martin Rudack !

Version history
Last update:
‎01-06-2021 12:06 AM
Updated by: