Skip to content

Commit 36ce4ca

Browse files
authored
Adding support for users and groups in people picker (#405)
* Adding support for users and groups in people picker * added filter to more than displayName for group id people searching * cleaned up IDynamicPerson * clean up * simplified enum statement * fixed people property
1 parent e47a557 commit 36ce4ca

File tree

8 files changed

+356
-48
lines changed

8 files changed

+356
-48
lines changed

src/components/mgt-people-picker/mgt-people-picker.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ mgt-people-picker .people-person-job-title {
245245
order: 3;
246246
font-weight: normal;
247247
font-size: 12px;
248-
text-transform: uppercase;
248+
&.uppercase {
249+
text-transform: uppercase;
250+
}
249251
}
250252
:host .people-person-text-area,
251253
mgt-people-picker .people-person-text-area {

src/components/mgt-people-picker/mgt-people-picker.ts

Lines changed: 166 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
* -------------------------------------------------------------------------------------------
66
*/
77

8-
import { customElement, html, property, query, TemplateResult } from 'lit-element';
8+
import { User } from '@microsoft/microsoft-graph-types';
9+
import { customElement, html, internalProperty, property, TemplateResult } from 'lit-element';
910
import { classMap } from 'lit-html/directives/class-map';
1011
import { repeat } from 'lit-html/directives/repeat';
11-
import { findPerson, getPeopleFromGroup } from '../../graph/graph.people';
12-
import { getUser } from '../../graph/graph.user';
12+
import { findGroups, GroupType } from '../../graph/graph.groups';
13+
import { findPeople, getPeopleFromGroup, PersonType } from '../../graph/graph.people';
14+
import { findUsers, getUser } from '../../graph/graph.user';
1315
import { IDynamicPerson } from '../../graph/types';
1416
import { Providers } from '../../Providers';
1517
import { ProviderState } from '../../providers/IProvider';
@@ -19,6 +21,8 @@ import { MgtFlyout } from '../sub-components/mgt-flyout/mgt-flyout';
1921
import { MgtTemplatedComponent } from '../templatedComponent';
2022
import { styles } from './mgt-people-picker-css';
2123

24+
export { GroupType } from '../../graph/graph.groups';
25+
export { PersonType } from '../../graph/graph.people';
2226
/**
2327
* An interface used to mark an object as 'focused',
2428
* so it can be rendered differently.
@@ -97,6 +101,78 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
97101
this.requestStateUpdate(true);
98102
}
99103

104+
/**
105+
* value determining if search is filtered to a group.
106+
* @type {string}
107+
*/
108+
@property({
109+
attribute: 'type',
110+
converter: (value, type) => {
111+
if (!value || value.length === 0) {
112+
return PersonType.Any;
113+
}
114+
115+
if (typeof PersonType[value] === 'undefined') {
116+
return PersonType.Any;
117+
} else {
118+
return PersonType[value];
119+
}
120+
}
121+
})
122+
public get type(): PersonType {
123+
return this._type;
124+
}
125+
public set type(value) {
126+
if (this._type === value) {
127+
return;
128+
}
129+
130+
this._type = value;
131+
this.requestStateUpdate(true);
132+
}
133+
134+
/**
135+
* type of group to search for - requires personType to be
136+
* set to "Group" or "All"
137+
* @type {string}
138+
*/
139+
@property({
140+
attribute: 'group-type',
141+
converter: (value, type) => {
142+
if (!value || value.length === 0) {
143+
return GroupType.Any;
144+
}
145+
146+
const values = value.split(',');
147+
const groupTypes = [];
148+
149+
for (let v of values) {
150+
v = v.trim();
151+
if (typeof GroupType[v] !== 'undefined') {
152+
groupTypes.push(GroupType[v]);
153+
}
154+
}
155+
156+
if (groupTypes.length === 0) {
157+
return GroupType.Any;
158+
}
159+
160+
// tslint:disable-next-line:no-bitwise
161+
return groupTypes.reduce((a, c) => a | c);
162+
}
163+
})
164+
public get groupType(): GroupType {
165+
return this._groupType;
166+
}
167+
public set groupType(value) {
168+
if (this._groupType === value) {
169+
return;
170+
}
171+
172+
this._groupType = value;
173+
this.requestStateUpdate(true);
174+
}
175+
100176
/**
101177
* User input in search.
102178
*
@@ -121,12 +197,17 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
121197
@property({ attribute: false }) private _showLoading: boolean;
122198

123199
private _groupId: string;
200+
private _type: PersonType = PersonType.Person;
201+
private _groupType: GroupType = GroupType.Any;
202+
124203
// tracking of user arrow key input for selection
125204
private _arrowSelectionCount: number = 0;
126205
// List of people requested if group property is provided
127206
private _groupPeople: IDynamicPerson[];
128207
private _debouncedSearch: { (): void; (): void };
129208

209+
@internalProperty() private _foundPeople: IDynamicPerson[];
210+
130211
constructor() {
131212
super();
132213

@@ -201,7 +282,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
201282
* @memberof MgtPeoplePicker
202283
*/
203284
public render(): TemplateResult {
204-
const defaultTemplate = this.renderTemplate('default', { people: this.people });
285+
const defaultTemplate = this.renderTemplate('default', { people: this._foundPeople });
205286
if (defaultTemplate) {
206287
return defaultTemplate;
207288
}
@@ -231,7 +312,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
231312
protected requestStateUpdate(force?: boolean) {
232313
if (force) {
233314
this._groupPeople = null;
234-
this.people = null;
315+
this._foundPeople = null;
235316
this.selectedPeople = [];
236317
}
237318

@@ -329,11 +410,13 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
329410
return this.renderLoading();
330411
}
331412

332-
if (!this.people || this.people.length === 0 || this.showMax === 0) {
413+
let people = this._foundPeople;
414+
415+
if (!people || people.length === 0 || this.showMax === 0) {
333416
return this.renderNoData();
334417
}
335418

336-
const people = this.people.slice(0, this.showMax);
419+
people = people.slice(0, this.showMax);
337420
(people[0] as IFocusable).isFocused = true;
338421

339422
return this.renderSearchResults(people);
@@ -389,7 +472,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
389472
* @memberof MgtPeoplePicker
390473
*/
391474
protected renderSearchResults(people?: IDynamicPerson[]) {
392-
people = people || this.people;
475+
people = people || this._foundPeople;
393476

394477
return html`
395478
<div class="people-list">
@@ -421,13 +504,21 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
421504
* @memberof MgtPeoplePicker
422505
*/
423506
protected renderPersonResult(person: IDynamicPerson): TemplateResult {
507+
const user = person as User;
508+
const subTitle = user.jobTitle || user.mail;
509+
510+
const classes = {
511+
'people-person-job-title': true,
512+
uppercase: !!user.jobTitle
513+
};
514+
424515
return (
425516
this.renderTemplate('person', { person }, person.id) ||
426517
html`
427518
<mgt-person .personDetails=${person} .personImage=${'@'}></mgt-person>
428519
<div class="people-person-text-area" id="${person.displayName}">
429520
${this.renderHighlightText(person)}
430-
<span class="people-person-job-title">${person.jobTitle}</span>
521+
<span class="${classMap(classes)}">${subTitle}</span>
431522
</div>
432523
`
433524
);
@@ -458,37 +549,73 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
458549
* set's `this.groupPeople` to those members.
459550
*/
460551
protected async loadState(): Promise<void> {
552+
let people = this.people;
553+
const input = this.userInput.toLowerCase();
554+
461555
const provider = Providers.globalProvider;
462-
if (!provider || provider.state !== ProviderState.SignedIn) {
463-
return;
464-
}
556+
if (!people && provider && provider.state === ProviderState.SignedIn) {
557+
if (this.groupId) {
558+
if (this._groupPeople === null) {
559+
try {
560+
const graph = provider.graph.forComponent(this);
561+
this._groupPeople = await getPeopleFromGroup(graph, this.groupId);
562+
} catch (_) {
563+
this._groupPeople = [];
564+
}
565+
}
465566

466-
const input = this.userInput.toLowerCase();
467-
let people: IDynamicPerson[];
567+
people = this._groupPeople || [];
568+
} else if (input) {
569+
const graph = provider.graph.forComponent(this);
570+
people = [];
468571

469-
if (this.groupId) {
470-
if (this._groupPeople === null) {
471-
try {
472-
const graph = provider.graph.forComponent(this);
473-
this._groupPeople = await getPeopleFromGroup(graph, this.groupId);
474-
} catch (_) {
475-
this._groupPeople = [];
572+
if (this.type === PersonType.Person || this.type === PersonType.Any) {
573+
try {
574+
people = (await findPeople(graph, input, this.showMax)) || [];
575+
} catch (e) {
576+
// nop
577+
}
578+
579+
if (people.length < this.showMax) {
580+
try {
581+
const users = (await findUsers(graph, input, this.showMax)) || [];
582+
583+
// make sure only unique people
584+
const peopleIds = new Set(people.map(p => p.id));
585+
for (const user of users) {
586+
if (!peopleIds.has(user.id)) {
587+
people.push(user);
588+
}
589+
}
590+
} catch (e) {
591+
// nop
592+
}
593+
}
476594
}
477-
}
478595

479-
people = this._groupPeople || [];
480-
} else if (input) {
481-
const graph = provider.graph.forComponent(this);
482-
people = await findPerson(graph, input);
596+
if ((this.type === PersonType.Group || this.type === PersonType.Any) && people.length < this.showMax) {
597+
try {
598+
const groups = (await findGroups(graph, input, this.showMax, this.groupType)) || [];
599+
people = people.concat(groups);
600+
} catch (e) {
601+
// nop
602+
}
603+
}
604+
}
483605
}
484606

485607
if (people) {
486-
people = people.filter((person: IDynamicPerson) => {
487-
return person.displayName.toLowerCase().indexOf(input) !== -1;
608+
people = people.filter((user: User) => {
609+
return (
610+
user.displayName.toLowerCase().indexOf(input) !== -1 ||
611+
(!!user.givenName && user.givenName.toLowerCase().indexOf(input) !== -1) ||
612+
(!!user.surname && user.surname.toLowerCase().indexOf(input) !== -1) ||
613+
(!!user.mail && user.mail.toLowerCase().indexOf(input) !== -1)
614+
);
488615
});
489616
}
490617

491-
this.people = this.filterPeople(people);
618+
this._foundPeople = this.filterPeople(people);
492619
}
493620

494621
/**
@@ -545,7 +672,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
545672
this.selectedPeople = [...this.selectedPeople, person];
546673
this.fireCustomEvent('selectionChanged', this.selectedPeople);
547674

548-
this.people = [];
675+
this._foundPeople = [];
549676
}
550677
}
551678
}
@@ -602,7 +729,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
602729
if (event.code === 'Escape') {
603730
input.value = '';
604731
this.userInput = '';
605-
this.people = [];
732+
this._foundPeople = [];
606733
return;
607734
}
608735
if (event.code === 'Backspace' && this.userInput.length === 0 && this.selectedPeople.length > 0) {
@@ -632,7 +759,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
632759
if (!this._debouncedSearch) {
633760
this._debouncedSearch = debounce(async () => {
634761
if (!this.userInput.length) {
635-
this.people = [];
762+
this._foundPeople = [];
636763
this.hideFlyout();
637764
this._showLoading = true;
638765
} else {
@@ -671,10 +798,10 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
671798
}
672799
}
673800
if (event.code === 'Tab' || event.code === 'Enter') {
674-
if (this.people.length) {
801+
if (this._foundPeople.length) {
675802
event.preventDefault();
676803
}
677-
this.addPerson(this.people[this._arrowSelectionCount]);
804+
this.addPerson(this._foundPeople[this._arrowSelectionCount]);
678805
this.hideFlyout();
679806
(event.target as HTMLInputElement).value = '';
680807
}
@@ -685,7 +812,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
685812
* @param event - tracks user key selection
686813
*/
687814
private handleArrowSelection(event: KeyboardEvent): void {
688-
if (this.people.length) {
815+
if (this._foundPeople.length) {
689816
// update arrow count
690817
if (event.keyCode === 38) {
691818
// up arrow
@@ -697,7 +824,10 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
697824
}
698825
if (event.keyCode === 40) {
699826
// down arrow
700-
if (this._arrowSelectionCount + 1 !== this.people.length && this._arrowSelectionCount + 1 < this.showMax) {
827+
if (
828+
this._arrowSelectionCount + 1 !== this._foundPeople.length &&
829+
this._arrowSelectionCount + 1 < this.showMax
830+
) {
701831
this._arrowSelectionCount++;
702832
} else {
703833
this._arrowSelectionCount = 0;

src/components/mgt-person-card/mgt-person-card.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
99
import { Presence } from '@microsoft/microsoft-graph-types-beta';
1010
import { customElement, html, property, TemplateResult } from 'lit-element';
11-
import { findPerson, getEmailFromGraphEntity } from '../../graph/graph.people';
11+
import { findPeople, getEmailFromGraphEntity } from '../../graph/graph.people';
1212
import { getPersonImage } from '../../graph/graph.photos';
1313
import { getUserPresence } from '../../graph/graph.presence';
1414
import { getUserWithPhoto } from '../../graph/graph.user';
@@ -588,7 +588,7 @@ export class MgtPersonCard extends MgtTemplatedComponent {
588588
this.personImage = this.getImage();
589589
} else if (this.personQuery) {
590590
// Use the personQuery to find our person.
591-
const people = await findPerson(graph, this.personQuery);
591+
const people = await findPeople(graph, this.personQuery, 1);
592592

593593
if (people && people.length) {
594594
this.personDetails = people[0];

src/components/mgt-person/mgt-person.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
99
import { Presence } from '@microsoft/microsoft-graph-types-beta';
1010
import { customElement, html, property, query, TemplateResult } from 'lit-element';
1111
import { classMap } from 'lit-html/directives/class-map';
12-
import { findPerson, getEmailFromGraphEntity } from '../../graph/graph.people';
12+
import { findPeople, getEmailFromGraphEntity } from '../../graph/graph.people';
1313
import { getPersonImage } from '../../graph/graph.photos';
1414
import { getUserPresence } from '../../graph/graph.presence';
1515
import { getUserWithPhoto } from '../../graph/graph.user';
@@ -681,7 +681,7 @@ export class MgtPerson extends MgtTemplatedComponent {
681681
this.personImage = this.getImage();
682682
} else if (this.personQuery) {
683683
// Use the personQuery to find our person.
684-
const people = await findPerson(graph, this.personQuery);
684+
const people = await findPeople(graph, this.personQuery, 1);
685685

686686
if (people && people.length) {
687687
this.personDetails = people[0];

0 commit comments

Comments
 (0)