Merge pull request #1819 from tristian2/tristian2-staff-directory

This commit is contained in:
Hugo Bernier 2021-04-21 22:45:49 -04:00 committed by GitHub
commit ad31c02a01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 150 additions and 21 deletions

View File

@ -31,7 +31,7 @@ This web part shows the current user's colleagues, and allows the user to search
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg) ![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg) ![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg) ![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams") ![Teams Yes: Designed for Microsoft Teams](https://img.shields.io/badge/Teams-Yes-green.svg "Designed for Microsoft Teams")
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench") ![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
## Applies to ## Applies to
@ -45,6 +45,8 @@ Solution|Author(s)
--------|--------- --------|---------
react-staffdirectory|Ari Gunawan ([@arigunawan3023](https://twitter.com/arigunawan3023)) react-staffdirectory|Ari Gunawan ([@arigunawan3023](https://twitter.com/arigunawan3023))
react-staffdirectory|João Mendes ([joaojmendes](https://github.com/joaojmendes)) react-staffdirectory|João Mendes ([joaojmendes](https://github.com/joaojmendes))
react-staffdirectory|[Tristian O'brien](https://github.com/tristian2)
## Version history ## Version history
@ -53,6 +55,7 @@ Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|February 16, 2021|Initial release 1.0.0|February 16, 2021|Initial release
1.0.1|March 28, 2021|Added missing Graph API Permission (User.Read.All) for getting users information 1.0.1|March 28, 2021|Added missing Graph API Permission (User.Read.All) for getting users information
1.0.2|April 14, 2021|Added About Me and Skills
## Disclaimer ## Disclaimer

View File

@ -9,7 +9,7 @@
"This web part shows the current user\u0027s colleagues, and allows the user to search AD directory, The user can configure the properties to show when expand the user card." "This web part shows the current user\u0027s colleagues, and allows the user to search AD directory, The user can configure the properties to show when expand the user card."
], ],
"created": "2021-03-09", "created": "2021-03-09",
"modified": "2021-03-28", "modified": "2021-04-14",
"products": [ "products": [
"SharePoint", "SharePoint",
"Office" "Office"

View File

@ -3,7 +3,7 @@
"solution": { "solution": {
"name": "staff-directory-client-side-solution", "name": "staff-directory-client-side-solution",
"id": "89d7389c-be48-41e9-9f72-5eb9a1099c1f", "id": "89d7389c-be48-41e9-9f72-5eb9a1099c1f",
"version": "1.0.1.0", "version": "1.0.2.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": true, "skipFeatureDeployment": true,
"isDomainIsolated": false, "isDomainIsolated": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "staff-directory", "name": "staff-directory",
"version": "0.0.1", "version": "1.0.2",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "engines": {

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>
@ -708,7 +723,81 @@ export const UserCard = (props: IUserCardProps) => {
</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
}), }),