feat: Update People Picker functionality and remove validation logic; bump version to 1.0.4

This commit is contained in:
Torsten Brendgen
2026-08-24 14:34:13 +02:00
parent a686f8c055
commit 4b7926d24a
11 changed files with 5 additions and 48 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ Es benötigt keine Microsoft-Graph-Berechtigungen und keine zusätzliche SharePo
## Startperson ## Startperson
Im Bearbeitungsmodus der modernen Seite wird direkt oberhalb des Organigramms ein People Picker angezeigt. Dort kann die Startperson per Name oder E-Mail-Adresse gesucht werden. Das WebPart akzeptiert nur einen von SharePoint aufgelösten Benutzer und prüft bei der Auswahl zusätzlich, ob dessen Profil über den User Profile Service gelesen werden kann. Gespeichert werden der kanonische Login und der Accountname als Fallback. Im Bearbeitungsmodus der modernen Seite wird direkt oberhalb des Organigramms ein People Picker angezeigt. Dort kann die Startperson per Name oder E-Mail-Adresse gesucht werden. Das WebPart akzeptiert nur einen von SharePoint eindeutig aufgelösten Benutzer. Gespeichert werden der kanonische Login und der Accountname als Fallback.
Wird die Auswahl entfernt, verwendet das WebPart wieder den angemeldeten Benutzer. Im Anzeigemodus ist der People Picker nicht sichtbar. Wird die Auswahl entfernt, verwendet das WebPart wieder den angemeldeten Benutzer. Im Anzeigemodus ist der People Picker nicht sichtbar.
+1 -1
View File
@@ -4,7 +4,7 @@
"name": "organization-chart-webpart", "name": "organization-chart-webpart",
"title": "organization-chart-webpart", "title": "organization-chart-webpart",
"id": "b2f88ea8-f7da-4d0e-a945-6973bf1acd1e", "id": "b2f88ea8-f7da-4d0e-a945-6973bf1acd1e",
"version": "1.0.3.0", "version": "1.0.4.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": true "skipFeatureDeployment": true
}, },
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "organization-chart-webpart", "name": "organization-chart-webpart",
"version": "1.0.3", "version": "1.0.4",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "node": ">=6.9.0 <9.0.0" }, "engines": { "node": ">=6.9.0 <9.0.0" },
@@ -113,12 +113,6 @@ export class OrganizationChartDataService {
); );
} }
public validatePerson(loginName: string, accountName: string, cacheMinutes: number): Promise<boolean> {
const candidates: string[] = uniqueAccounts([loginName, accountName], '', 2);
return this.getFirstAvailableProfile(candidates, cacheMinutes, 0)
.then((): boolean => true, (): boolean => false);
}
private getConfiguredProfile( private getConfiguredProfile(
accountName: string, accountName: string,
cacheMinutes: number, cacheMinutes: number,
@@ -142,10 +142,7 @@ export default class OrganizationChartWebPart extends BaseClientSideWebPart<IOrg
selectedAccountName: normalizeAccount(this.properties.startUserAccountName), selectedAccountName: normalizeAccount(this.properties.startUserAccountName),
selectedDisplayName: selectedDisplayName, selectedDisplayName: selectedDisplayName,
selectedLoginName: normalizeAccount(this.properties.startUser), selectedLoginName: normalizeAccount(this.properties.startUser),
suggestionsHeaderText: strings.PeoplePickerSuggestionsLabel, suggestionsHeaderText: strings.PeoplePickerSuggestionsLabel
validationErrorText: strings.PeoplePickerValidationError,
validatePerson: (loginName: string, accountName: string): Promise<boolean> =>
this.service.validatePerson(loginName, accountName, normalizeCacheMinutes(this.properties.cacheMinutes))
}); });
ReactDom.render(picker, this.pickerHost); ReactDom.render(picker, this.pickerHost);
} }
@@ -7,7 +7,6 @@
.pickerPanel { background: "[theme:neutralLighterAlt, default:#faf9f8]"; border: 1px solid "[theme:neutralLight, default:#edebe9]"; margin-bottom: 16px; padding: 12px; } .pickerPanel { background: "[theme:neutralLighterAlt, default:#faf9f8]"; border: 1px solid "[theme:neutralLight, default:#edebe9]"; margin-bottom: 16px; padding: 12px; }
.pickerLabel { color: "[theme:bodyText, default:#323130]"; display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; } .pickerLabel { color: "[theme:bodyText, default:#323130]"; display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.pickerDescription { color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 12px; line-height: 17px; margin-top: 6px; } .pickerDescription { color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 12px; line-height: 17px; margin-top: 6px; }
.pickerError { color: "[theme:errorText, default:#a80000]"; font-size: 12px; line-height: 17px; margin-top: 6px; }
.header { align-items: baseline; display: flex; justify-content: space-between; margin-bottom: 12px; } .header { align-items: baseline; display: flex; justify-content: space-between; margin-bottom: 12px; }
.heading { font-size: 24px; font-weight: 300; line-height: 32px; margin: 0; } .heading { font-size: 24px; font-weight: 300; line-height: 32px; margin: 0; }
.count { color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 12px; } .count { color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 12px; }
@@ -26,24 +26,16 @@ export interface IOrganizationChartPeoplePickerProps {
selectedAccountName: string; selectedAccountName: string;
selectedLoginName: string; selectedLoginName: string;
suggestionsHeaderText: string; suggestionsHeaderText: string;
validationErrorText: string;
validatePerson: (loginName: string, accountName: string) => Promise<boolean>;
}
export interface IOrganizationChartPeoplePickerState {
validationError: string;
} }
export class OrganizationChartPeoplePicker export class OrganizationChartPeoplePicker
extends React.Component<IOrganizationChartPeoplePickerProps, IOrganizationChartPeoplePickerState> { extends React.Component<IOrganizationChartPeoplePickerProps, {}> {
public constructor(props: IOrganizationChartPeoplePickerProps) { public constructor(props: IOrganizationChartPeoplePickerProps) {
super(props); super(props);
this.resolveSuggestions = this.resolveSuggestions.bind(this); this.resolveSuggestions = this.resolveSuggestions.bind(this);
this.onSelectionChanged = this.onSelectionChanged.bind(this); this.onSelectionChanged = this.onSelectionChanged.bind(this);
this.onItemSelected = this.onItemSelected.bind(this);
this.getTextFromItem = this.getTextFromItem.bind(this); this.getTextFromItem = this.getTextFromItem.bind(this);
this.state = { validationError: '' };
} }
public render(): React.ReactElement<HTMLDivElement> { public render(): React.ReactElement<HTMLDivElement> {
@@ -61,7 +53,6 @@ export class OrganizationChartPeoplePicker
selectedItems={selectedItems} selectedItems={selectedItems}
onResolveSuggestions={this.resolveSuggestions} onResolveSuggestions={this.resolveSuggestions}
onChange={this.onSelectionChanged} onChange={this.onSelectionChanged}
onItemSelected={this.onItemSelected}
getTextFromItem={this.getTextFromItem} getTextFromItem={this.getTextFromItem}
onValidateInput={(): ValidationState => ValidationState.invalid} onValidateInput={(): ValidationState => ValidationState.invalid}
itemLimit={1} itemLimit={1}
@@ -76,8 +67,6 @@ export class OrganizationChartPeoplePicker
}} }}
removeButtonAriaLabel={this.props.removeButtonAriaLabel} removeButtonAriaLabel={this.props.removeButtonAriaLabel}
/> />
{this.state.validationError ?
<div className={styles.pickerError} role='alert'>{this.state.validationError}</div> : null}
<div className={styles.pickerDescription}>{this.props.description}</div> <div className={styles.pickerDescription}>{this.props.description}</div>
</div> </div>
); );
@@ -98,29 +87,12 @@ export class OrganizationChartPeoplePicker
} }
private onSelectionChanged(items?: IOrganizationChartPickerItem[]): void { private onSelectionChanged(items?: IOrganizationChartPickerItem[]): void {
this.setState({ validationError: '' });
this.props.onChange( this.props.onChange(
items && items.length ? items[0].loginName : '', items && items.length ? items[0].loginName : '',
items && items.length ? items[0].accountName : '' items && items.length ? items[0].accountName : ''
); );
} }
private onItemSelected(item?: IOrganizationChartPickerItem): Promise<IOrganizationChartPickerItem> {
if (!item) { return Promise.resolve(undefined as IOrganizationChartPickerItem); }
this.setState({ validationError: '' });
return this.props.validatePerson(item.loginName, item.accountName).then(
(valid: boolean): IOrganizationChartPickerItem => {
if (valid) { return item; }
this.setState({ validationError: this.props.validationErrorText });
return undefined as IOrganizationChartPickerItem;
},
(): IOrganizationChartPickerItem => {
this.setState({ validationError: this.props.validationErrorText });
return undefined as IOrganizationChartPickerItem;
}
);
}
private getTextFromItem(item: IOrganizationChartPickerItem): string { private getTextFromItem(item: IOrganizationChartPickerItem): string {
return item.primaryText || item.loginName; return item.primaryText || item.loginName;
} }
@@ -11,7 +11,6 @@ define([], function() {
PeoplePickerNoResultsText: 'Keine passenden Personen gefunden.', PeoplePickerNoResultsText: 'Keine passenden Personen gefunden.',
PeoplePickerRemoveLabel: 'Startperson entfernen', PeoplePickerRemoveLabel: 'Startperson entfernen',
PeoplePickerSuggestionsLabel: 'Vorgeschlagene Personen', PeoplePickerSuggestionsLabel: 'Vorgeschlagene Personen',
PeoplePickerValidationError: 'Für diese Person ist kein lesbares Profil im SharePoint User Profile Service vorhanden.',
MaxDepthFieldLabel: 'Maximale Ebenen nach unten', MaxDepthFieldLabel: 'Maximale Ebenen nach unten',
MaxPeopleFieldLabel: 'Maximale Personenzahl', MaxPeopleFieldLabel: 'Maximale Personenzahl',
CacheMinutesFieldLabel: 'Profilcache (Minuten, 0 = aus)', CacheMinutesFieldLabel: 'Profilcache (Minuten, 0 = aus)',
@@ -11,7 +11,6 @@ define([], function() {
PeoplePickerNoResultsText: 'No matching people found.', PeoplePickerNoResultsText: 'No matching people found.',
PeoplePickerRemoveLabel: 'Remove starting person', PeoplePickerRemoveLabel: 'Remove starting person',
PeoplePickerSuggestionsLabel: 'Suggested people', PeoplePickerSuggestionsLabel: 'Suggested people',
PeoplePickerValidationError: 'This person has no readable profile in the SharePoint User Profile Service.',
MaxDepthFieldLabel: 'Maximum levels below', MaxDepthFieldLabel: 'Maximum levels below',
MaxPeopleFieldLabel: 'Maximum number of people', MaxPeopleFieldLabel: 'Maximum number of people',
CacheMinutesFieldLabel: 'Profile cache (minutes, 0 = off)', CacheMinutesFieldLabel: 'Profile cache (minutes, 0 = off)',
-1
View File
@@ -10,7 +10,6 @@ declare interface IOrganizationChartWebPartStrings {
PeoplePickerNoResultsText: string; PeoplePickerNoResultsText: string;
PeoplePickerRemoveLabel: string; PeoplePickerRemoveLabel: string;
PeoplePickerSuggestionsLabel: string; PeoplePickerSuggestionsLabel: string;
PeoplePickerValidationError: string;
MaxDepthFieldLabel: string; MaxDepthFieldLabel: string;
MaxPeopleFieldLabel: string; MaxPeopleFieldLabel: string;
CacheMinutesFieldLabel: string; CacheMinutesFieldLabel: string;
-2
View File
@@ -37,8 +37,6 @@ assert(webPart.indexOf('OrganizationChartPeoplePicker') >= 0 &&
'The verified starting-person picker must be rendered on the canvas instead of the property pane.'); 'The verified starting-person picker must be rendered on the canvas instead of the property pane.');
assert(picker.indexOf('NormalPeoplePicker') >= 0 && picker.indexOf('ValidationState.invalid') >= 0, assert(picker.indexOf('NormalPeoplePicker') >= 0 && picker.indexOf('ValidationState.invalid') >= 0,
'The on-canvas people picker must reject unverified free-text values.'); 'The on-canvas people picker must reject unverified free-text values.');
assert(picker.indexOf('validatePerson') >= 0 && service.indexOf('public validatePerson') >= 0,
'Selected people must be validated against the User Profile Service.');
assert(webPart.indexOf("PropertyPaneSlider('maxDepth'") >= 0 && webPart.indexOf("PropertyPaneSlider('maxPeople'") >= 0, assert(webPart.indexOf("PropertyPaneSlider('maxDepth'") >= 0 && webPart.indexOf("PropertyPaneSlider('maxPeople'") >= 0,
'Tree safety limits are missing.'); 'Tree safety limits are missing.');
assert(component.indexOf("role='tree'") >= 0 && component.indexOf("role='treeitem'") >= 0, assert(component.indexOf("role='tree'") >= 0 && component.indexOf("role='treeitem'") >= 0,