Skip to content

Commit

Permalink
Merge pull request #3168 from pnp/version-4
Browse files Browse the repository at this point in the history
v4.7.0 Release merge to main
  • Loading branch information
patrick-rodgers authored Nov 18, 2024
2 parents 783d143 + 54bc410 commit 7252895
Show file tree
Hide file tree
Showing 29 changed files with 964 additions and 65 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## 4.7.0 - 2024-Nov-18

- sp
- Introduces new filter lamda patterns as beta

- graph
- Renamed OneNote Pages to OneNotePages
- Basic Pages API support as beta
- Site Open Extensions as beta
- Fixed #3136 for improving paging support for query params

- queryable
- Introduced DebugHeaders behavior

## 4.6.0 - 2024-Oct-14

- Only documentation and package updates
Expand Down
4 changes: 2 additions & 2 deletions debug/launch/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ import { Example } from "./sp.js";
// create a settings file using settings.example.js as a template
import(findup("settings.js")).then((settings: { settings: ITestingSettings }) => {

Logger.activeLogLevel = LogLevel.Info;
Logger.activeLogLevel = LogLevel.Info;

// // setup console logger
Logger.subscribe(ConsoleListener("Debug", {
color: "skyblue",
error: "red",
verbose: "lightslategray",
warning: "yellow",
}));
}));

Example(settings.settings);

Expand Down
30 changes: 17 additions & 13 deletions debug/launch/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ import { SPDefault, GraphDefault } from "@pnp/nodejs";
import { spfi, SPFI } from "@pnp/sp";
import { GraphFI, graphfi } from "@pnp/graph";
import { LogLevel, PnPLogging } from "@pnp/logging";
import { Queryable } from "@pnp/queryable";
import { Queryable, DebugHeaders } from "@pnp/queryable";

export function spSetup(settings: ITestingSettings): SPFI {

const sp = spfi(settings.testing.sp.url).using(SPDefault({
msal: {
config: settings.testing.sp.msal.init,
scopes: settings.testing.sp.msal.scopes,
},
})).using(
const sp = spfi(settings.testing.sp.url).using(
SPDefault({
msal: {
config: settings.testing.sp.msal.init,
scopes: settings.testing.sp.msal.scopes,
},
}),
PnPLogging(LogLevel.Verbose),
DebugHeaders(),
function (instance: Queryable) {

instance.on.pre(async (url, init, result) => {
Expand All @@ -29,13 +31,15 @@ export function spSetup(settings: ITestingSettings): SPFI {

export function graphSetup(settings: ITestingSettings): GraphFI {

const graph = graphfi().using(GraphDefault({
msal: {
config: settings.testing.graph.msal.init,
scopes: settings.testing.graph.msal.scopes,
},
})).using(
const graph = graphfi().using(
GraphDefault({
msal: {
config: settings.testing.graph.msal.init,
scopes: settings.testing.graph.msal.scopes,
},
}),
PnPLogging(LogLevel.Verbose),
DebugHeaders(),
function (instance: Queryable) {

instance.on.pre(async (url, init, result) => {
Expand Down
5 changes: 2 additions & 3 deletions debug/launch/sp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@ import { Logger, LogLevel } from "@pnp/logging";
import { spSetup } from "./setup.js";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";

declare var process: { exit(code?: number): void };

export async function Example(settings: ITestingSettings) {

const sp = spSetup(settings);

const w = await sp.web();
const w = await sp.web.lists();

Logger.log({
data: w,
level: LogLevel.Info,
message: "Web Data",
});

process.exit(0);
}
3 changes: 3 additions & 0 deletions docs/graph/site-openextensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Site Open Extensions

// TODO
37 changes: 37 additions & 0 deletions docs/queryable/behaviors.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,40 @@ setTimeout(() => {
// this is awaiting the results of the request
await p;
```

### DebugHeaders

Adds logging for the request id and timestamp of the request, helpful when contacting Microsoft Support. It works for both Graph and SP libraries.

```TypeScript
import { DebugHeaders } from "@pnp/queryable";
import { spfi } from "@pnp/sp";

const sp = spfi().using(DebugHeaders());

sp.some_action();

// output to log:
// Server Request Id: {guid}
// Server Date: {date}
```

You can also supply additional headers to log from the response:


```TypeScript
import { DebugHeaders } from "@pnp/queryable";
import { spfi } from "@pnp/sp";

const sp = spfi().using(DebugHeaders(["X-MyHeader", "client-request-id"]));

sp.some_action();

// output to log:
// Server Request Id: {guid}
// Server Date: {date}
// X-MyHeader: {value}
// client-request-id: {guid}
```


97 changes: 97 additions & 0 deletions docs/sp/items.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,89 @@ const r = await sp.web.lists.getByTitle("TaxonomyList").getItemsByCAMLQuery({
});
```

### Filter using fluent filter

>Note: This feature is currently in preview and may not work as expected.
PnPjs supports a fluent filter for all OData endpoints, including the items endpoint. this allows you to write a strongly fluent filter that will be parsed into an OData filter.

```TypeScript
import { spfi } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";

const sp = spfi(...);

const r = await sp.web.lists.filter(l => l.number("ItemCount").greaterThan(5000))();
```

The following field types are supported in the fluent filter:

- Text
- Choice
- MultiChoice
- Number
- Date
- Boolean
- Lookup
- LookupId

The following operations are supported in the fluent filter:

| Field Type | Operators/Values |
| -------------------- | -------------------------------------------------------------------------------------------- |
| All field types | `equals`, `notEquals`, `in`, `notIn` |
| Text & choice fields | `startsWith`, `contains` |
| Numeric fields | `greaterThan`, `greaterThanOrEquals`, `lessThan`, `lessThanOrEquals` |
| Date fields | `greaterThan`, `greaterThanOrEquals`, `lessThan`, `lessThanOrEquals`, `isBetween`, `isToday` |
| Boolean fields | `isTrue`, `isFalse`, `isFalseOrNull` |
| Lookup | `id`, Text and Number field types |

#### Complex Filter

For all the regular endpoints, the fluent filter will infer the type automatically, but for the list items filter, you'll need to provide your own types to make the parser work.

You can use the `and` and `or` operators to create complex filters that nest different grouping.

```TypeScript
import { spfi } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";

const sp = spfi(...);

interface ListItem extends IListItem {
FirstName: string;
LastName: string;
Age: number;
Manager: IListItem;
StartDate: Date;
}


// Get all employees named John
const r = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.text("FirstName").equal("John"))();

// Get all employees not named John who are over 30
const r1 = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.text("FirstName").notEquals("John").and().number("Age").greaterThan(30))();

// Get all employees that are named John Doe or Jane Doe
const r2 = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.or(
f.and(
f.text("FirstName").equals("John"),
f.text("LastName").equals("Doe")
),
f.and(
f.text("FirstName").equals("Jane"),
f.text("LastName").equals("Doe")
)
))();

// Get all employees who are managed by John and start today
const r3 = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.lookup("Manager").text("FirstName").equals("John").and().date("StartDate").isToday())();
```

### Retrieving PublishingPageImage

The PublishingPageImage and some other publishing-related fields aren't stored in normal fields, rather in the MetaInfo field. To get these values you need to use the technique shown below, and originally outlined in [this thread](https://github.com/SharePoint/PnP-JS-Core/issues/178). Note that a lot of information can be stored in this field so will pull back potentially a significant amount of data, so limit the rows as possible to aid performance.
Expand Down Expand Up @@ -326,6 +409,8 @@ const sp = spfi(...);

// you are getting back a collection here
const items: any[] = await sp.web.lists.getByTitle("MyList").items.top(1).filter("Title eq 'A Title'")();
// Using fluent filter
const items1: any[] = await sp.web.lists.getByTitle("MyList").items.top(1).filter(f => f.text("Title").equals("A Title"))();

// see if we got something
if (items.length > 0) {
Expand Down Expand Up @@ -425,6 +510,9 @@ const sp = spfi(...);
// first we need to get the hidden field's internal name.
// The Title of that hidden field is, in my case and in the linked article just the visible field name with "_0" appended.
const fields = await sp.web.lists.getByTitle("TestList").fields.filter("Title eq 'MultiMetaData_0'").select("Title", "InternalName")();
// Using fluent filter
const fields1 = await sp.web.lists.getByTitle("TestList").fields.filter(f => f.text("Title").equals("MultiMetaData_0")).select("Title", "InternalName")();

// get an item to update, here we just create one for testing
const newItem = await sp.web.lists.getByTitle("TestList").items.add({
Title: "Testing",
Expand Down Expand Up @@ -593,6 +681,15 @@ const response =
.filter(`Hidden eq false and Title eq '[Field's_Display_Name]'`)
();

// Using fluent filter
const response1 =
await sp.web.lists
.getByTitle('[Lists_Title]')
.fields
.select('Title, EntityPropertyName')
.filter(l => l.boolean("Hidden").isFalse().and().text("Title").equals("[Field's_Display_Name]"))
();

console.log(response.map(field => {
return {
Title: field.Title,
Expand Down
15 changes: 12 additions & 3 deletions docs/sp/webs.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,15 @@ const infos2 = await web.webinfos.select("Title", "Description")();

// or filter
const infos3 = await web.webinfos.filter("Title eq 'MyWebTitle'")();
// Using fluent filter
const infos4 = await web.webinfos.filter(w => w.text("Title").equals('MyWebTitle'))();


// or both
const infos4 = await web.webinfos.select("Title", "Description").filter("Title eq 'MyWebTitle'")();
const infos5 = await web.webinfos.select("Title", "Description").filter(w => w.text("Title").equals('MyWebTitle'))();

// get the top 4 ordered by Title
const infos5 = await web.webinfos.top(4).orderBy("Title")();
const infos6 = await web.webinfos.top(4).orderBy("Title")();
```

> Note: webinfos returns [IWebInfosData](#IWebInfosData) which is a subset of all the available fields on IWebInfo.
Expand Down Expand Up @@ -537,9 +540,12 @@ const folders = await sp.web.folders();

// you can also filter and select as with any collection
const folders2 = await sp.web.folders.select("ServerRelativeUrl", "TimeLastModified").filter("ItemCount gt 0")();
// Using fluent filter
const folders3 = await sp.web.folders.select("ServerRelativeUrl", "TimeLastModified").filter(f => f.number("ItemCount").greaterThan(0))();


// or get the most recently modified folder
const folders2 = await sp.web.folders.orderBy("TimeLastModified").top(1)();
const folders4 = await sp.web.folders.orderBy("TimeLastModified").top(1)();
```

### rootFolder
Expand Down Expand Up @@ -856,6 +862,9 @@ const users = await sp.web.siteUsers();
const users2 = await sp.web.siteUsers.top(5)();

const users3 = await sp.web.siteUsers.filter(`startswith(LoginName, '${encodeURIComponent("i:0#.f|m")}')`)();
// Using fluent filter
const user4 = await sp.web.siteUsers.filter(u => u.text("LoginName").startsWith(encodeURIComponent("i:0#.f|m")))();

```

### currentUser
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ nav:
- search: 'graph/search.md'
- shares: 'graph/shares.md'
- sites: 'graph/sites.md'
- 'site openextensions': 'graph/site-openextensions.md'
- subscriptions: 'graph/subscriptions.md'
- taxonomy: 'graph/taxonomy.md'
- teams: 'graph/teams.md'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@pnp/monorepo",
"private": true,
"type": "module",
"version": "4.6.0",
"version": "4.7.0",
"description": "A JavaScript library for SharePoint & Graph development.",
"devDependencies": {
"@azure/identity": "4.4.1",
Expand Down
3 changes: 1 addition & 2 deletions packages/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ export function combine(...paths: (string | null | undefined)[]): string {

return paths
.filter(path => !stringIsNullOrEmpty(path))
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map(path => path!.replace(/^[\\|/]/, "").replace(/[\\|/]$/, ""))
.map(path => path.replace(/^[\\|/]/, "").replace(/[\\|/]$/, ""))
.join("/")
.replace(/\\/g, "/");
}
Expand Down
4 changes: 2 additions & 2 deletions packages/graph/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ export function getById<R>(factory: (...args: any[]) => R) {
return function <T extends { new(...args: any[]): {} }>(target: T) {

return class extends target {
public getById(this: IGraphQueryable, id: string): R {
return factory(this, id);
public getById(this: IGraphQueryable, id: any): R {
return factory(this, `${id}`);
}
};
};
Expand Down
Loading

0 comments on commit 7252895

Please sign in to comment.