Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OData lambda style filter query #3155

Merged
merged 24 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9f9987b
Initial Pass at Lambda
bcdmi Aug 19, 2023
4c43d58
🔨 - OData filter inital prototype
Tanddant Nov 20, 2023
fbf1f56
🎉 - Initial work on filter queryable with lambda expressions
Tanddant Dec 11, 2023
430a3c0
🔨 - Move away from the "class based" model towards just a string array
Tanddant Dec 12, 2023
181de6e
🔒 - Updated package-lock (version: 4.0.0)
Tanddant Dec 12, 2023
c955e88
🔨 - SPText filters working - not auto detecting types
Tanddant Dec 18, 2023
ed46762
🔨 - switch to f => f.Text('x').Equals('y')
Tanddant Dec 18, 2023
fc6fd40
🔨 - Remove the SP-prefixed options
Tanddant Dec 18, 2023
ca0b26f
Revert "🔨 - Remove the SP-prefixed options"
Tanddant Dec 19, 2023
7d0462b
🔨 - Inital work on rewrite inspired by Beau
Tanddant Dec 19, 2023
8efc415
🔨 - Added In operator
Tanddant Dec 19, 2023
22360ba
🔨 - Lookup field support
Tanddant Dec 19, 2023
44b0795
🔨 - a bit of lint cleanup
Tanddant Dec 19, 2023
0df4ec6
🔨 - Support for quick inline field edits
Tanddant Dec 19, 2023
9019579
🔨 - Move to arrow function syntax in filters
Tanddant Oct 18, 2024
5582271
🔨 - Linting | fixed Dates being suggested as internal name in lookup …
Tanddant Oct 18, 2024
5d7ea44
🔨 Slightly more linting
Tanddant Oct 18, 2024
f92c6ba
🔨 - Allowed for mixed queries in .some and .all combiners
Tanddant Oct 18, 2024
5be7693
🔨 - Move around the code to allow .All and .Some, not only on "top le…
Tanddant Oct 19, 2024
f1c3663
🔨 - Change from taking an array of queries to using spread operator
Tanddant Oct 19, 2024
4d3bbea
Merge remote-tracking branch 'PnP/version-4' into v4-ODataFilterQuery
Tanddant Oct 20, 2024
c777844
🔨 - Changed based on feedback in initial PR
Tanddant Oct 20, 2024
76dca87
🔨- First draft of docs
Tanddant Oct 23, 2024
759bbcf
🎨 - rename to equals notEquals greaterThanOrEquals lessThanOrEquals
Tanddant Oct 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading