Примеры разработки CQD

Сводка: Ознакомьтесь с руководством и примерами разработки для панели мониторинга качества звонков. Панель мониторинга качества звонков — это средство для Skype для бизнеса Server.

Эта статья представляет собой учебное пособие по разработке панели мониторинга качества вызовов (CQD) с примерами.

Примеры разработки панели мониторинга качества вызовов (CQD)

Учебное пособие. Построение презентаций нестандартных отчетов с помощью интерфейсов API для службы данных и службы репозитория в CQD.

Введение в CQD

CQD обеспечивает быстрый и удобный доступ к сводным данным о качестве вызова в локальных развертываниях Skype для бизнеса Server. CQD состоит из трех компонентов: архивной базы данных качества взаимодействия, куба и портала. Портал – это основной уровень презентации, который в свою очередь подразделяется на три следующих компонента:

  1. Служба данных, доступная для пользователей, прошедших проверку подлинности, через API данных для панели мониторинга качества звонков (CQD) в Skype для бизнеса Server.

  2. Служба репозитория, которая доступна для пользователей, прошедших проверку подлинности, через API репозитория для панели мониторинга качества звонков (CQD) в Skype для бизнеса Server.

  3. веб-портал — интерфейс на основе HTML5, доступный пользователям CQD для просмотра и взаимодействия. Пользователи должны пройти проверку подлинности.

Отчеты, отображаемые на веб-портале, группируются в "наборы отчетов". На рисунке показан такой набор, состоящий из двух отчетов. В каждом отчете на показанной ниже панели мониторинга отображаются результаты запроса, указывающие количество вызовов высокого качества, количество вызовов низкого качества и долю вызовов низкого качества в процентах за несколько месяцев с применением различных фильтров.

Пример отчета CQD.

Панель CQD разработана согласно методологии оценки качества вызовов (CQM), поэтому используемый по умолчанию набор отчетов соответствует процессу анализа, предусмотренному этой методологией. Пользователи могут также редактировать эти отчеты и создавать собственные отчеты с учетом конкретных задач. Однако существуют различные способы визуализации данных, и тот способ, который применяется в CQD, может не полностью соответствовать задачам каждого пользователя. В таких ситуациях пользователь может создавать нестандартные страницы отчетов с помощью интерфейсов API для данных и репозитория. В этом учебном пособии приведен ряд примеров.

Использование службы данных на панели мониторинга

При переходе на домашнюю страницу CQD (например http://localhost/cqd), набор отчетов и соответствующие отчеты для аутентифицированного и авторизованного пользователя будут получены из службы репозитория). Полный URL-адрес будет создан на основе идентификатора набора отчетов и года-месяца (идентификатор набора отчетов — это целое число после раздела "/#/" в URL-адресе, а по умолчанию текущий год-месяц добавляется в конце идентификатора набора отчетов после косой черты). Определения отчетов хранятся в формате JSON и после извлечения из службы репозитория вводятся в службу данных. В службе данных на основе введенных данных формируются запросы с многомерными выражениями (MDX), которые затем выполняются применительно к кубу для извлечения данных по каждому отчету.

Составление нестандартных отчетов

В CQD предусмотрены широкие возможности настройки отчетов, однако в некоторых ситуациях пользователям необходимы сводные данные по нескольким отчетам, созданным в CQD. Например, может потребоваться отчет, где в табличной форме показана доля вызовов низкого качества для всех возможных вариантов вызовов по проводной связи (пример результата такого отчета показан на рисунке):

Таблица CQD.

Если пользователь работает на портале, предусмотренном в CQD, для извлечения и записи доли вызовов низкого качества из разных отчетов он должен перемещаться между отчетами; при сборе большого количества точек данных этот процесс может быть трудоемким. С помощью интерфейсов API для данных пользователи могут запрограммировать эту операцию; в этом случае данные извлекаются из службы данных (например, посредством вызовов AJAX).

Пример 1. Образец простого отчета

Сначала рассмотрим простой пример. Предположим, что требуется отобразить на странице HTML количество потоков звуковых данных высокого и низкого качества за февраль 2015 года, как показано на рисунке:

Пример отчета CQD.

Для этого необходимо отправить в службу данных вызов с надлежащими параметрами и вывести результаты запроса в таблице HTML. Ниже приведен образец кода JavaScript.

$($.fn.freeFormReport = function (queries, urlApi, presentation) {
            var query = {
                Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                Filters: [{
                    DataModelName: '[StartDate].[Month]',
                    Value: '[2015-02-01T00:00:00]',
                    Operand: 0
                }],
                Measurements:
                    [{ DataModelName: '[Measures].[Audio Good Streams JPDR Count]' },
                     { DataModelName: '[Measures].[Audio Poor Streams JPDR Count]' },]
            };            

            $.ajax({
                url: 'http://localhost/QoEDataService/RunQuery',
                data: JSON.stringify(query),
                type: 'POST',
                async: true,
                contentType: 'application/json;charset=utf-8',
                success: function (data) {
                    //This is the jQuery syntax for document.GetElementById()
                    $('#AudioGoodStreamsJPDRCount').html(data.DataResult[0][1]);
                    $('#AudioPoorStreamsJPDRCount').html(data.DataResult[0][2]);
                }
                error: function (error) {
                    alert('Error getting data, check that the data service is running and that the URL is correct.');
           }
            });
        });

Этот пример можно разбить на три этапа.

  1. Создайте запрос (в примере он определен в переменной query). Запрос определяется как объект JSON, включающий в себя следующие данные.

    а) Измерения (не обязательно). Каждое измерение обозначается параметром DataModelName.

    б) Фильтры (не обязательно). Для каждого фильтра задаются следующие составляющие:

    • DataModelName (измерение, для которого будет задан фильтр);

    • значение (для сравнения с применением операнда);

    • Операнд (тип сравнения, 0 означает "Равно").

      в. Хотя бы один показатель.

  2. Отправка запроса в службу данных посредством вызова AJAX. Необходимо указать следующие параметры запроса.

    а) url (в качестве URL-адреса следует указать http://[имя_сервера]/QoEDataService/RunQuery).

    б) data (это строковое представление объекта JSON, определенного в переменной query). Служба данных возвращает результаты запроса в виде параметра функции обратного вызова для успешного выполнения.

    в. type (для QoEDataService RunQuery принимает только запросы POST).

    г. async (признак, указывающий, должен ли вызов AJAX быть синхронным или асинхронным).

    E. contentType (должно быть "application/json").

    F. success (функция обратного вызова в случае успешного завершения вызова AJAX).

    Г. error (функция обработки ошибок в случае сбоя вызова AJAX).

  3. Внедрение данных в элементы div в коде HTML (в данном примере кода для этого после успешного выполнения запроса AJAX осуществляется анонимный вызов функции).

При внедрении кода JavaScript в страницу HTML на этой странице отображается отчет, подобный показанному на рисунке. Ниже код HTML показан полностью:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
    <script src="OpenSourceSoftware/Scripts/jquery-2.1.1.js"></script>

    <script>
        $($.fn.freeFormReport = function (queries, urlApi, presentation) {

            var query = {
                Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                Filters: [{
                    DataModelName: '[StartDate].[Month]',
                    Value: '[2015-02-01T00:00:00]',
                    Operand: 0
                }],
                Measurements:
                    [{ DataModelName: '[Measures].[Audio Good Streams JPDR Count]' },
                     { DataModelName: '[Measures].[Audio Poor Streams JPDR Count]' },]
            };            

            $.ajax({
                url: 'http://localhost/QoEDataService/RunQuery',
                data: JSON.stringify(query),
                type: 'POST',
                async: true,
                contentType: 'application/json;charset=utf-8',
                success: function (data) {
                    //This is the jQuery syntax for document.GetElementById()
                    $('#AudioGoodStreamsJPDRCount').html(data.DataResult[0][1]);
                    $('#AudioPoorStreamsJPDRCount').html(data.DataResult[0][2]);
                }
                error: function (error) {
                    alert('Error getting data, check that the data service is running and that the URL is correct.');
           }

            });
        });
    </script>
    <table border="1">
        <tr>
            <td></td>
            <td><div>Audio Good Streams JPDR Count</div></td>
            <td><div>Audio Poor Streams JPDR Count</div></td>
        </tr>
        <tr>
            <td>February</td>
            <td><div id="AudioGoodStreamsJPDRCount"></div></td>
            <td><div id="AudioPoorStreamsJPDRCount"></div></td>
        </tr>
    </table>
</body>
</html>

Такой отчет по-прежнему очень прост. Пользователь может настроить его, добавляя дополнительные показатели, измерения или фильтры. Например, если требуется отобразить долю вызовов низкого качества в AppSharing, необходимо добавить новый показатель, связанный с AppSharing. Для отображения всех вызовов TCP в сравнении с вызовами UDP следует добавить новое измерение, относящееся к типу транспортного протокола. Для отображения количества вызовов низкого качества в конкретном здании следует добавить новый фильтр, позволяющий выбрать входящие и исходящие вызовы в этом здании.

Пример 2. Образец определения отчета

Некоторые пользователи могут не знать, как составляется полный список показателей/измерений/фильтров и соответствующих значений при формировании запроса. В этом случае можно перейти на портал, сформировать отчет в редакторе отчетов, найти строку JSON в определении отчета и скопировать ее в пользовательский отчет.

В этом примере будет создана веб-страница, подобная показанной на рисунке; если пользователь введет на этой веб-странице идентификатор любого существующего набора отчетов (или отчета), на ней отобразится определение этого набора отчетов или отчета. После этого пользователь может включить строку JSON для каждого отчета в код, аналогичный рассмотренному в примере 1, и составить любой требуемый отчет.

Пример CQD.

При создании средства просмотра определений отчетов потребуется отправить вызовы в службу репозитория для извлечения определения каждого требуемого набора отчетов, представленного в виде строки JSON. Интерфейс API репозитория возвращает определение набора отчетов по заданному определению набора отчетов.

В качестве примера рассмотрим приведенный ниже код. Он содержит блок, который служит простым образцом отправки в службу репозитория запроса на получение содержимого элемента из репозитория по идентификатору этого элемента. Следующий фрагмент кода (метод processReportSetData) обеспечивает отправку вызовов AJAX для получения определения каждого отчета из данного набора. Дополнительные сведения об API репозитория и, в частности, GetItems, см. в разделе Получение элементов.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta http-equiv='cache-control' content='no-cache'>
    <meta http-equiv='expires' content='0'>
    <meta http-equiv='pragma' content='no-cache'>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="x-content-type-options" content="nosniff">
    <title>CQD Report definition viewer</title>
</head>
<body>    
    <div style="font-size:32pt">CQD Report definition viewer</div>
        <p>ReportSet Id: <input id="reportSetId" /></p>
        <button onclick='loadReportSet()'>Load</button>
        <div id="Report"></div>
    <!-- Third party Libraries -->
    <script src="OpenSourceSoftware/Scripts/jquery-2.1.1.js"></script>

    <script>
        var urlRepositoryApi = 'http://localhost/QoERepositoryService/repository/item/';

        var loadReportSet = function ()
        {
            var reportSetId = document.getElementById('reportSetId').value;

            $.ajax({
                url: urlRepositoryApi + reportSetId,
                data: '',
                type: 'GET',
                async: false,
                contentType: 'application/json;charset=utf-8',
                success: function (data) {
                    var reportSetDiv = document.getElementById('Report');
                    reportSetDiv.innerHTML = '';
                    processReportSetData(reportSetDiv, data);
                },
                error: function (error) {
                    alert('Error getting Report, check that the qoe data service is running and url is correct.');
                }
            });
        };

        var processReportSetData = function (divReportSet, reportSetDef) {
             //show the report set definition like Title, Description, etc
            showReportSetDefinition(divReportSet, reportSetDef);

            //for each Report in the Reportset, get the Report definition from the Repository Service
            for (var i = 0; i < reportSetDef.subItemIds.length; i++)
            {
                //the reportId is in the subItemIds array.  This is not shown in the CQD UI at all
                var reportId = reportSetDef.subItemIds[i];

                var divReport = document.createElement('div');
                divReport.style.margin = '12px';                
                divReportSet.appendChild(divReport);

                //retrieve the report definition with the reportId
                $.ajax({
                    url: urlRepositoryApi + reportId,
                    data: '',
                    type: 'GET',
                    async: false,
                    contentType: 'application/json;charset=utf-8',
                    success: function (reportData) {
                        processReportData(divReport, reportData, reportId);
                    },
                    error: function (error) {
                        alert('Error getting Report ' + reportId.toString() + ', check that the qoe data service is running and url is correct.');
                    }
                });
            }
        };

        //helper function to render the ReportSet definition
        var showReportSetDefinition = function (divReportSet, reportSetDef) {
            var div = document.createElement('div');
            ReportSetDefinition = reportSetDef.content;
            txt = document.createTextNode(ReportSetDefinition);
            div.style.margin = '12px';
            div.appendChild(txt);
            divReportSet.appendChild(div);
        };

        //show the report definition
        var processReportData = function (divReport, reportData, itemId) {
            if (divReport != undefined &amp;&amp; reportData.content != undefined) {
                var Report = JSON.parse(reportData.content);

                var divReportId = document.createElement('div');
                var subItemId = reportData.subItemIds.length > 0 ? reportData.subItemIds[0].toString() : 'none';
                divReportId.innerHTML = 'ItemId: ' + itemId.toString() + ' (' + Report.Title + ') [subItemId:' + subItemId + ']';
                divReport.appendChild(divReportId);

                var divReportDef = document.createElement('div');
                txt = document.createTextNode(JSON.stringify(Report));
                divReportDef.style.margin = '12px';
                divReportDef.appendChild(txt);                            
                divReport.appendChild(divReportDef);
            }
        };
    </script>
</body>
</html>

Результатом приведенного выше кода является веб-страница, подобная показанной на рисунке (без определения отчета при первом посещении). Получите идентификатор набора отчетов на портале CQD (он находится после входа "/#/" в URL-адресе портала CQD (например, на первом рисунке идентификатор набора отчетов — 3024) и поместите этот идентификатор набора отчетов во входной раздел этой веб-страницы. Нажмите кнопку "загрузить" и просмотрите полное определение (измерения, измерения, списки фильтров) набора отчетов.

Таким образом, чтобы быстро получить полное определение набора отчетов или отчетов. Для этого требуется выполнить следующие действия:

  1. Перейдите на портал и используйте редактор запросов для настройки отчета (нажмите кнопку "Изменить" над отчетом, чтобы изменить, добавить, удалить измерения, измерения и фильтры, а затем сохранить отчет).

  2. Получите идентификатор набора отчетов по URL-адресу (целое число после URL-адреса входа "/#/").

  3. Вызовите веб-страницу определения отчета, созданную в примере 2, введите идентификатора набора отчетов и извлеките полное определение этого набора (которое будет применяться в вызовах интерфейса API для данных).

    Пример 3. Образец системы показателей

Перейдем к более сложной задаче. Требуется создать веб-страницу, подобную показанной на рисунке. Поскольку при этом увеличивается объем обрабатываемых данных, следует обновить код из примера 1 (с помощью сформированной в примере 2 веб-страницы, позволяющей извлекать полное определение любого отчета).

В данном случае требуется обновить список показателей и измерений. Для добавления/изменения показателя и/или размерности следует выполнить инструкции из примера 2 и извлечь полное определение отчета, в том числе полные списки показателей и измерений. Включите полное определение отчета в образец кода.

Ниже приведена пошаговая процедура получения показанной на рисунке страницы системы показателей на основе кода из примера 1.

  1. Обновите значения измерений в переменной query с [Measures].[Audio Good Streams JPDR Count] и [Measures].[Audio Poor Streams JPDR Count] на [Measures].[AudioPoorJPDRPercentage].

  2. Обновите фильтры. Данные JSON для фильтров в примере 1 имеют один фильтр, который задается в измерении [StartDate].[Month]. Поскольку фильтры являются массивом JSON, к списку фильтров можно добавлять дополнительные измерения. Например, чтобы получить сервер-клиент внутри проводных вызовов для currentMonth, у нас должны быть следующие фильтры:

    Filters: [
      { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
     {
         "DataModelName": "[Scenarios].[ScenarioPair]",
          "Caption": " Server-Inside-wired,Client-Inside-wired",
          "Value": "[1]&amp;[0]&amp;[1]&amp;[1]&amp;[Wired]&amp;[Wired]",
          "Operand": 0,
          "UnionGroup": ""
      },
    
      { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
    ],
    

    Здесь для измерения [Scenarios].[ScenarioPair] задано значение , равное [1]&amp;[0]&amp;[1]&amp;[1]&amp;[Wired]&amp;[Wired]. — [Scenario.][ScenarioPair] это специальное измерение, созданное для упрощения создания отчетов. Имеет шесть значений, [FirstIsServer], [SecondIsServer], [FirstInside], [SecondIsServer], [FirstConnectionType], [SecondConnectionType]соответствующих . Таким образом, для определения не требуется сочетание шести фильтров: достаточно одного фильтра. В нашем примере значение [1]&amp;[0]&amp;[1]&amp;[1]&amp;[Wired]&amp;[Wired] преобразуется в сценарий, в котором: первый — это сервер, второй — не сервер, первый — внутри, второй — внутри, первый тип подключения — проводной, а второй — проводной тип, который является точным определением "Server-Client-Inside Wired".

  3. Создайте по одному фильтру для каждого сценария. В системе показателей на рисунке каждая строка представляет отдельный сценарий, для которого задается отдельный фильтр (измерения и показатели являются общими для всех сценариев).

  4. Выполните синтаксический разбор результатов вызовов AJAX и поместите их в правильное положение в таблице. Поскольку эта операция выполняется преимущественно средствами HTML и JavaScript, ее детали здесь опущены. Соответствующий код приведен в приложении A.

    Примечание.

    Если включен общий доступ к ресурсам между источниками (CORS), пользователи могут столкнуться с такими ошибками, как "Нет заголовка Access-Control-Allow-Origin" в запрошенном ресурсе. Таким образом, доступ к источнику null не разрешен". Чтобы устранить эту проблему, поместите HTML-файл в папку, в которой установлен портал (по умолчанию он должен иметь значение %SystemDrive%\Program Files\Skype for Business 2015 CQD\CQD). Затем получите доступ к html-коду через любой браузер с URL-адресом http://<servername>/cqd/<html_file_name>. (URL-адрес по умолчанию для локальной панели мониторинга CQD — http://<servername>/cqd.)

Приложение A

Код HTML для примера 3 (образец системы показателей):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <meta http-equiv='cache-control' content='no-cache'>
    <meta http-equiv='expires' content='0'>
    <meta http-equiv='pragma' content='no-cache'>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Scoreboard Sample</title>

    <style>
        .row {
            margin-right: -15px;
            margin-left: -15px;
            display: table-row;
        }
        .col-md-3 {
            width: 25%;
            display: table-cell;
        }
        .col-md-2 {
            width: 16.66666667%;
            display: table-cell;
        }
        .col-md-1 {
            width: 8.33333333%;
            display: table-cell;
        }

    </style>
</head>
<body>    

    <!-- Third party Libraries -->
    <script src="OpenSourceSoftware/Scripts/jquery-2.1.1.js"></script>

    <table id="ScoreCardTable" style="margin:100px">
        <tr>
            <td width="250px" style="text-align: center; font-size: 24px; font-family: 'Segoe UI'; font-weight: lighter; color: white; background-color: #505050">
                <div style="margin:10px">Scoreboard Sample</div>
            </td>
            <td width="1200px">
                <div style="margin:10px;background-color:#D9D9D9" >
                    <div class="row" id="Header" style="font-size:24px;font-family:'Segoe UI';font-weight:lighter;color:white;background-color:#505050">
                        <div class="col-md-3">Poor Call %</div>
                        <div class="col-md-1">Month1</div>
                        <div class="col-md-1">Month2</div>
                        <div class="col-md-1">Month3</div>
                        <div class="col-md-1">Month4</div>
                        <div class="col-md-1">Month5</div>
                        <div class="col-md-1">Month6</div>
                    </div>                    
                    <div class="row"><div class="col-md-3" style="font-weight:bold">Wired</div></div>
                    <div class="row" id="SS"><div class="col-md-3">Server-Server</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="SWI"><div class="col-md-3">Server-Client (inside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="SWO"><div class="col-md-3">Server-Client (outside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="WWI"><div class="col-md-3">Client-Client (inside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="WIWO"><div class="col-md-3">Client-Client (outside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row"><div class="col-md-3" style="font-weight:bold">Wireless</div></div>
                    <div class="row" id="SWFI"><div class="col-md-3">Server-Client (inside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="SWFO"><div class="col-md-3">Server-Client (outside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="WFIWFI"><div class="col-md-3">Client-Client (inside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="WFOWFO"><div class="col-md-3">Client-Client (outside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row"><div class="col-md-3" style="font-weight:bold">Mobile/Broadband</div></div>
                    <div class="row" id="SMP"><div class="col-md-3">Server-MobilePhone</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row" id="SMBB"><div class="col-md-3">Server-MobileBroadBand</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                    <div class="row"><div class="col-md-3" style="font-weight:bold">Lync Web App</div></div>
                    <div class="row" id="SLWA"><div class="col-md-3">Server-Client (inside &amp; outside)</div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div><div class="col-md-1"></div></div>
                </div>
            </td>
        </tr>
        <tr>
            <td><br /></td>
        </tr>
    </table>

    <script>

        $(function () {
            var month_names_short = ['NAM', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
            var currentMonth = '2015-3';

            //update the header with the month names
            var row = document.getElementById('Header');
            var numMonthsToShow = 6;
            for (var m = numMonthsToShow-1; m >= 0; m--) {
                var dateSplit = currentMonth.split('-');
                var monthInt = parseInt(dateSplit[1]);
                var yearInt = parseInt(dateSplit[0]);
                monthInt = monthInt - m;
                if (monthInt < 1)
                {
                    monthInt += 12;
                    yearInt--;
                }
                row.children[numMonthsToShow-m].innerHTML = month_names_short[monthInt];
            }

            var queries = [
            {
                Label: "Server-Server",
                ID: "SS",
                Query:
                {
                  Dimensions: [{ DataModelName: '[StartDate].[Month]'}],
                  Filters: [
                      {
                          "DataModelName": "[Scenarios].[ScenarioPair]",
                          "Caption": " Server-Inside-wired,Server-Inside-wired",
                          "Value": "[1]&amp;[1]&amp;[1]&amp;[1]&amp;[Wired]&amp;[Wired]",
                          "Operand": 0,
                          "UnionGroup": ""
                      },
                      { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                      { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: ""}
                  ],
                  Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]'}],
                  Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                 }
            },
            {
                Label: "Server-Client (inside)",
                ID: "SWI",
                Query:
                {
                  Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                  Filters: [
                      {
                          "DataModelName": "[Scenarios].[ScenarioPair]",
                          "Caption": " Server-Inside-wired,Client-Inside-wired",
                          "Value": "[1]&amp;[0]&amp;[1]&amp;[1]&amp;[Wired]&amp;[Wired]",
                          "Operand": 0,
                          "UnionGroup": ""
                      },
                      { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                      { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                  ],
                  Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                  Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Server-Client (outside)",
                ID: "SWO",
                Query:
                {
                  Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                  Filters: [
                      {
                          "DataModelName": "[Scenarios].[ScenarioPair]",
                          "Caption": " Server-Inside-wired,Client-Outside-wired",
                          "Value": "[1]&amp;[0]&amp;[1]&amp;[0]&amp;[Wired]&amp;[Wired]",
                          "Operand": 0,
                          "UnionGroup": ""
                      },
                      { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                      { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                  ],
                  Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                  Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                 }
            },
            {
                Label: "Client-Client (inside)",
                ID: "WWI",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {
                            "DataModelName": "[Scenarios].[ScenarioPair]",
                            "Caption": " Client-Inside-wired,Client-Inside-wired",
                            "Value": "[0]&amp;[0]&amp;[1]&amp;[1]&amp;[Wired]&amp;[Wired]",
                            "Operand": 0,
                            "UnionGroup": ""
                        },
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                        { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            }
            ,
            {
                Label: "Client-Client (outside)",
                ID: "WIWO",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {
                          "DataModelName": "[Scenarios].[ScenarioPair]",
                          "Caption": "Client-Outside-Wired,Client-Outside-Wired",
                          "Value": "[0]&amp;[0]&amp;[0]&amp;[0]&amp;[Wired]&amp;[Wired]",
                          "Operand": 0,
                          "UnionGroup": ""
                        },
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                        { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Server-Client (inside)",
                ID: "SWFI",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {
                            "DataModelName": "[Scenarios].[ScenarioPair]",
                            "Caption": " Server-Inside-wired,Client-Inside-wifi",
                            "Value": "[1]&amp;[0]&amp;[1]&amp;[1]&amp;[Wired]&amp;[Wifi]",
                            "Operand": 0,
                            "UnionGroup": ""
                        },
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                        { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Server-Client (outside)",
                ID: "SWFO",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                         {
                             "DataModelName": "[Scenarios].[ScenarioPair]",
                             "Caption": " Server-Inside-wired,Client-Outside-wifi",
                             "Value": "[1]&amp;[0]&amp;[1]&amp;[0]&amp;[Wired]&amp;[Wifi]",
                             "Operand": 0,
                             "UnionGroup": ""
                         },
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                        { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Client-Client (inside)",
                ID: "WFIWFI",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {
                            "DataModelName": "[Scenarios].[ScenarioPair]",
                            "Caption": " Client-Inside-Wifi,Client-Inside-Wifi",
                            "Value": "[0]&amp;[0]&amp;[1]&amp;[1]&amp;[Wifi]&amp;[Wifi]",
                            "Operand": 0,
                            "UnionGroup": ""
                        },
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                        { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Client-Client (outside)",
                ID: "WFOWFO",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {
                          "DataModelName": "[Scenarios].[ScenarioPair]",
                          "Caption": "Client-Outside-Wifi,Client-Outside-Wifi",
                          "Value": "[0]&amp;[0]&amp;[0]&amp;[0]&amp;[Wifi]&amp;[Wifi]",
                          "Operand": 0,
                          "UnionGroup": ""
                        },
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                        { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Server-MobilePhone",
                ID: "SMP",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {"DataModelName": "[First Is Server].[Agent]","Caption": "Server","Value": "[1]","Operand": 0,"UnionGroup": ""},
                        {"DataModelName": "[Second User Agent].[User Agent Type]","Caption": "AndroidLync | iPhoneLync | WPLync","Value": "[AndroidLync],[iPhoneLync],[WPLync]","Operand": 0,"UnionGroup": ""},
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 },
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Server-MobileBroadBand",
                ID: "SMBB",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {"DataModelName": "[Second Network Connection Type].[Network Connection Detail]","Caption": "MobileBB","Value": "[MobileBB]","Operand": 0,"UnionGroup": ""},
                        {"DataModelName": "[First Is Server].[Agent]","Caption": "Server","Value": "[1]","Operand": 0,"UnionGroup": ""},
                        {"DataModelName": "[Second Is Server].[Agent]","Caption": "Client ","Value": "[0]","Operand": 0,"UnionGroup": ""},
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 }                       
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            },
            {
                Label: "Server-LWA",
                ID: "SLWA",
                Query:
                {
                    Dimensions: [{ DataModelName: '[StartDate].[Month]' }],
                    Filters: [
                        {"DataModelName": "[First Is Server].[Agent]","Caption": "Server","Value": "[1]","Operand": 0,"UnionGroup": ""},
                        {"DataModelName": "[Second User Agent].[User Agent Type]","Caption": "LWA","Value": "[LWA]","Operand": 0,"UnionGroup": ""},
                        { DataModelName: '[StartDate].[Month]', Value: currentMonth, Operand: 0 }                       
                    ],
                    Measurements: [{ DataModelName: '[Measures].[AudioPoorJPDRPercentage]' }],
                    Trend: { EnableTrend: true, SpanCount: numMonthsToShow, TrendDate: currentMonth, Type: 0 }
                }
            }

            ];

            //get the overall corpnet data
            for (var i = 0; i < queries.length; i++) {
                $.ajax({

                    url: 'http://localhost/QoEDataService/RunQuery',
                    data: JSON.stringify(queries[i].Query),
                    type: 'POST',
                    async: false,
                    withCredentials: true,
                    contentType: 'application/json;charset=utf-8',
                    success: function (data) {

                        //find the table row corresponding to the name of this query
                        var row = document.getElementById(queries[i].ID);

                        //update the values for each month
                        for (var m = 0; m < data.DataResult.length; m++)
                        {
                            row.children[m + 1].innerHTML = data.DataResult[m][1].toFixed(2).toString();
                        }

                    },
                    error: function (error) {
                        var row = document.getElementById(queries[i].ID);
                        row.children[1].innerHTML = 'error';
                    }
                });
            }
        });
    </script>
</body>
</html>