If you know me or follow me on Twitter/LinkedIn, you must have realized by now how much I like the PnPjs library. Enough to venture myself to speak about it on 3 SharePoint Saturday events last year. The library has packages for SharePoint and Graph endpoints and can be easily used on SPFx solutions. But if you need an SPFx solution that consumes Project Online API, what options do you have?
Kudos to Paweł Hawrylak who started creating the Project module for PnPjs and currently already offers support for a wide range of endpoints. The module is currently in a dev branch and requires additional work and testing, but it’s already a phenomenal effort.
This blog post will cover the required steps to generate a local PnPjs Project package to consume Project Online REST APIs and create an SPFx web part that uses it.
Published npm packaged
2019-12-31 update
If you only want to give the current version a try, there is now a package published to npm that you can simply install. Don’t forget to also install the required dependencies of @pnp/common, @pnp/odata, @pnp/logging and you are ready to rock!
npm i pnpjs-project-online-package @pnp/common@1.3.7 @pnp/logging@1.3.7 @pnp/odata@1.3.7 --save-exact
Get the code from GitHub
2019-11-14 update: I now have a repository forked from the main PnPjs project where I added the code for the project module. The good news is that this is using the latest PnPjs version (1.3.7 at the time)
https://github.com/joelfmrodrigues/pnpjs/tree/feature/project-online-api
From here, you can download, clone, or fork it, whatever makes sense for what you plan to do with it. Just be aware that if you plan to fork it to contribute, and you have also previously forked the original PnPjs repository, you may need to delete the last and fork after that.
You can find the original code under Paweł’s GitHub fork of the main PnPjs project on GitHub.
Build PnPjs packages
Since we have the PnPjs source code, we need to build and generate local packages. You can get additional information on the PnPjs gulp commands in the official documentation.
Run the following commands in the order specified:
- npm install – to install the npm packages required by PnPjs
- gulp build – to ensure that the solution can build successfully
- gulp package – to generate all the different library packages, including the new Project package
Peer dependencies
We are going to install the local Project package that we have just built in our SPFx solution. Unfortunately, the peer dependencies don’t seem to work as expected when you do so and you get errors when trying to use the packages on your web part.
If you inspect the package.json file for the Project package created (dist/packages/project), you can find the following required peerDependencies which are not resolved by default
"peerDependencies": {
"@pnp/common": "1.3.7",
"@pnp/logging": "1.3.7",
"@pnp/odata": "1.3.7"
},
Not the ideal solution for sure, but a simple way to get around this problem is to simply install the packages yourself.
Remember that we are only testing the new Project package and all the others are kept untouched, so seems sensible to me to install them directly from npm.
Ensure that your command prompt is on the
dist/packages/project directory and run the following command
npm install @pnp/common@1.3.7 @pnp/logging@1.3.7 @pnp/odata@1.3.7
The Project package is now ready to be consumed by our solution.
Create a new SPFx solution
I’m not going to provide any specific instructions here as the official documentation is excellent and gives you all the information you need. If you are new to SharePoint Framework development, please check it out and learn how to get started.
Add PnPjs packages to the SPFx solution
Again, remember that Project is the only package that is not published to npm, so is the only one that we need to install from a local path.
Start by installing all the common PnPjs packages that you usually install. In this case, I’m using the 1.2.5 version as it’s the version that matches the local version.
npm install @pnp/common@1.3.7 @pnp/logging@1.3.7 @pnp/odata@1.3.7 --save-exact
Next, install the local Project package by providing a relative path to the package folder. In my example:
npm install ../PnPjs/dist/packages/project
Establish Context
Following the official guidance, we also need to establish the context when using the library in SPFx. Simply add the following block of code into your web part main file as provided on the documentation:
import { project } from "pnpjs-project-online-package"; // or import from your local package for development
// ...
public onInit(): Promise<void> {
return super.onInit().then(_ => {
// other init code may be present
project.setup({
spfxContext: this.context,
project: {
baseUrl: 'https://XXXXXXXXXX.sharepoint.com/sites/pwa'
}
});
});
}
// ...
Please note that we are setting the baseUrl property to be the PWA site. This is to allow the solution to work from any SharePoint site, not only Project Online.
Use it!
It’s all done and you can now use the fluent library to interact with project online!
All you need to do is to import the Project package when you need it
import { project } from "@pnp/project";
Some usage examples:
// get all projects
const projects = await project.projects.get();
// get projects, filtering by name, returning only the Id and Name properties, and limiting the results count to 1
const projectInfo = await project.projects.filter(`Name eq '${projectName}'`).select('Id,Name').top(1).get();
// create timesheet
const createResult = await project.timeSheetPeriods.getById('XXXXXXXXXXXXXXXX').createTimeSheet();
Hello, nice article.
For some reasons cant add project/task. Have htpp error 400.
Faced with this?
Hi Andrew, thanks for the feedback.
I have not tried that specific endpoint unfortunately. May be worth checking if the http request being sent is correct – please remember this is a dev version not yet fully tested.
Using the Network tab on Chrome dev tools may be enough to inspect the request and quickly check if all is in place. I had a similar issue when trying to create timesheet lines and it was simply down to a bug on the request.
I tried this
const randId = pnpjs.util.getGUID();
const projectParam: ProjectCreationInformation = {
id: randId,
enterpriseProjectTypeId: ‘EPT’,
name: ‘Project1’,
description: ‘description’
};
await project.projects.add(projectParam);
This first thing that comes to mind is the ID that you are providing, as I would expect the ID to be generated internally. Have you tried to not pass the ID (assuming it’s not required)?
Also, does it work if you try to simply retrieve a list of projects? Just to validate that all is setup correctly and you are able to get data back
Additionally, enterpriseProjectTypeId I believe will expect a numeric ID or maybe a GUID. You should be able to query those 🙂
Yes, i cat get project/task lists with this lib.
But i can’t create project.
Also i can create project with my code:
const bodyString: string = JSON.stringify({
‘parameters’: {
‘EnterpriseProjectTypeId’: “09fa52b4-059b-4527-926e-99f9be96437a”,
‘Name’: ‘Project5’
}
});
const spOpts = {
body: bodyString
};
this.props.spfxContext.spHttpClient.post(`https://tenant.sharepoint.com/sites/pwa/_api/ProjectServer/Projects/Add`, SPHttpClient.configurations.v1, spOpts)
.then((response: SPHttpClientResponse) => {
// Access properties of the response object.
console.log(`Status code: ${response.status}`);
console.log(`Status text: ${response.statusText}`);
//response.json() returns a promise so you get access to the json in the resolve callback.
response.json().then((responseJSON: JSON) => {
console.log(responseJSON);
});
});
Ok so it should be relatively easy for you to identify the error if you already have a sample request that works 🙂
Looking at your request, it reminded me of what was failing in my case: basically, it was missing the /Add at the end of the request and also some of the properties were using the incorrect casing which was also a problem.
Hopefully your error is similar and can be easily fixed.
Are you able to validate this on the Project module of PnPjs?
This is a PR that I submitted to the original repository created by Pawel: https://github.com/phawrylak/pnpjs/pull/1/files
If the issue is similar, you can apply similar changes to the project class and give it a try.
Please let me know if that works, and I’m sure Pawel (and everyone else interested on the project) would welcome a PR with the fix!
Just quickly looked at the code and I think that what I posted on my previous comment is very likely.
In summary:
Go to packages/project/src/projects.ts and update the add method
replace:
const data = await this.postCore({ body: jsS(parameters) });
with:
this.concat(“/add”);
const params: any = {
“parameters”: parameters,
};
const data = await this.postCore({ body: jsS(params) });
Next, go to the bottom of that file and rename the properties of the interface to start with uppercase.
If you have any issues with this let me know and I will make the changes on my fork.
Hope this helps
When i try to add i got this error:
“{\”error\”:{\”code\”:\”-1, Microsoft.SharePoint.Client.InvalidClientQueryException\”,\”message\”:{\”lang\”:\”en-US\”,\”value\”:\”
An entry without a type name was found, but no expected type was specified.
To allow entries without type information, the expected type must also be specified when the model is specified.\”}}}”
Interesting project here… I wish there was an official effort and repo for PnP/Project, but we all know that Microsoft has “written-off” EPM and PJO. Gonna give this a shot!
Hi Jack,
I actually have a more up to date repository on GitHub that has some bug fixes added to it. Just trying to find the time to publish an updated blog post.
Will try to do that before the end of the week and also update it to align with the latest PnPjs version.
I’m also planning to release a version to npm either via PnP or my own, as that would make it much easier to use and hopefully attract more developers. I’m finding it really hard to progress development due to lack of time