Code Creative

How to Build Custom ServiceNow Charts and Reports (Video)

The ServiceNow Community can be a tough place to communicate without winding up with a wall of intimidating technical text.  Add to that the gaps in technical knowledge and understanding as well as differences in jargon and we often end up with walls of text and frequent churn in the comments in order to arrive at a solution.  Thankfully, the Community offers some great rich media features like videos!  With that in mind, I am very excited to kick off the a new webcast series called Code Creative!  With Code Creative, I will have the opportunity to answer questions by demonstrating techniques rather than just explaining them.  

I have been kicking around the idea of starting a webcast series dedicated to answering Community questions for a while and found the perfect opportunity with a question from vaigai.kothandaraman.  In Episode 1, Vaigai wants to know how to create custom reports when the native reporting engine is unable to provide the right look and feel and I demonstrate a technique using HighCharts and a UI Page to wield absolute control over the data and the final report.  This video demystifies a fairly advanced scripting technique that I have used to achieve some pretty amazing charts in the past and by the end I am sure the community will come up with some even more amazing ones!

I hope you enjoy the video and I look forward to answering your questions in future episodes!

Original Question: Custom Chart Scripts

Additional Charting Resources:

Another Option for Custom Charts using the Legacy JFreeChart

Here is the UI Page used in the video:

<?xml version="1.0" encoding="utf-8" ?>  
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">  
    <script type="text/javascript" src="/scripts/GlideV2ChartingIncludes.jsx?v=10-19-2014_0741"></script>  
    <g2:evaluate>  
        var openQuery = 'stateIN1,2,3,4,5',  
            resolvedQuery = 'state=6^resolved_atONToday@javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(0)',  
            closedQuery = 'state=7^closed_atONToday@javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(0)',  
            allQuery = 'stateIN1,2,3,4,5^NQstate=6^resolved_atONToday@javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(0)^NQstate=7^closed_atONToday@javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(0)',  
            ga,  
            categories = [],  
            series = [];  


        // Setup Categories  
        var ga = new GlideAggregate('incident');  
        ga.addEncodedQuery(allQuery);  
        ga.addAggregate('COUNT');  
        ga.groupBy('location');  
        ga.orderBy('location');  
        ga.query();  
        while (ga.next()) {  
            categories.push(ga.location.getDisplayValue() + '' || '(empty)');  
        }  


        // Reusable function for building the 3 Series  
        function getSeries(name, index, query, categories) {  
            var ga = new GlideAggregate('incident'),  
                data = [],  
                i,  
                cat;  


            // Fill data with 0's  
            for (i = 0; i != categories.length; i++) {  
                data.push(0);  
            }  


            ga.addEncodedQuery(query);  
            ga.addAggregate('COUNT');  
            ga.groupBy('location');  
            ga.orderBy('location');  
            ga.query();  
            while (ga.next()) {  
                // Find category index  
                for (i = 0; i != categories.length; i++) {  
                    cat = ga.location.getDisplayValue() + '' || '(empty)';  
                    if (categories[i] == cat) {  
                        break;  
                    }  
                }  


                data[i] = ga.getAggregate('COUNT') * 1;  
            }  


            return { 'name': name, 'legendIndex': index, 'data': data };  
        }  


        // Add the 3 series to an array for output  
        series.push(getSeries('Open', 0, openQuery, categories));  // Add Open Series  
        series.push(getSeries('Resolved', 1, resolvedQuery, categories)); // Add Resolved Series  
        series.push(closedSeries = getSeries('Closed', 1, closedQuery, categories)); // Add Closed Series  


        // JSON encode the series and categories.  The JSON strings will be embedded with a JEXL expression into the client side code  
        series = new JSON().encode(series);  
        categories = new JSON().encode(categories);  
    </g2:evaluate>  
    <table class="wide"><tr><td align="center">  
        <div id="custom_report" class="highcharts-container" style="position: static; overflow: hidden; width: 650px; height: 450px; text-align: left; line-height: normal; z-index: 0; font-family: 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif; font-size: 12px;"></div>  
    </td></tr></table>  
    <script type="text/javascript">  
            var chart1 = new Highcharts.Chart({  
                chart: {  
                    renderTo: 'custom_report',  
                    type: 'column'  
                },  
                title: {  
                    text: null  
                },  
                xAxis: {  
                    categories: $[categories],  
                    title: {  
                        text: null  
                    },  
                    labels: {  
                        rotation: -45  
                    }  
                },  
                yAxis: {  
                    min: 0,  
                    title: {  
                        text: null  
                    }  
                },  
                series: $[series]  
            });  
    </script>  
</j:jelly>
Travis ToulsonVideo