Showing posts with label PnP Modern Search. Show all posts
Showing posts with label PnP Modern Search. Show all posts

Friday, October 27, 2023

Using PnP Modern Search to display today's work anniversaries

In a way this is part 2 on how to using PnP Modern Search to display days of celebration. In Using PnP Modern Search to display today's birthdays I showed how to display birthdays, so a natural progression is to show how to display work anniversaries.


I was really surprised how difficult this proved to be. I even discussed the topic with Mikael Svenson and Marc Anderson and I realized that I had to cheat in order for it to work.


The problem is that date manipulation in KQL is hard at best and sometimes impossible. In order to find a work anniversary I must compare Today and the Hiredate managed property, but ONLY the day and month part.

Calculating how many years the employee has been with the company is also required and I have NO idea how to get that using KQL.
























Objective

I wanted to be able to display two options: 
  • Employees having a work anniversary today, and also how many years they have been with the company
  • Employees having a work anniversary within the next 7 days + number of years.

How to cheat

In order to achieve the objectives, I had to get:
  • The account 
  • The hiredate, but with the year segment being the current year
  • The number of years the employee has been with company at the next anniversary




 










I added birthdayThisYear while I was at it, in order to be able to show upcoming birthdays.


The basic idea is that I change the hiredate from eg. 10/26/2005 to 10/26/2023 as that allows me to compare Today with this value πŸ˜€
(RefinableDate12 is mapped to the HiredateThisYear property)

Who is having a work anniversary today?
RefinableDate12=Today

Anniversary within the next x days is also a piece of cake:
 RefinableDate12<{Today+7} AND RefinableDate12>{Today}

Implementation

Asking the intern to keep the list above in sync would be a cruel and unusual punishment, and hence actually forbidden in the USA, something to do with a constitution or something like that.

The list should of course be synced with the source, in this case the User Profile Application, at least once each month. The reason I am not using Graph is that birthday is not in the schema.







Prereq: Map SPS-Birthday and SPS-Hiredate to RefinableDate00/01

Once data is showing up in those RefinableDate properties you should be ready to create the list.
Grab the script here . It will create a few Site Columns and a Content type on the site collection of your choosing. The list is then created, and the Content type added to the list.
The Add-UserDataToList function will query the UPA for accounts and write the data we need to the list.
 
Hit the Reindex site in the site collections Search and Offline Availability section for the site columns to be picked up by search.

Map the crawled properties to a couple of RefinableDates. I prefer to make this mapping on the tenant level as I usually don't know where I might need them in the future. 

Find the Content Type ID of the Content type created by the script.


Add a Results web part, name it "Todays work anniversaries"

Set the Query template to: 

Contenttypeid:0x01009290F0FA40E7CB42B55D6D96F897262B* RefinableDate12=Today

(replace 0x01009290F0FA40E7CB42B55D6D96F897262B with the value for the your content type) 

Add a Results web part, name it "Upcoming anniversaries (7 days)"

Set the Query template to: 

Contenttypeid:0x01009290F0FA40E7CB42B55D6D96F897262B* RefinableDate12<{Today+7} AND RefinableDate12>{Today}

(replace 0x01009290F0FA40E7CB42B55D6D96F897262B with the value for your content type) 


Add the managed properties birthdayhiredateAccountOWSUSER, nextWorkAnniversaryInYearsOWSTNMBR, RefinableDate10, RefinableDate12 to the Selected Properties in both web parts.

Enter Layout slots

Change UserEmail to use the  birthdayhiredateAccountOWSUSER property

Change Date from Created to RefinableDate12


For both web parts you can select the People layout on "page 2" in the web part configuration.

Set Secondary text to: 

{{{Title}}} has been with us {{{nextWorkAnniversaryInYearsOWSTNMBR}}} years

(remember to click Use Handlebars expression)

Set Tertiary text to:

{{getDate (slot item @root.slots.Date) "MMMM-D"}}

(remember to click Use Handlebars expression)

Set the sorting on the Upcoming Anniversaries to RefinableDate12 asc


Run the Add-UserDataToList  function in a runbook or similar once a month and you should be done😊

Sunday, October 22, 2023

Using PnP Modern Search to display today's birthdays

 

In the PnP Modern Search Repository bigbear48858 asked about a separate web part for showing birthdays and work anniversaries: 

Looking for help on filters for pnp search · microsoft-search/pnp-modern-search · Discussion #3300 (github.com)



That got me thinking about how to set up a Search Result web part to display today's birthdays.


The tricky part is that in the User profile application the birthday value is store is a rather usual datatype "date no year"








In my tenant the SPS-Birthday property was mapped to RefinableDate00 and the actual value in the property is 2000-[the date]:

(I used the magnificent SP Editor tool to inspect the managed properties)


So, the query had to be something like "those accounts where RefinableDate00 = 2000 + the value of today's date


In KQL we have the token "today" that will give us today's date, but as far as I know, we can't get the components the date consist of, like Month and Day.

Luckily, the PnP Modern search provides additional Tokens, see Tokens - PnP Modern Search (v4) (microsoft-search.github.io)


So the final query is:

RefinableDate00=2000-{CurrentMonth}-{CurrentDate}T00:00:00Z

            ( and of cause a criteria to exclude former employees, like SPS-HideFromAddressLists<>1) 


















This scenario will be added to the PnP Modern Search documentation ASAP :-)

Saturday, October 21, 2023

Work with your Handlebar based layouts in an interactive way

 

Just a small tips and tricks blog post about working with custom layouts in PnP Modern Search Results web parts.

The Results web part comes with a build in result template editor:








However, my preferred way to work with display template is to use the External Template URL:




  1. Select the out of the box template you intend to customize.
  2. Grap the base template config from the "Edit result template"
  3. Paste it into VS Code/Notepad and save it as a HTML file on your PC.
  4. Upload the file to a document library on your tenant
  5. Copy the URL of the HTML file (get it from Version history or similar)
  6. Paste it into the "Use an external template URL" field 
  7. Sync the document library to your PC
  8. Open the HTML file from the sync'ed document library on your PC using VS Code or similar.
  9. Update the code and save, and all it takes to see the change is a F5 on the page with the web part.

 Easy, right?  πŸ‘ŒπŸ˜Š

(And you get all of the modern editor stuff and GitHub Copilot as well)





Monday, October 9, 2023

New video on YouTube - Options for integrating 3rd party data with SP/MS Search as of Q4 2023

 


5 October I gave a presentation at the Danish SharePoint User Group's meeting, covering the options that we have as of Q4 2023 when we want to integrate 3rd party data into Microsoft or SharePoint Search.

This is a shortened version and in English rather than Danish.  ;-) 



View from MAN Energy (the host for the meeting, and thanks for that) is magnificent on a beautiful evening. 


Tuesday, September 12, 2023

How SharePoint handles DateTime stamps - and why it causes PnP Search Filter URLs to be offset

 

TimeZones and how SharePoint handles timestamps

We recently had an issue logged in the PnP Modern Search repository regarding why the date filter values were "wrong".

Actually, the date filter values were correct as they were offset to match the current users Time Zone, which can be pretty hard to understand, hence this summery:


Disclamer: I am in Denmark, currently in UTC+2 (summertime)

Each time a timestamp is saved in SharePoint it is saved in UTC0, so when I upload a document at 13.45 local time the timestamp saved in the backend will read 11.45.


But in the Document library it clearly shows 13.45, what gives?

Each time the SharePoint Front End displays a timestamp it is offset to the local timezone, so my colleague in Los Angeles looking at the same document will see the timestamp as 04.45 (9 hours behind my timezone).


However, when you get the timestamp from an API you get the time in UTC0 !!!!

So, if I am looking for documents that was created between 4 April and 10 April I must answer the question: from which timezone do I define when 4 April begins?

If I lived in New Zealand(UTC+12) 4 April starts a lot earlier than it does here in Denmark(UTC+2).

So, when somebody asks you to set up a PowerShell script to pull all Contracts (content type) created on 10 April, you have to ask is the offset should be based on the Time Zone of the requestor or something else.


In PnP Modern Search any time-based query is automatically offset to match the Time Zone of the current user. 
When I query for something created between 4 April and 10 April the query is changed to:
creased between 3 April 22h00m and 9 April 22h00m.
The reason is that when 4 April started in Denmark the time was 3 April 22h00m UTC0 which is the values in the backend.

You can find your current TimeZone info by navigating to a classic SP Page, hitting F12, go to the Console tab, enter _spPageContextInfo and hit Enter, look for webTimeZoneData:




Time Zones might be hard to deal with, but I guess it is there to stay :-)


Thursday, August 17, 2023

PnP Modern search: Set refiners in the URL

 

The PnP Modern Search Filter web part supports deep URL linking, so if you provide the correct URL both the Search text and selected filters can be activated once the page loads:









However, getting the correct URL can be tricky. The recommended approach is to set the search text and the relevant Filters and then grab the browser URL. It will look like this:


https://[Tenant].sharepoint.com/sites/PnPModernSearch/SitePages/Filters-read-from-url-are-not-set-in-the-filter-web-part--3157.aspx?q=cmo&f=%5B%7B%22filterName%22%3A%22RefinableString09%22%2C%22values%22%3A%5B%7B%22name%22%3A%22CMO%22%2C%22value%22%3A%22%C7%82%C7%82434d4f%22%2C%22operator%22%3A0%2C%22disabled%22%3Afalse%7D%5D%2C%22operator%22%3A%22or%22%7D%5D#

The components are: the URL to the page + the q section + the f section.

The q section is just the text to be applied in the Search Box. 

The f section is the Filter settings. It looks pretty messy, but this PowerShell snippet will clean it up:


$url = Read-Host "Enter URL"

$pageURL = $url.Substring(0, $url.IndexOf("?"))

$filterURL = $url.Substring($url.IndexOf("?")+1)

$filterURL = $filterURL.Replace("%5B", "[")

$filterURL = $filterURL.Replace("%7B", "{")

$filterURL = $filterURL.Replace("%22", '"')

$filterURL = $filterURL.Replace("%3A", ":")

$filterURL = $filterURL.Replace("%2C", ",")

$filterURL = $filterURL.Replace("%5D", "]")

$filterURL = $filterURL.Replace("%7D", "}")

$filterURL = $filterURL.Replace("%C7%82%C7%82", "Η‚Η‚")


$newUrl = $pageURL + "?" + $filterURL

$newUrl


And once cleaned up it looks like this:

f=[{"filterName":"RefinableString09","values":[{"name":"CMO","value":"Η‚Η‚434d4f","operator":0,"disabled":false}],"operator":"or"}]


The value tag above is the Filter Token, and you will be able to find those using the SP Search Query Tool.












Monday, May 22, 2023

SharePoint People search - How to clean up your results by exclude accounts


People search in SharePoint is based on the accounts you can see in the User Profiles blade in the SharePoint Admin center



And as you properly know the accounts in the User Profiles will very often be a mess of expired accounts, Meeting Room, External users, Test account and a lot more.


These are my notes for cleaning up the People/Employee search in SharePoint 



Exclude former employees

“SPS-HideFromAddressLists” originates in Exchange and is primarily used to decide which accounts that should not be shown in the Global Address list. In many companies/orgs this property is set to true/1 when somebody leaves the org.

This makes it a very useful property as it is the best indicator we have on whether an account is active or not.

"SPS-HideFromAddressLists"<>1  (show only accounts that should not be hidden in the GAL)

Exclude test, admin or similar accounts

-preferredname:admin*

-preferredname:test*


Exclude accounts which does not use a specific email domain

This filter is an excellent way to exclude external users and consultants with a full account within the org. Please note that we often exclude accounts by specifying that the field should contain a specific value.

WorkEmail:@contoso.com 


Exclude members of a specific department

-Department:External

-Department:Management

Exclude accounts which does not have a value in a given property

It is possible to define a query that will exclude the accounts that do not have any value in a specific field.

Since KQL (the Language we are using in the search queries) can't search for "Fields which does not contain any value" we have to use a little trick:

If we want to exclude the accounts that does not have a cell phone number, we simply must reverse that requirement, and hence it will be "include those accounts that have a value in the cell phone field":

(MobilPhone:0* OR MobilPhone:1* OR MobilPhone:2* OR MobilPhone:3* OR MobilPhone:4* OR MobilPhone:5* OR MobilPhone:6* OR MobilPhone:7* OR MobilPhone:8* OR MobilPhone:9*)


User Profile properties such as Department and JobTitle are based on Term Store Term sets and we can use the Term Guid values in our query.

In this example we are using the auto generated  property owstaxIdSPShDepartment and the guid specified is the root node of the Department term set: (look in the Term Store for those guids)

owstaxIdSPShDepartment:"#8ed8c9ea-7052-4c1d-a4d7-b9c10bffea6f" 


So using that in our query will insure that only account with a value in the Department field in included in our result. Neat, right?

Exclude accounts having a specific AccountName

-AccountName:Prod\B

This will enable you to exclude specific Accounts or groups of accounts like meeting rooms, cars and similar object as the AccountName often indicates which kind of account it is.

 

Search Scope

In this case search scope is defined as the list of fields/properties in the SharePoint User Profile used in the matching. By default we are matching against every field, however this will sometimes cause some rather strange results, espcially if you are not using Ranking.

Example: In this Org we have a department called QA, and would expect the members of that department to show up when doing a search for the query "QA", however Bob from Accounting shows up as number 4 result, ahead of several collegues from the QA department. Why?

Well, after going through Bobs account we can see he is a member of a security group named "Financial QA".......and that is sufficient to mess up the search results.

Of the UPA fields Description, Interests, Memberships and PeopleKeywords can contains values that causes unexpected results.

 

One option that will solve this issue could be to limit your search to only match on a set of specific fields. This will to some extend reduce the general usebility of people search as the end users might not be awere of this limitation.

Often these fields are used in such at limited search: 

FirstName

LastName

PreferredName

Department

PeopleKeywords

WorkEmail

MobilePhone

Location

Responsibilities

PastProjects

MobilePhone

Interests

Description

JobTitle

MobilePhone

Skills


Ranking and sorting

Ranking and sorting decides in which order the search results will be shown. 


Sorting by Name

FirstName

LastName

PreferredName

 

Sorting using XRANK

XRANK specifies a numeric value for each result, usually calculated by Microsoft using the selected Ranking Model. But we are able to tweak this ranking calculation by boosting results that matches a keyword. 

Example:

XRANK(cb=1.5) FirstName:{searchTerms} OR XRANK(cb=0.5) Department:{searchTerms}

In this case we are boosting an account by factor 1.5 (150%) if the query matches FirstName and by 50% if the query matches Department.

Ranking model

In most cases we are using the ”People Search social distance model” ranking model as it is a good allround model for people search.

 

Additional info: Tech and me: People Search ranking for dummies (techmikael.com)

 

Tooling

When working with User profile data and search you should be familiar with these tools:

SharePoint Administation, specificly User Profiles (raw data) and the Search Schema on the tenant level ( mapning fields from UPA to Managed Properties)

SP Query Tool, PnP-Tools/Solutions/SharePoint.Search.QueryTool at master · pnp/PnP-Tools · GitHub, a desktop app that allows you to test both your quiries and your data.

 

SP Editor, a Chrome/Edge extension , GitHub - tavikukko/Chrome-SP-Editor: Extension for creating and updating files (js, css) in SharePoint Online from Developer Tools

Similar to the SP Quiry Tool as far as Search goes, and contains a lot of other SP related features.