From 634560e48a24ff114320519407f0f09e08b558bc Mon Sep 17 00:00:00 2001 From: IRRDC Date: Thu, 8 Feb 2024 11:09:30 +0100 Subject: [PATCH 01/26] Bugfix for issue #1758 Added missing references to webURL to service calls. --- src/controls/dynamicForm/DynamicForm.tsx | 12 +++++++----- src/services/SPService.ts | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index eb15b71e5..040185b36 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -915,11 +915,11 @@ export class DynamicForm extends React.Component< try { // Fetch form rendering information from SharePoint - const listInfo = await this._spService.getListFormRenderInfo(listId); + const listInfo = await this._spService.getListFormRenderInfo(listId, this.webURL); // Fetch additional information about fields from SharePoint // (Number fields for min and max values, and fields with validation) - const additionalInfo = await this._spService.getAdditionalListFormFieldInfo(listId); + const additionalInfo = await this._spService.getAdditionalListFormFieldInfo(listId, this.webURL); const numberFields = additionalInfo.filter((f) => f.TypeAsString === "Number" || f.TypeAsString === "Currency"); // Build a dictionary of validation formulas and messages @@ -1166,7 +1166,8 @@ export class DynamicForm extends React.Component< const response = await this._spService.getSingleManagedMetadataLabel( listId, listItemId, - field.InternalName + field.InternalName, + this.webURL ); if (response) { selectedTags.push({ @@ -1229,7 +1230,7 @@ export class DynamicForm extends React.Component< } dateFormat = field.DateFormat || "DateOnly"; - defaultDayOfWeek = (await this._spService.getRegionalWebSettings()).FirstDayOfWeek; + defaultDayOfWeek = (await this._spService.getRegionalWebSettings(this.webURL)).FirstDayOfWeek; } // Setup Thumbnail, Location and Boolean fields @@ -1318,7 +1319,8 @@ export class DynamicForm extends React.Component< listItemId, file.fileName, buffer, - undefined + undefined, + this.webURL ); } }; diff --git a/src/services/SPService.ts b/src/services/SPService.ts index 7a276ab73..3edcf6f56 100644 --- a/src/services/SPService.ts +++ b/src/services/SPService.ts @@ -654,9 +654,9 @@ export default class SPService implements ISPService { } } - public async getSingleManagedMetadataLabel(listId: string, listItemId: number, fieldName: string): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any + public async getSingleManagedMetadataLabel(listId: string, listItemId: number, fieldName: string, webUrl?: string): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any try { - const webAbsoluteUrl = this._context.pageContext.web.absoluteUrl; + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; const apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/RenderListDataAsStream?@listId=guid'${encodeURIComponent(listId)}'`; const data = await this._context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1, { body: JSON.stringify({ From e0a64eeff5a8e0c820fc586a58cca61b54011503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Maillot?= Date: Sun, 7 Jan 2024 02:12:53 +0100 Subject: [PATCH 02/26] Upgrade version references in config files to publish v3.16.1 --- src/common/telemetry/version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/telemetry/version.ts b/src/common/telemetry/version.ts index e4ed6e8bb..9af12e599 100644 --- a/src/common/telemetry/version.ts +++ b/src/common/telemetry/version.ts @@ -1 +1 @@ -export const version: string = "3.18.0"; \ No newline at end of file +export const version: string = "3.18.0"; From b97fc24b141d74786611f01370b6c620ba17a5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Maillot?= Date: Tue, 13 Feb 2024 22:44:18 +0100 Subject: [PATCH 03/26] Update the PeoplePicker control: - optimize context prop by requesting only necessary properties from BaseComponentContext - update documentation accordingly - move the state properties into the core component --- .../docs/controls/PeoplePicker.md | 33 +++++++++++++------ .../dynamicForm/dynamicField/DynamicField.tsx | 12 +++++-- .../collectionDataItem/CollectionDataItem.tsx | 9 +++-- src/controls/peoplepicker/IPeoplePicker.ts | 16 ++------- .../peoplepicker/IPeoplePickerContext.ts | 19 +++++++++++ .../peoplepicker/PeoplePickerComponent.tsx | 13 +++++++- src/controls/peoplepicker/index.ts | 1 + src/services/PeopleSearchService.ts | 21 +++++++----- .../controlsTest/components/ControlsTest.tsx | 28 ++++++++++------ 9 files changed, 103 insertions(+), 49 deletions(-) create mode 100644 src/controls/peoplepicker/IPeoplePickerContext.ts diff --git a/docs/documentation/docs/controls/PeoplePicker.md b/docs/documentation/docs/controls/PeoplePicker.md index ab8f623ba..372386807 100644 --- a/docs/documentation/docs/controls/PeoplePicker.md +++ b/docs/documentation/docs/controls/PeoplePicker.md @@ -17,21 +17,26 @@ This control renders a People picker field which can be used to select one or mo ![Selected people](../assets/Peoplepicker-multiplechoices.png) - ## How to use this control in your solutions - Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency. - Import the following modules to your component: ```typescript -import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker"; +import { IPeoplePickerContext, PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker"; ``` - Use the `PeoplePicker` control in your code as follows: ```typescript +const peoplePickerContext: IPeoplePickerContext = { + absoluteUrl: this.props.context.pageContext.web.absoluteUrl, + msGraphClientFactory: this.props.context.msGraphClientFactory, + spHttpClient: this.props.context.spHttpClient +}; + | no | Styles to apply on control | +| placeholder | string | no | Short text hint to display in empty picker | | +| styles | Partial | no | Styles to apply on control | | | searchTextLimit | number | no | Specifies the minimum character count needed to begin retrieving search results. | 2 | Enum `PrincipalType` @@ -97,13 +102,21 @@ The `PrincipalType` enum can be used to specify the types of information you wan | SecurityGroup | 4 | | SharePointGroup | 8 | +Interface `IPeoplePickerContext` -## MSGraph Permissions required +Provides mandatory properties to search users on the tenant -This control requires the following scopes if groupId is of type String: +| Value | Type | Description | +| ---- | ---- | ---- | +| absoluteUrl | string | Current `SPWeb` absolute URL. | +| msGraphClientFactory | MSGraphClientFactory | Instance of MSGraphClientFactory used for querying Microsoft Graph REST API. | +| spHttpClient | SPHttpClient | Instance of SPHttpClient used for querying SharePoint REST API. | -at least : GroupMember.Read.All, Directory.Read.All +## MSGraph Permissions required +This control requires at least one the following scopes if `groupId` is of type `string`: -![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/PeoplePicker) +- *GroupMember.Read.All* + *User.ReadBasic.All* +- *Directory.Read.All* +![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/PeoplePicker) diff --git a/src/controls/dynamicForm/dynamicField/DynamicField.tsx b/src/controls/dynamicForm/dynamicField/DynamicField.tsx index c8b009d96..e1f8607c0 100644 --- a/src/controls/dynamicForm/dynamicField/DynamicField.tsx +++ b/src/controls/dynamicForm/dynamicField/DynamicField.tsx @@ -16,7 +16,7 @@ import { DateTimePicker } from '../../dateTimePicker/DateTimePicker'; import { FilePicker, IFilePickerResult } from '../../filePicker'; import { ListItemPicker } from '../../listItemPicker'; import { LocationPicker } from '../../locationPicker'; -import { PeoplePicker, PrincipalType } from '../../peoplepicker'; +import { IPeoplePickerContext, PeoplePicker, PrincipalType } from '../../peoplepicker'; import { RichText } from '../../richText'; import { IPickerTerms, TaxonomyPicker } from '../../taxonomyPicker'; import styles from '../DynamicForm.module.scss'; @@ -93,6 +93,12 @@ export class DynamicField extends React.Component{label}; @@ -377,7 +383,7 @@ export class DynamicField extends React.Component IPersonaProps[]; } -export interface IPeoplePickerState { - mostRecentlyUsedPersons?: IPersonaProps[]; - errorMessage?: string; - internalErrorMessage?: string; - resolveDelay?: number; - - selectedPersons?: IPersonaProps[]; - peoplePersonaMenu?: IPersonaProps[]; - delayResults?: boolean; -} - export interface IPeoplePickerUserItem { /** * LoginName or Id of the principal in the site. diff --git a/src/controls/peoplepicker/IPeoplePickerContext.ts b/src/controls/peoplepicker/IPeoplePickerContext.ts new file mode 100644 index 000000000..7519e487f --- /dev/null +++ b/src/controls/peoplepicker/IPeoplePickerContext.ts @@ -0,0 +1,19 @@ +import { MSGraphClientFactory, SPHttpClient } from "@microsoft/sp-http"; + +/** + * Context for the PeoplePicker control + */ +export interface IPeoplePickerContext { + /** + * Current `SPWeb` absolute URL. + */ + absoluteUrl: string; + /** + * Instance of MSGraphClientFactory used for querying Microsoft Graph REST API. + */ + msGraphClientFactory: MSGraphClientFactory; + /** + * Instance of SPHttpClient used for querying SharePoint REST API. + */ + spHttpClient: SPHttpClient; +} \ No newline at end of file diff --git a/src/controls/peoplepicker/PeoplePickerComponent.tsx b/src/controls/peoplepicker/PeoplePickerComponent.tsx index 2fa52a8dd..511d86d74 100644 --- a/src/controls/peoplepicker/PeoplePickerComponent.tsx +++ b/src/controls/peoplepicker/PeoplePickerComponent.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import * as telemetry from '../../common/telemetry'; import styles from './PeoplePickerComponent.module.scss'; import SPPeopleSearchService from "../../services/PeopleSearchService"; -import { IPeoplePickerProps, IPeoplePickerState } from './IPeoplePicker'; +import { IPeoplePickerProps } from './IPeoplePicker'; import { TooltipHost } from '@fluentui/react/lib/Tooltip'; import { DirectionalHint } from '@fluentui/react/lib/Callout'; import { NormalPeoplePicker } from '@fluentui/react/lib/components/pickers/PeoplePicker/PeoplePicker'; @@ -14,6 +14,17 @@ import FieldErrorMessage from '../errorMessage/ErrorMessage'; import isEqual from 'lodash/isEqual'; import uniqBy from 'lodash/uniqBy'; +interface IPeoplePickerState { + mostRecentlyUsedPersons?: IPersonaProps[]; + errorMessage?: string; + internalErrorMessage?: string; + resolveDelay?: number; + + selectedPersons?: IPersonaProps[]; + peoplePersonaMenu?: IPersonaProps[]; + delayResults?: boolean; +} + /** * PeoplePicker component */ diff --git a/src/controls/peoplepicker/index.ts b/src/controls/peoplepicker/index.ts index 73502887f..b4602fd13 100644 --- a/src/controls/peoplepicker/index.ts +++ b/src/controls/peoplepicker/index.ts @@ -1,3 +1,4 @@ export * from './IPeoplePicker'; +export * from './IPeoplePickerContext'; export * from './PeoplePickerComponent'; export * from './PrincipalType'; diff --git a/src/services/PeopleSearchService.ts b/src/services/PeopleSearchService.ts index 85c16a80c..c7c2820ed 100644 --- a/src/services/PeopleSearchService.ts +++ b/src/services/PeopleSearchService.ts @@ -1,4 +1,3 @@ -import { BaseComponentContext } from '@microsoft/sp-component-base'; import { ISPHttpClientOptions, SPHttpClient } from '@microsoft/sp-http'; import { findIndex } from "@microsoft/sp-lodash-subset"; import { sp } from '@pnp/sp'; @@ -7,7 +6,7 @@ import "@pnp/sp/sputilities"; import "@pnp/sp/webs"; import { Web } from "@pnp/sp/webs"; import { IUserInfo } from "../controls/peoplepicker/IUsers"; -import { IPeoplePickerUserItem, PrincipalType } from "../PeoplePicker"; +import { IPeoplePickerContext, IPeoplePickerUserItem, PrincipalType } from "../PeoplePicker"; /** * Service implementation to search people in SharePoint @@ -19,12 +18,16 @@ export default class SPPeopleSearchService { /** * Service constructor */ - constructor(private context: BaseComponentContext) { + constructor(private context: IPeoplePickerContext) { this.cachedPersonas = {}; this.cachedLocalUsers = {}; - this.cachedLocalUsers[this.context.pageContext.web.absoluteUrl] = []; + this.cachedLocalUsers[context.absoluteUrl] = []; // Setup PnPjs - sp.setup({ pageContext: this.context.pageContext }); + sp.setup({ pageContext: { + web: { + absoluteUrl: context.absoluteUrl + } + }}); } /** @@ -33,7 +36,7 @@ export default class SPPeopleSearchService { * @param value */ public generateUserPhotoLink(value: string): string { - return `${this.context.pageContext.web.absoluteUrl}/_layouts/15/userphoto.aspx?accountname=${encodeURIComponent(value)}&size=M`; + return `${this.context.absoluteUrl}/_layouts/15/userphoto.aspx?accountname=${encodeURIComponent(value)}&size=M`; } /** @@ -109,7 +112,7 @@ export default class SPPeopleSearchService { private async searchTenant(siteUrl: string, query: string, maximumSuggestions: number, principalTypes: PrincipalType[], ensureUser: boolean, allowUnvalidated: boolean, groupId: number | string): Promise { 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`; + const userRequestUrl: string = `${siteUrl || this.context.absoluteUrl}/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser`; // eslint-disable-next-line @typescript-eslint/no-explicit-any const searchBody: any = { queryParams: { @@ -143,7 +146,7 @@ export default class SPPeopleSearchService { // Get user loginName from user email const _users = []; - const batch = Web(this.context.pageContext.web.absoluteUrl).createBatch(); + const batch = Web(this.context.absoluteUrl).createBatch(); for (const value of graphUserResponse.value) { sp.web.inBatch(batch).ensureUser(value.userPrincipalName).then(u => _users.push(u.data)).catch(() => { // no-op @@ -203,7 +206,7 @@ export default class SPPeopleSearchService { 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" && value.EntityData.PrincipalType !== "UNVALIDATED_EMAIL_ADDRESS")) { - const id = await this.ensureUser(value.Key, siteUrl || this.context.pageContext.web.absoluteUrl); + const id = await this.ensureUser(value.Key, siteUrl || this.context.absoluteUrl); value.LoginName = value.Key; value.Key = id; } diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 603c9c5bd..486694b7d 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -142,6 +142,7 @@ import { MapType } from "../../../Map"; import { + IPeoplePickerContext, PeoplePicker, PrincipalType } from "../../../controls/peoplepicker"; @@ -481,6 +482,7 @@ export default class ControlsTest extends React.Component