Ejemplos de desarrollo de CQD

Skype for Business Server 2015
 

Última modificación del tema:2015-08-18

Resumen: revise un tutorial y muestras de desarrollo para el Panel de calidad de la llamada. El panel de calidad de la llamada es una herramienta Skype Empresarial Server 2015.

En este artículo se proporcionan un tutorial y ejemplos sobre el desarrollo del panel de calidad de llamada (CQD).

Tutorial: crear presentaciones de informes personalizadas con el servicio de datos y los API del servicio de repositorio del CQD.

El CQD ofrece acceso rápido y fácil a la información agregada de calidad de la llamada para las implementaciones de Skype Empresarial Server locales. El CQD consta de tres componentes: la base de datos de QoE, el cubo y el portal. El portal es el nivel de presentación principal y se puede dividir en los siguientes tres componentes:

  1. Servicio de datos, que es accesible para los usuarios autenticados a través del API de datos de llamada Quality Dashboard (CQD) en Skype para Business Server 2015.

  2. Servicio de repositorio, que es accesible para los usuarios autenticados a través del API del depósito para el panel de calidad de la llamada (CQD) en Skype para Business Server 2015.

  3. Portal web, que es la interfaz basada en HTML5 que los usuarios del CQD ven y con la que interactúan. Es accesible para los usuarios autenticados.

Los informes que se muestran en el portal web se agrupan en "conjunto de informes". La ilustración muestra un conjunto de informes con dos informes. Cada informe en el siguiente panel muestra los resultados de la consulta del número de llamadas buenas, llamadas deficientes y el porcentaje de llamadas deficientes durante varios meses, con varios filtros aplicados.

Informe de muestra de CQD

El CQD se crea según la Metodología de calidad de llamadas (CQM), de modo que el conjunto predeterminado de informes se diseña para alinearse con el flujo de investigación introducido por el CQM. Los usuarios también tienen la flexibilidad necesaria para editar o crear informes personalizados para satisfacer sus necesidades. Pero, ya que hay varias formas para visualizar los datos, la visualización proporcionada por el CQD puede no satisfacer por completo las necesidades de cada usuario. En estas situaciones, un usuario puede sacar provecho de los API de datos y de repositorio para crear páginas de informes personalizadas. En este tutorial se verán varios ejemplos.

Al ir a la página principal del CQD (p. ej. http://localhost/cqd), el conjunto de informes y los informes correspondientes para un usuario autorizado y autenticado se recuperará desde el servicio de repositorios. Una dirección URL completa se creará a partir del id. del conjunto de informes y el año-mes (el id. del conjunto de informes es el número entero después de la sección "/#/" en la URL y de forma predeterminada el año-mes actual se anexa al final del id. del conjunto de informes después de la barra diagonal). Las definiciones del informe se almacenan en formato JSON y al recuperarlas desde el servicio de repositorios, se usarán como entrada para el servicio de datos. El servicio de datos genera consultas de expresiones multidimensionales (MDX) basadas en la entrada y, luego, ejecuta estas consultas de MDX contra el cubo para recuperar los datos para cada informe.

El CQD ya tiene mucha flexibilidad para personalizar informes, pero podría haber situaciones en las que los usuarios deseen agregar los datos en varios informes creados en el CQD. Por ejemplo, es posible que necesite crear un informe que muestre el porcentaje de llamadas deficientes de todas las combinaciones posibles de llamadas por cable en una tabla (un resultado como la ilustración):

Tabla de CQD

Al usar el portal proporcionado por el CQD, el usuario tendría que ir a varios informes para extraer y registrar el % de llamadas deficientes de cada uno, que puede ser difícil si hay que recopilar muchos puntos de datos. Los API de datos ofrecen a los usuarios una forma programática para llevar a cabo esta tarea, al recuperar los datos del servicio de datos (por ejemplo, por medio de las llamadas AJAX).

Ejemplo 1: ejemplo de informe simple

Veremos un ejemplo sencillo primero. Si desea mostrar el recuento de secuencias de buen audio y de mal audio de febrero de 2015 en una página web como la de la ilustración:

Informe de ejemplo de CQD

Lo que necesitamos es enviar una llamada al servicio de datos con los parámetros adecuados y mostrar los resultados de la consulta en una tabla HTML. A continuación se muestra un ejemplo del código 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.');
	       }
            });
        });

Este ejemplo se puede deconstruir en tres pasos:

  1. Construir la consulta (en el ejemplo se define en la variable "consulta"). La consulta se define como un objeto JSON, que incluye los siguientes datos:

    1. Cero o más dimensiones. Cada dimensión se indica con un DataModelName.

    2. Cero o más filtros. Cada filtro tiene:

      • DataModelName (la dimensión que tendrá el conjunto de filtros).

      • Valor (el valor que comparará el operando).

      • Operando (tipo de comparación, 0 significa "igual").

    3. Una o más medidas.

  2. Enviar la consulta al servicio de datos por medio de llamadas AJAX. Hay que proporcionar los siguientes parámetros de solicitud:

    1. dirección URL (que necesita ser http://[NombreDelServidor]/QoEDataService/RunQuery).

    2. datos (esta es la representación de cadena del objeto JSON definida en la variable "consulta"). El servicio de datos devolverá los resultados de la consulta como un parámetro de la función de devolución de llamada correcta.

    3. tipo (para QoEDataService, RunQuery solo acepta las solicitudes "POST").

    4. asincrónico (una marca que indica si la llamada AJAX tendría que ser sincrónica o asincrónica).

    5. contentType (tendría que ser "application/json").

    6. correcto (función de devolución de llamadas para cuando la llamada AJAX finaliza correctamente).

    7. error (función de control de errores para cuando la llamada AJAX produce un error).

  3. Colocar datos en elementos div en el código HTML (en el ejemplo en el código, este paso se realiza a través de la llamada de función anónima una vez que la solicitud de AJAX se ha completado correctamente).

Enmarcar el código JavaScript en una página HTML y la página mostrará un informe como el que se muestra en la ilustración. El HTML completo es el siguiente:


<!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>

Hasta el momento, el informe es todavía muy simple. El usuario puede agregar más medidas, dimensiones, o filtros para personalizar el informe. Por ejemplo, si desea mostrar el porcentaje de llamadas deficientes de AppSharing, hay que agregar una nueva medida relacionada con AppSharing. Si desea mostrar todas las llamadas TCP frente a las llamadas UDP, tendría que agregar una nueva dimensión relacionada con el tipo de transporte. Si desea mostrar el número de llamadas deficientes dentro de un edificio determinado, tendría que agregar un nuevo filtro para seleccionar las llamadas a y desde ese edificio.

Ejemplo 2: ejemplo de definición de informe

Podría ser difícil averiguar cómo escribir la lista completa de medidas/dimensiones/filtros y sus valores correspondientes al construir una consulta. En este caso, puede ir al Portal, crear un informe con el editor de informes y ver la cadena JSON de la definición de informe y, luego, copiar la definición en un informe personalizado.

En este ejemplo, vamos a crear una página web como la que se muestra en la ilustración en la que el usuario puede especificar el id. de cualquier conjunto de informes existente (o del informe) y mostrar la definición del conjunto de informes o del informe en la página web. Luego, el usuario puede conectar la cadena JSON de cada informe a un código similar al que se muestra en el Ejemplo 1 y construir cualquier informe personalizado que desee el usuario.

Ejemplo de CQD

Para crear la herramienta del visor de definición de informe, es necesario enviar llamadas al servicio de repositorio para recuperar las representaciones de cadena JSON de las definiciones de cada conjunto de informes que queremos. El API de repositorio devolverá definiciones del conjunto de informes en función de un id. de conjunto de informes determinado.

Un ejemplo rápido es el siguiente, el código contiene un bloque que es un ejemplo sencillo para enviar una consulta al servicio de repositorio para obtener el contenido de un elemento del repositorio basado en su identificador. Y la siguiente parte de código (método processReportSetData) envía las llamadas AJAX para obtener la definición de cada informe dentro de ese conjunto de informes. Debido a que el id. en el portal web CQD es el id. de un conjunto de informes, la llamada AJAX devolverá un elemento del conjunto de informes. Puede encontrar más información sobre el API de repositorio y en concreto, GetItems, en la Get Items.


<!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>

Este ejemplo dará como resultado una página web como la de la ilustración (sin la definición de informe tras la visita inicial). Obtenga el id. del conjunto de informes desde el portal CQD (está después de "/#/' en la URL del portal CQD al iniciar sesión, por ejemplo, en la primera ilustración el id. del conjunto de informes es 3024), y ponga este id. del conjunto de informes en la sección de entrada de esta página web. Presione el botón "cargar" y vea la definición completa (medidas, dimensiones, listas de filtros) del conjunto de informes.

En resumen, para obtener de forma rápida la definición completa de un informe o conjunto de informes. Los pasos son:

  1. Vaya al Portal y use el editor de consultas para personalizar un informe (haga clic en el botón "Editar" encima de un informe para editar, agregar, quitar medidas/dimensiones/filtros y, luego, guarde el informe).

  2. Obtenga el id. del conjunto de informes desde la URL (el entero después de "/#/" en la URL para iniciar sesión).

  3. Inicie esta página web de definición de informes creada en el Ejemplo 2, escriba el id. de conjunto de informes y recupere la definición completa de un conjunto de informes (para usar en las llamadas de API de datos).

Ejemplo 3: ejemplo de cuadro de mandos

Es hora de una tarea más complicada. ¿Qué ocurre si desea crear una página web como la de la ilustración? Hay que actualizar el Ejemplo 1, (con la ayuda de la página web generada en el Ejemplo 2 para recuperar la definición completa de cualquier informe) de modo que podamos controlar más cantidad de datos.

En este caso, hay que actualizar la lista de medidas y dimensiones. La forma de averiguar cómo agregar/editar una medida o una dimensión es seguir las instrucciones en el Ejemplo 2 y recuperar la definición completa del informe, incluidas las listas de medidas y dimensiones completas. Conecte la definición completa del informe en el código de ejemplo.

Estos son los pasos detallados para ir a la página del cuadro de mandos en la ilustración del ejemplo proporcionado en el Ejemplo 1:

  1. Actualice las medidas en la variable de "consulta" desde [Measures].[Audio Good Streams JPDR Count] y [Measures].[Audio Poor Streams JPDR Count] a [Measures].[AudioPoorJPDRPercentage].

  2. Actualice los filtros. Los datos JSON para filtros en el Ejemplo 1 tienen un filtro, que se establece en la dimensión [StartDate].[Month]. Puesto que los filtros son una matriz JSON, se pueden agregar dimensiones adicionales a la lista de filtros. Por ejemplo, para obtener las llamadas por cable internas del servidor-cliente para el "currentMonth", se necesitan tener los siguientes filtros:

    
    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: "" }
    ],
    
    

    Aquí la dimensión [Scenarios].[ScenarioPair] es igual que [1]&[0]&[1]&[1]&[Wired]&[Wired]. El [Scenario.][ScenarioPair] es una dimensión especial creada para simplificar la creación de informes. Tiene seis valores que se corresponden con [FirstIsServer], [SecondIsServer], [FirstInside], [SecondIsServer], [FirstConnectionType], [SecondConnectionType]. Por lo tanto, en lugar de usar una combinación de 6 filtros para definir un escenario, solo tiene que usar 1 filtro. En nuestro ejemplo, el valor [1]&[0]&[1]&[1]&[Wired]&[Wired] se traduce en el escenario donde: el primero es servidor, el segundo no es servidor, el primero está dentro, el segundo está dentro, el primer tipo de conexión es con cable y el segundo tipo de conexión es con cable, que es la definición exacta de "Por cable dentro del servidor-cliente".

  3. Cree un conjunto de filtros para cada escenario. Cada fila en el cuadro de mandos, en la ilustración, representa un escenario diferente, que será un filtro diferente (mientras que las dimensiones y medidas son las mismas).

  4. Analice los resultados de las llamadas AJAX y colóquelos en la posición correcta de la tabla. Puesto que se trata principalmente de manipulación del HTML y del JavaScript, no entraremos en detalles. En su lugar, se proporciona el código en el Apéndice A.

    noteNota:
    Si el uso compartido de recursos entre orígenes (CORS) está habilitado, los usuarios pueden encontrarse con errores como "No se encuentra el encabezado 'Access-Control-Allow-Origin' en el recurso solicitado. El origen 'null' es, por lo tanto, un acceso no permitido". Para resolver el problema, coloque el archivo HTML en la carpeta en la que está instalado el Portal (de forma predeterminada, necesita ser %SystemDrive%\Program Files\Skype for Business 2015 CQD\CQD). Luego obtenga acceso al html a través de cualquier explorador con la URL http://<servername>/cqd/<html_file_name>. (La dirección URL predeterminada para el panel CQD local es http://<servername>/cqd.)

Código HTML para el Ejemplo 3 (ejemplo de cuadro de mandos):


<!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>

 
Mostrar: