Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="report-widget-wrap">

<h2 ng-if="c.showTitle" tabindex="0" id="{{'title-' + c.rectangleId }}" class="report-widget-title">{{c.title}}</h2>

<div id="report-widget-{{c.rectangleId}}">
{{::c.initialMessage}}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.report-widget-wrap {
background:#fff;
padding:15px;
margin: 0 0 15px 0;
}

.report-widget-title {
padding: $sp-space--xl;
font-weight:bold;
margin-top: 0;
margin-bottom: 0;
font-family: $now-sp-font-family-sans-serif;
color: $text-color;
font-size: $font-size-h4;

}

.highcharts-container g.highcharts-button *,
.highcharts-container image.hc-image {
transition: fill-opacity 0.3s linear, stroke-opacity 0.3s linear, opacity 0.3s linear;
fill-opacity: 0;
stroke-opacity: 0;
opacity:0;
}

.highcharts-container:hover g.highcharts-button *,
.highcharts-container:hover image.hc-image {
fill-opacity: 1;
stroke-opacity: 1;
opacity:1;
}

.highcharts-legend-item span::after,
.highcharts-legend-item::after {
content: "\200E";
}

table.wide .pivot_cell,
table.wide .pivot_caption,
table.wide .pivot_caption_dark {
padding: 3px 5px;
}
.highlight-wrap {
display: none;
}

.fc-week-number {
width: 42px;
background-color: #ededed;
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
function($scope, $uibModal, $timeout, spUtil) {
var c = this;
var reportId = c.options.report_id || '';
c.rectangleId = c.widget.rectangle_id || c.data.rectangleId;
c.showTitle = (c.options.show_title === true || c.options.show_title === 'true');
c.title = c.options.title || '';

if (c.options.widget_parameters) {
c.initialMessage = c.data.ch.i18n.building;
window.chartHelpers = window.chartHelpers || {};
$.extend(window.chartHelpers, c.data.ch);

$timeout(function() {
var targetEl = $("#report-widget-" + c.rectangleId);
embedReportById(targetEl, reportId);

$timeout(function() {
targetEl.off('click', 'a[href*="change_request.do?sys_id"]');

targetEl.on('click', 'a[href*="change_request.do?sys_id"]', function(event) {
event.preventDefault();
var href = $(this).attr('href') || '';
var match = href.match(/sys_id=([a-f0-9]{32})/i);
var sysId = match ? match[1] : '';

var modalData = {
number: '',
short_description: '',
description: '',
sys_id: sysId
};

// Open modal immediately
$uibModal.open({
controller: function($scope, $uibModalInstance, $sce) {
$scope.data = modalData;

$scope.getTrustedDescription = function() {
if (!$scope.data.description) return '';
var text = $scope.data.description;

// Convert line breaks to <br>
text = text.replace(/\n/g, '<br>');

// Convert URLs to links
var urlRegex = /(\bhttps?:\/\/[^\s<]+)/gi;
text = text.replace(urlRegex, function(url) {
return '<a href="' + url + '" target="_blank" rel="noopener noreferrer">' + url + '</a>';
});

// Convert emails to mailto links
var emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})/gi;
text = text.replace(emailRegex, function(email) {
return '<a href="mailto:' + email + '">' + email + '</a>';
});

return $sce.trustAsHtml(text);
};

$scope.close = function() {
$uibModalInstance.dismiss('cancel');
};
},
resolve: {
$sce: function() {
return angular.injector(['ng']).get('$sce');
}
},
template: '<div class="modal-header">' +
'<h4 class="modal-title">Change Details</h4>' +
'</div>' +
'<div class="modal-body">' +
'<p><strong>Change Number:</strong> {{data.number}}</p>' +
'<p><strong>Short Description:</strong> {{data.short_description}}</p>' +
'<p><strong>Description:</strong></p>' +
'<p ng-bind-html="getTrustedDescription()"></p>' +
'</div>' +
'<div class="modal-footer">' +
'<a class="btn btn-primary" ng-href="/change_request.do?sys_id={{data.sys_id}}" target="_blank">View Record</a>' +
'<button class="btn btn-default" ng-click="close()">Close</button>' +
'</div>'
});

// Populate modal data via server
spUtil.get(c.widget.sys_id, {
action: 'getChangeDetails',
sys_id: sysId
}).then(function(response) {
if (response.data.changeDetails && !response.data.changeDetails.error) {
modalData.number = response.data.changeDetails.number;
modalData.short_description = response.data.changeDetails.short_description;
modalData.description = response.data.changeDetails.description;
}
});
});
}, 1000);
});
} else {
c.initialMessage = c.data.ch.i18n.selectReport;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Report IT Change Request: Change Calendar Widget

A **Service Portal widget** for displaying interactive **Change Request Calendar Reports** in ServiceNow.
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.

---

## Features

* **Report Embedding:** Displays a selected ServiceNow report dynamically in the portal.
* **Interactive Legend:** Color legend automatically updates based on selected highlight field (`risk`, `type`, `state`).
* **Change Request Details Modal:** Clicking a change number opens a modal showing detailed record information (number, description, risk, state, start and end dates).
* **Dynamic Color Mapping:** Fetches `sys_ui_style` and `sys_choice` data to visualize change request status colors.
* **Accessible & Responsive UI:** Fully keyboard-accessible with clear color indicators and responsive design.

---

## Configuration

### **Widget Options**

| Option | Type | Description |
| ------------ | ------------------------ | ------------------------------------- |
| `report_id` | Reference (`sys_report`) | Select the ServiceNow report to embed |
| `show_title` | Boolean | Toggle visibility of report title |

### **Installation Steps**

1. Import the widget XML into your ServiceNow instance via **Studio** or **Update Set**.
2. Add the widget to a Service Portal page (e.g., Change Dashboard).
3. In widget options:

* Select your desired **report** (`sys_report`).
* Enable “Show Title” if required.
4. Save and reload the page — the report will render dynamically.

---

## Color Legend

* Automatically generated from `sys_ui_style` table for elements `type`, `state`, and `risk`.
* Displays color-coded labels for visual clarity.
* Updates automatically when the highlight field dropdown changes.

**Example:**

| Element | Color | Meaning |
| ------- | ------------ | ------------------ |
| State | 🔵 Implement | Change in progress |
| Risk | 🟡 High | Requires review |
| Type | 🟢 Normal | Standard change |

---

## Modal Preview of Change Details

Clicking a change number in the calendar opens a modal window with:

| Field | Description |
| ------------------- | ---------------------------- |
| Change Number | Linked record reference |
| Short Description | Summary of change |
| Description | Detailed explanation |
| Type / Risk / State | Key metadata fields |
| Planned Start & End | Change implementation window |

---

## Technical Overview

| Component | Technology | Purpose |
| ----------------- | --------------------------------------- | -------------------------------------------------- |
| **Client Script** | AngularJS + jQuery + `$uibModal` | Event handling, modal logic, legend updates |
| **Server Script** | GlideRecord API | Fetch report and change details securely |
| **CSS** | Custom SCSS / SP Variables | Responsive layout, color blocks, and accessibility |
| **Template** | Angular bindings (`ng-if`, `ng-repeat`) | Dynamic rendering of legend and report |

---

## Security & Performance

* Uses `spUtil.get()` for secure data retrieval via the widget server script.
* Enforces ACL-based record access (`change_request` table).
* Sanitizes HTML using `$sce.trustAsHtml` for safe modal rendering.
* Optimized DOM operations and `$timeout` to reduce UI latency.

---

## Dependencies

* **ServiceNow Studio or App Engine Studio**
* **Service Portal Enabled**
* **Change Management Application** (`change_request` table)
* **Performance Analytics & Reporting Plugin** (`com.snc.pa.sp.widget`)

Optional:

* **Color Mapping in `sys_ui_style`**
* **Active `sys_report` record**

---

## Example Use Case

> The Change Manager wants a visual, color-coded view of all scheduled changes for the month.
> Using the **Report ITS Change Request** widget, they embed their “Change Calendar by Risk” report into the Service Portal.
> They can quickly filter changes, view color-coded statuses, and open detailed records—all from one place.

---

## Testing Scenarios

| Test | Expected Result |
| -------------------------- | ----------------------------------------------------- |
| Load widget without report | Displays “Select a report in widget options!” message |
| Click on change link | Modal opens with record details |
| Change highlight dropdown | Legend updates to reflect new color group |
| No matching record | Displays “Record not found” in modal |

---

## Future Enhancements

* Filter changes by assignment group or service.
* Add “Export as PDF” or “Add to Calendar” options.
* Integrate with CAB meeting module for review visualization.
* Replace jQuery with native AngularJS `$element` bindings for performance.

---

## Contributors

* **Developer:** Admin / ServiceNow Platform Engineer
* **Maintainers:** Performance Analytics & Reporting Widget Team
* **Scope:** Global (`x_snc_pa.sp.widget`)

---
Please find the screenshot below

![WhatsApp Image 2025-10-26 at 09 59 27 (1)](https://github.com/user-attachments/assets/a2e024cf-87be-4f29-9c5a-aee3e2dffbfd)

![WhatsApp Image 2025-10-26 at 09 59 27 (2)](https://github.com/user-attachments/assets/bd610e94-08ac-47be-842d-e8c59dadce70)

![WhatsApp Image 2025-10-26 at 09 59 27](https://github.com/user-attachments/assets/1333e974-6b56-48b8-b1c2-340e0a35e0af)

<img width="637" height="454" alt="image" src="https://github.com/user-attachments/assets/07d34c8b-429d-4522-b68d-71603f0206eb" />


Loading
Loading