Merge pull request #1464 from joaojmendes/master

updates
This commit is contained in:
Hugo Bernier 2020-08-29 16:15:36 -04:00 committed by GitHub
commit df87dd9cfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 300 additions and 160 deletions

View File

@ -25,6 +25,8 @@ Each Site has a symbol indicate if it is SharePoint Site, Group, Group and OnDri
If the Group has a microsoft team associated and user has permissions to access the symbol of teams will be displayed. If the Group has a microsoft team associated and user has permissions to access the symbol of teams will be displayed.
     
![MySites](./assets/MySites.gif) ![MySites](./assets/MySites.gif)
![MySites](./assets/Screenshot%202020-08-06%20at%2013.50.51.png) ![MySites](./assets/Screenshot%202020-08-06%20at%2013.50.51.png)
@ -56,6 +58,7 @@ react-my-sites|João Mendes
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|August 6, 2020|Initial release 1.0.0|August 6, 2020|Initial release
1.0.1|August 29, 2020|Additional updates
## Disclaimer ## Disclaimer

View File

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

View File

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

View File

@ -1,6 +1,7 @@
export enum Filters { export enum Filters {
"SharePoint", "All",
"Group", "Group",
"OneDrive", "OneDrive",
"All", "SharePoint",
"Site"
} }

View File

@ -49,7 +49,8 @@ export const useUserSites = () => {
const getUserSites = async ( const getUserSites = async (
searchString?: string, searchString?: string,
itemsPerPage?: number, itemsPerPage?: number,
filter?: Filters filter?: Filters,
site?:string
): Promise<SearchResults> => { ): Promise<SearchResults> => {
let searchResults: SearchResults = null; let searchResults: SearchResults = null;
let _filter: string = ""; let _filter: string = "";
@ -62,12 +63,17 @@ export const useUserSites = () => {
_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*`; _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; break;
case Filters.OneDrive: case Filters.OneDrive:
_filter = " SiteGroup:Onedrive"; _filter = " WebTemplate:SPSPERS"; // OneDrive
// _filter = " SiteGroup:Onedrive";
break; break;
case Filters.SharePoint: case Filters.SharePoint:
_filter = _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*)"; " 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*)";
break; break;
case Filters.Site:
_filter = `Path:${site}`;
break;
} }
const q = SearchQueryBuilder( const q = SearchQueryBuilder(
@ -79,6 +85,11 @@ export const useUserSites = () => {
Direction: SortDirection.Descending, Direction: SortDirection.Descending,
}) })
.selectProperties( .selectProperties(
"ParentLink",
"SPSiteURL",
"SiteID",
"SPWebUrl",
"WebId",
"SiteLogo", "SiteLogo",
"SiteClosed", "SiteClosed",
"RelatedHubSites", "RelatedHubSites",
@ -94,8 +105,10 @@ export const useUserSites = () => {
"ModifiedById", "ModifiedById",
"LastModifiedTime", "LastModifiedTime",
"OriginalPath", "OriginalPath",
"Path",
"Title", "Title",
"Created" "Created",
"WebTemplate"
); );
const results = await sp.search(q); const results = await sp.search(q);
searchResults = results; // set the current results searchResults = results; // set the current results
@ -103,5 +116,47 @@ export const useUserSites = () => {
return searchResults; return searchResults;
}; };
return { getUserSites, checkGroupHasTeam }; // Get User Sites
const getUserWebs = async (
): Promise<SearchResults> => {
let searchResults: SearchResults = null;
const q = SearchQueryBuilder(
`(contentclass:STS_Web)`
)
.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
console.log("webs",searchResults);
return searchResults;
};
return { getUserSites, checkGroupHasTeam, getUserWebs };
}; };

View File

@ -1,3 +1,4 @@
import { IContextualMenuProps } from "office-ui-fabric-react";
export interface IMySitesState { export interface IMySitesState {
sites: any[]; sites: any[];
@ -9,4 +10,7 @@ export interface IMySitesState {
totalPages:number; totalPages:number;
searchValue:string; searchValue:string;
currentFilter?:number; currentFilter?:number;
currentFilterName?:string;
currentSelectedSite?:string;
filterMenuProps: IContextualMenuProps;
} }

View File

@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Filters} from '../../../../Entities/EnumFilters'; import { Filters } from "../../../../Entities/EnumFilters";
import "./paginationOverride.module.scss"; import "./paginationOverride.module.scss";
import { IMySitesProps } from "./IMySitesProps"; import { IMySitesProps } from "./IMySitesProps";
import { escape } from "@microsoft/sp-lodash-subset"; import { escape } from "@microsoft/sp-lodash-subset";
@ -18,6 +18,7 @@ import {
IContextualMenuItem, IContextualMenuItem,
FontIcon, FontIcon,
Label, Label,
ContextualMenuItemType,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { WebPartTitle } from "@pnp/spfx-controls-react"; import { WebPartTitle } from "@pnp/spfx-controls-react";
import { useUserSites } from "../../../../Hooks/useUserSites"; import { useUserSites } from "../../../../Hooks/useUserSites";
@ -32,6 +33,10 @@ import { MSGraphClient } from "@microsoft/sp-http";
let _searchResults: SearchResults = null; let _searchResults: SearchResults = null;
let _msGraphClient: MSGraphClient = undefined; let _msGraphClient: MSGraphClient = undefined;
let _filterMenuProps: IContextualMenuProps = undefined;
// Get Hook functions
const { getUserSites, getUserWebs } = useUserSites();
export const MySites: React.FunctionComponent<IMySitesProps> = ( export const MySites: React.FunctionComponent<IMySitesProps> = (
props: IMySitesProps props: IMySitesProps
@ -47,12 +52,11 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
}, },
webPartTile: { webPartTile: {
fontWeight: 500, fontWeight: 500,
marginBottom: 20 marginBottom: 20,
}, },
}); });
// Document Card Styles // Document Card Styles
// state // state
const [state, setState] = React.useState<IMySitesState>({ const [state, setState] = React.useState<IMySitesState>({
errorMessage: "", errorMessage: "",
@ -61,32 +65,40 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
hasError: false, hasError: false,
title: props.title, title: props.title,
currentPage: 1, currentPage: 1,
totalPages: 50, totalPages: 0,
searchValue: "", searchValue: "",
currentFilter: Filters.All, currentFilter: Filters.All,
currentFilterName: "All",
currentSelectedSite: undefined,
filterMenuProps: undefined,
}); });
const filterIcon: IIconProps = { iconName: "Filter" }; const filterIcon: IIconProps = { iconName: "Filter" };
// Get Hook functions
const { getUserSites } = useUserSites();
// get User Sites // get User Sites
const _getUserSites = async ( const _getUserSites = async (
searchString?: string, searchString?: string,
currentFilter?: Filters currentFilter?: Filters,
currentFilterName?:string,
site?:string
) => { ) => {
try { try {
console.log('tiles var', props.themeVariant);
setState({ ...state, isLoading: true }); setState({ ...state, isLoading: true });
const { itemsPerPage } = props; const { itemsPerPage } = props;
const searchResults = await getUserSites(searchString, itemsPerPage, currentFilter); const searchResults = await getUserSites(
searchString,
itemsPerPage,
currentFilter,
site
);
_searchResults = searchResults; _searchResults = searchResults;
let _totalPages: number = searchResults.TotalRows / itemsPerPage; let _totalPages: number = searchResults.TotalRows / itemsPerPage;
const _modulus: number = searchResults.TotalRows % itemsPerPage; const _modulus: number = searchResults.TotalRows % itemsPerPage;
_totalPages = _totalPages =
_modulus > 0 ? toInteger(_totalPages) + 1 : toInteger(_totalPages); _modulus > 0 ? toInteger(_totalPages) + 1 : toInteger(_totalPages);
setState({ setState({
...state,
searchValue: "", searchValue: "",
currentPage: 1, currentPage: 1,
totalPages: _totalPages, totalPages: _totalPages,
@ -95,7 +107,11 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
hasError: false, hasError: false,
errorMessage: "", errorMessage: "",
sites: _searchResults.PrimarySearchResults, sites: _searchResults.PrimarySearchResults,
currentFilter: currentFilter currentFilter: currentFilter,
currentSelectedSite: site,
currentFilterName: currentFilterName,
// tslint:disable-next-line: no-use-before-declare
filterMenuProps: _filterMenuProps,
}); });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -108,34 +124,42 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
} }
}; };
const _Filtersites = async (filter: string) => { const _Filtersites = async (filter: string, site?:string) => {
setState({ ...state, isLoading: true });
let _filteredSites: any[] = [];
switch (filter) { switch (filter) {
case "All": case "All":
setState({...state,currentFilter: Filters.All}); await _getUserSites("", Filters.All, "All");
await _getUserSites('', Filters.All);
break; break;
case "Groups": case "Groups":
setState({...state,currentFilter: Filters.Group}); await _getUserSites("", Filters.Group, "Groups");
await _getUserSites('', Filters.Group);
break; break;
case "OneDrive": case "OneDrive":
setState({...state,currentFilter: Filters.OneDrive}); await _getUserSites("", Filters.OneDrive, "OneDrive");
await _getUserSites('', Filters.OneDrive);
break; break;
case "SharePoint": case "SharePoint":
setState({...state,currentFilter: Filters.SharePoint}); await _getUserSites("", Filters.SharePoint, "SharePoint");
await _getUserSites('', Filters.SharePoint);
break; break;
default: default:
setState({ ...state, isLoading: false }); await _getUserSites("", Filters.Site, filter, site);
break;
} }
}; };
const filterMenuProps: IContextualMenuProps = {
// useEffect component did mount or modified
React.useEffect(() => {
(async () => {
_msGraphClient = await props.context.msGraphClientFactory.getClient();
const _sitesWithSubSties = await getUserWebs();
console.log("subsites", _sitesWithSubSties);
const _uniqweb = _.uniqBy(
_sitesWithSubSties.PrimarySearchResults,
"ParentLink"
);
_filterMenuProps = {
items: [ items: [
{ {
key: "0", key: "0",
@ -191,27 +215,63 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
}, },
], ],
}; };
// useEffect component did mount or modified
React.useEffect(() => {
(async () => { if (_sitesWithSubSties.PrimarySearchResults.length > 0){
_msGraphClient = await props.context.msGraphClientFactory.getClient(); _filterMenuProps.items.push(
await _getUserSites("",state.currentFilter); {
key: 'sites',
itemType: ContextualMenuItemType.Header,
text: 'Sites with Sub Sites',
itemProps: {
lang: 'en-us',
},
}
);
}
// Add Site Collections with sub
for (const web of _uniqweb) {
// tslint:disable-next-line: no-use-before-declare
const _lastHasPosition: number = (web.ParentLink as string).lastIndexOf(
"/"
);
const _siteName: string = (web.ParentLink as string).substring(
_lastHasPosition + 1
);
// tslint:disable-next-line: no-use-before-declare
_filterMenuProps.items.push({
key: web.ParentLink,
text: _siteName,
iconProps: { iconName: "DrillExpand" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
// tslint:disable-next-line: no-use-before-declare
_Filtersites(item.text, item.key);
},
});
}
await _getUserSites("", state.currentFilter, state.currentFilterName);
})(); })();
}, [props.title, props.itemsPerPage]); }, [props.title, props.itemsPerPage]);
// On Search Sites // On Search Sites
const _onSearch = async (value: string) => { const _onSearch = async (value: string) => {
await _getUserSites(value,state.currentFilter); await _getUserSites(value, state.currentFilter,state.currentFilterName, state.currentSelectedSite);
}; };
// On Search Sites // On Search Sites
const _onClear = async (ev: any) => { const _onClear = async (ev: any) => {
await _getUserSites("",state.currentFilter); await _getUserSites("", state.currentFilter,state.currentFilterName, state.currentSelectedSite);
}; };
// Render component // Render component
if (state.hasError) { // render message error if (state.hasError) {
// render message error
return ( return (
<MessageBar messageBarType={MessageBarType.error}> <MessageBar messageBarType={MessageBarType.error}>
{state.errorMessage} {state.errorMessage}
@ -229,9 +289,8 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
themeVariant={props.themeVariant} themeVariant={props.themeVariant}
updateProperty={props.updateProperty} updateProperty={props.updateProperty}
className={stylesComponent.webPartTile} className={stylesComponent.webPartTile}
/> />
<Stack horizontal horizontalAlign="end" tokens={{ childrenGap: 10 }}> <Stack horizontal verticalAlign="center" horizontalAlign="end" wrap tokens={{ childrenGap: 5 }}>
<SearchBox <SearchBox
placeholder="Search my sites" placeholder="Search my sites"
underlined={true} underlined={true}
@ -246,10 +305,11 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
/> />
<CommandButton <CommandButton
iconProps={filterIcon} iconProps={filterIcon}
text={Filters[state.currentFilter]} text={state.currentFilterName}
menuProps={filterMenuProps} menuProps={state.filterMenuProps}
disabled={false} disabled={false}
checked={true} checked={true}
title="filter"
/> />
</Stack> </Stack>
{state.isLoading ? ( {state.isLoading ? (
@ -259,9 +319,9 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
></Spinner> ></Spinner>
) : ( ) : (
<> <>
{ // has sites ? {
// has sites ?
state.sites.length > 0 ? state.sites.length > 0 ? (
<div className={stylesComponent.containerTiles}> <div className={stylesComponent.containerTiles}>
{state.sites.map((site: any, i: number) => { {state.sites.map((site: any, i: number) => {
return ( return (
@ -273,14 +333,25 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
); );
})} })}
</div> </div>
: ) : (
<> <>
<Stack horizontal verticalAlign="center" horizontalAlign="center" tokens={{childrenGap: 20}} styles={{root: {marginTop: 50}}}> <Stack
<FontIcon iconName="Tiles" style={{fontSize: 48}}></FontIcon> horizontal
<Label styles={{root:{fontSize: 26}}}>No Sites Found </Label> verticalAlign="center"
horizontalAlign="center"
tokens={{ childrenGap: 20 }}
styles={{ root: { marginTop: 50 } }}
>
<FontIcon
iconName="Tiles"
style={{ fontSize: 48 }}
></FontIcon>
<Label styles={{ root: { fontSize: 26 } }}>
No Sites Found{" "}
</Label>
</Stack> </Stack>
</> </>
)
} }
{state.totalPages > 1 && ( {state.totalPages > 1 && (

View File

@ -94,10 +94,9 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
fontSize: 20, fontSize: 20,
color: props.themeVariant ? props.themeVariant.palette.themePrimary: 'white', color: props.themeVariant ? props.themeVariant.palette.themePrimary: 'white',
marginTop: 8, marginTop: 8,
marginRight: 16, marginRight: 7,
}, },
}; };
const DocumentCardActivityStyles: Partial<IDocumentCardActivityStyles> = { const DocumentCardActivityStyles: Partial<IDocumentCardActivityStyles> = {
root: { paddingBottom: 0 }, root: { paddingBottom: 0 },
}; };
@ -107,7 +106,6 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
}; };
let _activityUserEmail: string = "N/A"; let _activityUserEmail: string = "N/A";
let _activityUser: string = "N/A"; let _activityUser: string = "N/A";
let _activityDate: string = "N/A"; let _activityDate: string = "N/A";
@ -124,6 +122,8 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
OriginalPath, OriginalPath,
CreatedBy, CreatedBy,
Created, Created,
IsHubSite,
WebTemplate
} = props.site; } = props.site;
@ -208,7 +208,7 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
previewImages={[ previewImages={[
{ {
previewImageSrc: previewImageSrc:
SiteGroup == "OneDrive" ? _siteLogoOndrive : _siteLogoSP, WebTemplate == "SPSPERS" ? _siteLogoOndrive : _siteLogoSP,
width: 68, width: 68,
height: 68, height: 68,
imageFit: ImageFit.cover, imageFit: ImageFit.cover,
@ -229,15 +229,22 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
/> />
)} )}
{GroupId && (
{GroupId && GroupId !== "00000000-0000-0000-0000-000000000000" && ( // (is groupId = undefined or 000000-0000-0000-0000000000000 guid) this is showned is some personal drives
<Icon <Icon
styles={groupIconStyles} styles={groupIconStyles}
iconName="Group" iconName="Group"
title="Office 365 Group" title="Office 365 Group"
></Icon> ></Icon>
)} )}
{IsHubSite == "true" && (
{SiteGroup == "OneDrive" && ( <Icon
styles={groupIconStyles}
iconName="DrillExpand"
title="is Hub Site"
></Icon>
)}
{WebTemplate == "SPSPERS" && (
<Icon <Icon
styles={groupIconStyles} styles={groupIconStyles}
iconName="onedrive" iconName="onedrive"

View File

@ -17,7 +17,6 @@
"no-switch-case-fall-through": true, "no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true, "no-unnecessary-semicolons": true,
"no-unused-expression": true, "no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true, "no-with-statement": true,
"semicolon": true, "semicolon": true,
"trailing-comma": false, "trailing-comma": false,