CQD 개발 샘플

요약: 통화 품질 대시보드에 대한 자습서 및 개발 샘플을 검토합니다. 통화 품질 대시보드는 비즈니스용 Skype 서버 위한 도구입니다.

이 문서에서는 CQD(통화 품질 대시보드)에 대한 개발에 대한 자습서 및 샘플을 제공합니다.

CQD(통화 품질 대시보드) 개발 샘플

자습서: CQD 데이터 서비스 및 리포지토리 서비스 API를 사용하여 사용자 지정된 보고서 프레젠테이션 빌드

CQD 소개

CQD는 온-프레미스 비즈니스용 Skype 서버 배포를 위해 집계된 통화 품질 정보에 빠르고 쉽게 액세스할 수 있도록 합니다. CQD는 QoE 보관 데이터베이스, 큐브 및 포털의 세 가지 구성 요소로 구성됩니다. 포털은 기본 프레젠테이션 계층이며 다음 세 가지 구성 요소로 더 자세히 나눌 수 있습니다.

  1. Data Service는 비즈니스용 Skype 서버 CQD(통화 품질 대시보드)용 데이터 API를 통해 인증된 사용자가 액세스할 수 있습니다.

  2. 리포지토리 서비스는 비즈니스용 Skype 서버 CQD(통화 품질 대시보드)용 리포지토리 API를 통해 인증된 사용자가 액세스할 수 있습니다.

  3. 웹 포털은 CQD 사용자가 보고 상호 작용하는 HTML5 기반 인터페이스입니다. 인증된 사용자가 액세스할 수 있습니다.

웹 포털에 표시된 보고서는 "보고서 집합"으로 그룹화됩니다. 그림은 두 개의 보고서가 있는 보고서 집합을 보여 줍니다. 아래 dashboard 각 보고서는 다양한 필터가 적용된 몇 달 동안 양호한 호출 수, 잘못된 호출 및 낮은 호출 비율에 대한 쿼리 결과를 보여 줍니다.

CQD 샘플 보고서.

CQD는 CQM(통화 품질 방법론) 다음에 만들어지므로 기본 보고서 집합은 CQM에서 도입한 조사 흐름에 맞게 설계되었습니다. 또한 사용자는 필요에 맞게 사용자 지정 보고서를 유연하게 편집하거나 만들 수 있습니다. 그러나 데이터를 시각화하는 방법에는 여러 가지가 있으므로 CQD에서 제공하는 시각화가 모든 사용자의 요구를 완전히 해결하지 못할 수 있습니다. 이러한 상황에서 사용자는 데이터 API 및 리포지토리 API를 활용하여 사용자 지정 보고서 페이지를 만들 수 있습니다. 이 자습서에서는 일련의 예제를 살펴보겠습니다.

dashboard 데이터 서비스를 사용하는 방법

CQD 홈페이지(예: http://localhost/cqd))로 이동하면 인증되고 권한이 부여된 사용자에 대한 보고서 집합 및 해당 보고서가 리포지토리 서비스에서 검색됩니다. 전체 URL은 보고서 집합 ID 및 연도 월에서 생성됩니다(보고서 집합 ID는 URL에서 '/#/' 섹션 뒤의 정수 번호이며, 기본적으로 현재 연도는 슬래시 후 보고서 집합 ID 끝에 추가됩니다). 보고서 정의는 JSON 형식으로 저장되고 리포지토리 서비스에서 검색되면 데이터 서비스에 대한 입력으로 사용됩니다. Data Service는 입력을 기반으로 MDX(다차원 식) 쿼리를 생성한 다음 큐브에 대해 이러한 MDX 쿼리를 실행하여 각 보고서에 대한 데이터를 검색합니다.

사용자 지정된 보고서 빌드

CQD는 이미 보고서를 사용자 지정하는 데 많은 유연성을 가지고 있지만 사용자가 CQD에서 만든 여러 보고서에서 데이터를 집계하려는 경우가 있을 수 있습니다. 예를 들어 테이블에서 가능한 모든 유선 호출 조합의 호출 비율이 좋지 않은 보고서를 만들어야 할 수 있습니다(그림과 같은 결과).

CQD 테이블.

CQD에서 제공하는 포털을 사용하여 사용자는 여러 보고서로 이동하여 각 보고서에 대한 잘못된 호출 %를 추출하고 기록해야 하며, 수집해야 하는 데이터 요소가 많은 경우 힘들 수 있습니다. 데이터 API는 사용자에게 데이터 서비스(예: AJAX 호출을 통해)에서 데이터를 검색하여 이를 수행하는 프로그래밍 방식의 방법을 제공합니다.

예제 1: 간단한 보고서 샘플

먼저 간단한 예제를 살펴보겠습니다. 그림과 같은 HTML 페이지에 2015년 2월의 오디오 양수 스트림 및 오디오 잘못된 스트림 수를 표시하려는 경우:

CQD 예제 보고서입니다.

필요한 것은 적절한 매개 변수를 사용하여 Data Service에 대한 호출을 보내고 쿼리 결과를 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 개체로 정의됩니다.

    a. 0개 이상의 차원입니다. 각 차원은 DataModelName으로 표시됩니다.

    b. 필터가 0개 이상입니다. 각 필터에는 다음이 있습니다.

    • DataModelName(필터가 설정된 차원).

    • 값(피연산자에서 비교할 값)입니다.

    • 피연산자(비교 형식, 0은 "등호"를 의미).

      C. 하나 이상의 측정값.

  2. AJAX 호출을 통해 데이터 서비스에 쿼리를 보냅니다. 다음 요청 매개 변수를 제공해야 합니다.

    a. url(http://[ServerName]/QoEDataService/RunQuery여야 합니다.

    b. data('query' 변수에 정의된 JSON 개체의 문자열 표현입니다). Data Service는 성공에 대한 콜백 함수의 매개 변수로 쿼리 결과를 반환합니다.

    C. type(QoEDataService의 경우 RunQuery는 'POST' 요청만 허용).

    D. 비동기(AJAX 호출이 동기 또는 비동기여야 하는지 여부를 나타내는 플래그).

    전자. contentType("application/json"이어야 함).

    F. success(AJAX 호출이 성공적으로 완료된 경우의 콜백 함수).

    G. error(AJAX 호출이 실패할 때의 오류 처리 함수).

  3. HTML의 div 요소에 데이터를 넣습니다(코드 예제에서는 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 호출 v.s. UDP 호출을 표시하려면 운송 유형에 대한 새 차원을 추가해야 합니다. 특정 건물 내에서 잘못된 호출 수를 표시하려면 새 필터를 추가하여 해당 건물과 주고받을 호출을 선택해야 합니다.

예제 2: 보고서 정의 샘플

쿼리를 생성할 때 측정값/차원/필터 및 해당 값의 전체 목록을 작성하는 방법을 파악하기 어려울 수 있습니다. 이 경우 포털로 이동하여 보고서 편집기를 사용하여 보고서를 빌드하고 보고서 정의의 JSON 문자열을 확인한 다음 정의를 사용자 지정 보고서에 복사할 수 있습니다.

이 예제에서는 사용자가 기존 보고서 집합(또는 보고서)의 ID를 입력하고 웹 페이지에 보고서 집합 또는 보고서의 정의를 표시할 수 있는 그림에 표시된 것과 같은 웹 페이지를 만듭니다. 그런 다음, 사용자는 각 보고서의 JSON 문자열을 예제 1에 표시된 코드와 유사한 코드에 연결하고 사용자가 원하는 사용자 지정된 보고서를 생성할 수 있습니다.

CQD 예제.

보고서 정의 뷰어 도구를 만들려면 리포지토리 서비스에 대한 호출을 보내 원하는 모든 보고서 집합의 정의에 대한 JSON 문자열 표현을 검색해야 합니다. 리포지토리 API는 지정된 보고서 집합 ID에 따라 보고서 집합 정의를 반환합니다.

빠른 예제는 다음과 같습니다. 코드에는 리포지토리 서비스에 쿼리를 전송하여 식별자를 기반으로 리포지토리 항목의 콘텐츠를 가져오는 간단한 예제인 블록이 포함되어 있습니다. 그리고 코드의 다음 부분(processReportSetData 메서드)은 AJAX 호출을 전송하여 해당 보고서 집합 내의 각 보고서의 정의를 가져옵니다. CQD 웹 포털의 ID는 보고서 집합의 ID이므로 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 포털에서 보고서 집합 ID를 가져오고(CQD 포털 URL에서 '/#/' 로그인 후임(예: 첫 번째 그림에서 보고서 집합 ID는 3024) 이 보고서 집합 ID를 이 웹 페이지의 입력 섹션에 넣습니다. "로드" 단추를 누르고 보고서 집합의 전체 정의(측정값, 차원, 필터 목록)를 확인합니다.

요약하자면, 보고서/보고서 집합의 전체 정의를 신속하게 가져오기 위한 것입니다. 단계는 다음과 같습니다.

  1. 포털로 이동하여 쿼리 편집기를 사용하여 보고서를 사용자 지정합니다(보고서 위의 "편집" 단추를 클릭하여 편집, 추가, 측정값/차원/필터 제거, 보고서 저장).

  2. URL('/#/' 로그인 URL 뒤의 정수)에서 보고서 집합 ID를 가져옵니다.

  3. 예제 2에서 만든 이 보고서 정의 웹 페이지를 시작하고 보고서 집합 ID를 입력하고 보고서 집합의 전체 정의를 검색합니다(데이터 API 호출에 사용).

    예제 3: 성과 기록표 샘플

더 복잡한 작업을 위한 시간입니다. 그림과 같은 웹 페이지를 만들려면 어떻게 해야 할까요? 더 많은 양의 데이터를 처리할 수 있도록 예제 1(예제 2에서 생성된 웹 페이지의 도움으로 보고서의 전체 정의를 검색)을 업데이트해야 합니다.

이 경우 측정값 및 차원 목록을 업데이트해야 합니다. 측정값 및/또는 차원을 추가/편집하는 방법을 파악하는 방법은 예제 2의 지침을 따르고 전체 측정 및 차원 목록을 포함하여 전체 보고서 정의를 검색하는 것입니다. 전체 보고서 정의를 샘플 코드에 연결합니다.

다음은 예제 1에 제공된 샘플의 그림에서 성과 기록표 페이지로 가져오는 자세한 단계입니다.

  1. 에서 로 'query' 변수 [Measures].[Audio Good Streams JPDR Count][Measures].[Audio Poor Streams JPDR Count] 의 측정값을 업데이트합니다 [Measures].[AudioPoorJPDRPercentage].

  2. 필터를 업데이트합니다. 예제 1의 필터에 대한 JSON 데이터에는 차원 [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] 보고서 만들기를 간소화하기 위해 만든 특수 차원입니다. 에 해당하는 6개의 값이 있습니다 [FirstIsServer], [SecondIsServer], [FirstInside], [SecondIsServer], [FirstConnectionType], [SecondConnectionType]. 따라서 시나리오를 정의하기 위해 6개의 필터 조합을 사용하는 대신 필터 1개만 사용해야 합니다. 이 예제에서 값 [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'은 액세스가 허용되지 않습니다." 문제를 resolve 포털이 설치된 폴더 아래에 HTML 파일을 배치합니다(기본적으로 여야 %SystemDrive%\Program Files\Skype for Business 2015 CQD\CQD)합니다.) 그런 다음 URL http://<servername>/cqd/<html_file_name>을 사용하여 브라우저를 통해 html에 액세스합니다. (로컬 CQD dashboard 기본 URL은 )입니다http://<servername>/cqd..

부록 A

예제 3의 HTML 코드(성과 기록표 샘플):

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