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

Add guidance to use composable functions instead of filter-as-segment feature #520

Open
wants to merge 6 commits into
base: vNext
Choose a base branch
from
Open
Changes from all commits
Commits
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
61 changes: 61 additions & 0 deletions graph/articles/filter-as-segment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Filter as segment

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this positioned as an article? i think it make sense to create a pattern and clearly specify when this solution is a good one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably just be included as another scenario of #512
We might rename 512 slightly to reflect this, but doesn't seem like this should be a separate pattern, when the pattern is "replacing a complex filter with a function".

There is an [OData feature](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_AddressingaSubsetofaCollection) which allows having a `$filter` in a URL segment.
This feature is useful whenever there are operations on a collection and the client wants to perform those operations on a *subset* of the collection.
For example, the `riskyUsers` API on Microsoft Graph has an action defined to let clients "dismiss" risky users (i.e. consider those users "not risky"):

```xml
<Action Name="dismiss" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(microsoft.graph.riskyUser)" />
<Parameter Name="userIds" Type="Collection(Edm.String)" />
</Action>
```

Using this action, clients can call

```http
POST /identityProtection/riskyUsers/dismiss
{
"userIds": [
"{userId1}",
"{userId2}",
...
]
}
```

in order to dismiss the risky users with the provided IDs. Using the filter-as-segment OData feature, the action could instead be defined as:

```xml
<Action Name="dismiss" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(self.riskyUser)" />
</Action>
```

and clients could call

```http
POST /identityProtection/riskyUsers/$filter=@f/dismiss?@f=id IN ('{userId1}','{userId2}',...)
```

Doing this is beneficial due to the robust nature of OData filter expressions: clients will be able to dismiss risky users based on any supported filter without the service team needing to implement a new `dismiss` overload that filters based on the new criteria.
However, there are some concerns about the discoverability of using the filter-as-segment feature, as well as the support of [parameter aliasing](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_ParameterAliases) that's required.
As a result, functions should be introduced that act in the same way as the filter-as-segment:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think naming a specific function as "filter" is very confusing for users who expect a standard $filter behavior. I believe it does not follow our naming guidelines without having a product-specific prefix. Unless we will treat this function similarly to delta?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namespace qualification of functions is optional in msgraph.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

customFilter or filterFunction


```xml
<Function Name="filter" IsBound="true" IsComposable="true">
<Parameter Name="bindingParameter" Type="Collection(microsoft.graph.riskyUser)" Nullable="false" />
<Parameter Name="expression" Type="Edm.String" Nullable="false" />
<ReturnType Type="Collection(microsoft.graph.riskyUser)" />
</Function>
```

Clients would now be able to call

```http
POST /identityProtection/riskyUsers/filter(expression='id IN (''{userId1}'',''{userId2}'',...)')/dismiss
```

NOTE: the `'` literal in the filter expression must be escaped with `''`

An example implementation of a filter function using OData WebApi can be found [here](https://github.com/OData/AspNetCoreOData/commit/7732f7e6b812d9a79a73529562f2e74b68e2794f).