Merge pull request #1468 from joaojmendes/master

react-my-sites. bug fix and new features
This commit is contained in:
Hugo Bernier 2020-09-02 18:46:20 -04:00 committed by GitHub
commit 466728e54e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 274 additions and 74 deletions

View File

@ -59,6 +59,7 @@ Version|Date|Comments
-------|----|--------
1.0.0|August 6, 2020|Initial release
1.0.1|August 29, 2020|Additional updates
1.0.2|August 30, 2020|Bug Fix, new Properties
## Disclaimer

View File

@ -3,7 +3,7 @@
"solution": {
"name": "react-my-sites-client-side-solution",
"id": "ad28b382-886b-4b2a-9646-92de8a0b1d13",
"version": "1.0.1.0",
"version": "1.0.2.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,

View File

@ -1,6 +1,6 @@
{
"name": "react-my-sites",
"version": "1.0.1",
"version": "1.0.2.0",
"private": true,
"main": "lib/index.js",
"engines": {

View File

@ -0,0 +1,22 @@
export interface ITeam {
guestSettings: GuestSettings;
memberSettings: GuestSettings;
messagingSettings: GuestSettings;
funSettings: GuestSettings;
discoverySettings: GuestSettings;
internalId: string;
isArchived: boolean;
webUrl: string;
displayName: string;
description: string;
classification: string;
specialization: string;
visibility: string;
classSettings: GuestSettings;
isMembershipLimitedToOwners: string;
id:string;
}
interface GuestSettings {
'@odata.type': string;
}

View File

@ -1,6 +1,10 @@
import { MSGraphClient, AadTokenProvider } from "@microsoft/sp-http";
import { Filters } from "../Entities/EnumFilters";
import { sp } from "@pnp/sp";
import "@pnp/sp/sites";
import { Web } from "@pnp/sp/webs";
import { Webs, IWebs } from "@pnp/sp/webs";
import { graph } from "@pnp/graph";
import { dateAdd, PnPClientStorage } from "@pnp/common";
@ -10,8 +14,10 @@ import {
SearchResults,
SearchQueryBuilder,
SortDirection,
ISearchResult
} from "@pnp/sp/search";
import { IWebInfo } from "@pnp/spfx-controls-react";
import {ITeam } from "../Entities/ITeam";
const storage = new PnPClientStorage();
@ -62,10 +68,10 @@ export const useUserSites = () => {
case Filters.Group:
_filter = ` GroupId:a* OR GroupId:b* OR GroupId:c* OR GroupId:d* OR GroupId:e* OR GroupId:f* OR GroupId:g* OR GroupId:h* OR GroupId:i* OR GroupId:j* OR GroupId:k* OR GroupId:l* OR GroupId:m* OR GroupId:n* OR GroupId:o* OR GroupId:p* OR GroupId:q* OR GroupId:r* OR GroupId:s* OR GroupId:t* OR GroupId:u* OR GroupId:v* OR GroupId:w* OR GroupId:x* OR GroupId:y* OR GroupId:z* OR GroupId:1* OR GroupId:2* OR GroupId:3* OR GroupId:4* OR GroupId:5* OR GroupId:6* OR GroupId:7* OR GroupId:8* OR GroupId:9* OR GroupId:0*`;
break;
case Filters.OneDrive:
/* case Filters.OneDrive:
_filter = " WebTemplate:SPSPERS"; // OneDrive
// _filter = " SiteGroup:Onedrive";
break;
break; */
case Filters.SharePoint:
_filter =
" SiteGroup:SharePoint AND NOT(GroupId:b* OR GroupId:c* OR GroupId:d* OR GroupId:e* OR GroupId:f* OR GroupId:g* OR GroupId:h* OR GroupId:i* OR GroupId:j* OR GroupId:k* OR GroupId:l* OR GroupId:m* OR GroupId:n* OR GroupId:o* OR GroupId:p* OR GroupId:q* OR GroupId:r* OR GroupId:s* OR GroupId:t* OR GroupId:u* OR GroupId:v* OR GroupId:w* OR GroupId:x* OR GroupId:y* OR GroupId:z* OR GroupId:1* OR GroupId:2* OR GroupId:3* OR GroupId:4* OR GroupId:5* OR GroupId:6* OR GroupId:7* OR GroupId:8* OR GroupId:9* OR GroupId:0*)";
@ -77,7 +83,7 @@ export const useUserSites = () => {
}
const q = SearchQueryBuilder(
`(contentclass:STS_Site OR contentclass:STS_Web) ${_filter} ${_searchString}`
`(contentclass:STS_Site OR contentclass:STS_Web) AND -Webtemplate:SPSPERS* ${_filter} ${_searchString}`
)
.rowLimit(itemsPerPage ? itemsPerPage : 20)
.enableSorting.sortList({
@ -112,7 +118,7 @@ export const useUserSites = () => {
);
const results = await sp.search(q);
searchResults = results; // set the current results
console.log(searchResults);
return searchResults;
};
@ -122,7 +128,7 @@ const getUserWebs = async (
): Promise<SearchResults> => {
let searchResults: SearchResults = null;
const q = SearchQueryBuilder(
`(contentclass:STS_Web)`
`(contentclass:STS_Web AND -Webtemplate:SPSPERS*)`
)
.rowLimit(100000)
.selectProperties(
@ -153,10 +159,100 @@ const getUserWebs = async (
);
const results = await sp.search(q);
searchResults = results; // set the current results
console.log("webs",searchResults);
return searchResults;
};
return { getUserSites, checkGroupHasTeam, getUserWebs };
// Get User Sites
const getUserGroups = async (
): Promise<SearchResults> => {
let searchResults: SearchResults = null;
const _filter = ` AND GroupId:a* OR GroupId:b* OR GroupId:c* OR GroupId:d* OR GroupId:e* OR GroupId:f* OR GroupId:g* OR GroupId:h* OR GroupId:i* OR GroupId:j* OR GroupId:k* OR GroupId:l* OR GroupId:m* OR GroupId:n* OR GroupId:o* OR GroupId:p* OR GroupId:q* OR GroupId:r* OR GroupId:s* OR GroupId:t* OR GroupId:u* OR GroupId:v* OR GroupId:w* OR GroupId:x* OR GroupId:y* OR GroupId:z* OR GroupId:1* OR GroupId:2* OR GroupId:3* OR GroupId:4* OR GroupId:5* OR GroupId:6* OR GroupId:7* OR GroupId:8* OR GroupId:9* OR GroupId:0*`;
const q = SearchQueryBuilder(
`(contentclass:STS_Site AND -Webtemplate:SPSPERS*) ${_filter}`
)
.rowLimit(100000)
.selectProperties(
"ParentLink",
"SPSiteURL",
"SiteID",
"SPWebUrl",
"WebId",
"SiteLogo",
"SiteClosed",
"RelatedHubSites",
"IsHubSite",
"GroupId",
"RelatedGroupId",
"SiteGroup",
"Author",
"CreatedBy",
"CreatedById",
"AccountName",
"ModifiedBy",
"ModifiedById",
"LastModifiedTime",
"OriginalPath",
"Path",
"Title",
"Created",
"WebTemplate"
);
const results = await sp.search(q);
searchResults = results; // set the current results
return searchResults;
};
// Get Properties for Web
const getSiteProperties = async (webUrl:string) : Promise<any> => {
const cachedWebIdValue:any = storage.local.get(webUrl);
if (!cachedWebIdValue){
const _openWeb = Web(webUrl);
const _webProps = await _openWeb();
storage.local.put(webUrl, _webProps, dateAdd(new Date(), "day", 1));
//we got all the data from the web as well
return _webProps.Title;
}
// we can chain
return cachedWebIdValue.Title;
};
const getUserTeams = async (userId:string,msGraphClient:MSGraphClient):Promise<ITeam[]> => {
const cachedListTeamsValue:ITeam[] = storage.local.get(userId);
if (!cachedListTeamsValue) {
try {
const _listOfTeams: any = await msGraphClient
.api(`/me/joinedTeams`)
.version("V1.0")
.get();
// put a value into storage with an expiration
storage.local.put(userId, _listOfTeams.value , dateAdd(new Date(), "day", 1));
return _listOfTeams.value as any[];
} catch (error) {
// put a value into storage with an expiration
storage.local.put(userId, [], dateAdd(new Date(), "day", 1));
return [];
}
}else{
// return cached value
return cachedListTeamsValue ;
}
};
return { getUserSites, checkGroupHasTeam, getUserWebs, getUserGroups, getSiteProperties, getUserTeams };
};

View File

@ -23,7 +23,10 @@
"officeFabricIconFontName": "SharepointLogo",
"properties": {
"title": "My Sites",
"itemsPerPage": 20
"itemsPerPage": 20,
"enableFilterSharepointSites": true,
"enableFilterO365groups": true,
"enableFilterSitesWithSubWebs": true
}
}]
}

View File

@ -4,9 +4,12 @@ import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneSlider
PropertyPaneSlider,
PropertyPaneLabel,
PropertyPaneHorizontalRule,
IPropertyPaneDropdownOption
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { BaseClientSideWebPart, PropertyPaneToggle, PropertyPaneCheckbox } from '@microsoft/sp-webpart-base';
import * as strings from 'MySitesWebPartStrings';
import { MySites } from './components/MySites/MySites';
@ -15,7 +18,17 @@ import { loadTheme } from "office-ui-fabric-react";
import { DisplayMode } from '@microsoft/sp-core-library';
import { ThemeProvider, ThemeChangedEventArgs, IReadonlyTheme } from '@microsoft/sp-component-base';
import { sp } from "@pnp/sp";
import { PropertyFieldSitePicker } from '@pnp/spfx-property-controls/lib/PropertyFieldSitePicker';
import { IPropertyFieldSite } from "@pnp/spfx-property-controls/lib/PropertyFieldSitePicker";
import { PropertyFieldMultiSelect } from '@pnp/spfx-property-controls/lib/PropertyFieldMultiSelect';
import { useUserSites } from '../../Hooks/useUserSites';
export interface IPropertyControlsTestWebPartProps {
sites: IPropertyFieldSite[];
}
import { graph } from "@pnp/graph";
import { ITeam } from '../../Entities/ITeam';
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
@ -26,11 +39,18 @@ export interface IMySitesWebPartProps {
displayMode: DisplayMode;
updateProperty: (value: string) => void;
itemsPerPage: number;
defaultSitesToFilter: IPropertyFieldSite[];
enableFilterSharepointSites:boolean;
enableFilterO365groups: boolean;
enableFilterSitesWithSubWebs: boolean;
DefaultTeamsToFilter: string[];
}
export default class MySitesWebPart extends BaseClientSideWebPart<IMySitesWebPartProps> {
private _themeProvider: ThemeProvider;
private _themeVariant: IReadonlyTheme | undefined;
private _userTeamsOptions:IPropertyPaneDropdownOption[] = [];
protected async onInit(): Promise<void> {
sp.setup({
@ -51,12 +71,15 @@ export default class MySitesWebPart extends BaseClientSideWebPart<IMySitesWebPar
// in teams ?
const context = this.context.sdks.microsoftTeams!.context;
console.log('theme', this._themeVariant);
this._applyTheme(context.theme || "default");
this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(
this._applyTheme
);
}
return Promise.resolve();
}
@ -67,7 +90,7 @@ export default class MySitesWebPart extends BaseClientSideWebPart<IMySitesWebPar
*/
private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
this._themeVariant = args.theme;
console.log('theme', this._themeVariant);
this.render();
}
@ -111,7 +134,11 @@ export default class MySitesWebPart extends BaseClientSideWebPart<IMySitesWebPar
itemsPerPage: this.properties.itemsPerPage,
updateProperty: (value: string) => {
this.properties.title = value;
}
},
defaultSitesToFilter: this.properties.defaultSitesToFilter,
enableFilterSharepointSites: this.properties.enableFilterSharepointSites,
enableFilterO365groups: this.properties.enableFilterO365groups,
enableFilterSitesWithSubWebs: this.properties.enableFilterSitesWithSubWebs
}
);
@ -130,6 +157,20 @@ export default class MySitesWebPart extends BaseClientSideWebPart<IMySitesWebPar
return true;
}
protected async onPropertyPaneConfigurationStart() {
const { getUserTeams } = useUserSites();
const _msGraphClient = await this.context.msGraphClientFactory.getClient();
const _userTeams:ITeam[] = await getUserTeams(this.context.pageContext.user.loginName, _msGraphClient);
for (const _team of _userTeams) {
this._userTeamsOptions.push({key: _team.id, text: _team.displayName });
}
this.context.propertyPane.refresh();
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
@ -145,13 +186,34 @@ export default class MySitesWebPart extends BaseClientSideWebPart<IMySitesWebPar
label: strings.TitleFieldLabel,
value: this.properties.title
}),
PropertyPaneLabel('',{
text: ''
}),
PropertyPaneSlider('itemsPerPage', {
min: 1,
max: 100,
value: this.properties.itemsPerPage,
step: 1,
label: strings.ItemsPerPageLabel,
})
}),
PropertyPaneLabel('',{
text: 'Filter scopes'
}),
PropertyPaneHorizontalRule(),
PropertyPaneCheckbox('enableFilterSharepointSites',{
checked: this.properties.enableFilterSharepointSites,
text: 'Filter SharePoint Sites',
}),
PropertyPaneCheckbox('enableFilterO365groups',{
checked: this.properties.enableFilterO365groups,
text: 'Filter Office 365 Groups'
}),
PropertyPaneCheckbox('enableFilterSitesWithSubWebs',{
checked: this.properties.enableFilterSitesWithSubWebs,
text: 'Filter sites with sub sites'
}),
]
}
]

View File

@ -1,6 +1,7 @@
import { IReadonlyTheme } from '@microsoft/sp-component-base';
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { DisplayMode } from "@microsoft/sp-core-library";
import { IPropertyFieldSite } from '@pnp/spfx-property-controls/lib/PropertyFieldSitePicker';
export interface IMySitesProps {
title: string;
context: WebPartContext;
@ -8,5 +9,9 @@ export interface IMySitesProps {
displayMode: DisplayMode;
updateProperty: (value: string) => void;
itemsPerPage:number;
defaultSitesToFilter: IPropertyFieldSite[];
enableFilterSharepointSites:boolean;
enableFilterO365groups: boolean;
enableFilterSitesWithSubWebs: boolean;
}

View File

@ -36,7 +36,7 @@ let _msGraphClient: MSGraphClient = undefined;
let _filterMenuProps: IContextualMenuProps = undefined;
// Get Hook functions
const { getUserSites, getUserWebs } = useUserSites();
const { getUserSites, getUserWebs, getUserGroups, getSiteProperties } = useUserSites();
export const MySites: React.FunctionComponent<IMySitesProps> = (
props: IMySitesProps
@ -44,7 +44,7 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
// Global Compoment Styles
const stylesComponent = mergeStyleSets({
containerTiles: {
marginTop: 25,
marginTop: 5,
display: "grid",
marginBottom: 10,
gridTemplateColumns: "repeat( auto-fit, minmax(300px, 1fr) )",
@ -133,9 +133,9 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
case "Groups":
await _getUserSites("", Filters.Group, "Groups");
break;
case "OneDrive":
/* case "OneDrive":
await _getUserSites("", Filters.OneDrive, "OneDrive");
break;
break; */
case "SharePoint":
await _getUserSites("", Filters.SharePoint, "SharePoint");
break;
@ -151,15 +151,15 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
_msGraphClient = await props.context.msGraphClientFactory.getClient();
const _sitesWithSubSties = await getUserWebs();
console.log("subsites", _sitesWithSubSties);
const _uniqweb = _.uniqBy(
_sitesWithSubSties.PrimarySearchResults,
"ParentLink"
);
const {enableFilterO365groups, enableFilterSharepointSites, enableFilterSitesWithSubWebs } = props;
_filterMenuProps = {
items: [
{
key: "0",
@ -174,7 +174,13 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
_Filtersites(item.text);
},
},
{
],
};
if( enableFilterSharepointSites ) {
_filterMenuProps.items.push({
key: "1",
text: "SharePoint",
iconProps: { iconName: "SharepointAppIcon16" },
@ -185,9 +191,13 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
}
}
);
}
if( enableFilterO365groups ) {
_filterMenuProps.items.push({
key: "2",
text: "Groups",
iconProps: { iconName: "Group" },
@ -198,37 +208,22 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
key: "3",
text: "OneDrive",
iconProps: { iconName: "onedrive" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
],
};
}
}
);
}
if (_sitesWithSubSties.PrimarySearchResults.length > 0){
if (enableFilterSitesWithSubWebs && _sitesWithSubSties.PrimarySearchResults.length > 0){
_filterMenuProps.items.push(
{
key: 'sites',
itemType: ContextualMenuItemType.Header,
text: 'Sites with Sub Sites',
text: 'Sites with sub sites',
itemProps: {
lang: 'en-us',
},
}
);
}
// Add Site Collections with sub
for (const web of _uniqweb) {
@ -239,10 +234,13 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
const _siteName: string = (web.ParentLink as string).substring(
_lastHasPosition + 1
);
const _webTitle = await getSiteProperties(web.ParentLink);
// tslint:disable-next-line: no-use-before-declare
_filterMenuProps.items.push({
key: web.ParentLink,
text: _siteName,
text: _webTitle,
iconProps: { iconName: "DrillExpand" },
onClick: (
ev:
@ -255,9 +253,11 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
},
});
}
}
await _getUserSites("", state.currentFilter, state.currentFilterName);
})();
}, [props.title, props.itemsPerPage]);
}, [props]);
// On Search Sites
const _onSearch = async (value: string) => {
@ -290,14 +290,17 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
updateProperty={props.updateProperty}
className={stylesComponent.webPartTile}
/>
<Stack horizontal verticalAlign="center" horizontalAlign="end" wrap tokens={{ childrenGap: 5 }}>
<Stack horizontal verticalAlign="center" horizontalAlign='start' styles={{root:{width: '100%'}}}>
<SearchBox
placeholder="Search my sites"
underlined={true}
styles={{root:{width:'100%', marginBottom: 10}}}
value={state.searchValue}
onSearch={_onSearch}
onClear={_onClear}
/>
</Stack>
<Stack horizontal verticalAlign="center" horizontalAlign="end" wrap tokens={{ childrenGap: 5 }}>
<CommandButton
iconProps={{ iconName: "refresh" }}
onClick={_onClear}
@ -329,6 +332,7 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
site={site}
msGraphClient={_msGraphClient}
themeVariant={props.themeVariant}
locale={props.context.pageContext.cultureInfo.currentCultureName}
></SiteTile>
);
})}
@ -367,6 +371,8 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
color="primary"
count={state.totalPages}
page={state.currentPage}
size="small"
siblingCount={0}
onChange={async (event: any, page: number) => {
const rs = await _searchResults.getPage(page);
_searchResults = rs;

View File

@ -5,4 +5,5 @@ export interface ISiteTileProps {
msGraphClient:MSGraphClient;
site:any;
themeVariant: IReadonlyTheme | undefined;
locale:string;
}

View File

@ -130,15 +130,16 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
// Use Effect on Mounting
React.useEffect(() => {
(async () => {
if (ModifiedById) {
if (ModifiedById && LastModifiedTime) {
const _modifiedBySplit = ModifiedById.split("|");
_activityUserEmail = _modifiedBySplit[0].trim();
_activityUser = _modifiedBySplit[1].trim();
_activityDate = new Date(LastModifiedTime).toLocaleString();
const _lastModified = new Date(LastModifiedTime);
_activityDate = _lastModified.toLocaleDateString() + ' ' + _lastModified.toLocaleTimeString();
_activityMessage = `${strings.ChangedOnLabel}${_activityDate}`;
try {
if (_activityUserEmail) {
console.log(_activityUserEmail);
_userPhoto = await getUserPhoto(_activityUserEmail);
}
} catch (error) {
@ -148,7 +149,9 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
} else {
_activityUserEmail = undefined;
_activityUser = CreatedBy;
_activityDate = new Date(Created).toLocaleString();
const _lastCreated = new Date(Created);
_activityDate = _lastCreated.toLocaleDateString() + ' ' + _lastCreated.toLocaleTimeString();
_activityMessage = `${strings.CreatedOnLabel}${_activityDate}`;
_userPhoto = undefined;
}
@ -189,6 +192,7 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
styles={documentCardStyles}
type={DocumentCardType.compact}
onClickHref={OriginalPath}
onClickTarget={"_blank"}
>
{props.site.SiteLogo ? (
<DocumentCardPreview
@ -244,13 +248,13 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
title="is Hub Site"
></Icon>
)}
{WebTemplate == "SPSPERS" && (
{/* {WebTemplate == "SPSPERS" && (
<Icon
styles={groupIconStyles}
iconName="onedrive"
title="User OneDrive"
></Icon>
)}
)} */}
{SiteGroup == "SharePoint" && !GroupId && (
<Icon
styles={groupIconStyles}
@ -259,13 +263,13 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
></Icon>
)}
</div>
<TooltipHost content={activityMessage} calloutProps={{gapSpace:5}}>
<div title={activityMessage} >
<DocumentCardActivity
styles={DocumentCardActivityStyles}
activity={activityMessage}
people={[{ name: activityUser, profileImageSrc: userPhoto }]}
/>
</TooltipHost>
</div>
</DocumentCardDetails>
</DocumentCard>
</>

View File

@ -4,7 +4,7 @@ define([], function() {
ChangedOnLabel: "Changed on ",
RefreshLabel: "Refresh",
LoadingLabel: "Loading...",
"PropertyPaneDescription": "List sites user has permissions, Include SharePoint Sites, Office365 Groups, OneDrive, Add-Ins App Sites",
"PropertyPaneDescription": "List sites user has permissions, Include SharePoint Sites, Office365 Groups, Add-Ins App Sites",
"BasicGroupName": "Properties",
"TitleFieldLabel": "Title",
"ItemsPerPageLabel": "Number items per page"