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

feat(PeoplePicker): add new allowUnvalidated option to allow adding non-tenant users #1232

Merged
merged 2 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions src/controls/peoplepicker/IPeoplePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export interface IPeoplePickerProps {
* When ensure user property is true, it will return the local user ID on the current site when doing a tenant wide search
*/
ensureUser?: boolean;
/**
* When true, allow email addresses that have not been validated to be entered, effectively allowing any user
*/
allowUnvalidated?: boolean;
/**
* Placeholder to be displayed in an empty term picker
*/
Expand Down Expand Up @@ -149,4 +153,5 @@ export interface IPeoplePickerUserItem {
secondaryText: string; // role
tertiaryText: string; // status
optionalText: string; // anything
userUnvalidated?: boolean;
}
6 changes: 3 additions & 3 deletions src/controls/peoplepicker/PeoplePickerComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
* Get initial persons
*/
private async getInitialPersons(props: IPeoplePickerProps) {
const { groupName, groupId, webAbsoluteUrl, defaultSelectedUsers, ensureUser, principalTypes } = props;
const { groupName, groupId, webAbsoluteUrl, defaultSelectedUsers, ensureUser, allowUnvalidated, principalTypes } = props;
// Check if a group property was provided, and get the group ID
if (groupName) {
this.groupId = await this.peopleSearchService.getGroupId(groupName, webAbsoluteUrl);
Expand All @@ -101,7 +101,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
valueAndTitle = userValue.split('/');
}

const userResult = await this.peopleSearchService.searchPersonByEmailOrLogin(valueAndTitle[0], principalTypes, webAbsoluteUrl, this.groupId, ensureUser);
const userResult = await this.peopleSearchService.searchPersonByEmailOrLogin(valueAndTitle[0], principalTypes, webAbsoluteUrl, this.groupId, ensureUser, allowUnvalidated);
if (userResult) {
selectedPersons.push(userResult);
}
Expand All @@ -124,7 +124,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
*/
private onSearchFieldChanged = async (searchText: string, currentSelected: IPersonaProps[]): Promise<IPersonaProps[]> => {
if (searchText.length > 2) {
const results = await this.peopleSearchService.searchPeople(searchText, this.suggestionsLimit, this.props.principalTypes, this.props.webAbsoluteUrl, this.groupId, this.props.ensureUser);
const results = await this.peopleSearchService.searchPeople(searchText, this.suggestionsLimit, this.props.principalTypes, this.props.webAbsoluteUrl, this.groupId, this.props.ensureUser, this.props.allowUnvalidated);
// Remove duplicates
const { selectedPersons, mostRecentlyUsedPersons } = this.state;
const filteredPersons = this.removeDuplicates(results, selectedPersons);
Expand Down
26 changes: 15 additions & 11 deletions src/services/PeopleSearchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ export default class SPPeopleSearchService {
if (Environment.type === EnvironmentType.Local) {
return 1;
} else {
const groups = await this.searchTenant(siteUrl, groupName, 1, [PrincipalType.SharePointGroup], false, 0);
const groups = await this.searchTenant(siteUrl, groupName, 1, [PrincipalType.SharePointGroup], false, false,0);
return (groups && groups.length > 0) ? parseInt(groups[0].id) : null;
}
}

/**
* Search person by its email or login name
*/
public async searchPersonByEmailOrLogin(email: string, principalTypes: PrincipalType[], siteUrl: string = null, groupId: number | string | (string|number)[] = null, ensureUser: boolean = false): Promise<IPeoplePickerUserItem> {
public async searchPersonByEmailOrLogin(email: string, principalTypes: PrincipalType[], siteUrl: string = null, groupId: number | string | (string|number)[] = null, ensureUser: boolean = false, allowUnvalidated: 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 @@ -79,7 +79,7 @@ export default class SPPeopleSearchService {
if (Array.isArray(groupId)) {
let userResults: IPeoplePickerUserItem[] = [];
for (const id of groupId) {
let tmpResults = await this.searchTenant(siteUrl, email, 1, principalTypes, ensureUser, id);
let tmpResults = await this.searchTenant(siteUrl, email, 1, principalTypes, ensureUser, allowUnvalidated, id);
userResults = userResults.concat(tmpResults);
}

Expand All @@ -88,7 +88,7 @@ export default class SPPeopleSearchService {
let filteredUserResults = userResults.filter(({loginName}, index) => !logins.includes(loginName, index + 1));
return (filteredUserResults && filteredUserResults.length > 0) ? filteredUserResults[0] : null;
} else {
const userResults = await this.searchTenant(siteUrl, email, 1, principalTypes, ensureUser, groupId);
const userResults = await this.searchTenant(siteUrl, email, 1, principalTypes, ensureUser, allowUnvalidated, groupId);
return (userResults && userResults.length > 0) ? userResults[0] : null;
}
}
Expand All @@ -97,7 +97,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 | string | (string|number)[] = null, ensureUser: boolean = false): Promise<IPeoplePickerUserItem[]> {
public async searchPeople(query: string, maximumSuggestions: number, principalTypes: PrincipalType[], siteUrl: string = null, groupId: number | string | (string|number)[] = null, ensureUser: boolean = false, allowUnvalidated: 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 All @@ -106,7 +106,7 @@ export default class SPPeopleSearchService {
if (Array.isArray(groupId)) {
let userResults: IPeoplePickerUserItem[] = [];
for (const id of groupId) {
let tmpResults = await this.searchTenant(siteUrl, query, maximumSuggestions, principalTypes, ensureUser, id);
let tmpResults = await this.searchTenant(siteUrl, query, maximumSuggestions, principalTypes, ensureUser, allowUnvalidated, id);
userResults = userResults.concat(tmpResults);
}

Expand All @@ -115,7 +115,7 @@ export default class SPPeopleSearchService {
let filteredUserResults = userResults.filter(({loginName}, index) => !logins.includes(loginName, index + 1));
return filteredUserResults;
} else {
return await this.searchTenant(siteUrl, query, maximumSuggestions, principalTypes, ensureUser, groupId);
return await this.searchTenant(siteUrl, query, maximumSuggestions, principalTypes, ensureUser, allowUnvalidated, groupId);
}
}
}
Expand Down Expand Up @@ -197,7 +197,7 @@ export default class SPPeopleSearchService {
/**
* Tenant search
*/
private async searchTenant(siteUrl: string, query: string, maximumSuggestions: number, principalTypes: PrincipalType[], ensureUser: boolean, groupId: number | string): Promise<IPeoplePickerUserItem[]> {
private async searchTenant(siteUrl: string, query: string, maximumSuggestions: number, principalTypes: PrincipalType[], ensureUser: boolean, allowUnvalidated: 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 Down Expand Up @@ -281,13 +281,16 @@ export default class SPPeopleSearchService {
}

// Filter out "UNVALIDATED_EMAIL_ADDRESS"
values = values.filter(v => !(v.EntityData && v.EntityData.PrincipalType && v.EntityData.PrincipalType === "UNVALIDATED_EMAIL_ADDRESS"));
if(!allowUnvalidated) {
values = values.filter(v => !(v.EntityData && v.EntityData.PrincipalType && v.EntityData.PrincipalType === "UNVALIDATED_EMAIL_ADDRESS"));
}


// Check if local user IDs need to be retrieved
if (ensureUser) {
for (const value of values) {
// Only ensure the user if it is not a SharePoint group
if (!value.EntityData || (value.EntityData && typeof value.EntityData.SPGroupID === "undefined")) {
if (!value.EntityData || (value.EntityData && typeof value.EntityData.SPGroupID === "undefined" && value.EntityData.PrincipalType !== "UNVALIDATED_EMAIL_ADDRESS")) {
const id = await this.ensureUser(value.Key);
value.LoginName = value.Key;
value.Key = id;
Expand Down Expand Up @@ -335,7 +338,8 @@ export default class SPPeopleSearchService {
loginName: element.EntityData.AccountName,
imageInitials: this.getFullNameInitials(element.DisplayText),
text: element.DisplayText,
secondaryText: element.EntityData.AccountName
secondaryText: element.EntityData.AccountName,
userUnvalidated: element.EntityData.PrincipalType === "UNVALIDATED_EMAIL_ADDRESS"
} as IPeoplePickerUserItem;
}
});
Expand Down