From c09c5c522545913cf99badf58ce0135e2ec8f938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Sat, 8 Aug 2020 21:52:37 +0100 Subject: [PATCH 1/2] Add new option on filter --- samples/react-my-sites/README.md | 2 + .../src/Entities/EnumFilters.ts | 5 +- .../react-my-sites/src/Hooks/useUserSites.ts | 63 +++- .../components/MySites/IMySitesState.ts | 4 + .../mySites/components/MySites/MySites.tsx | 301 +++++++++++------- .../mySites/components/SiteTile/SiteTile.tsx | 21 +- samples/react-my-sites/tslint.json | 59 ++-- 7 files changed, 297 insertions(+), 158 deletions(-) diff --git a/samples/react-my-sites/README.md b/samples/react-my-sites/README.md index 7274bc9be..dbe14fa05 100644 --- a/samples/react-my-sites/README.md +++ b/samples/react-my-sites/README.md @@ -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.    + + ![MySites](./assets/MySites.gif) ![MySites](./assets/Screenshot%202020-08-06%20at%2013.50.51.png) diff --git a/samples/react-my-sites/src/Entities/EnumFilters.ts b/samples/react-my-sites/src/Entities/EnumFilters.ts index bb77bcf70..97a2ce3f1 100644 --- a/samples/react-my-sites/src/Entities/EnumFilters.ts +++ b/samples/react-my-sites/src/Entities/EnumFilters.ts @@ -1,6 +1,7 @@ export enum Filters { - "SharePoint", + "All", "Group", "OneDrive", - "All", + "SharePoint", + "Site" } diff --git a/samples/react-my-sites/src/Hooks/useUserSites.ts b/samples/react-my-sites/src/Hooks/useUserSites.ts index 610b95bed..068b31cbb 100644 --- a/samples/react-my-sites/src/Hooks/useUserSites.ts +++ b/samples/react-my-sites/src/Hooks/useUserSites.ts @@ -49,7 +49,8 @@ export const useUserSites = () => { const getUserSites = async ( searchString?: string, itemsPerPage?: number, - filter?: Filters + filter?: Filters, + site?:string ): Promise => { let searchResults: SearchResults = null; 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*`; break; case Filters.OneDrive: - _filter = " SiteGroup:Onedrive"; + _filter = " WebTemplate:SPSPERS"; // OneDrive + // _filter = " SiteGroup:Onedrive"; 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*)"; break; + case Filters.Site: + _filter = `Path:${site}`; + + break; } const q = SearchQueryBuilder( @@ -79,6 +85,11 @@ export const useUserSites = () => { Direction: SortDirection.Descending, }) .selectProperties( + "ParentLink", + "SPSiteURL", + "SiteID", + "SPWebUrl", + "WebId", "SiteLogo", "SiteClosed", "RelatedHubSites", @@ -94,8 +105,10 @@ export const useUserSites = () => { "ModifiedById", "LastModifiedTime", "OriginalPath", + "Path", "Title", - "Created" + "Created", + "WebTemplate" ); const results = await sp.search(q); searchResults = results; // set the current results @@ -103,5 +116,47 @@ export const useUserSites = () => { return searchResults; }; - return { getUserSites, checkGroupHasTeam }; +// Get User Sites +const getUserWebs = async ( + +): Promise => { + 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 }; }; diff --git a/samples/react-my-sites/src/webparts/mySites/components/MySites/IMySitesState.ts b/samples/react-my-sites/src/webparts/mySites/components/MySites/IMySitesState.ts index b23d072e1..d6949b51b 100644 --- a/samples/react-my-sites/src/webparts/mySites/components/MySites/IMySitesState.ts +++ b/samples/react-my-sites/src/webparts/mySites/components/MySites/IMySitesState.ts @@ -1,3 +1,4 @@ +import { IContextualMenuProps } from "office-ui-fabric-react"; export interface IMySitesState { sites: any[]; @@ -9,4 +10,7 @@ export interface IMySitesState { totalPages:number; searchValue:string; currentFilter?:number; + currentFilterName?:string; + currentSelectedSite?:string; + filterMenuProps: IContextualMenuProps; } diff --git a/samples/react-my-sites/src/webparts/mySites/components/MySites/MySites.tsx b/samples/react-my-sites/src/webparts/mySites/components/MySites/MySites.tsx index 498a2cca0..e5d1fa80a 100644 --- a/samples/react-my-sites/src/webparts/mySites/components/MySites/MySites.tsx +++ b/samples/react-my-sites/src/webparts/mySites/components/MySites/MySites.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Filters} from '../../../../Entities/EnumFilters'; +import { Filters } from "../../../../Entities/EnumFilters"; import "./paginationOverride.module.scss"; import { IMySitesProps } from "./IMySitesProps"; import { escape } from "@microsoft/sp-lodash-subset"; @@ -18,6 +18,7 @@ import { IContextualMenuItem, FontIcon, Label, + ContextualMenuItemType, } from "office-ui-fabric-react"; import { WebPartTitle } from "@pnp/spfx-controls-react"; import { useUserSites } from "../../../../Hooks/useUserSites"; @@ -31,7 +32,11 @@ import _ from "lodash"; import { MSGraphClient } from "@microsoft/sp-http"; 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 = ( props: IMySitesProps @@ -47,12 +52,11 @@ export const MySites: React.FunctionComponent = ( }, webPartTile: { fontWeight: 500, - marginBottom: 20 + marginBottom: 20, }, }); // Document Card Styles - // state const [state, setState] = React.useState({ errorMessage: "", @@ -61,32 +65,40 @@ export const MySites: React.FunctionComponent = ( hasError: false, title: props.title, currentPage: 1, - totalPages: 50, + totalPages: 0, searchValue: "", currentFilter: Filters.All, + currentFilterName: "All", + currentSelectedSite: undefined, + filterMenuProps: undefined, }); const filterIcon: IIconProps = { iconName: "Filter" }; - // Get Hook functions - const { getUserSites } = useUserSites(); - // get User Sites const _getUserSites = async ( searchString?: string, - currentFilter?: Filters + currentFilter?: Filters, + currentFilterName?:string, + site?:string ) => { try { - console.log('tiles var', props.themeVariant); setState({ ...state, isLoading: true }); const { itemsPerPage } = props; - const searchResults = await getUserSites(searchString, itemsPerPage, currentFilter); + const searchResults = await getUserSites( + searchString, + itemsPerPage, + currentFilter, + site + ); _searchResults = searchResults; let _totalPages: number = searchResults.TotalRows / itemsPerPage; const _modulus: number = searchResults.TotalRows % itemsPerPage; _totalPages = _modulus > 0 ? toInteger(_totalPages) + 1 : toInteger(_totalPages); + setState({ + ...state, searchValue: "", currentPage: 1, totalPages: _totalPages, @@ -95,7 +107,11 @@ export const MySites: React.FunctionComponent = ( hasError: false, errorMessage: "", sites: _searchResults.PrimarySearchResults, - currentFilter: currentFilter + currentFilter: currentFilter, + currentSelectedSite: site, + currentFilterName: currentFilterName, + // tslint:disable-next-line: no-use-before-declare + filterMenuProps: _filterMenuProps, }); } catch (error) { console.log(error); @@ -108,110 +124,154 @@ export const MySites: React.FunctionComponent = ( } }; - const _Filtersites = async (filter: string) => { - setState({ ...state, isLoading: true }); - let _filteredSites: any[] = []; + const _Filtersites = async (filter: string, site?:string) => { switch (filter) { case "All": - setState({...state,currentFilter: Filters.All}); - await _getUserSites('', Filters.All); + await _getUserSites("", Filters.All, "All"); break; case "Groups": - setState({...state,currentFilter: Filters.Group}); - await _getUserSites('', Filters.Group); + await _getUserSites("", Filters.Group, "Groups"); break; case "OneDrive": - setState({...state,currentFilter: Filters.OneDrive}); - await _getUserSites('', Filters.OneDrive); + await _getUserSites("", Filters.OneDrive, "OneDrive"); break; case "SharePoint": - setState({...state,currentFilter: Filters.SharePoint}); - await _getUserSites('', Filters.SharePoint); + await _getUserSites("", Filters.SharePoint, "SharePoint"); break; default: - setState({ ...state, isLoading: false }); - break; + await _getUserSites("", Filters.Site, filter, site); } }; - const filterMenuProps: IContextualMenuProps = { - items: [ - { - key: "0", - text: "All", - iconProps: { iconName: "ThumbnailView" }, - onClick: ( - ev: - | React.MouseEvent - | React.KeyboardEvent, - item: IContextualMenuItem - ) => { - _Filtersites(item.text); - }, - }, - { - key: "1", - text: "SharePoint", - iconProps: { iconName: "SharepointAppIcon16" }, - onClick: ( - ev: - | React.MouseEvent - | React.KeyboardEvent, - item: IContextualMenuItem - ) => { - _Filtersites(item.text); - }, - }, - { - key: "2", - text: "Groups", - iconProps: { iconName: "Group" }, - onClick: ( - ev: - | React.MouseEvent - | React.KeyboardEvent, - item: IContextualMenuItem - ) => { - _Filtersites(item.text); - }, - }, - { - key: "3", - text: "OneDrive", - iconProps: { iconName: "onedrive" }, - onClick: ( - ev: - | React.MouseEvent - | React.KeyboardEvent, - item: IContextualMenuItem - ) => { - _Filtersites(item.text); - }, - }, - ], - }; + // useEffect component did mount or modified React.useEffect(() => { (async () => { _msGraphClient = await props.context.msGraphClientFactory.getClient(); - await _getUserSites("",state.currentFilter); + + const _sitesWithSubSties = await getUserWebs(); + console.log("subsites", _sitesWithSubSties); + const _uniqweb = _.uniqBy( + _sitesWithSubSties.PrimarySearchResults, + "ParentLink" + ); + + + _filterMenuProps = { + + items: [ + { + key: "0", + text: "All", + iconProps: { iconName: "ThumbnailView" }, + onClick: ( + ev: + | React.MouseEvent + | React.KeyboardEvent, + item: IContextualMenuItem + ) => { + _Filtersites(item.text); + }, + }, + { + key: "1", + text: "SharePoint", + iconProps: { iconName: "SharepointAppIcon16" }, + onClick: ( + ev: + | React.MouseEvent + | React.KeyboardEvent, + item: IContextualMenuItem + ) => { + _Filtersites(item.text); + }, + }, + { + key: "2", + text: "Groups", + iconProps: { iconName: "Group" }, + onClick: ( + ev: + | React.MouseEvent + | React.KeyboardEvent, + item: IContextualMenuItem + ) => { + _Filtersites(item.text); + }, + }, + { + key: "3", + text: "OneDrive", + iconProps: { iconName: "onedrive" }, + onClick: ( + ev: + | React.MouseEvent + | React.KeyboardEvent, + item: IContextualMenuItem + ) => { + _Filtersites(item.text); + }, + }, + ], + }; + + + if (_sitesWithSubSties.PrimarySearchResults.length > 0){ + _filterMenuProps.items.push( + { + 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 + | React.KeyboardEvent, + 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]); // On Search Sites const _onSearch = async (value: string) => { - await _getUserSites(value,state.currentFilter); + await _getUserSites(value, state.currentFilter,state.currentFilterName, state.currentSelectedSite); }; // On Search Sites const _onClear = async (ev: any) => { - await _getUserSites("",state.currentFilter); + await _getUserSites("", state.currentFilter,state.currentFilterName, state.currentSelectedSite); }; - -// Render component - if (state.hasError) { // render message error + // Render component + if (state.hasError) { + // render message error return ( {state.errorMessage} @@ -229,9 +289,8 @@ export const MySites: React.FunctionComponent = ( themeVariant={props.themeVariant} updateProperty={props.updateProperty} className={stylesComponent.webPartTile} - /> - + = ( /> {state.isLoading ? ( @@ -259,29 +319,40 @@ export const MySites: React.FunctionComponent = ( > ) : ( <> - { // has sites ? - - state.sites.length > 0 ? -
- {state.sites.map((site:any, i: number) => { - return ( - - ); - })} -
- : - <> - - - - - - - } + { + // has sites ? + state.sites.length > 0 ? ( +
+ {state.sites.map((site: any, i: number) => { + return ( + + ); + })} +
+ ) : ( + <> + + + + + + ) + } {state.totalPages > 1 && ( <> @@ -296,7 +367,7 @@ export const MySites: React.FunctionComponent = ( color="primary" count={state.totalPages} page={state.currentPage} - onChange={async (event:any, page:number) => { + onChange={async (event: any, page: number) => { const rs = await _searchResults.getPage(page); _searchResults = rs; setState({ diff --git a/samples/react-my-sites/src/webparts/mySites/components/SiteTile/SiteTile.tsx b/samples/react-my-sites/src/webparts/mySites/components/SiteTile/SiteTile.tsx index 2810e845e..f22cda897 100644 --- a/samples/react-my-sites/src/webparts/mySites/components/SiteTile/SiteTile.tsx +++ b/samples/react-my-sites/src/webparts/mySites/components/SiteTile/SiteTile.tsx @@ -94,10 +94,9 @@ export const SiteTile: React.FunctionComponent = ( fontSize: 20, color: props.themeVariant ? props.themeVariant.palette.themePrimary: 'white', marginTop: 8, - marginRight: 16, + marginRight: 7, }, }; - const DocumentCardActivityStyles: Partial = { root: { paddingBottom: 0 }, }; @@ -107,7 +106,6 @@ export const SiteTile: React.FunctionComponent = ( }; - let _activityUserEmail: string = "N/A"; let _activityUser: string = "N/A"; let _activityDate: string = "N/A"; @@ -124,6 +122,8 @@ export const SiteTile: React.FunctionComponent = ( OriginalPath, CreatedBy, Created, + IsHubSite, + WebTemplate } = props.site; @@ -208,7 +208,7 @@ export const SiteTile: React.FunctionComponent = ( previewImages={[ { previewImageSrc: - SiteGroup == "OneDrive" ? _siteLogoOndrive : _siteLogoSP, + WebTemplate == "SPSPERS" ? _siteLogoOndrive : _siteLogoSP, width: 68, height: 68, imageFit: ImageFit.cover, @@ -229,15 +229,22 @@ export const SiteTile: React.FunctionComponent = ( /> )} - {GroupId && ( + + {GroupId && GroupId !== "00000000-0000-0000-0000-000000000000" && ( // (is groupId = undefined or 000000-0000-0000-0000000000000 guid) this is showned is some personal drives )} - - {SiteGroup == "OneDrive" && ( + {IsHubSite == "true" && ( + + )} + {WebTemplate == "SPSPERS" && ( Date: Sat, 29 Aug 2020 16:14:27 -0400 Subject: [PATCH 2/2] Updated solution number and readme --- samples/react-my-sites/README.md | 1 + samples/react-my-sites/config/package-solution.json | 2 +- samples/react-my-sites/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/react-my-sites/README.md b/samples/react-my-sites/README.md index dbe14fa05..abb7e604b 100644 --- a/samples/react-my-sites/README.md +++ b/samples/react-my-sites/README.md @@ -58,6 +58,7 @@ react-my-sites|João Mendes Version|Date|Comments -------|----|-------- 1.0.0|August 6, 2020|Initial release +1.0.1|August 29, 2020|Additional updates ## Disclaimer diff --git a/samples/react-my-sites/config/package-solution.json b/samples/react-my-sites/config/package-solution.json index 75b88baa1..fff3cab88 100644 --- a/samples/react-my-sites/config/package-solution.json +++ b/samples/react-my-sites/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "react-my-sites-client-side-solution", "id": "ad28b382-886b-4b2a-9646-92de8a0b1d13", - "version": "1.0.0.0", + "version": "1.0.1.0", "includeClientSideAssets": true, "skipFeatureDeployment": true, "isDomainIsolated": false, diff --git a/samples/react-my-sites/package.json b/samples/react-my-sites/package.json index ff89d0020..2aefc91f0 100644 --- a/samples/react-my-sites/package.json +++ b/samples/react-my-sites/package.json @@ -1,6 +1,6 @@ { "name": "react-my-sites", - "version": "0.0.1", + "version": "1.0.1", "private": true, "main": "lib/index.js", "engines": {