Skip to content

Commit 50feef6

Browse files
authored
Change Calendar Report (#2506)
* Create Body HTML template.html * Create CSS * Create Server Side Script * Create Client Controller * Create README.md * Update README.md
1 parent 2c4621a commit 50feef6

File tree

5 files changed

+439
-0
lines changed

5 files changed

+439
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div class="report-widget-wrap">
2+
3+
<h2 ng-if="c.showTitle" tabindex="0" id="{{'title-' + c.rectangleId }}" class="report-widget-title">{{c.title}}</h2>
4+
5+
<div id="report-widget-{{c.rectangleId}}">
6+
{{::c.initialMessage}}
7+
</div>
8+
</div>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
.report-widget-wrap {
2+
background:#fff;
3+
padding:15px;
4+
margin: 0 0 15px 0;
5+
}
6+
7+
.report-widget-title {
8+
padding: $sp-space--xl;
9+
font-weight:bold;
10+
margin-top: 0;
11+
margin-bottom: 0;
12+
font-family: $now-sp-font-family-sans-serif;
13+
color: $text-color;
14+
font-size: $font-size-h4;
15+
16+
}
17+
18+
.highcharts-container g.highcharts-button *,
19+
.highcharts-container image.hc-image {
20+
transition: fill-opacity 0.3s linear, stroke-opacity 0.3s linear, opacity 0.3s linear;
21+
fill-opacity: 0;
22+
stroke-opacity: 0;
23+
opacity:0;
24+
}
25+
26+
.highcharts-container:hover g.highcharts-button *,
27+
.highcharts-container:hover image.hc-image {
28+
fill-opacity: 1;
29+
stroke-opacity: 1;
30+
opacity:1;
31+
}
32+
33+
.highcharts-legend-item span::after,
34+
.highcharts-legend-item::after {
35+
content: "\200E";
36+
}
37+
38+
table.wide .pivot_cell,
39+
table.wide .pivot_caption,
40+
table.wide .pivot_caption_dark {
41+
padding: 3px 5px;
42+
}
43+
.highlight-wrap {
44+
display: none;
45+
}
46+
47+
.fc-week-number {
48+
width: 42px;
49+
background-color: #ededed;
50+
}
51+
52+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
function($scope, $uibModal, $timeout, spUtil) {
2+
var c = this;
3+
var reportId = c.options.report_id || '';
4+
c.rectangleId = c.widget.rectangle_id || c.data.rectangleId;
5+
c.showTitle = (c.options.show_title === true || c.options.show_title === 'true');
6+
c.title = c.options.title || '';
7+
8+
if (c.options.widget_parameters) {
9+
c.initialMessage = c.data.ch.i18n.building;
10+
window.chartHelpers = window.chartHelpers || {};
11+
$.extend(window.chartHelpers, c.data.ch);
12+
13+
$timeout(function() {
14+
var targetEl = $("#report-widget-" + c.rectangleId);
15+
embedReportById(targetEl, reportId);
16+
17+
$timeout(function() {
18+
targetEl.off('click', 'a[href*="change_request.do?sys_id"]');
19+
20+
targetEl.on('click', 'a[href*="change_request.do?sys_id"]', function(event) {
21+
event.preventDefault();
22+
var href = $(this).attr('href') || '';
23+
var match = href.match(/sys_id=([a-f0-9]{32})/i);
24+
var sysId = match ? match[1] : '';
25+
26+
var modalData = {
27+
number: '',
28+
short_description: '',
29+
description: '',
30+
sys_id: sysId
31+
};
32+
33+
// Open modal immediately
34+
$uibModal.open({
35+
controller: function($scope, $uibModalInstance, $sce) {
36+
$scope.data = modalData;
37+
38+
$scope.getTrustedDescription = function() {
39+
if (!$scope.data.description) return '';
40+
var text = $scope.data.description;
41+
42+
// Convert line breaks to <br>
43+
text = text.replace(/\n/g, '<br>');
44+
45+
// Convert URLs to links
46+
var urlRegex = /(\bhttps?:\/\/[^\s<]+)/gi;
47+
text = text.replace(urlRegex, function(url) {
48+
return '<a href="' + url + '" target="_blank" rel="noopener noreferrer">' + url + '</a>';
49+
});
50+
51+
// Convert emails to mailto links
52+
var emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})/gi;
53+
text = text.replace(emailRegex, function(email) {
54+
return '<a href="mailto:' + email + '">' + email + '</a>';
55+
});
56+
57+
return $sce.trustAsHtml(text);
58+
};
59+
60+
$scope.close = function() {
61+
$uibModalInstance.dismiss('cancel');
62+
};
63+
},
64+
resolve: {
65+
$sce: function() {
66+
return angular.injector(['ng']).get('$sce');
67+
}
68+
},
69+
template: '<div class="modal-header">' +
70+
'<h4 class="modal-title">Change Details</h4>' +
71+
'</div>' +
72+
'<div class="modal-body">' +
73+
'<p><strong>Change Number:</strong> {{data.number}}</p>' +
74+
'<p><strong>Short Description:</strong> {{data.short_description}}</p>' +
75+
'<p><strong>Description:</strong></p>' +
76+
'<p ng-bind-html="getTrustedDescription()"></p>' +
77+
'</div>' +
78+
'<div class="modal-footer">' +
79+
'<a class="btn btn-primary" ng-href="/change_request.do?sys_id={{data.sys_id}}" target="_blank">View Record</a>' +
80+
'<button class="btn btn-default" ng-click="close()">Close</button>' +
81+
'</div>'
82+
});
83+
84+
// Populate modal data via server
85+
spUtil.get(c.widget.sys_id, {
86+
action: 'getChangeDetails',
87+
sys_id: sysId
88+
}).then(function(response) {
89+
if (response.data.changeDetails && !response.data.changeDetails.error) {
90+
modalData.number = response.data.changeDetails.number;
91+
modalData.short_description = response.data.changeDetails.short_description;
92+
modalData.description = response.data.changeDetails.description;
93+
}
94+
});
95+
});
96+
}, 1000);
97+
});
98+
} else {
99+
c.initialMessage = c.data.ch.i18n.selectReport;
100+
}
101+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Report IT Change Request: Change Calendar Widget
2+
3+
A **Service Portal widget** for displaying interactive **Change Request Calendar Reports** in ServiceNow.
4+
This widget embeds a ServiceNow report, allows visual exploration of change data, and enhances user experience through color-coded legends and a modal view for detailed record insights.
5+
6+
---
7+
8+
## Features
9+
10+
* **Report Embedding:** Displays a selected ServiceNow report dynamically in the portal.
11+
* **Interactive Legend:** Color legend automatically updates based on selected highlight field (`risk`, `type`, `state`).
12+
* **Change Request Details Modal:** Clicking a change number opens a modal showing detailed record information (number, description, risk, state, start and end dates).
13+
* **Dynamic Color Mapping:** Fetches `sys_ui_style` and `sys_choice` data to visualize change request status colors.
14+
* **Accessible & Responsive UI:** Fully keyboard-accessible with clear color indicators and responsive design.
15+
16+
---
17+
18+
## Configuration
19+
20+
### **Widget Options**
21+
22+
| Option | Type | Description |
23+
| ------------ | ------------------------ | ------------------------------------- |
24+
| `report_id` | Reference (`sys_report`) | Select the ServiceNow report to embed |
25+
| `show_title` | Boolean | Toggle visibility of report title |
26+
27+
### **Installation Steps**
28+
29+
1. Import the widget XML into your ServiceNow instance via **Studio** or **Update Set**.
30+
2. Add the widget to a Service Portal page (e.g., Change Dashboard).
31+
3. In widget options:
32+
33+
* Select your desired **report** (`sys_report`).
34+
* Enable “Show Title” if required.
35+
4. Save and reload the page — the report will render dynamically.
36+
37+
---
38+
39+
## Color Legend
40+
41+
* Automatically generated from `sys_ui_style` table for elements `type`, `state`, and `risk`.
42+
* Displays color-coded labels for visual clarity.
43+
* Updates automatically when the highlight field dropdown changes.
44+
45+
**Example:**
46+
47+
| Element | Color | Meaning |
48+
| ------- | ------------ | ------------------ |
49+
| State | 🔵 Implement | Change in progress |
50+
| Risk | 🟡 High | Requires review |
51+
| Type | 🟢 Normal | Standard change |
52+
53+
---
54+
55+
## Modal Preview of Change Details
56+
57+
Clicking a change number in the calendar opens a modal window with:
58+
59+
| Field | Description |
60+
| ------------------- | ---------------------------- |
61+
| Change Number | Linked record reference |
62+
| Short Description | Summary of change |
63+
| Description | Detailed explanation |
64+
| Type / Risk / State | Key metadata fields |
65+
| Planned Start & End | Change implementation window |
66+
67+
---
68+
69+
## Technical Overview
70+
71+
| Component | Technology | Purpose |
72+
| ----------------- | --------------------------------------- | -------------------------------------------------- |
73+
| **Client Script** | AngularJS + jQuery + `$uibModal` | Event handling, modal logic, legend updates |
74+
| **Server Script** | GlideRecord API | Fetch report and change details securely |
75+
| **CSS** | Custom SCSS / SP Variables | Responsive layout, color blocks, and accessibility |
76+
| **Template** | Angular bindings (`ng-if`, `ng-repeat`) | Dynamic rendering of legend and report |
77+
78+
---
79+
80+
## Security & Performance
81+
82+
* Uses `spUtil.get()` for secure data retrieval via the widget server script.
83+
* Enforces ACL-based record access (`change_request` table).
84+
* Sanitizes HTML using `$sce.trustAsHtml` for safe modal rendering.
85+
* Optimized DOM operations and `$timeout` to reduce UI latency.
86+
87+
---
88+
89+
## Dependencies
90+
91+
* **ServiceNow Studio or App Engine Studio**
92+
* **Service Portal Enabled**
93+
* **Change Management Application** (`change_request` table)
94+
* **Performance Analytics & Reporting Plugin** (`com.snc.pa.sp.widget`)
95+
96+
Optional:
97+
98+
* **Color Mapping in `sys_ui_style`**
99+
* **Active `sys_report` record**
100+
101+
---
102+
103+
## Example Use Case
104+
105+
> The Change Manager wants a visual, color-coded view of all scheduled changes for the month.
106+
> Using the **Report ITS Change Request** widget, they embed their “Change Calendar by Risk” report into the Service Portal.
107+
> They can quickly filter changes, view color-coded statuses, and open detailed records—all from one place.
108+
109+
---
110+
111+
## Testing Scenarios
112+
113+
| Test | Expected Result |
114+
| -------------------------- | ----------------------------------------------------- |
115+
| Load widget without report | Displays “Select a report in widget options!” message |
116+
| Click on change link | Modal opens with record details |
117+
| Change highlight dropdown | Legend updates to reflect new color group |
118+
| No matching record | Displays “Record not found” in modal |
119+
120+
---
121+
122+
## Future Enhancements
123+
124+
* Filter changes by assignment group or service.
125+
* Add “Export as PDF” or “Add to Calendar” options.
126+
* Integrate with CAB meeting module for review visualization.
127+
* Replace jQuery with native AngularJS `$element` bindings for performance.
128+
129+
---
130+
131+
## Contributors
132+
133+
* **Developer:** Admin / ServiceNow Platform Engineer
134+
* **Maintainers:** Performance Analytics & Reporting Widget Team
135+
* **Scope:** Global (`x_snc_pa.sp.widget`)
136+
137+
---
138+
Please find the screenshot below
139+
140+
![WhatsApp Image 2025-10-26 at 09 59 27 (1)](https://github.com/user-attachments/assets/a2e024cf-87be-4f29-9c5a-aee3e2dffbfd)
141+
142+
![WhatsApp Image 2025-10-26 at 09 59 27 (2)](https://github.com/user-attachments/assets/bd610e94-08ac-47be-842d-e8c59dadce70)
143+
144+
![WhatsApp Image 2025-10-26 at 09 59 27](https://github.com/user-attachments/assets/1333e974-6b56-48b8-b1c2-340e0a35e0af)
145+
146+
<img width="637" height="454" alt="image" src="https://github.com/user-attachments/assets/07d34c8b-429d-4522-b68d-71603f0206eb" />
147+
148+

0 commit comments

Comments
 (0)