Skip to content

Commit

Permalink
Merge branch 'anom/people-picker-teams-filter' of https://github.com/…
Browse files Browse the repository at this point in the history
…SherpasGroup/sp-dev-fx-controls-react into SherpasGroup-anom/people-picker-teams-filter
  • Loading branch information
joelfmrodrigues committed Nov 18, 2021
2 parents 41be03e + aab47ae commit fa7cafb
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 11 deletions.
11 changes: 10 additions & 1 deletion docs/documentation/docs/controls/PeoplePicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The People picker control can be configured with the following properties:
| context | BaseComponentContext | yes | Context of the current web part. | |
| titleText | string | no | Text to be displayed on the control | |
| groupName | string | no | Group from which users are fetched. Leave it blank if need to filter all users. When both groupName and groupId specified groupName takes precedence. | _none_ |
| groupId | number | no | Group from which users are fetched. Leave it blank if need to filter all users. When both groupId and groupName specified groupName takes precedence. | _none_ |
| groupId | number \| string | no | Group from which users are fetched. Leave it blank if need to filter all users. When both groupId and groupName specified groupName takes precedence. If string is specified, Microsoft 365 Group is used | _none_ |
| personSelectionLimit | number | no | Defines the limit of people that can be selected in the control | 1 |
| required | boolean | no | Set if the control is required or not | false |
| disabled | boolean | no | Set if the control is disabled or not | false |
Expand Down Expand Up @@ -93,4 +93,13 @@ The `PrincipalType` enum can be used to specify the types of information you wan
| SecurityGroup | 4 |
| SharePointGroup | 8 |


## MSGraph Permissions required

This control requires the following scopes if groupId is of type String:

at least : GroupMember.Read.All, Directory.Read.All


![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/PeoplePicker)

2 changes: 1 addition & 1 deletion src/common/dal/ListItemRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class ListItemRepository {
const webAbsoluteUrl = !webUrl ? this.SiteUrl : webUrl;
let apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/items?$select=${keyInternalColumnName || 'Id'},${internalColumnName}&$filter=${filterText}&$top=${top}`;
if (orderBy) {
apiUrl += `&$orderBy=${orderBy}`
apiUrl += `&$orderBy=${orderBy}`;
}
const data = await this.SPClient.get(apiUrl, SPHttpClient.configurations.v1);
if (data.ok) {
Expand Down
6 changes: 3 additions & 3 deletions src/controls/peoplepicker/IPeoplePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ export interface IPeoplePickerProps {
*/
groupName?: string;
/**
* Id of SharePoint Group
* Id of SharePoint Group (Number) or Office365 Group (String)
*/
groupId?: number;
/**
groupId?: number | string;
/**
* Maximum number of suggestions to show in the full suggestion list. (default: 5)
*/
suggestionsLimit?: number;
Expand Down
2 changes: 1 addition & 1 deletion src/controls/peoplepicker/PeoplePickerComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import uniqBy from 'lodash/uniqBy';
export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePickerState> {
private peopleSearchService: SPPeopleSearchService;
private suggestionsLimit: number;
private groupId: number;
private groupId: number | string;

constructor(props: IPeoplePickerProps) {
super(props);
Expand Down
55 changes: 50 additions & 5 deletions src/services/PeopleSearchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { MockUsers, PeoplePickerMockClient } from './PeoplePickerMockClient';
import { PrincipalType, IPeoplePickerUserItem } from "../PeoplePicker";
import { IUsers, IUserInfo } from "../controls/peoplepicker/IUsers";
import { cloneDeep, findIndex } from "@microsoft/sp-lodash-subset";
import { sp } from '@pnp/sp';
import "@pnp/sp/sputilities";
import { Web } from "@pnp/sp/webs";
import "@pnp/sp/webs";
import "@pnp/sp/site-users/web";

/**
* Service implementation to search people in SharePoint
Expand All @@ -20,6 +25,8 @@ export default class SPPeopleSearchService {
this.cachedPersonas = {};
this.cachedLocalUsers = {};
this.cachedLocalUsers[this.context.pageContext.web.absoluteUrl] = [];
// Setup PnPjs
sp.setup(this.context);
}

/**
Expand Down Expand Up @@ -62,7 +69,7 @@ export default class SPPeopleSearchService {
/**
* Search person by its email or login name
*/
public async searchPersonByEmailOrLogin(email: string, principalTypes: PrincipalType[], siteUrl: string = null, groupId: number = null, ensureUser: boolean = false): Promise<IPeoplePickerUserItem> {
public async searchPersonByEmailOrLogin(email: string, principalTypes: PrincipalType[], siteUrl: string = null, groupId: number | string = null, ensureUser: boolean = false): Promise<IPeoplePickerUserItem> {
if (Environment.type === EnvironmentType.Local) {
// If the running environment is local, load the data from the mock
const mockUsers = await this.searchPeopleFromMock(email);
Expand All @@ -76,7 +83,7 @@ export default class SPPeopleSearchService {
/**
* Search All Users from the SharePoint People database
*/
public async searchPeople(query: string, maximumSuggestions: number, principalTypes: PrincipalType[], siteUrl: string = null, groupId: number = null, ensureUser: boolean = false): Promise<IPeoplePickerUserItem[]> {
public async searchPeople(query: string, maximumSuggestions: number, principalTypes: PrincipalType[], siteUrl: string = null, groupId: number | string = null, ensureUser: boolean = false): Promise<IPeoplePickerUserItem[]> {
if (Environment.type === EnvironmentType.Local) {
// If the running environment is local, load the data from the mock
return this.searchPeopleFromMock(query);
Expand Down Expand Up @@ -162,7 +169,7 @@ export default class SPPeopleSearchService {
/**
* Tenant search
*/
private async searchTenant(siteUrl: string, query: string, maximumSuggestions: number, principalTypes: PrincipalType[], ensureUser: boolean, groupId: number): Promise<IPeoplePickerUserItem[]> {
private async searchTenant(siteUrl: string, query: string, maximumSuggestions: number, principalTypes: PrincipalType[], ensureUser: boolean, groupId: number | string): Promise<IPeoplePickerUserItem[]> {
try {
// If the running env is SharePoint, loads from the peoplepicker web service
const userRequestUrl: string = `${siteUrl || this.context.pageContext.web.absoluteUrl}/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser`;
Expand All @@ -183,11 +190,49 @@ export default class SPPeopleSearchService {
searchBody.queryParams["SharePointGroupID"] = 0;
}

// Check if users need to be searched in a specific group
if (groupId) {
// Check if users need to be searched in a specific SharePoint Group
if (groupId && typeof(groupId) === 'number') {
searchBody.queryParams["SharePointGroupID"] = groupId;
}

// Check if users need to be searched in a specific Office365 Group
else if(groupId && typeof(groupId) === 'string') {
const graphUserRequestUrl = `/groups/${groupId}/members?$count=true&$search="displayName:${query}" OR "mail:${query}"`;
const graphClient = await this.context.msGraphClientFactory.getClient();
const graphUserResponse = await graphClient.api(graphUserRequestUrl).header('ConsistencyLevel', 'eventual').get();

if(graphUserResponse.value && graphUserResponse.value.length > 0) {

// Get user loginName from user email
const _users = [];
const batch = Web(this.context.pageContext.web.absoluteUrl).createBatch();
for (const value of graphUserResponse.value) {
sp.web.inBatch(batch).ensureUser(value.userPrincipalName).then(u => _users.push(u.data));
}

await batch.execute();

let userResult: IPeoplePickerUserItem[] = [];
for (const user of _users) {
userResult.push({
id: ensureUser ? user.Id : user.LoginName,
loginName: user.LoginName,
imageUrl: this.generateUserPhotoLink(user.Email),
imageInitials: this.getFullNameInitials(user.Title),
text: user.Title, // name
secondaryText: user.Email, // email
tertiaryText: '', // status
optionalText: '' // anything
});
}

return userResult;
}

//Nothing to return
return [];
}

const httpPostOptions: ISPHttpClientOptions = {
headers: {
'accept': 'application/json',
Expand Down

0 comments on commit fa7cafb

Please sign in to comment.