Added "About me" and "Skills" to the webpart.

* add Interface for the /me/ endpoint for aboutMe and skills
* add webpart properties so the "About Me" and "Skills" can be selected
* enabled the display of "About Me", "Skills" with appropriate icons
* enabled Name and Job Title to open a users delve page in a new browser window
This commit is contained in:
tristian o'brien 2021-04-14 15:21:44 +01:00 committed by GitHub
parent 19b2dece0d
commit 7ae0ee59c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 17 deletions

View File

@ -19,7 +19,6 @@ import { IUserExtended } from "../../entites/IUserExtended";
import { IAppContext } from "../../common/IAppContext"; import { IAppContext } from "../../common/IAppContext";
import { IUserCardProps } from "./IUserCardProps"; import { IUserCardProps } from "./IUserCardProps";
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json"); const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json"); const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json"); const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
@ -97,6 +96,7 @@ export const UserCard = (props: IUserCardProps) => {
} }
}; };
//tris added onclick event
const _onRenderPrimaryText = (persona: IPersonaProps) => { const _onRenderPrimaryText = (persona: IPersonaProps) => {
return ( return (
<> <>
@ -108,7 +108,12 @@ export const UserCard = (props: IUserCardProps) => {
root: { justifyContent: "flex-start", width: "100%" }, root: { justifyContent: "flex-start", width: "100%" },
}} }}
> >
<Text <Text onClick={
(event) => {
event.preventDefault();
window.open("https://gbr.delve.office.com/?u=" + userData.id + "&v=work", "_blank");
}
}
variant="medium" variant="medium"
block block
nowrap nowrap
@ -116,8 +121,9 @@ export const UserCard = (props: IUserCardProps) => {
width: "100%", width: "100%",
fontWeight: 600, fontWeight: 600,
padding: 0, padding: 0,
marginBottom: 3, marginBottom: 3
}} }}
title={persona.text}
> >
{persona.text} {persona.text}
</Text> </Text>
@ -130,6 +136,7 @@ export const UserCard = (props: IUserCardProps) => {
> >
<div style={{ fontSize: 12 }}> <div style={{ fontSize: 12 }}>
<ActionButton <ActionButton
styles={{ styles={{
root: { root: {
height: 21, height: 21,
@ -184,11 +191,19 @@ export const UserCard = (props: IUserCardProps) => {
); );
}; };
//tris added onclick event
const _onRenderSecondaryText = (persona: IPersonaProps) => { const _onRenderSecondaryText = (persona: IPersonaProps) => {
return ( return (
<> <>
<Stack verticalAlign="start" tokens={{ childrenGap: 0 }}> <Stack verticalAlign="start" tokens={{ childrenGap: 0 }}>
<Text title={persona.secondaryText} variant="medium" block nowrap> <Text onClick={
(event) => {
event.preventDefault();
window.open("https://gbr.delve.office.com/?u=" + userData.id + "&v=work", "_blank");
}
}
title={persona.secondaryText} variant="medium" block nowrap>
{" "} {" "}
{persona.secondaryText} {persona.secondaryText}
</Text> </Text>
@ -221,11 +236,11 @@ export const UserCard = (props: IUserCardProps) => {
} }
text={userData.displayName} text={userData.displayName}
title={userData.displayName} title={userData.displayName}
tertiaryText={userData.mail} tertiaryText={userData.mail}
secondaryText={userData.jobTitle} secondaryText={userData.jobTitle}
onRenderPrimaryText={_onRenderPrimaryText} onRenderPrimaryText={_onRenderPrimaryText}
onRenderSecondaryText={_onRenderSecondaryText} onRenderSecondaryText={_onRenderSecondaryText}
></Persona> ></Persona>
</Stack> </Stack>
{isDetailsOpen && ( {isDetailsOpen && (
<> <>
@ -707,8 +722,82 @@ export const UserCard = (props: IUserCardProps) => {
<div className={styleClasses.separator}></div> <div className={styleClasses.separator}></div>
</div> </div>
); );
break; break;
} case "aboutMe":
return (
<div
className={`${styleClasses.styleField}`}
title={userData.aboutMe ? userData.aboutMe.replace(/<[^>]+>/g, '').replace(/&nbsp;/gi,' ') : "Not available"}
>
<Stack
horizontal={true}
verticalAlign="center"
tokens={{ childrenGap: 5 }}
style={{ marginRight: 20, marginLeft: 20 }}
>
<FontIcon
className={styleClasses.styleIconDetails}
iconName="Medal"
/>
<Label className={styleClasses.styleFieldLabel}>
About Me
</Label>
</Stack>
<Text
styles={styleTextField}
variant="medium"
block={true}
nowrap={true}
>
{userData.aboutMe ? userData.aboutMe.replace(/<[^>]+>/g, '').replace(/&nbsp;/gi,' ') : "Not available"}
</Text>
<div className={styleClasses.separator}></div>
</div>
);
break;
case "skills":
return (
<div
className={`${styleClasses.styleField}`}
title={
userData.skills.join(",")
? userData.skills.join(",")
: "Not available"
}
>
<Stack
horizontal={true}
verticalAlign="center"
tokens={{ childrenGap: 5 }}
style={{ marginRight: 20, marginLeft: 20 }}
>
<FontIcon
className={styleClasses.styleIconDetails}
iconName="Education"
/>
<Label className={styleClasses.styleFieldLabel}>
Skills
</Label>
</Stack>
<Text
styles={styleTextField}
variant="medium"
block={true}
nowrap={true}
>
{userData.skills.join(",") ? (
<>
{userData.skills.join(",")}
</>
) : (
"Not available"
)}
</Text>
<div className={styleClasses.separator}></div>
</div>
);
break;
}
})} })}
</Stack> </Stack>
</> </>

View File

@ -15,5 +15,4 @@ export interface IUser {
officeLocation: string; officeLocation: string;
postalCode: string; postalCode: string;
userType: string; userType: string;
} }

View File

@ -0,0 +1,5 @@
export interface IUserBio {
id?: string;
aboutMe: string;
skills: string[];
}

View File

@ -1,6 +1,7 @@
import { IUser } from "./IUser"; import { IUser } from "./IUser";
import { IUserPresence } from "./IUserPresence"; import { IUserPresence } from "./IUserPresence";
export interface IUserExtended extends IUser, IUserPresence { import { IUserBio } from "./IUserBio";
export interface IUserExtended extends IUser, IUserPresence, IUserBio {
count: number; count: number;
pictureBase64: string; pictureBase64: string;
} }

View File

@ -4,8 +4,10 @@ import "@pnp/graph/users";
import { IUserExtended } from "../entites/IUserExtended"; import { IUserExtended } from "../entites/IUserExtended";
import { IUser } from "../entites/IUser"; import { IUser } from "../entites/IUser";
import { IUserPresence } from "../entites/IUserPresence"; import { IUserPresence } from "../entites/IUserPresence";
import { IUserBio } from "../entites/IUserBio";
import { SPComponentLoader } from "@microsoft/sp-loader"; import { SPComponentLoader } from "@microsoft/sp-loader";
import { findIndex } from "lodash"; import { findIndex, join, values } from "lodash";
import { DetailsRow } from "office-ui-fabric-react";
/*************************************************************************************/ /*************************************************************************************/
// Hook to search users // Hook to search users
@ -22,6 +24,7 @@ export const useSearchUsers = async (
pageSize?: number pageSize?: number
): Promise<{ usersExtended: IUserExtended[]; nextPage: string }> => { ): Promise<{ usersExtended: IUserExtended[]; nextPage: string }> => {
pageSize = pageSize ? pageSize : 5; pageSize = pageSize ? pageSize : 5;
const _searchResults: any = await _MSGraphClient const _searchResults: any = await _MSGraphClient
.api('/users?$search="' + searchString + '"') .api('/users?$search="' + searchString + '"')
.version("beta") .version("beta")
@ -40,8 +43,11 @@ export const useSearchUsers = async (
for (const _user of _users) { for (const _user of _users) {
const _userPresence = await getUserPresence(_user.id, _MSGraphClient); const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
const _pictureBase64: string = await getUserPhoto(_user.mail); const _pictureBase64: string = await getUserPhoto(_user.mail);
const _userBio = await getUserBio(_user.id, _MSGraphClient);
_usersExtended.push({ _usersExtended.push({
..._user, ..._user,
..._userBio,
..._userPresence, ..._userPresence,
pictureBase64: _pictureBase64, pictureBase64: _pictureBase64,
count: 0, count: 0,
@ -77,14 +83,18 @@ export const useGetUsersByDepartment = async (
.count(true) .count(true)
.get(); .get();
const _users: IUser[] = _searchResults.value; const _users: IUser[] = _searchResults.value;
let _usersExtended: IUserExtended[] = []; let _usersExtended: IUserExtended[] = [];
for (const _user of _users) { for (const _user of _users) {
const _userPresence = await getUserPresence(_user.id, _MSGraphClient); const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
const _pictureBase64: string = await getUserPhoto(_user.mail); const _pictureBase64: string = await getUserPhoto(_user.mail);
const _userBio = await getUserBio(_user.id, _MSGraphClient);
_usersExtended.push({ _usersExtended.push({
..._user, ..._user,
..._userBio,
..._userPresence, ..._userPresence,
pictureBase64: _pictureBase64, pictureBase64: _pictureBase64,
count: 0, count: 0,
@ -119,8 +129,10 @@ export const useGetUsersNextPage = async (
for (const _user of _users) { for (const _user of _users) {
const _userPresence = await getUserPresence(_user.id, _MSGraphClient); const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
const _pictureBase64: string = await getUserPhoto(_user.mail); const _pictureBase64: string = await getUserPhoto(_user.mail);
const _userBio = await getUserBio(_user.id, _MSGraphClient);
_usersExtended.push({ _usersExtended.push({
..._user, ..._user,
..._userBio,
..._userPresence, ..._userPresence,
pictureBase64: _pictureBase64, pictureBase64: _pictureBase64,
count: 0, count: 0,
@ -185,6 +197,21 @@ const getUserPresence = async (
return _presence; return _presence;
}; };
//*************************************************************************************//
// function Get Users About Me and skillz
//*************************************************************************************//
const getUserBio = async (
userObjId,
_MSGraphClient
): Promise<IUserBio> => {
let _bio : IUserBio = await _MSGraphClient
.api("/users/{" + userObjId + "}?$select=aboutMe,skills")
.version("beta")
.get();
return _bio;
};
/** /**
* Gets user photo * Gets user photo
* @param userId * @param userId

View File

@ -18,11 +18,11 @@
"preconfiguredEntries": [{ "preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "SPFx Custom Web Parts" }, "group": { "default": "SPFx Custom Web Parts" },
"title": { "default": "Search Directory" }, "title": { "default": "UoB Search Directory" },
"description": { "default": "Search Directory" }, "description": { "default": "University of Brighton Search Directory" },
"officeFabricIconFontName": "ProfileSearch", "officeFabricIconFontName": "ProfileSearch",
"properties": { "properties": {
"title": "Search Directory", "title": "UoB Search Directory",
"maxHeight": 700, "maxHeight": 700,
"showBox": true, "showBox": true,
"refreshInterval": 3, "refreshInterval": 3,

View File

@ -42,8 +42,6 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
protected async onInit(): Promise<void> { protected async onInit(): Promise<void> {
this._themeProvider = this.context.serviceScope.consume( this._themeProvider = this.context.serviceScope.consume(
ThemeProvider.serviceKey ThemeProvider.serviceKey
); );
@ -189,7 +187,6 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
PropertyFieldMultiSelect('userAttributes', { PropertyFieldMultiSelect('userAttributes', {
key: 'userAttributes', key: 'userAttributes',
label: strings.UserAttributesLabel, label: strings.UserAttributesLabel,
options: [ options: [
{ {
key: "company", key: "company",
@ -223,6 +220,14 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
key: "userType", key: "userType",
text: "User Type" text: "User Type"
}, },
{
key: "aboutMe",
text: "About Me"
},
{
key: "skills",
text: "Skills"
},
], ],
selectedKeys: this.properties.userAttributes selectedKeys: this.properties.userAttributes
}), }),