Enhance MsPlanner project listing with additional metadata#892
Enhance MsPlanner project listing with additional metadata#892vladvysotsky wants to merge 1 commit intojoniles:masterfrom
Conversation
- Add modifiedOn, createdOn, stateCode, projectManagerName, programId, programName fields to MsPlannerProject - Expand getProjects() to fetch and populate additional fields - Add getPrograms() method to list programs/portfolios - Add loadProgramNames() to resolve program names for projects - Add OData annotation header for formatted value support - Fix typo: "Unititled" -> "Untitled"
There was a problem hiding this comment.
Pull request overview
This PR enhances the Microsoft Planner/Dynamics project listing APIs by enriching MsPlannerProject with additional metadata, adding program/portfolio listing, and attempting to resolve program names for projects.
Changes:
- Expanded
getProjects()to request and populate additional project metadata fields (timestamps, state, manager display name, program reference). - Added
getPrograms()plus aloadProgramNames()helper to resolve program names for projects. - Added OData annotation support via a
Preferrequest header and fixed a calendar name typo (“Unititled” → “Untitled”).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/main/java/org/mpxj/msplanner/MsPlannerReader.java | Extends project/program retrieval and adds program-name resolution + OData annotation header + typo fix. |
| src/main/java/org/mpxj/msplanner/MsPlannerProject.java | Adds new metadata fields and getters/setters to carry additional API data. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| catch (Exception e) | ||
| { | ||
| // Programs entity may not be available - ignore |
There was a problem hiding this comment.
loadProgramNames catches Exception and ignores it entirely. This will silently hide genuine failures (e.g., malformed URL, auth/network issues) and makes troubleshooting very difficult. Consider only treating "programs entity not available" as non-fatal by checking the HTTP status (e.g., 404/501) and rethrowing (or wrapping) other errors.
| } | |
| catch (Exception e) | |
| { | |
| // Programs entity may not be available - ignore | |
| else if (code == 404 || code == 501) | |
| { | |
| // Programs entity may not be available - treat as non-fatal and return empty map | |
| } | |
| else | |
| { | |
| throw new MsPlannerException(getExceptionMessage(connection, code)); | |
| } | |
| } | |
| catch (IOException e) | |
| { | |
| // Unexpected I/O error when loading program names - propagate as runtime exception | |
| throw new RuntimeException("Error loading program names", e); |
| connection.setRequestProperty("Accept", "application/json"); | ||
| connection.setRequestProperty("Accept-Encoding", "gzip"); | ||
| connection.setRequestProperty("Authorization", "Bearer " + m_token); | ||
| connection.setRequestProperty("Prefer", "odata.include-annotations=\"*\""); |
There was a problem hiding this comment.
Setting Prefer: odata.include-annotations="*" on every request can significantly increase payload size (especially for large reads like readData) and may impact performance. Consider narrowing this to only the needed annotation (OData.Community.Display.V1.FormattedValue) and/or only applying it to calls that actually consume formatted values (e.g., the projects listing).
| connection.setRequestProperty("Prefer", "odata.include-annotations=\"*\""); | |
| connection.setRequestProperty("Prefer", "odata.include-annotations=\"OData.Community.Display.V1.FormattedValue\""); |
| @@ -113,7 +159,12 @@ public List<MsPlannerProject> getProjects() | |||
| MapRow data = getMapRow(connection); | |||
|
|
|||
| return data.getList("value").stream() | |||
| .map(d -> new MsPlannerProject(d.getUUID("msdyn_projectid"), d.getString("msdyn_subject"))) | |||
| .map(d -> { | |||
| MsPlannerProject program = new MsPlannerProject(d.getUUID("msdyn_programid"), d.getString("msdyn_name")); | |||
| program.setModifiedOn(d.getDate("modifiedon")); | |||
| program.setCreatedOn(d.getDate("createdon")); | |||
| return program; | |||
| }) | |||
There was a problem hiding this comment.
getPrograms() returns List<MsPlannerProject> but populates it with msdyn_programid/msdyn_name. This makes the API confusing because consumers must call getProjectId()/getProjectName() to get program identity/name. Consider introducing a dedicated MsPlannerProgram type or generalizing/renaming the model so the semantics match the data being returned.
| .map(id -> "msdyn_programid eq " + id) | ||
| .collect(Collectors.joining(" or ")); | ||
|
|
||
| HttpURLConnection connection = createConnection("msdyn_programs?$select=msdyn_programid,msdyn_name&$filter=" + filter); |
There was a problem hiding this comment.
$filter is concatenated with unescaped spaces and or operators (e.g., "msdyn_programid eq " + id), but createConnection builds a raw URL without encoding. This will produce an invalid URL (spaces) and cause program name loading to fail (currently swallowed by the catch). Encode the filter/query string (e.g., replace spaces with %20 / use a proper URI builder) before passing it to createConnection.
| HttpURLConnection connection = createConnection("msdyn_programs?$select=msdyn_programid,msdyn_name&$filter=" + filter); | |
| String encodedFilter = filter.replace(" ", "%20"); | |
| HttpURLConnection connection = createConnection("msdyn_programs?$select=msdyn_programid,msdyn_name&$filter=" + encodedFilter); |
Summary
MsPlannerProject(modifiedOn, createdOn, stateCode, projectManagerName, programId, programName)getProjects()to fetch and populate these fields from the APIgetPrograms()method to list available programs/portfoliosloadProgramNames()helper to resolve program names for projectsTest plan
getProjects()returns projects with populated metadata fieldsgetPrograms()returns programs with correct IDs and names