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

Skype for Business Server 2015
 

Дата изменения раздела:2015-08-18

Краткое содержание. В этой статье представлен обзор учебных пособий и примеров по разработке панели мониторинга качества вызовов, которая является инструментом Skype для бизнеса Server 2015.

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

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

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

  1. служба данных, к которой пользователи после проверки подлинности могут обращаться через интерфейс Data API for Call Quality Dashboard (CQD) in Skype for Business Server 2015;

  2. служба репозитория, к которой пользователи после проверки подлинности могут обращаться через интерфейс Repository API for Call Quality Dashboard (CQD) in Skype for Business Server 2015;

  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, включающий в себя следующие данные.

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

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

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

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

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

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

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

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

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

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

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

    5. contentType (следует задать значение “application/json”).

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

    7. 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 для получения определения каждого отчета из данного набора. Get Items содержит дополнительные сведения об интерфейсе 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 && 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>

Результатом приведенного выше кода является веб-страница, подобная показанной на рисунке (без определения отчета при первом посещении). По URL‑адресу портала CQD определите идентификатор набора отчетов (он указан после знака ‘/#/’ в URL‑адресе; например, на первом рисунке набору отчетов присвоен идентификатор 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]&[0]&[1]&[1]&[Wired]&[Wired]",
             "Operand": 0,
             "UnionGroup": ""
         },
    
         { DataModelName: '[StreamType].[StreamType]', Caption: "Valid", Value: "[False]", Operand: 0, UnionGroup: "" }
    ],
    
    

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

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

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

    noteПримечание.
    Если включен общий доступ к ресурсам независимо от источника (CORS), для пользователей может отображаться сообщение об ошибке, указывающее на отсутствие заголовка "Доступ-Контроль-Разрешить-Происхождение" в запрашиваемом ресурсе и на запрет доступа при неопределенном происхождении. Для устранения это неполадки поместите файл HTML в папку, где установлен портал (по умолчанию это папка %SystemDrive%\Program Files\Skype for Business 2015 CQD\CQD). Затем откройте файл HTML в любом браузере по URL‑адресу http://<servername>/cqd/<html_file_name>. (По умолчанию локальной панели мониторинга CQD присвоен URL‑адрес http://<servername>/cqd.)

Код 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 & 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]&[1]&[1]&[1]&[Wired]&[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]&[0]&[1]&[1]&[Wired]&[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]&[0]&[1]&[0]&[Wired]&[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]&[0]&[1]&[1]&[Wired]&[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]&[0]&[0]&[0]&[Wired]&[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]&[0]&[1]&[1]&[Wired]&[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]&[0]&[1]&[0]&[Wired]&[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]&[0]&[1]&[1]&[Wifi]&[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]&[0]&[0]&[0]&[Wifi]&[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>

 
Показ: