[project] Remove obsolete Project article#5666
Conversation
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
There was a problem hiding this comment.
Pull request overview
Updates the Project REST/OData tutorial to align with a Yo Office-based workflow and modernize the sample code and setup steps for building/running the add-in.
Changes:
- Replaces the Visual Studio-based creation flow with Yeoman (
yo office) guidance and a manual task pane file structure. - Modernizes the sample code (Office.onReady,
fetch, DOM APIs) and refreshes the test/run instructions for local hosting and sideloading. - Removes reliance on older jQuery/MicrosoftAjax patterns and updates narrative around prerequisites and limitations.
Reviewed changes
Copilot reviewed 1 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/project/create-a-project-add-in-that-uses-rest-with-an-on-premises-odata-service.md | Revamps the end-to-end tutorial to use Yo Office guidance, new project structure, and updated JS/HTML/CSS + local serving instructions. |
| docs/images/pj15-hello-project-o-data-initial-solution-explorer.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/pj15-hello-project-o-data-creating-app.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/pj15-hello-project-o-data-choose-project.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/pj15-hello-project-data-test-the-app.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/pj15-hello-project-data-rest-results.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/pj15-hello-project-data-not-published.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/pj15-hello-project-data-no-connection.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/pj15-hello-project-data-new-icon.jpg | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
| docs/images/create-office-add-in.png | Legacy tutorial image asset (part of PR file set; now presumably unused by the updated doc). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
|
Learn Build status updates of commit a9ceb1c: ✅ Validation status: passed
For more details, please refer to the build report. |
|
Learn Build status updates of commit e07283b: ✅ Validation status: passed
For more details, please refer to the build report. |
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
There was a problem hiding this comment.
Pull request overview
This PR updates the Project on-premises OData REST add-in tutorial to use the Yo Office (Yeoman) workflow and a modernized JavaScript implementation, replacing the legacy Visual Studio/jQuery-based guidance.
Changes:
- Reworks the article steps to scaffold the add-in via Yo Office and replaces the generated
taskpane.html/taskpane.jswith the sample’s UI and logic. - Modernizes the JavaScript sample by removing jQuery/MicrosoftAjax patterns in favor of
Office.onReady,fetch, andJSON.parse. - Removes legacy screenshots that are no longer referenced after the tutorial rewrite.
Reviewed changes
Copilot reviewed 1 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/project/create-a-project-add-in-that-uses-rest-with-an-on-premises-odata-service.md | Rewrites the end-to-end tutorial to use Yo Office and updates the embedded HTML/JS samples accordingly. |
| docs/images/pj15-hello-project-o-data-initial-solution-explorer.png | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/pj15-hello-project-o-data-creating-app.png | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/pj15-hello-project-o-data-choose-project.png | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/pj15-hello-project-data-test-the-app.png | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/pj15-hello-project-data-rest-results.png | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/pj15-hello-project-data-not-published.png | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/pj15-hello-project-data-no-connection.png | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/pj15-hello-project-data-new-icon.jpg | Removes an obsolete Visual Studio-era screenshot. |
| docs/images/create-office-add-in.png | Removes an obsolete Visual Studio-era screenshot. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <button class="button-wide" onclick="setOdataUrl()">Get ProjectData Endpoint</button> | ||
| <br /><br /> | ||
| <span class="rest" id="projectDataEndPoint">Endpoint of the | ||
| <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">&nbsp;</td> | ||
| <td class="row_rightCol" id="CurrentProjectCost">&nbsp;</td> | ||
| </tr> | ||
| <tr> | ||
| <td class="row_leftCol"><strong>Project Actual Cost</strong></td> | ||
| <td class="row_midCol" id="AverageProjectActualCost">&nbsp;</td> | ||
| <td class="row_rightCol" id="CurrentProjectActualCost">&nbsp;</td> | ||
| </tr> | ||
| <tr> | ||
| <td class="row_leftCol"><strong>Project Work</strong></td> | ||
| <td class="row_midCol" id="AverageProjectWork">&nbsp;</td> | ||
| <td class="row_rightCol" id="CurrentProjectWork">&nbsp;</td> | ||
| </tr> | ||
| <tr> | ||
| <td class="row_leftCol"><strong>Project % Complete</strong></td> | ||
| <td class="row_midCol" id="AverageProjectPercentComplete">&nbsp;</td> | ||
| <td class="row_rightCol" id="CurrentProjectPercentComplete">&nbsp;</td> | ||
| </tr> | ||
| </table> | ||
| </div> | ||
| <img alt="Corporation" class="logo" src="../../images/NewLogo.png" /> | ||
| <br /> | ||
| <textarea id="odataText" rows="12" cols="40"></textarea> | ||
| </body> | ||
| ``` | ||
|
|
||
| ## Create the JavaScript code for the add-in | ||
|
|
||
| The template for a Project task pane add-in includes default initialization code that's designed to demonstrate basic get and set actions for data in a document for an Office add-in that uses the [Common APIs](../develop/office-javascript-api-object-model.md). Because Project doesn't support actions that write to the active project, and the **HelloProjectOData** add-in doesn't use the `getSelectedDataAsync` method, you can delete the script within the `Office.initialize` function and delete the `setData` function and `getData` function in the default HelloProjectOData.js file. | ||
|
|
||
| The JavaScript includes global constants for the REST query and global variables that are used in several functions. The **Get ProjectData Endpoint** button calls the `setOdataUrl` function, which initializes the global variables and determines whether Project is connected with Project Web App. | ||
|
|
||
| The remainder of the HelloProjectOData.js file includes two functions: the `retrieveOData` function is called when the user selects **Compare All Projects**; and the `parseODataResult` function calculates averages and then populates the comparison table with values that are formatted for color and units. | ||
|
|
||
| ### Procedure 5. Create the JavaScript code | ||
|
|
||
| 1. Delete all code in the default HelloProjectOData.js file and add the global variables and `Office.initialize` function. Variable names that are all capitals imply that they are constants; they're later used with the `_pwa` variable to create the REST query in this example. | ||
|
|
||
| ```js | ||
| let PROJDATA = "/_api/ProjectData"; | ||
| let PROJQUERY = "/Projects?"; | ||
| let QUERY_FILTER = "$filter=ProjectName ne 'Timesheet Administrative Work Items'"; | ||
| let QUERY_SELECT1 = "&$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. | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| 1. Add `setOdataUrl` and related functions. The `setOdataUrl` function calls `getProjectGuid` and `getDocumentUrl` to initialize the global variables. In the [getProjectFieldAsync method](/javascript/api/office/office.document), the anonymous function for the *callback* parameter enables the **Compare All Projects** button by using the `removeAttr` method in the jQuery library, and then displays the URL of the **ProjectData** service. If Project isn't connected with Project Web App, the function throws an error, which displays a pop-up error message. The SurfaceErrors.js file includes the `throwError` function. | ||
|
|
||
| > [!NOTE] | ||
| > If you run Visual Studio on the Project Server computer, to use **F5** debugging, uncomment the code after the line that initializes the `_pwa` global variable. To enable using the jQuery `ajax` method when debugging on the Project Server computer, you must set the `localhost` value for the PWA URL.If you run Visual Studio on a remote computer, the `localhost` URL isn't required. Before you deploy the add-in, comment out that code. | ||
|
|
||
| ```js | ||
| 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; | ||
| } | ||
| ``` | ||
|
|
||
| 1. Add the `retrieveOData` function, which concatenates values for the REST query and then calls the `ajax` function in jQuery to get the requested data from the **ProjectData** service. The `support.cors` variable enables cross-origin resource sharing (CORS) with the `ajax` function. If the `support.cors` statement is missing or is set to `false`, the `ajax` function returns a **No transport** error. | ||
|
|
||
| > [!NOTE] | ||
| > The following code works with an on-premises installation of Project Server. For Project on the web, you can use OAuth for token-based authentication. For more information, see [Addressing same-origin policy limitations in Office Add-ins](../develop/addressing-same-origin-policy-limitations.md). | ||
|
|
||
| In the `ajax` call, you can use either the *headers* parameter or the *beforeSend* parameter. The *complete* parameter is an anonymous function so that it's in the same scope as the variables in `retrieveOData`. The function for the *complete* parameter displays results in the `odataText` control and also calls the `parseODataResult` method to parse and display the JSON response. The *error* parameter specifies the named `getProjectDataErrorHandler` function, which writes an error message to the `odataText` control and also uses the `throwError` function to display a pop-up message. | ||
|
|
||
| ```js | ||
| // 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 doesn't 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 && xhr.status != 1223 && 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); | ||
| } | ||
| ``` | ||
|
|
||
| 1. Add the `parseODataResult` function, which deserializes and processes the JSON response from the OData service. The `parseODataResult` function calculates average values of the cost and work data to an accuracy of one or two decimal places, formats values with the correct color and adds a unit ( **$**, **hrs**, or **%**), and then displays the values in specified table cells. | ||
|
|
||
| If the GUID of the active project matches the `ProjectId` value, the `myProjectIndex` variable is set to the project index. If `myProjectIndex` indicates the active project is published on Project Server, the `parseODataResult` method formats and displays cost and work data for that project. If the active project isn't published, values for the active project are displayed as a blue **NA**. | ||
|
|
||
| ```js | ||
| // 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" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Test the HelloProjectOData add-in | ||
|
|
||
| To test and debug the **HelloProjectOData** add-in with Visual Studio, Project Professional must be installed on the development computer. To enable different test scenarios, make sure you can choose whether Project opens for files on the local computer or connects with Project Web App. The following are example steps. | ||
|
|
||
| 1. On the **File** tab, choose the **Info** tab in the Backstage view, and then choose **Manage Accounts**. | ||
|
|
||
| 1. In the **Project web app Accounts** dialog box, the **Available accounts** list can have multiple Project Web App accounts in addition to the local **Computer** account. In the **When starting** section, select **Choose an account**. | ||
|
|
||
| 1. Close Project so that Visual Studio can start it for debugging the add-in. | ||
|
|
||
| Basic tests should include the following: | ||
|
|
||
| - Run the add-in from Visual Studio and open a published project from Project Web App that contains cost and work data. Verify that the add-in displays the **ProjectData** endpoint and correctly displays the cost and work data in the table. You can use the output in the **odataText** control to check the REST query and other information. | ||
|
|
||
| - Run the add-in again, where you choose the local computer profile in the **Login** dialog box when Project starts. Open a local .mpp file and test the add-in. Verify that the add-in displays an error message when you try to get the **ProjectData** endpoint. | ||
|
|
||
| - Run the add-in again, where you create a project that has tasks with cost and work data. You can save the project to Project Web App, but don't publish it. Verify that the add-in displays data from Project Server, but **NA** for the current project. | ||
|
|
||
| ### Procedure 6. Test the add-in | ||
|
|
||
| 1. Run Project Professional, connect with Project Web App, and then create a test project. Assign tasks to local resources or to enterprise resources, set various values of percent complete on some tasks, and then publish the project. Quit Project, which enables Visual Studio to start Project for debugging the add-in. | ||
|
|
||
| 1. In Visual Studio, press <kbd>F5</kbd>. Log on to Project Web App, and then open the project that you created in the previous step. You can open the project in read-only mode or in edit mode. | ||
|
|
||
| 1. On the **PROJECT** tab of the ribbon, in the **Office Add-ins** drop-down list, select **Hello ProjectData** (see Figure 5). The **Compare All Projects** button should be disabled. | ||
|
|
||
| *Figure 5. Start the HelloProjectOData add-in* | ||
|
|
||
| :::image type="content" source="../images/pj15-hello-project-data-test-the-app.png" alt-text="Test the HelloProjectOData app."::: | ||
|
|
||
| 1. In the **Hello ProjectData** task pane, select **Get ProjectData Endpoint**. The **projectDataEndPoint** line should show the URL of the **ProjectData** service, and the **Compare All Projects** button should be enabled (see Figure 6). | ||
|
|
||
| 1. Select **Compare All Projects**. The add-in may pause while it retrieves data from the **ProjectData** service, and then it should display the formatted average and current values in the table. | ||
|
|
||
| *Figure 6. View results of the REST query* | ||
|
|
||
| :::image type="content" source="../images/pj15-hello-project-data-rest-results.png" alt-text="View results of the REST query."::: | ||
|
|
||
| 1. Examine the output in the text box. It should show the document path, REST query, status information, and JSON results from the calls to `ajax` and `parseODataResult`. The output helps you understand, create, and debug code in the `parseODataResult` function such as `projCost += Number(res.d.results[i].ProjectCost);`. | ||
|
|
||
| Following is an example of the output with line breaks and spaces added to the text for clarity, for three projects in a Project Web App instance. | ||
|
|
||
| ```json | ||
| Document path: <>\WinProj test1 | ||
|
|
||
| REST query: | ||
| http://sphvm-37189/pwa/_api/ProjectData/Projects?$filter=ProjectName ne 'Timesheet Administrative Work Items' | ||
| &$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"} | ||
| ]}} | ||
| ``` | ||
|
|
||
| 1. Stop debugging (press <kbd>Shift</kbd>+<kbd>F5</kbd>), and then press <kbd>F5</kbd> again to run a new instance of Project. In the **Login** dialog box, choose the local **Computer** profile, not Project Web App. Create or open a local project .mpp file, open the **Hello ProjectData** task pane, and then select **Get ProjectData Endpoint**. The add-in should show a **No connection!** error (see Figure 7), and the **Compare All Projects** button should remain disabled. | ||
|
|
||
| *Figure 7. Use the add-in without a Project web app connection* | ||
|
|
||
| :::image type="content" source="../images/pj15-hello-project-data-no-connection.png" alt-text="Use the app without a Project Web App connection."::: | ||
|
|
||
| 1. Stop debugging and press <kbd>F5</kbd> again. Log on to Project Web App and create a project that contains cost and work data. You can save the project, but don't publish it. | ||
|
|
||
| In the **Hello ProjectData** task pane, when you select **Compare All Projects**, you should see a blue **NA** for fields in the **Current** column (see Figure 8). | ||
|
|
||
| *Figure 8. Compare an unpublished project with other projects* | ||
|
|
||
| :::image type="content" source="../images/pj15-hello-project-data-not-published.png" alt-text="Compare an unpublished project with others."::: | ||
|
|
||
| Even if your add-in works correctly in the previous tests, there are other tests that should be run. For example: | ||
|
|
||
| - Open a project from Project Web App that has no cost or work data for the tasks. You should see values of zero in the fields in the **Current** column. | ||
|
|
||
| - Test a project that has no tasks. | ||
|
|
||
| - If you modify the add-in and publish it, you should run similar tests again with the published add-in. For other considerations, see [Next steps](#next-steps). | ||
|
|
||
| > [!NOTE] | ||
| > There are limits to the amount of data that can be returned in one query of the **ProjectData** service; the amount of data varies by entity. For example, the `Projects` entity set has a default limit of 100 projects per query, but the `Risks` entity set has a default limit of 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](#next-steps) and [Querying OData feeds for Project reporting data](/previous-versions/office/project-odata/jj163048(v=office.15)). | ||
|
|
||
| ## Example code for the HelloProjectOData add-in | ||
|
|
||
| ### HelloProjectOData.html file | ||
|
|
||
| The following code is in the `Pages\HelloProjectOData.html` file of the **HelloProjectODataWeb** project. | ||
|
|
||
| ```HTML | ||
| <!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> | ||
| onclick="retrieveOData()">Compare All Projects</button> |
| const res = JSON.parse(oDataResult); | ||
| const 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) { | ||
| for (let 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" | ||
| } | ||
| const avgProjCost = (projCost / len).toFixed(2); | ||
| const avgProjWork = (projWork / len).toFixed(1); | ||
| const avgProjActualCost = (projActualCost / len).toFixed(2); | ||
| const avgProjPercentCompleted = (projPercentCompleted / len).toFixed(1); |
| </body> | ||
| </div> | ||
| <br /> | ||
| <textarea id="odataText" rows="12" cols="40"></textarea> |
|
Learn Build status updates of commit f457f99: ✅ Validation status: passed
For more details, please refer to the build report. |
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
There was a problem hiding this comment.
Yes! I'll remove the REST references in that page.
|
Learn Build status updates of commit 7fdaf40: ✅ Validation status: passed
For more details, please refer to the build report. |
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
|
Learn Build status updates of commit 11ca4ea: ✅ Validation status: passed
For more details, please refer to the build report. |
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
|
Learn Build status updates of commit 87e1022: ✅ Validation status: passed
For more details, please refer to the build report. |
I reworked this article to use yo office, but in trying to test it, came to realize many features are soon to be unsupported. Here's a quick summary of the content relevance:
Detailed Report: Relevance Assessment
Product Variants Mentioned in the Article
The article references these Project products:
Current Support Status (2026)
🔴 Critical Finding: ProjectData OData Service - REMOVED
The ProjectData OData service has been completely removed in Project Server Subscription Edition, which is Microsoft's
current and supported version. According to Microsoft's official documentation, organizations migrating to
Subscription Edition must transition from OData queries to T-SQL queries against the reporting database schema
(pjrep).
Sources:
n/3949870
🔴 Project Online - Retiring September 30, 2026
After retirement, all Project Online data and OData services will be completely inaccessible.
Sources:
558
🔴 Project Server 2016 & 2019 - End of Support July 14, 2026
Both Project Server 2016 and 2019 reach extended support end on July 14, 2026. These are the last versions that
include the ProjectData OData service.
Source:
end-of-support/4268794
✅ Project Server Subscription Edition - Supported through 2031
This is Microsoft's recommended on-premises solution, supported through at least 2031. However, it does NOT include
the ProjectData OData service, making this article's approach incompatible.
Source:
✅ Project Professional - Still Supported
Project Professional continues to support task pane add-ins. However, without the ProjectData OData backend service,
the specific functionality demonstrated in this article cannot work.
Source:
🔴 Project for the web - Retired August 1, 2025
Already retired, with capabilities moved to Microsoft Planner.
Source:
Conclusion & Recommendations
Article Relevance: OBSOLETE - Action Required by September 2026
This article teaches developers how to build add-ins that depend on the ProjectData OData service, which has been:
Timeline of obsolescence:
Recommended Actions
- ProjectData OData service removed in Subscription Edition
- Project Server 2016/2019 end of support: July 2026
- Project Online retirement: September 2026
- This tutorial only works with unsupported/retiring products
- Direct SQL queries against the reporting database (pjrep schema)
- Alternative reporting approaches for modern Project deployments
add-in capabilities with Subscription Edition
Bottom Line
By September 30, 2026 (6 months from now), this article will describe a development approach that no longer works with
any supported Microsoft Project product. The article should be deprecated or completely rewritten to reflect current
Project Server Subscription Edition capabilities.
Sources:
558
end-of-support/4268794