Share via


Criar um suplemento de Project que usa REST com um serviço OData local do Project Server

Este artigo descreve como criar um suplemento de painel de tarefas para Project Professional que compara os dados de custo e trabalho no projeto ativo com as médias de todos os projetos na instância atual do Project Web App. O suplemento usa REST com a biblioteca jQuery para acessar o serviço de relatórios ProjectData OData no Project Server.

O código deste artigo é baseado em um exemplo desenvolvido por Saurabh Sanghvi e Arvind Iyer, da Microsoft Corporation.

Pré-requisitos

Veja a seguir os pré-requisitos para criar um suplemento de painel de tarefas do Project que lê o serviço ProjectData de uma instância do Project Web App em uma instalação local do Project Server.

  • Project Professional é necessário para se conectar ao Project Web App. O computador de desenvolvimento deve ter Project Professional instalado para habilitar a depuração do F5 com o Visual Studio.

    Observação

    Project Standard também pode hospedar suplementos do painel de tarefas, mas não pode entrar no Project Web App.

  • O Visual Studio 2015 com as Ferramentas de Desenvolvedor do Office para Visual Studio inclui modelos para criar suplementos do Office e do SharePoint. Verifique se você instalou a versão mais recente do Office Developer Tools; consulte a seção Ferramentas dos suplementos do Office e downloads do SharePoint.

  • Os procedimentos e exemplos de código neste artigo acessam o serviço ProjectData do Project Server em um domínio local. Os métodos jQuery neste artigo não funcionam com o Project na Web.

    Verifique se o serviço ProjectData está acessível no computador de desenvolvimento.

Procedimento 1. Verifique se o serviço ProjectData está acessível

  1. Para permitir que seu navegador mostre diretamente os dados XML de uma consulta REST, desative a exibição de leitura do feed. Para obter informações sobre como fazer isso na Internet Explorer, consulte Procedimento 1, etapa 4 em Consultar feeds OData para dados de relatório de projeto.

  2. Consulte o serviço ProjectData usando seu navegador com a seguinte URL: http://ServerName /ProjectServerName /_api/ProjectData. Por exemplo, se a instância do Project Web App for http://MyServer/pwa, o navegador mostrará os resultados a seguir.

    <?xml version="1.0" encoding="utf-8"?>
        <service xml:base="http://myserver/pwa/_api/ProjectData/"
        xmlns="https://www.w3.org/2007/app"
        xmlns:atom="https://www.w3.org/2005/Atom">
        <workspace>
            <atom:title>Default</atom:title>
            <collection href="Projects">
                <atom:title>Projects</atom:title>
            </collection>
            <collection href="ProjectBaselines">
                <atom:title>ProjectBaselines</atom:title>
            </collection>
            <!-- ... and 33 more collection elements -->
        </workspace>
        </service>
    
  3. Talvez você precise fornecer suas credenciais de rede para ver os resultados. Se o navegador exibir "Erro 403, acesso negado" você não tem permissão de logon para essa instância do Project Web App ou há um problema de rede que exige ajuda administrativa.

Usar o Visual Studio para criar um suplemento de painel de tarefas para o Project

As Ferramentas de Desenvolvedor do Office para Visual Studio incluem um modelo para suplementos de painel de tarefas para Project. Se você criar uma solução chamada HelloProjectOData, a solução conterá os dois projetos do Visual Studio a seguir:

  • O projeto de suplemento usa o nome da solução. Ele inclui o arquivo de manifesto XML para o suplemento e serve para o .NET Framework 4.5. O procedimento 3 mostra as etapas para modificar o manifesto do suplemento HelloProjectOData .

  • O projeto Web se chama HelloProjectODataWeb. Ele inclui as páginas da Web, os arquivos JavaScript, os arquivos CSS, as imagens, as referências e os arquivos de configuração para o conteúdo Web no painel de tarefas. O projeto Web serve para o .NET Framework 4. O procedimento 4 e o Procedimento 5 mostram como modificar os arquivos no projeto Web para criar a funcionalidade do suplemento HelloProjectOData .

Procedimento 2. Criar o suplemento HelloProjectOData para Project

  1. Execute o Visual Studio 2015 como administrador e selecione Novo Projeto na página Iniciar.

  2. Na caixa de diálogo Novo Projeto, expanda os nós Modelos, Visual C#e Office/SharePoint e selecione Suplementos do Office. Selecione .NET Framework 4.5.2 na lista suspensa da estrutura de destino na parte superior do painel central e selecione Suplemento do Office (confira a próxima captura de tela).

  3. Para colocar ambos os projetos do Visual Studio no mesmo diretório, selecione Criar diretório para solução e navegue até o local desejado.

  4. No campo Nome , digiteHelloProjectOData e escolha OK.

    Figura 1. Criar um Suplemento do Office

    Crie um suplemento do Office.

  5. Na caixa de diálogo Escolher o tipo de suplemento , selecione Painel tarefa e escolha Avançar (confira a próxima captura de tela).

    Figura 2. Escolher o tipo de suplemento a criar

    Escolha o tipo de suplemento a ser criado.

  6. Na caixa de diálogo Escolher aplicativos host, desmarque todas as caixas de marcar exceto a caixa de marcar do Projeto (confira a próxima captura de tela) e escolha Concluir.

    Figura 3. Choosing the host application

    Escolha Project como o único aplicativo host.

    O Visual Studio cria o projeto HelloProjectOdata e o projeto HelloProjectODataWeb .

A pasta AddIn (consulte a próxima captura de tela) contém o arquivo App.css para estilos CSS personalizados. Na subpasta Home, o arquivo Home.html contém referências para arquivos CSS e JavaScript que o suplemento usa, e o conteúdo HTML5 para o suplemento. Além disso, o arquivo Home.js é para o seu código JavaScript personalizado. A pasta Scripts inclui os arquivos da biblioteca jQuery. A subpasta Office inclui as bibliotecas JavaScript, como office.js e project-15.js, além das bibliotecas de linguagem para cadeias de caracteres padrão nos suplementos do Office. Na pasta Content, o arquivo Office.css contém os estilos padrão de todos os Suplementos do Office.

Figura 4. Exibir os arquivos padrão do projeto Web no Gerenciador de Soluções

Exiba os arquivos do projeto Web no Gerenciador de Soluções.

O manifesto do projeto HelloProjectOData é o arquivo HelloProjectOData.xml. Opcionalmente, você pode modificar o manifesto para adicionar uma descrição do suplemento, uma referência a um ícone, informações de linguagem adicionais e outras configurações. O Procedimento 3 simplesmente modifica o nome de exibição e a descrição do suplemento e adiciona um ícone.

Para obter mais informações sobre o manifesto, consulte Manifesto de suplementos do Office e referência de esquema para manifestos de suplementos do Office.

Procedimento 3. Modificar o manifesto de suplemento

  1. No Visual Studio, abra o arquivo HelloProjectOData.xml.

  2. O nome de exibição padrão é o nome do projeto do Visual Studio ("HelloProjectOData"). Por exemplo, altere o valor padrão do <elemento DisplayName> para"Hello ProjectData".

  3. A descrição padrão também é "HelloProjectOData". Por exemplo, altere o valor padrão do elemento Description para "Testar consultas REST do serviço ProjectData".

  4. Adicione um ícone a ser exibido na lista suspensa Suplementos do Office na guia PROJECT da faixa de opções. Você pode adicionar um arquivo de ícone na solução do Visual Studio ou usar uma URL para um ícone.

As etapas a seguir mostram como adicionar um arquivo de ícone à solução do Visual Studio.

  1. Em Gerenciador de Soluções, acesse a pasta chamada Imagens.

  2. Para ser exibido na lista suspensa Suplementos do Office , o ícone deve ter 32 x 32 pixels. Use seu próprio ícone 32 x 32; ou copie a imagem a seguir para um arquivo chamado NewIcon.pnge adicione esse arquivo à HelloProjectODataWeb\Images pasta.

    Ícone para o aplicativo HelloProjectOData.

  3. No manifesto HelloProjectOData.xml, adicione um <elemento IconUrl> abaixo do <elemento Description> , em que o valor da URL do ícone é o caminho relativo para o arquivo de ícone 32x32. Por exemplo, adicione a seguinte linha: <IconUrl DefaultValue="~remoteAppUrl/Images/NewIcon.png" />. O arquivo de manifesto HelloProjectOData.xml agora contém o seguinte (o <valor da ID> será diferente):

    <?xml version="1.0" encoding="UTF-8"?>
    <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="TaskPaneApp">
        <!--IMPORTANT! Id must be unique for each add-in. If you copy this manifest ensure that you change this id to your own GUID. -->
        <Id>c512df8d-a1c5-4d74-8a34-d30f6bbcbd82</Id>
        <Version>1.0</Version>
        <ProviderName> [Provider name]</ProviderName>
        <DefaultLocale>en-US</DefaultLocale>
        <DisplayName DefaultValue="Hello ProjectData" />
        <Description DefaultValue="Test REST queries of the ProjectData service"/>
        <IconUrl DefaultValue="~remoteAppUrl/Images/NewIcon.png" />
        <SupportUrl DefaultValue="[Insert the URL of a page that provides support information for the app]" />
        <Hosts>
            <Host Name="Project" />
        </Hosts>
        <DefaultSettings>
            <SourceLocation DefaultValue="~remoteAppUrl/AddIn/Home/Home.html" />
        </DefaultSettings>
        <Permissions>ReadWriteDocument</Permissions>
    </OfficeApp>
    

Criar o conteúdo HTML para o suplemento HelloProjectOData

O suplemento HelloProjectOData é um exemplo que inclui depuração e saída de erro; ela não se destina ao uso de produção. Antes de começar a codificar o conteúdo HTML, crie a interface do usuário e a experiência do usuário para o suplemento e delineie as funções JavaScript que interagem com o código HTML. Para obter mais informações, consulte Diretrizes de design para suplementos do Office.

O painel de tarefas mostra o nome de exibição do suplemento na parte superior, que é o valor do <elemento DisplayName> no manifesto. O elemento body no arquivo HelloProjectOData.html contém outros elementos de interface do usuário, da seguinte maneira:

  • Uma legenda indica a funcionalidade geral ou o tipo de operação, por exemplo, ODATA REST QUERY.

  • O botão Obter Ponto de Extremidade do ProjectData chama a setOdataUrl função para obter o ponto de extremidade do serviço ProjectData e exibi-la em uma caixa de texto. Se o projeto não estiver conectado ao Project Web App, o suplemento chama um identificador de erro para exibir uma mensagem de erro pop-up.

  • O botão Comparar Todos os Projetos está desabilitado até que o suplemento obtenha um ponto de extremidade OData válido. Quando você seleciona o botão, ele chama a retrieveOData função, que usa uma consulta REST para obter os dados de custo do projeto e de trabalho do serviço ProjectData .

  • Uma tabela exibe os valores médios de custo do projeto, custo real, trabalho e porcentagem concluída. A tabela também compara os valores atuais do projeto ativo com a média. Se o valor atual for maior que a média de todos os projetos, será exibido em vermelho. Se o valor atual for menor que a média, será exibido em verde. Se o valor atual não estiver disponível, a tabela exibirá um NA azul.

    A retrieveOData função chama a parseODataResult função, que calcula e exibe valores para a tabela.

    Observação

    Neste exemplo, os dados de custo e trabalho do projeto ativo são derivados dos valores publicados. Se você alterar valores no Project, o serviço ProjectData não terá as alterações até que o projeto seja publicado.

Procedimento 4. Criar o conteúdo HTML

  1. No elemento principal do arquivo Home.html, adicione quaisquer elementos de link adicionais para arquivos CSS que seu suplemento usa. O modelo de projeto do Visual Studio inclui um link para o arquivo App.css que você pode usar para os estilos CSS personalizados.

  2. Adicione quaisquer elementos de script adicionais para bibliotecas JavaScript que seu suplemento usa. O modelo de projeto inclui links para os arquivos jQuery- [versão].js, office.js e MicrosoftAjax.js na pasta Scripts .

    Observação

    Antes de implantar o suplemento, altere a referência office.js e a referência jQuery à referência da CDN (rede de entrega de conteúdo). A referência CDN fornece a versão mais recente e melhora o desempenho.

    O suplemento HelloProjectOData também usa um arquivo SurfaceErrors.js , que exibe erros em uma mensagem pop-up. Copie o código da seção de arquivoSurfaceErrors.js deste artigo para a pasta Scripts\Office do projeto HelloProjectODataWeb como um novo arquivo chamado SurfaceErrors.js.

    A seguir está o código HTML atualizado para o elemento principal , com a linha adicional para o arquivo SurfaceErrors.js .

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
    <title>Test ProjectData Service</title>
    
    <link rel="stylesheet" type="text/css" href="../Content/Office.css" />
    
    <!-- Add your CSS styles to the following file. -->
    <link rel="stylesheet" type="text/css" href="../Content/App.css" />
    
    <!-- Use the CDN reference to the mini-version of jQuery when deploying your add-in. -->
    <!--<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script> -->
    <script src="../Scripts/jquery-1.7.1.js"></script>
    
    <!-- Use the CDN reference to office.js when deploying your add-in. -->
    <!--<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>-->
    
    <!-- Use the local script references for Office.js to enable offline debugging -->
    <script src="../Scripts/Office/1.0/MicrosoftAjax.js"></script>
    <script src="../Scripts/Office/1.0/Office.js"></script>
    
    <!-- Add your JavaScript to the following files. -->
    <script src="../Scripts/HelloProjectOData.js"></script>
    <script src="../Scripts/SurfaceErrors.js"></script>
    </head>
    <body>
    <!-- See the code in Step 3. -->
    </body>
    </html>
    
  3. No elemento body , exclua o código existente do modelo e adicione o código para a interface do usuário. Se um elemento deve ser preenchido com os dados ou manipulado por uma instrução jQuery, deve incluir um atributo id exclusivo. No código a seguir, os atributos de id para os elementos de botão, span e td (definição de célula de tabela) que as funções jQuery usam são mostrados na fonte em negrito.

    O HTML a seguir adiciona uma imagem gráfica (NewLogo.png), que pode ser um logotipo da empresa.

    <body>
        <div id="SectionContent">
        <div id="odataQueries">
            ODATA REST QUERY
        </div>
        <div id="odataInfo">
            <button class="button-wide" onclick="setOdataUrl()">Get ProjectData Endpoint</button>
            <br /><br />
            <span class="rest" id="projectDataEndPoint">Endpoint of the 
                <strong>ProjectData</strong> service</span>
            <br />
        </div>
        <div id="compareProjectData">
            <button class="button-wide" disabled="disabled" id="compareProjects"
            onclick="retrieveOData()">Compare All Projects</button>
            <br />
        </div>
        </div>
        <div id="corpInfo">
            <table class="infoTable" aria-readonly="True" style="width: 100%;">
                <tr>
                    <td class="heading_leftCol"></td>
                    <td class="heading_midCol"><strong>Average</strong></td>
                    <td class="heading_rightCol"><strong>Current</strong></td>
                </tr>
                <tr>
                    <td class="row_leftCol"><strong>Project Cost</strong></td>
                    <td class="row_midCol" id="AverageProjectCost">&amp;nbsp;</td>
                    <td class="row_rightCol" id="CurrentProjectCost">&amp;nbsp;</td>
                </tr>
                <tr>
                    <td class="row_leftCol"><strong>Project Actual Cost</strong></td>
                    <td class="row_midCol" id="AverageProjectActualCost">&amp;nbsp;</td>
                    <td class="row_rightCol" id="CurrentProjectActualCost">&amp;nbsp;</td>
                </tr>
                <tr>
                    <td class="row_leftCol"><strong>Project Work</strong></td>
                    <td class="row_midCol" id="AverageProjectWork">&amp;nbsp;</td>
                    <td class="row_rightCol" id="CurrentProjectWork">&amp;nbsp;</td>
                </tr>
                <tr>
                    <td class="row_leftCol"><strong>Project % Complete</strong></td>
                    <td class="row_midCol" id="AverageProjectPercentComplete">&amp;nbsp;</td>
                    <td class="row_rightCol" id="CurrentProjectPercentComplete">&amp;nbsp;</td>
                </tr>
            </table>
        </div>
        <img alt="Corporation" class="logo" src="../../images/NewLogo.png" />
        <br />
        <textarea id="odataText" rows="12" cols="40"></textarea>
    </body>
    

Criar o código JavaScript para o suplemento

O modelo para um suplemento de painel de tarefas do Project inclui o código de inicialização padrão projetado para demonstrar ações básicas de obter e definir dados em um documento para um suplemento do Office que usa as APIs comuns. Como o Project não dá suporte a ações que gravam no projeto ativo e o suplemento HelloProjectOData não usa o getSelectedDataAsync método, você pode excluir o script dentro da Office.initialize função e excluir a função e getData a setData função no arquivo padrão HelloProjectOData.js.

O JavaScript inclui constantes globais para a consulta REST e variáveis globais que são usadas em várias funções. O botão Obter Ponto de Extremidade do ProjectData chama a setOdataUrl função, que inicializa as variáveis globais e determina se o Project está conectado ao Project Web App.

O restante do arquivo HelloProjectOData.js inclui duas funções: a retrieveOData função é chamada quando o usuário seleciona Comparar Todos os Projetos; e a parseODataResult função calcula médias e, em seguida, preenche a tabela de comparação com valores formatados para cores e unidades.

Procedimento 5. Criar o código JavaScript

  1. Exclua todo o código no arquivo HelloProjectOData.js padrão e adicione as variáveis e Office.initialize a função globais. Nomes de variáveis que são todos capitais implicam que são constantes; eles são usados posteriormente com a _pwa variável para criar a consulta REST neste exemplo.

    let PROJDATA = "/_api/ProjectData";
    let PROJQUERY = "/Projects?";
    let QUERY_FILTER = "$filter=ProjectName ne 'Timesheet Administrative Work Items'";
    let QUERY_SELECT1 = "&amp;$select=ProjectId, ProjectName";
    let QUERY_SELECT2 = ", ProjectCost, ProjectWork, ProjectPercentCompleted, ProjectActualCost";
    let _pwa;           // URL of Project Web App.
    let _projectUid;    // GUID of the active project.
    let _docUrl;        // Path of the project document.
    let _odataUrl = ""; // URL of the OData service: http[s]://ServerName /ProjectServerName /_api/ProjectData
    
    // Ensure the Office.js library is loaded.
    Office.onReady(function() {
        // Office is ready.
        $(document).ready(function () {
            // The document is ready.
        });
    });
    
  2. Adicionar setOdataUrl e funções relacionadas. A setOdataUrl função chama getProjectGuid e getDocumentUrl para inicializar as variáveis globais. No método getProjectFieldAsync, a função anônima para o parâmetro de retorno de chamada habilita o botão Comparar Todos os Projetos usando o removeAttr método na biblioteca jQuery e exibe a URL do serviço ProjectData . Se o Project não estiver conectado ao Project Web App, a função gera um erro e exibe uma mensagem de erro pop-up. O arquivo SurfaceErrors.js inclui a throwError função.

    Observação

    Se você executar o Visual Studio no computador Project Server, para usar a depuração F5 , descompacte o código após a linha que inicializa a _pwa variável global. Para habilitar o uso do método jQuery ajax ao depurar no computador project server, você deve definir o localhost valor para a URL PWA. Se você executar o Visual Studio em um computador remoto, a localhost URL não será necessária. Before you deploy the add-in, comment out that code.

    function setOdataUrl() {
        Office.context.document.getProjectFieldAsync(
            Office.ProjectProjectFields.ProjectServerUrl,
            function (asyncResult) {
                if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
                    _pwa = String(asyncResult.value.fieldValue);
    
                    // If you debug with Visual Studio on a local Project Server computer, 
                    // uncomment the following lines to use the localhost URL.
                    //let localhost = location.host.split(":", 1);
                    //let pwaStartPosition = _pwa.lastIndexOf("/");
                    //let pwaLength = _pwa.length - pwaStartPosition;
                    //let pwaName = _pwa.substr(pwaStartPosition, pwaLength);
                    //_pwa = location.protocol + "//" + localhost + pwaName;
    
                    if (_pwa.substring(0, 4) == "http") {
                        _odataUrl = _pwa + PROJDATA;
                        $("#compareProjects").removeAttr("disabled");
                        getProjectGuid();
                    }
                    else {
                        _odataUrl = "No connection!";
                        throwError(_odataUrl, "You are not connected to Project Web App.");
                    }
                    getDocumentUrl();
                    $("#projectDataEndPoint").text(_odataUrl);
                }
                else {
                    throwError(asyncResult.error.name, asyncResult.error.message);
                }
            }
        );
    }
    
    // Get the GUID of the active project.
    function getProjectGuid() {
        Office.context.document.getProjectFieldAsync(
            Office.ProjectProjectFields.GUID,
            function (asyncResult) {
                if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
                    _projectUid = asyncResult.value.fieldValue;
                }
                else {
                    throwError(asyncResult.error.name, asyncResult.error.message);
                }
            }
        );
    }
    
    // Get the path of the project in Project web app, which is in the form <>\ProjectName .
    function getDocumentUrl() {
        _docUrl = "Document path:\r\n" + Office.context.document.url;
    }
    
  3. Adicione a retrieveOData função, que concatena valores para a consulta REST e, em seguida, chama a ajax função em jQuery para obter os dados solicitados do serviço ProjectData . A support.cors variável permite o CORS (compartilhamento de recursos entre origens) com a ajax função. Se a support.cors instrução estiver ausente ou estiver definida como false, a ajax função retornará um erro de não transporte .

    Observação

    O código a seguir funciona com uma instalação local do Project Server. Para o Project na Web, você pode usar o OAuth para autenticação baseada em token. For more information, see Addressing same-origin policy limitations in Office Add-ins.

    ajax Na chamada, você pode usar o parâmetro cabeçalhos ou o parâmetro beforeSend. O parâmetro completo é uma função anônima para que ela esteja no mesmo escopo que as variáveis em retrieveOData. A função para o parâmetro completo exibe resultados odataText no controle e também chama o parseODataResult método para analisar e exibir a resposta JSON. O parâmetro de erro especifica a função nomeada getProjectDataErrorHandler , que grava uma mensagem de erro no odataText controle e também usa a throwError função para exibir uma mensagem pop-up.

    // Functions to get and parse the Project Server reporting data./
    
    // Get data about all projects on Project Server,
    // by using a REST query with the ajax method in jQuery.
    function retrieveOData() {
        let restUrl = _odataUrl + PROJQUERY + QUERY_FILTER + QUERY_SELECT1 + QUERY_SELECT2;
        let accept = "application/json; odata=verbose";
        accept.toLocaleLowerCase();
    
        // Enable cross-origin scripting (required by jQuery 1.5 and later).
        // This does not work with Project on the web.
        $.support.cors = true;
    
        $.ajax({
            url: restUrl,
            type: "GET",
            contentType: "application/json",
            data: "",      // Empty string for the optional data.
            //headers: { "Accept": accept },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("ACCEPT", accept);
            },
            complete: function (xhr, textStatus) {
                // Create a message to display in the text box.
                let message = "\r\ntextStatus: " + textStatus +
                    "\r\nContentType: " + xhr.getResponseHeader("Content-Type") +
                    "\r\nStatus: " + xhr.status +
                    "\r\nResponseText:\r\n" + xhr.responseText;
    
                // xhr.responseText is the result from an XmlHttpRequest, which
                // contains the JSON response from the OData service.
                parseODataResult(xhr.responseText, _projectUid);
    
                // Write the document name, response header, status, and JSON to the odataText control.
                $("#odataText").text(_docUrl);
                $("#odataText").append("\r\nREST query:\r\n" + restUrl);
                $("#odataText").append(message);
    
                if (xhr.status != 200 &amp;&amp; xhr.status != 1223 &amp;&amp; xhr.status != 201) {
                    $("#odataInfo").append("<div>" + htmlEncode(restUrl) + "</div>");
                }
            },
            error: getProjectDataErrorHandler
        });
    }
    
    function getProjectDataErrorHandler(data, errorCode, errorMessage) {
        $("#odataText").text("Error code: " + errorCode + "\r\nError message: \r\n"
        + errorMessage);
        throwError(errorCode, errorMessage);
    }
    
  4. Adicione a parseODataResult função, que desserializa e processa a resposta JSON do serviço OData. A parseODataResult função calcula valores médios dos dados de custo e trabalho para uma precisão de uma ou duas casas decimais, formata valores com a cor correta e adiciona uma unidade ( $, hrs ou %), e exibe os valores em células de tabela especificadas.

    Se o GUID do projeto ativo corresponder ao ProjectId valor, a myProjectIndex variável será definida como o índice do projeto. Se myProjectIndex indicar que o projeto ativo será publicado no Project Server, o parseODataResult método formata e exibe dados de custo e trabalho para esse projeto. If the active project is not published, values for the active project are displayed as a blue NA.

    // Calculate the average values of actual cost, cost, work, and percent complete
    // for all projects, and compare with the values for the current project.
    function parseODataResult(oDataResult, currentProjectGuid) {
        // Deserialize the JSON string into a JavaScript object.
        let res = Sys.Serialization.JavaScriptSerializer.deserialize(oDataResult);
        let len = res.d.results.length;
        let projActualCost = 0;
        let projCost = 0;
        let projWork = 0;
        let projPercentCompleted = 0;
        let myProjectIndex = -1;
        for (i = 0; i < len; i++) {
            // If the current project GUID matches the GUID from the OData query,  
            // store the project index.
            if (currentProjectGuid.toLocaleLowerCase() == res.d.results[i].ProjectId) {
                myProjectIndex = i;
            }
            projCost += Number(res.d.results[i].ProjectCost);
            projWork += Number(res.d.results[i].ProjectWork);
            projActualCost += Number(res.d.results[i].ProjectActualCost);
            projPercentCompleted += Number(res.d.results[i].ProjectPercentCompleted);
        }
        let avgProjCost = projCost / len;
        let avgProjWork = projWork / len;
        let avgProjActualCost = projActualCost / len;
        let avgProjPercentCompleted = projPercentCompleted / len;
    
        // Round off cost to two decimal places, and round off other values to one decimal place.
        avgProjCost = avgProjCost.toFixed(2);
        avgProjWork = avgProjWork.toFixed(1);
        avgProjActualCost = avgProjActualCost.toFixed(2);
        avgProjPercentCompleted = avgProjPercentCompleted.toFixed(1);
    
        // Display averages in the table, with the correct units.
        document.getElementById("AverageProjectCost").innerHTML = "$"
            + avgProjCost;
        document.getElementById("AverageProjectActualCost").innerHTML
            = "$" + avgProjActualCost;
        document.getElementById("AverageProjectWork").innerHTML
            = avgProjWork + " hrs";
        document.getElementById("AverageProjectPercentComplete").innerHTML
            = avgProjPercentCompleted + "%";
    
        // Calculate and display values for the current project.
        if (myProjectIndex != -1) {
            let myProjCost = Number(res.d.results[myProjectIndex].ProjectCost);
            let myProjWork = Number(res.d.results[myProjectIndex].ProjectWork);
            let myProjActualCost = Number(res.d.results[myProjectIndex].ProjectActualCost);
            let myProjPercentCompleted =
            Number(res.d.results[myProjectIndex].ProjectPercentCompleted);
    
            myProjCost = myProjCost.toFixed(2);
            myProjWork = myProjWork.toFixed(1);
            myProjActualCost = myProjActualCost.toFixed(2);
            myProjPercentCompleted = myProjPercentCompleted.toFixed(1);
    
            document.getElementById("CurrentProjectCost").innerHTML = "$" + myProjCost;
    
            if (Number(myProjCost) <= Number(avgProjCost)) {
                document.getElementById("CurrentProjectCost").style.color = "green"
            }
            else {
                document.getElementById("CurrentProjectCost").style.color = "red"
            }
    
            document.getElementById("CurrentProjectActualCost").innerHTML = "$" + myProjActualCost;
    
            if (Number(myProjActualCost) <= Number(avgProjActualCost)) {
                document.getElementById("CurrentProjectActualCost").style.color = "green"
            }
            else {
                document.getElementById("CurrentProjectActualCost").style.color = "red"
            }
    
            document.getElementById("CurrentProjectWork").innerHTML = myProjWork + " hrs";
    
            if (Number(myProjWork) <= Number(avgProjWork)) {
                document.getElementById("CurrentProjectWork").style.color = "red"
            }
            else {
                document.getElementById("CurrentProjectWork").style.color = "green"
            }
    
            document.getElementById("CurrentProjectPercentComplete").innerHTML = myProjPercentCompleted + "%";
    
            if (Number(myProjPercentCompleted) <= Number(avgProjPercentCompleted)) {
                document.getElementById("CurrentProjectPercentComplete").style.color = "red"
            }
            else {
                document.getElementById("CurrentProjectPercentComplete").style.color = "green"
            }
        }
        else {
            document.getElementById("CurrentProjectCost").innerHTML = "NA";
            document.getElementById("CurrentProjectCost").style.color = "blue"
    
            document.getElementById("CurrentProjectActualCost").innerHTML = "NA";
            document.getElementById("CurrentProjectActualCost").style.color = "blue"
    
            document.getElementById("CurrentProjectWork").innerHTML = "NA";
            document.getElementById("CurrentProjectWork").style.color = "blue"
    
            document.getElementById("CurrentProjectPercentComplete").innerHTML = "NA";
            document.getElementById("CurrentProjectPercentComplete").style.color = "blue"
        }
    }
    

Testar o suplemento HelloProjectOData

Para testar e depurar o suplemento HelloProjectOData com o Visual Studio, Project Professional deve ser instalado no computador de desenvolvimento. Para habilitar cenários de teste diferentes, certifique-se de poder escolher se o Project abre no caso de arquivos no computador local ou se ele se conecta ao Project Web App. A seguir estão as etapas de exemplo.

  1. Na guia Arquivo , escolha a guia Informações na exibição Bastidores e escolha Gerenciar Contas.

  2. Na caixa de diálogo Contas do aplicativo Web project , a lista Contas disponíveis pode ter várias contas do Project Web App além da conta de computador local. Na seção Ao iniciar, selecione Escolher uma conta.

  3. Feche o Project para que o Visual Studio possa iniciá-lo na depuração do suplemento.

Os testes básicos devem incluir o seguinte:

  • Execute o suplemento no Visual Studio, e abra um projeto publicado do Project Web App que contém dados de custos e trabalho. Verifique se o suplemento exibe o ponto de extremidade ProjectData e exibe corretamente os dados de custo e trabalho na tabela. Você pode usar a saída no controle odataText para verificar a consulta REST e outras informações.

  • Execute o suplemento novamente, em que você escolhe o perfil do computador local na caixa de diálogo Logon quando o Project for iniciado. Abra um arquivo .mpp local e teste o suplemento. Verifique se o suplemento exibe uma mensagem de erro ao tentar acessar o ponto de extremidade ProjectData.

  • Execute o suplemento novamente e crie um projeto com tarefas com dados de custo e de trabalho. Você pode salvar o projeto no Project Web App, mas não o publique. Verifique se o suplemento exibe dados do Project Server, mas NA para o projeto atual.

Procedimento 6. Testar o suplemento

  1. Execute Project Professional, conecte-se ao Project Web App e crie um projeto de teste. Atribua tarefas aos recursos locais ou a recursos da empresa, defina vários valores de porcentagem concluída em algumas tarefas e publique o projeto. Feche o projeto, o que permite que o Visual Studio inicie o Project para depurar o suplemento.

  2. No Visual Studio, pressione F5. Faça logon no Project Web App e abra o projeto que você criou na etapa anterior. Você pode abrir o projeto no modo somente leitura ou no modo de edição.

  3. Na guia PROJECT da faixa de opções, na lista suspensa Suplementos do Office , selecione Hello ProjectData (consulte Figura 5). O botão Comparar Todos os Projetos deve estar desativado.

    Figura 5. Iniciar o suplemento HelloProjectOData

    Teste o aplicativo HelloProjectOData.

  4. No painel de tarefas Hello ProjectData , selecione Obter ponto de extremidade ProjectData. A linha projectDataEndPoint deve mostrar a URL do serviço ProjectData e o botão Comparar Todos os Projetos deve estar habilitado (consulte Figura 6).

  5. Selecione Comparar todos os projetos. O suplemento pode pausar enquanto recupera dados do serviço ProjectData e, em seguida, deve exibir os valores médios e atuais formatados na tabela.

    Figura 6. Exibir resultados da consulta REST

    Exibir os resultados da consulta REST.

  6. Examine a saída na caixa de texto. Ele deve mostrar o caminho do documento, a consulta REST, status informações e os resultados JSON das chamadas para ajax e parseODataResult. A saída ajuda a entender, criar e depurar código na parseODataResult função como projCost += Number(res.d.results[i].ProjectCost);.

    A seguir está um exemplo da saída com quebras de linha e espaços adicionados ao texto para clareza, para três projetos em uma instância do Project Web App.

    Document path: <>\WinProj test1
    
    REST query:
    http://sphvm-37189/pwa/_api/ProjectData/Projects?$filter=ProjectName ne 'Timesheet Administrative Work Items'
        &amp;$select=ProjectId, ProjectName, ProjectCost, ProjectWork, ProjectPercentCompleted, ProjectActualCost
    
    textStatus: success
    ContentType: application/json;odata=verbose;charset=utf-8
    Status: 200
    
    ResponseText:
    {"d":{"results":[
    {"__metadata":
        {"id":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'ce3d0d65-3904-e211-96cd-00155d157123')",
        "uri":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'ce3d0d65-3904-e211-96cd-00155d157123')",
        "type":"ReportingData.Project"},
        "ProjectId":"ce3d0d65-3904-e211-96cd-00155d157123",
        "ProjectActualCost":"0.000000",
        "ProjectCost":"0.000000",
        "ProjectName":"Task list created in PWA",
        "ProjectPercentCompleted":0,
        "ProjectWork":"16.000000"},
    {"__metadata":
        {"id":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'c31023fc-1404-e211-86b2-3c075433b7bd')",
        "uri":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'c31023fc-1404-e211-86b2-3c075433b7bd')",
        "type":"ReportingData.Project"},
        "ProjectId":"c31023fc-1404-e211-86b2-3c075433b7bd",
        "ProjectActualCost":"700.000000",
        "ProjectCost":"2400.000000",
        "ProjectName":"WinProj test 2",
        "ProjectPercentCompleted":29,
        "ProjectWork":"48.000000"},
    {"__metadata":
        {"id":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'dc81fbb2-b801-e211-9d2a-3c075433b7bd')",
        "uri":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'dc81fbb2-b801-e211-9d2a-3c075433b7bd')",
        "type":"ReportingData.Project"},
        "ProjectId":"dc81fbb2-b801-e211-9d2a-3c075433b7bd",
        "ProjectActualCost":"1900.000000",
        "ProjectCost":"5200.000000",
        "ProjectName":"WinProj test1",
        "ProjectPercentCompleted":37,
        "ProjectWork":"104.000000"}
    ]}}
    
  7. Pare de depurar (pressione Shift + F5) e pressione F5 novamente para executar uma nova instância do Project. Na caixa de diálogo Login, escolha o perfil local Computador, e não o Project Web App. Crie ou abra um arquivo .mpp de projeto local, abra o painel de tarefas Hello ProjectData e selecione Obter Ponto de Extremidade de ProjectData. O suplemento deve mostrar um erro Sem conexão! (consulte Figura 7) e o botão Comparar Todos os Projetos deve permanecer desabilitado .

    Figura 7. Usar o suplemento sem uma conexão de aplicativo Web do Project

    Use o aplicativo sem uma conexão do Project Web App.

  8. Pare de depurar e pressione F5 novamente. Faça logon no Project Web App e crie um projeto com dados de custo e de trabalho. Você pode salvar o projeto, mas não o publique.

    No painel de tarefas Hello ProjectData , ao selecionar Comparar Todos os Projetos, você deverá ver um NA azul para campos na coluna Atual (consulte Figura 8).

    Figura 8. Comparar um projeto não publicado com outros projetos

    Compare um projeto não publicado com outros.

Mesmo que seu suplemento tenha funcionado corretamente nos testes anteriores, há outros testes que devem ser executados. Por exemplo:

  • Abra um projeto do Project Web App que não tenha nenhum dado de custo ou de trabalho para as tarefas. Você deve ver valores de zero nos campos na coluna Atual .

  • Teste um projeto sem tarefas.

  • Se você modificar o suplemento e publicá-lo, deve executar testes semelhantes novamente com o suplemento publicado. Para outras considerações, confira Próximas etapas.

Observação

Há limites para a quantidade de dados que podem ser retornados em uma consulta do serviço ProjectData ; a quantidade de dados varia de acordo com a entidade. Por exemplo, o Projects conjunto de entidades tem um limite padrão de 100 projetos por consulta, mas o Risks conjunto de entidades tem um limite padrão de 200. For a production installation, the code in the HelloProjectOData example should be modified to enable queries of more than 100 projects. For more information, see Next steps and Querying OData feeds for Project reporting data.

Exemplo de código para o suplemento de HelloProjectOData

Arquivo HelloProjectOData.html

O código a seguir está no arquivo Pages\HelloProjectOData.html do projeto HelloProjectODataWeb.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
        <title>Test ProjectData Service</title>

        <link rel="stylesheet" type="text/css" href="../Content/Office.css" />

        <!-- Add your CSS styles to the following file. -->
        <link rel="stylesheet" type="text/css" href="../Content/App.css" />

        <!-- Use the CDN reference to the mini-version of jQuery when deploying your add-in. -->
        <!--<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script> -->
        <script src="../Scripts/jquery-1.7.1.js"></script>

        <!-- Use the CDN reference to Office.js when deploying your add-in -->
        <!--<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>-->

        <!-- Use the local script references for Office.js to enable offline debugging -->
        <script src="../Scripts/Office/1.0/MicrosoftAjax.js"></script>
        <script src="../Scripts/Office/1.0/Office.js"></script>

        <!-- Add your JavaScript to the following files. -->
        <script src="../Scripts/HelloProjectOData.js"></script>
        <script src="../Scripts/SurfaceErrors.js"></script>
    </head>
    <body>
        <div id="SectionContent">
        <div id="odataQueries">
            ODATA REST QUERY
        </div>
        <div id="odataInfo">
            <button class="button-wide" onclick="setOdataUrl()">Get ProjectData Endpoint</button>
            <br />
            <br />
            <span class="rest" id="projectDataEndPoint">Endpoint of the 
            <strong>ProjectData</strong> service</span>
            <br />
        </div>
        <div id="compareProjectData">
            <button class="button-wide" disabled="disabled" id="compareProjects"
            onclick="retrieveOData()">
            Compare All Projects</button>
            <br />
        </div>
        </div>
        <div id="corpInfo">
        <table class="infoTable" aria-readonly="True" style="width: 100%;">
            <tr>
            <td class="heading_leftCol"></td>
            <td class="heading_midCol"><strong>Average</strong></td>
            <td class="heading_rightCol"><strong>Current</strong></td>
            </tr>
            <tr>
            <td class="row_leftCol"><strong>Project Cost</strong></td>
            <td class="row_midCol" id="AverageProjectCost">&amp;nbsp;</td>
            <td class="row_rightCol" id="CurrentProjectCost">&amp;nbsp;</td>
            </tr>
            <tr>
            <td class="row_leftCol"><strong>Project Actual Cost</strong></td>
            <td class="row_midCol" id="AverageProjectActualCost">&amp;nbsp;</td>
            <td class="row_rightCol" id="CurrentProjectActualCost">&amp;nbsp;</td>
            </tr>
            <tr>
            <td class="row_leftCol"><strong>Project Work</strong></td>
            <td class="row_midCol" id="AverageProjectWork">&amp;nbsp;</td>
            <td class="row_rightCol" id="CurrentProjectWork">&amp;nbsp;</td>
            </tr>
            <tr>
            <td class="row_leftCol"><strong>Project % Complete</strong></td>
            <td class="row_midCol" id="AverageProjectPercentComplete">&amp;nbsp;</td>
            <td class="row_rightCol" id="CurrentProjectPercentComplete">&amp;nbsp;</td>
            </tr>
        </table>
        </div>
        <img alt="Corporation" class="logo" src="../../images/NewLogo.png" />
        <br />
        <textarea id="odataText" rows="12" cols="40"></textarea>
    </body>
</html>

Arquivo HelloProjectOData.js

O código a seguir está no arquivo Scripts\Office\HelloProjectOData.js do projeto HelloProjectODataWeb.

/* File: HelloProjectOData.js
* JavaScript functions for the HelloProjectOData example task pane app.
* October 2, 2012
*/

let PROJDATA = "/_api/ProjectData";
let PROJQUERY = "/Projects?";
let QUERY_FILTER = "$filter=ProjectName ne 'Timesheet Administrative Work Items'";
let QUERY_SELECT1 = "&amp;$select=ProjectId, ProjectName";
let QUERY_SELECT2 = ", ProjectCost, ProjectWork, ProjectPercentCompleted, ProjectActualCost";
let _pwa;           // URL of Project Web App.
let _projectUid;    // GUID of the active project.
let _docUrl;        // Path of the project document.
let _odataUrl = ""; // URL of the OData service: http[s]://ServerName /ProjectServerName /_api/ProjectData

// The initialize function is required for all add-ins.
Office.initialize = function (reason) {
    // Checks for the DOM to load using the jQuery ready method.
    $(document).ready(function () {
        // After the DOM is loaded, app-specific code can run.
    });
}

// Set the global variables, enable the Compare All Projects button,
// and display the URL of the ProjectData service.
// Display an error if Project is not connected with Project Web App.
function setOdataUrl() {
    Office.context.document.getProjectFieldAsync(
        Office.ProjectProjectFields.ProjectServerUrl,
        function (asyncResult) {
            if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
                _pwa = String(asyncResult.value.fieldValue);

                // If you debug with Visual Studio on a local Project Server computer,
                // uncomment the following lines to use the localhost URL.
                //let localhost = location.host.split(":", 1);
                //let pwaStartPosition = _pwa.lastIndexOf("/");
                //let pwaLength = _pwa.length - pwaStartPosition;
                //let pwaName = _pwa.substr(pwaStartPosition, pwaLength);
                //_pwa = location.protocol + "//" + localhost + pwaName;

                if (_pwa.substring(0, 4) == "http") {
                    _odataUrl = _pwa + PROJDATA;
                    $("#compareProjects").removeAttr("disabled");
                    getProjectGuid();
                }
                else {
                    _odataUrl = "No connection!";
                    throwError(_odataUrl, "You are not connected to Project Web App.");
                }
                getDocumentUrl();
                $("#projectDataEndPoint").text(_odataUrl);
            }
            else {
                throwError(asyncResult.error.name, asyncResult.error.message);
            }
        }
    );
}

// Get the GUID of the active project.
function getProjectGuid() {
    Office.context.document.getProjectFieldAsync(
        Office.ProjectProjectFields.GUID,
        function (asyncResult) {
            if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
                _projectUid = asyncResult.value.fieldValue;
            }
            else {
                throwError(asyncResult.error.name, asyncResult.error.message);
            }
        }
    );
}

// Get the path of the project in Project web app, which is in the form <>\ProjectName .
function getDocumentUrl() {
    _docUrl = "Document path:\r\n" + Office.context.document.url;
}

//  Functions to get and parse the Project Server reporting data./

// Get data about all projects on Project Server,
// by using a REST query with the ajax method in jQuery.
function retrieveOData() {
    let restUrl = _odataUrl + PROJQUERY + QUERY_FILTER + QUERY_SELECT1 + QUERY_SELECT2;
    let accept = "application/json; odata=verbose";
    accept.toLocaleLowerCase();

    // Enable cross-origin scripting (required by jQuery 1.5 and later).
    // This does not work with Project on the web.
    $.support.cors = true;

    $.ajax({
        url: restUrl,
        type: "GET",
        contentType: "application/json",
        data: "",      // Empty string for the optional data.
        //headers: { "Accept": accept },
        beforeSend: function (xhr) {
            xhr.setRequestHeader("ACCEPT", accept);
        },
        complete: function (xhr, textStatus) {
            // Create a message to display in the text box.
            let message = "\r\ntextStatus: " + textStatus +
                "\r\nContentType: " + xhr.getResponseHeader("Content-Type") +
                "\r\nStatus: " + xhr.status +
                "\r\nResponseText:\r\n" + xhr.responseText;

            // xhr.responseText is the result from an XmlHttpRequest, which 
            // contains the JSON response from the OData service.
            parseODataResult(xhr.responseText, _projectUid);

            // Write the document name, response header, status, and JSON to the odataText control.
            $("#odataText").text(_docUrl);
            $("#odataText").append("\r\nREST query:\r\n" + restUrl);
            $("#odataText").append(message);

            if (xhr.status != 200 &amp;&amp; xhr.status != 1223 &amp;&amp; xhr.status != 201) {
                $("#odataInfo").append("<div>" + htmlEncode(restUrl) + "</div>");
            }
        },
        error: getProjectDataErrorHandler
    });
}

function getProjectDataErrorHandler(data, errorCode, errorMessage) {
    $("#odataText").text("Error code: " + errorCode + "\r\nError message: \r\n"
        + errorMessage);
    throwError(errorCode, errorMessage);
}

// Calculate the average values of actual cost, cost, work, and percent complete
// for all projects, and compare with the values for the current project.
function parseODataResult(oDataResult, currentProjectGuid) {
    // Deserialize the JSON string into a JavaScript object.
    let res = Sys.Serialization.JavaScriptSerializer.deserialize(oDataResult);
    let len = res.d.results.length;
    let projActualCost = 0;
    let projCost = 0;
    let projWork = 0;
    let projPercentCompleted = 0;
    let myProjectIndex = -1;

    for (i = 0; i < len; i++) {
        // If the current project GUID matches the GUID from the OData query,  
        // then store the project index.
        if (currentProjectGuid.toLocaleLowerCase() == res.d.results[i].ProjectId) {
            myProjectIndex = i;
        }
        projCost += Number(res.d.results[i].ProjectCost);
        projWork += Number(res.d.results[i].ProjectWork);
        projActualCost += Number(res.d.results[i].ProjectActualCost);
        projPercentCompleted += Number(res.d.results[i].ProjectPercentCompleted);

    }
    let avgProjCost = projCost / len;
    let avgProjWork = projWork / len;
    let avgProjActualCost = projActualCost / len;
    let avgProjPercentCompleted = projPercentCompleted / len;

    // Round off cost to two decimal places, and round off other values to one decimal place.
    avgProjCost = avgProjCost.toFixed(2);
    avgProjWork = avgProjWork.toFixed(1);
    avgProjActualCost = avgProjActualCost.toFixed(2);
    avgProjPercentCompleted = avgProjPercentCompleted.toFixed(1);

    // Display averages in the table, with the correct units. 
    document.getElementById("AverageProjectCost").innerHTML = "$"
        + avgProjCost;
    document.getElementById("AverageProjectActualCost").innerHTML
        = "$" + avgProjActualCost;
    document.getElementById("AverageProjectWork").innerHTML
        = avgProjWork + " hrs";
    document.getElementById("AverageProjectPercentComplete").innerHTML
        = avgProjPercentCompleted + "%";

    // Calculate and display values for the current project.
    if (myProjectIndex != -1) {

        let myProjCost = Number(res.d.results[myProjectIndex].ProjectCost);
        let myProjWork = Number(res.d.results[myProjectIndex].ProjectWork);
        let myProjActualCost = Number(res.d.results[myProjectIndex].ProjectActualCost);
        let myProjPercentCompleted = Number(res.d.results[myProjectIndex].ProjectPercentCompleted);

        myProjCost = myProjCost.toFixed(2);
        myProjWork = myProjWork.toFixed(1);
        myProjActualCost = myProjActualCost.toFixed(2);
        myProjPercentCompleted = myProjPercentCompleted.toFixed(1);

        document.getElementById("CurrentProjectCost").innerHTML = "$" + myProjCost;

        if (Number(myProjCost) <= Number(avgProjCost)) {
            document.getElementById("CurrentProjectCost").style.color = "green"
        }
        else {
            document.getElementById("CurrentProjectCost").style.color = "red"
        }

        document.getElementById("CurrentProjectActualCost").innerHTML = "$" + myProjActualCost;

        if (Number(myProjActualCost) <= Number(avgProjActualCost)) {
            document.getElementById("CurrentProjectActualCost").style.color = "green"
        }
        else {
            document.getElementById("CurrentProjectActualCost").style.color = "red"
        }

        document.getElementById("CurrentProjectWork").innerHTML = myProjWork + " hrs";

        if (Number(myProjWork) <= Number(avgProjWork)) {
            document.getElementById("CurrentProjectWork").style.color = "red"
        }
        else {
            document.getElementById("CurrentProjectWork").style.color = "green"
        }

        document.getElementById("CurrentProjectPercentComplete").innerHTML = myProjPercentCompleted + "%";

        if (Number(myProjPercentCompleted) <= Number(avgProjPercentCompleted)) {
            document.getElementById("CurrentProjectPercentComplete").style.color = "red"
        }
        else {
            document.getElementById("CurrentProjectPercentComplete").style.color = "green"
        }
    }
    else {    // The current project is not published.
        document.getElementById("CurrentProjectCost").innerHTML = "NA";
        document.getElementById("CurrentProjectCost").style.color = "blue"

        document.getElementById("CurrentProjectActualCost").innerHTML = "NA";
        document.getElementById("CurrentProjectActualCost").style.color = "blue"

        document.getElementById("CurrentProjectWork").innerHTML = "NA";
        document.getElementById("CurrentProjectWork").style.color = "blue"

        document.getElementById("CurrentProjectPercentComplete").innerHTML = "NA";
        document.getElementById("CurrentProjectPercentComplete").style.color = "blue"
    }
}

Arquivo App.css

O código a seguir está no arquivo Content\App.css do projeto HelloProjectODataWeb.

/*
*  File: App.css for the HelloProjectOData app.
*  Updated: 10/2/2012
*/

body
{
    font-size: 11pt;
}
h1
{
    font-size: 22pt;
}
h2
{
    font-size: 16pt;
}

/******************************************************************
Code label class
******************************************************************/

.rest 
{
    font-family: 'Courier New';
    font-size: 0.9em;
}

/******************************************************************
Button classes
******************************************************************/

.button-wide {
    width: 210px;
    margin-top: 2px;
}
.button-narrow 
{
    width: 80px;
    margin-top: 2px;
}

/******************************************************************
Table styles
******************************************************************/

.infoTable
{
    text-align: center; 
    vertical-align: middle
}
.heading_leftCol
{
    width: 20px;
    height: 20px;
}
.heading_midCol
{
    width: 100px;
    height: 20px;
    font-size: medium; 
    font-weight: bold; 
}
.heading_rightCol
{
    width: 101px;
    height: 20px;
    font-size: medium;
    font-weight: bold;
}
.row_leftCol
{
    width: 20px;
    font-size: small;
    font-weight: bold;
}
.row_midCol
{
    width: 100px;
}
.row_rightCol
{
    width: 101px;
}
.logo
{
    width: 135px;
    height: 53px;
}

Arquivo SurfaceErrors.js

O código a seguir inclui uma throwError função que cria um Toast objeto.

/*
 * Show error messages in a "toast" notification.
 */

// Throws a custom defined error.
function throwError(errTitle, errMessage) {
    try {
        // Define and throw a custom error.
        let customError = { name: errTitle, message: errMessage }
        throw customError;
    }
    catch (err) {
        // Catch the error and display it to the user.
        Toast.showToast(err.name, err.message);
    }
}

// Add a dynamically-created div "toast" for displaying errors to the user.
let Toast = {

    Toast: "divToast",
    Close: "btnClose",
    Notice: "lblNotice",
    Output: "lblOutput",

    // Show the toast with the specified information.
    showToast: function (title, message) {

        if (document.getElementById(this.Toast) == null) {
            this.createToast();
        }

        document.getElementById(this.Notice).innerText = title;
        document.getElementById(this.Output).innerText = message;

        $("#" + this.Toast).hide();
        $("#" + this.Toast).show("slow");
    },

    // Create the display for the toast.
    createToast: function () {
        let divToast;
        let lblClose;
        let btnClose;
        let divOutput;
        let lblOutput;
        let lblNotice;

        // Create the container div.
        divToast = document.createElement("div");
        let toastStyle = "background-color:rgba(220, 220, 128, 0.80);" +
            "position:absolute;" +
            "bottom:0px;" +
            "width:90%;" +
            "text-align:center;" +
            "font-size:11pt;";
        divToast.setAttribute("style", toastStyle);
        divToast.setAttribute("id", this.Toast);

        // Create the close button.
        lblClose = document.createElement("div");
        lblClose.setAttribute("id", this.Close);
        let btnStyle = "text-align:right;" +
            "padding-right:10px;" +
            "font-size:10pt;" +
            "cursor:default";
        lblClose.setAttribute("style", btnStyle);
        lblClose.appendChild(document.createTextNode("CLOSE "));

        btnClose = document.createElement("span");
        btnClose.setAttribute("style", "cursor:pointer;");
        btnClose.setAttribute("onclick", "Toast.close()");
        btnClose.innerText = "X";
        lblClose.appendChild(btnClose);

        // Create the div to contain the toast title and message.
        divOutput = document.createElement("div");
        divOutput.setAttribute("id", "divOutput");
        let outputStyle = "margin-top:0px;";
        divOutput.setAttribute("style", outputStyle);

        lblNotice = document.createElement("span");
        lblNotice.setAttribute("id", this.Notice);
        let labelStyle = "font-weight:bold;margin-top:0px;";
        lblNotice.setAttribute("style", labelStyle);

        lblOutput = document.createElement("span");
        lblOutput.setAttribute("id", this.Output);

        // Add the child nodes to the toast div.
        divOutput.appendChild(lblNotice);
        divOutput.appendChild(document.createElement("br"));
        divOutput.appendChild(lblOutput);
        divToast.appendChild(lblClose);
        divToast.appendChild(divOutput);

        // Add the toast div to the document body.
        document.body.appendChild(divToast);
    },

    // Close the toast.
    close: function () {
        $("#" + this.Toast).hide("slow");
    }
}

Próximas etapas

Se HelloProjectOData fosse um suplemento de produção a ser vendido no AppSource ou distribuído em um catálogo de aplicativos do SharePoint, ele seria projetado de forma diferente. Por exemplo, não haveria nenhuma saída de depuração em uma caixa de texto e provavelmente nenhum botão para obter o ponto de extremidade ProjectData. Você também teria que reescrever a retrieveOData função para lidar com instâncias do Project Web App que têm mais de 100 projetos.

O suplemento deveria conter mais verificações de erro, além de lógica para capturar e explicar ou mostrar casos extremos. Por exemplo, se uma instância do Project Web App tiver mil projetos com uma duração média de cinco dias e custo médio de US$ 2.400, e o projeto ativo for o único que tem uma duração de mais de 20 dias, a comparação de custo e trabalho poderá ficar desequilibrada. Isso poderia ser exibido com um gráfico de frequência. Você poderia adicionar opções para exibir a duração, comparar projetos de tamanhos semelhantes ou comparar projetos de um mesmo departamento ou de departamentos diferentes. Ou poderia adicionar uma forma de o usuário selecionar os campos a exibir em uma lista.

Para outras consultas do serviço ProjectData , há limites para o comprimento da cadeia de caracteres de consulta, o que afeta o número de etapas que uma consulta pode levar de uma coleção pai para um objeto em uma coleção filho. Por exemplo, uma consulta de duas etapas de Projects para Tasks para itens de tarefa funciona, mas uma consulta de três etapas, como Projects para Tasks para Assignments para itens de atribuição pode exceder o comprimento máximo de URL padrão. Para obter mais informações, consulte Consultar feeds OData para dados de relatório de projeto.

Se você modificar o suplemento HelloProjectOData para uso de produção, siga as etapas a seguir.

  • No arquivo HelloProjectOData.html, para obter melhor desempenho, mude a referência ao office.js do projeto local para a referência da CDN:

    <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
    
  • Reescreva a retrieveOData função para habilitar consultas de mais de 100 projetos. Por exemplo, você pode obter o número de projetos com uma consulta ~/ProjectData/Projects()/$count e usar os operadores $skip e $top na consulta REST para dados de projeto. Execute várias consultas em sequência e tire a média dos dados de cada consulta. Cada consulta para dados do projeto seria do formulário:

    ~/ProjectData/Projects()?skip= [numSkipped]&amp;$top=100&amp;$filter=[filter]&amp;$select=[field1,field2, ???????]

    Para obter mais informações, confira Opções de consulta do sistema OData usando o ponto de extremidade REST. You can also use the Set-SPProjectOdataConfiguration command in Windows PowerShell to override the default page size for a query of the Projects entity set (or any of the 33 entity sets). See ProjectData - Project OData service reference.

  • Para implantar o suplemento, confira Publicar seu suplemento do Office.

Confira também