The CreatorCon Call for Content is officially open! Get started here.

zhaoyanghan
ServiceNow Employee
ServiceNow Employee

Summary  

Create custom event categories and types in Workforce Optimization for ITSM using extension points. 

In the Workforce Optimization for ITSM manager workspace, managers can use Schedule to use the team calendar to create events. Managers can use these five event categories that are available by default to create events of these types: 

  • Meeting 
  • Break 
  • Training 
  • Time Off 
  • Work 

If you need event categories that are not available by default, you can create custom ones using extension points. 

 

Extension Points for Schedules in Manager Workspace 

The extension point to create custom event categories in the manager workspace Schedule module is sn_shift_planning.EventManager. This extension point is similar to an API blueprint for all event categories and only has API names but does not contain the code for the functionalities. The event manager in the sn_shift_planning.EventManager extension point is used for the custom implementation of event categories.  

 

Two event categories that use the same API may be implemented differently. For example, to create events, the implementation of the createEvent API could be different for the Work versus Training event categories. The Schedule application will call the API implementation specific to each event category.  For more information, see  Event type extension points in Workforce Optimization for ITSM.

 

Content Overview

1. Creating New Event Categories for a Custom Event Type 

Step 1- Create a new event category 

Step 2- Locate the extension point 

Step 3 - Create an Implementation for the new Category 

Step 4- Update the SYS_ID of the new Event Category 

Step 5 - (Optional) Customize the createEvent method as needed 

Step 6- Create a new Event type 

2. APIs for Creating New Event Categories 

API – readEventForUsers 

API – processFilter 

API – createEvent & editEvent 

API – deleteEvent 

3. Testing creating a new event with the new event category you just created 

4. Creating New Event Categories for a Custom Event Type (Advanced) 

Step 1 - Create new Event Category 

Step 2 – Locate the form viewport from UIB 

Step 3 – Create new page variant 

Step 4 – Continue with step 2 in the Creating New Event Categories for a Custom Event Type 

 

 

1. Creating New Event Categories for a Custom Event Type

Step 1 - Create a new event category.  

Note: There's an advanced way to create event category, which is illustrated at the end of this blog.

  1. Navigate to Workforce Optimization for ITSM > Scheduling > Event Categories from navigation bar. 
  2. Click New. zhaoyanghan_0-1727990734590.png
  3. In the Name field, enter a unique name for the event type you want to create. 
  4. Customize the code in the Event field configuration field as needed.  
    Note:The event field configuration field in the Event Category form is used to configure the Event type forms such as creating, editing, deleting events in the Manager Workspace zhaoyanghan_1-1727990734591.png

The Event type form in the image below is a representation of a Create event form rendered from the configuration output implemented in the Event field configuration field.  

 

zhaoyanghan_2-1727990734592.png

 

Step 2 - Navigate to System Extension Points > Scripted Extension Points and go to the sn_shift_planning.EventManager. 

zhaoyanghan_3-1727990734592.png

 

zhaoyanghan_4-1727990734594.png

 

 

Step 3 - Create an Implementation for the new Category [Example Category: Overtime] 

In the Related Links section, click Create Implementation. 

zhaoyanghan_5-1727990734596.png

 

Step 4 - Update the SYS_ID of the new Event Category 

In the script, add the SYS_ID for the Overtime event category.  

zhaoyanghan_6-1727990734598.png

 

Step 5 – (Optional) Customize the createEvent method as needed 

 

zhaoyanghan_7-1727990734599.png

 

 

Step 6 - Create a new Event type  

Note: 

  • An event type is a child of an event category. It is what shows up on the calendar when a user creates an event. It also specifies the look and the feel of the event. 
  • All event types under an event category share a common API implementation and the Event field configuration for the event category.  

 

  1. From the navigation bar, go to Workforce Optimization for ITSM > Scheduling>Event Types. 
  2. Click New and create an event type as shown in this image: 

zhaoyanghan_8-1727990734600.png

 

2. APIs for Creating New Event Categories 

Below are the sn_shift_planning.EventManager APIs outlined as a blueprint. You can implement these APIs as needed.  

 

API - readEventForUsers  

Description  

Query events for the users between startDate and endDate (inclusive), adding results to collectedUserSchedules object.  

Usage: Used for loading events into the calendar.  

NOTE: non-optimal implementation will impact calendar load times.  

 

What is expected: 

  1. We collect schedules from multiple event categories/event extensions.  
  2. In the “collectedUserSchedules” accumulator object there might be schedules from other event categories that were read before your event category.   
  3. “collectedUserSchedules” is object with keys as “sysId” of the user. For each user there are objects with keys as start date in “YYYY-MM-DD” format.   
  4. As part of the read implementation, add events to the “collectedUserSchedules” collection per user per date. The schema of the object that needs to be added should match as shown in the Output section below.   
  5. In summary, given these inputs: user, startDate and endDate, you can get the events per user per date.  
  6. Important! Do not modify “collectedUserSchedules” when not needed as this is an accumulation of objects from other event categories too.   
  7. Use “this.queryFilter” which was created as part of “processFilter” while reading. So, when a user applies filters on calendar this will be populated using “processFilter” and hence will be used in your read method. Refer to  the “processFilter “API described below for more information on filtering.  
  8. Since your events can be from multiple time zones, return an object of time zones that will be used in calendar dropdown of time zones.  

Input  

Input to Read Method:  

  • collectedUserSchedules: accumulator object  
  • startDate: String, YYYY-MM-DD  
  • endDate: String, YYYY-MM-DD  
  • users:  Object with sys_id of sys_user record as keys and values as name, example: 

 

 

* {  
*   02acf036b3120010ed7fc9c316a8dc0c: {  
*     name: Francene Palma  
*     userGroups: Object of groups user belongs to  
*   }  
* }  

 

 

 

Output  

Return the timezone of events as {value : displayValue} used to show timezones in the calendar dropdown.  

 

 

* {  
*   "US/Pacific": "Pacific Time Zone",  
*   "Europe/London": "London"  
* }  

 

 

CollectedUserSchedules accumulator object should have content, example:  

 

 

* { 02acf036b3120010ed7fc9c316a8dc0c: {  
*   "2020-10-09": [  
*     {  
*       "schedule": {  
*         "eventType": {  
*           "sysId": "49891e0d0faf0010717cc562ff767e72",// We will pull event configuration details like color based on this 
*         },  
*         "workSchedule": {  
*           "sysId": "330a730d89a75010f8771496f1e387c7" //ID used in Manager Workspace for edit and delete operations  
*         },  
*         "isPublished": true // If false, this event is not shown on agent calendar  
*       },  
*       "name": "Planned Leave", // Title that shows up in calendar  
*       "description": "Laquita, Len & Lenore PTO", // Description that is show in popover  
*       "isExclusion": true, // determines if this event will be counted as available time in coverage  
*       "readInTimezone": "America/Los_Angeles",// Used to   
*       "scheduledStartTime": "2020-10-09 00:00:00", // start time of the event. Should be YYYY-MM-DD HH:mm:ss  
*       "scheduledEndTime": "2020-10-13 23:59:59", // end time of the event. Should be YYYY-MM-DD HH:mm:ss  
*       "startDate": "2020-10-09"  
*   }]  
* }}  

 

 

 

API – processFilter  

Description  

Filter is passed as param in REST call to read schedules. This method is called before reading so the query can be used while reading. Storing the query in this queryFilter will help in reading.  

 

What is expected:  

  1. This method is called by us before calling the read method. Hence, you can use this method to set up objects that you can use in your read method so filtering can be considered.  
  2. Filter object has keys with table names.   
  3. Each object inside table name will have field name as the key with array of strings that correspond to values being searched.  
  4. You can use this object to create your own filter object that can be used in your “readEventForUsers” method. That way when user applies filter on UI your response will use that filter.   
  5. We expect you to return true if there was some object inside the filter that corresponds to the event category. This will help us in not reading other event categories when the filter is specific to your category.   

Example:  

Consider a filter looks like: 

 

 

* { 
* "sn_shift_planning_agent_schedule": { 
*   " type": “Time off”, 
*   "state”: “Approved” 
*   } 
* } 

 

 

You can build processFilter API as: 

 

 

processFilter: function(filter) {  
  var tableFilter = filter[tableName]; 
  var fieldQuery = []; 
  for (var fieldName in tableFilter) { 
    var fieldFilterValue = tableFilter[fieldName]; 
    for (var i = 0; i < fieldFilterValue.length; i += 1) { 
      fieldQuery.push(fieldName + "=" + fieldFilterValue[i].trim()); 
    } 
  } 
  this.encodedQuery = fieldQuery.join('^'); 
  return true; 
} 

 

 

The above implementation build a query: “type=Time off^state=Approved”, and it’s assigned to a global variable: encodedQuery. 

Later when implementing ReadEventForUsers API, the encodedQuery can be used: 

 

 

readEventForUsers: function(collectedUserSchedules, startDate, endDate, users, eventsToRead) { 
  // ... your implementation 
  // ... your implementation 
  // ... 
  var gr = new GlideRecord(“sn_shift_planning_agent_schedule”); 
  gr.addEncodedQuery(this.encodedQuery); 
  // ... more implementation 
  // ... 
} 

 

 

This will fetch all the approved time-off schedules, and it’s your choice how to process them. 

 

Input  

Each object format is table: { field: [values] } }

Example:  

 

 

* filter: with out of box filters configured  
* {  
*   "sn_shift_planning_agent_schedule": {  
*     "shift_plan": ["ce12289e080b0010f87758e77b93f204"]  
*   },  
*   "sys_user_has_skill": {  
*     "skill": ["48c9eda9c0a8018b4b6aca082d5d1e41"]  
*   },  
*   "sys_user": {  
*     "location.city": ["Aurora"]  
*   },  
*   "sn_wfo_manager_groups": {  
*     "grmember_group": ["f31b182fb3210010ed7fc9c316a8dc2a"]  
*   },  
*   "is_published": "0",  
*   "eventsFilter": [],  
*   "eventsCategoryFilter": ["618350da777610100f7a72f969106191"]  
* }  

 

 

 

Output  

Retrun True if the filter object has properties that are valid for this event category.  

 

API – createEvent & editEvent  

Description  

This function should create records for the event category for which this implementation is for, using the parameters passed.  

What is expected:  

  1. We use the JSON configuration mentioned in the event category to populate fields on the modal for create/edit. And payload will have the same fields passed into the API.  
  2. API then decides the implementation based on the event category and calls your implementation.  
  3. The start and end times are in the time zone passed “readInTimezone”.  
  4. If there are any errors that you want to propagate to users, you can pass the output object with “error” status.  

Input  

The parameters passed will have the JSON configuration mentioned in the event_category record. A JS Object that may vary depending on the event type, but below are common properties in that object:  

 

 

* {  
*   eventCategory: String, //sys_id of sn_shift_planning_event_category record, should be for the category this script is for  
*   title: String,  
*   attendees: [{id: String, label: String}], //id properties are sys_ids of sys_user records, labels are the user's names  
*   startDateTime: String, //format YYYY-MM-DD HH:mm:ss  
*   endDateTime: String, //format YYYY-MM-DD HH:mm:ss  
*   description: String,  
*   readInTimezone: String //of the querying user  
* }  

 

 

Example: for Work Shift creation payload  

 

 

* {  
*   "recordId": "",  
*   "eventCategory": "07e433f449a11010f877de942e7c1d18",  
*   "eventType": "8d03fd9b0f310010717cc562ff767e4a",  
*   "readInTimezone": "America/Los_Angeles",  
*   "group": "",  
*   "requestor": "6816f79cc0a8016401c5a33be04be441",  
*   "mode": "create",  
*   "context": "manager_workspace",  
*   "title": "",  
*   "attendees": [{  
*     "id": "8aacb036b3120010ed7fc9c316a8dcec",  
*     "label": "Alberta Viveros"  
*   }],  
*   "startDate": "2020-11-25",  
*   "endDate": "2020-11-26",  
*   "description": "",  
*   "additionalSelectors": {  
*     "sn_shift_planning_shift_plan": {  
*       "name": "cae52812084b0010f87758e77b93f2ee"  
*     }  
*   }  
* }  

 

 

Output  

 

 

* {status: "success", message: "Created event successfully"}  
* {status: "error", message: "Incorrect params"}  

 

 

 

API – deleteEvent  

Description  

This function should delete records for the event category this implementation is for, using the parameters passed. If the event has multiple participants, it will be removed for all participants  

 

What is expected:  

  1. We support deleting one event at a time.   
  2. The parameters will have the record Id that can be used in the method implementation.  
  3. Output can have a status that directs the message that is shown to the user.   
  4. If the event has multiple participants, it will be removed for all participants  

Input  

A JS Object that may vary depending on the event type, but below are common properties in that object: 

 

 

* {  
*   eventCategory: String, //sys_id of sn_shift_planning_event_category record, should be for the category this script is for  
*   startDate: String, //YYYY-MM-DD, represents date of particular span to remove.  
*   recordId: String, //sys_id of object that represents the event that you are deleting  
*   attendees: [{"id": String }], //an array of Object with key as id and value as sysid of users who are a collection of participants for the event  
* }  

 

 

 

Output  

Should return an object in one of two formats: 

 

 

* {  
*   body: {  
*     status: "success",  
*     message: String //Return message, for conveying success  
*   }  
* }  
*   
* or  
*   
* {  
*   error: String //Error message that explains what went wrong  
* }  

 

 

 

3. Testing creating a new event with the new event category you just created 

Let’s see how to create a new Overtime Event in the Manager Workspace 

  1. Impersonate the WFO manager persona. 
  2. Go to Workspaces > Manager Workspace. The Overtime event type appears in the Team calendar tab in the Schedule module.  
  3. Click Overtime to create a new Overtime event. 

zhaoyanghan_7-1727992278095.png

 

4. (Advanced) Customize an Extension Point 

Thus far, we’ve seen how to use an existing extension point to create new event categories with defining the Event field configuration field.  

Now we’ll see how to customize the form without having to use the Event field configuration field. 

Step 1 - Create new Event Category 

  1. Navigate to Workforce Optimization for ITSM > Scheduling > Event Categories from navigation bar. 
  2. Click New.zhaoyanghan_0-1727992278089.png 
  3. In the Name field, enter a unique name for the event type you want to create. 
  4. Leave the Event field configuration field blank as shown in the image below, and in the Modal Route field, enter ‘recurring-event-modal'. zhaoyanghan_1-1727992278089.png

 

Step 2 – Locate the form viewport from UIB 

  1. Navigate to UIB > Manager Workspace > Schedule default 
  2. Do the following: 
    1. Within Schedule default, click “Schedule tabs” component 
    2. On the right panel, click Team Calendar  
    3. Click Team Calendar defaultzhaoyanghan_2-1727992278090.png
    4. Go to Overlays > Modals
       
    5. Navigate to Viewport Event Modal -> Event Viewport -> Default RecurringEvent Modalzhaoyanghan_3-1727992278091.png

Step 3 – Create a new page variant 

After you complete the previous step, the following screen appears. This screen is a collection of modals for Event Categories where the Event field configuration field is empty.

zhaoyanghan_4-1727992278092.png

The url is “recurring-event-modal" from the last screen which is the same as what was filled in Modal Route when creating an Event Category. 

Note: The conditions set in the Conditions field for the Default Recurring Event Modal is only used for creating, editing, or deleting the Recurring Training and Recurring Meeting Event categories. For all other event categories, users have one of two options: 

  1. If the Default Recurring Event Modal fits the use-case you’re trying to implement for, then you can modify the conditions to include the newly created event category 
  2. If the Default Recurring Event Modal does not fit your use-case, you can follow these steps to create your own modal:
    1. Do the following: 
      1. Click the “+” sign next to  “Pages and variants” 
      2. Add a variant to a page 
      3. You can also choose default Recurring Event Modal, if you want to duplicate it. You can do this to begin with and then make changes to it.
      4. For information on page variant, see Create a page variant 
    2. Provide a unique name for the new modal and update the eventCategory in the Conditions field to be the sysId of the new Event Categoryzhaoyanghan_5-1727992278093.png
    3. In the new variant just created, start to build your own modal. if you duplicated it from some other variant, make the appropriate changes for your changes to reflect.

Here’s the example of the form if no changes were made to the duplicate variant you’ve created: 

zhaoyanghan_6-1727992278094.png

 

Step 4 – Continue with step 2 in the Creating New Event Categories for a Custom Event Type section that’s at the beginning of this document.