import * as React from 'react' ; import { IDataSourceData , BaseDataSource , ITokenService , ITemplateSlot , IDataFilterResult , IDataFilterResultValue , BuiltinTemplateSlots , FilterBehavior , FilterSortType , FilterSortDirection } from "@pnp/modern-search-extensibility" ; import { IPropertyPaneGroup , IPropertyPaneDropdownOption , PropertyPaneDropdownOptionType , PropertyPaneToggle , PropertyPaneDropdown , PropertyPaneLabel } from "@microsoft/sp-property-pane" ; import * as commonStrings from 'CommonStrings' ; import { ServiceScope , Guid , Text } from '@microsoft/sp-core-library' ; import { sortBy , isEmpty , uniq , cloneDeep } from "@microsoft/sp-lodash-subset" ; import { PagingBehavior } from "@pnp/modern-search-extensibility" ; import { IDataContext } from "@pnp/modern-search-extensibility" ; import { ISharePointSearchService } from "../services/searchService/ISharePointSearchService" ; import { SharePointSearchService } from "../services/searchService/SharePointSearchService" ; import LocalizationHelper from "../helpers/LocalizationHelper" ; import { PageContext } from "@microsoft/sp-page-context" ; import { TokenService } from "../services/tokenService/TokenService" ; import { ITaxonomyService } from "../services/taxonomyService/ITaxonomyService" ; import { TaxonomyService } from "../services/taxonomyService/TaxonomyService" ; import { ISharePointSearchResult } from "../models/search/ISharePointSearchResults" ; import { ILocalizableSearchResult , ILocalizableSearchResultProperty } from "../models/search/ILocalizableSearchResult" ; import { PropertyPaneAsyncCombo } from "../controls/PropertyPaneAsyncCombo/PropertyPaneAsyncCombo" ; import { IComboBoxOption } from "office-ui-fabric-react" ; import { ISharePointSearchQuery , SortDirection , ISort } from "../models/search/ISharePointSearchQuery" ; import update from 'immutability-helper' ; import { AsyncCombo } from '../controls/PropertyPaneAsyncCombo/components/AsyncCombo' ; import { IAsyncComboProps } from '../controls/PropertyPaneAsyncCombo/components/IAsyncComboProps' ; import { DateHelper } from '../helpers/DateHelper' ; import { PropertyPaneNonReactiveTextField } from '../controls/PropertyPaneNonReactiveTextField/PropertyPaneNonReactiveTextField' ; import { ITerm } from '../services/taxonomyService/ITaxonomyItems' ; import { DataFilterHelper } from '../helpers/DataFilterHelper' ; import { ISortFieldConfiguration , SortFieldDirection } from '../models/search/ISortFieldConfiguration' ; import { EnumHelper } from '../helpers/EnumHelper' ; export enum BuiltinSourceIds { Documents = 'e7ec8cee-ded8-43c9-beb5-436b54b31e84' , ItemsMatchingContentType = '5dc9f503-801e-4ced-8a2c-5d1237132419' , ItemsMatchingTag = 'e1327b9c-2b8c-4b23-99c9-3730cb29c3f7' , ItemsRelatedToCurrentUser = '48fec42e-4a92-48ce-8363-c2703a40e67d' , ItemsWithSameKeywordAsThisItem = '5c069288-1d17-454a-8ac6-9c642a065f48' , LocalPeopleResults = 'b09a7990-05ea-4af9-81ef-edfab16c4e31' , LocalReportsAndDataResults = '203fba36-2763-4060-9931-911ac8c0583b' , LocalSharePointResults = '8413cd39-2156-4e00-b54d-11efd9abdb89' , LocalVideoResults = '78b793ce-7956-4669-aa3b-451fc5defebf' , Pages = '5e34578e-4d08-4edc-8bf3-002acf3cdbcc' , Pictures = '38403c8c-3975-41a8-826e-717f2d41568a' , Popular = '97c71db1-58ce-4891-8b64-585bc2326c12' , RecentlyChangedItems = 'ba63bbae-fa9c-42c0-b027-9a878f16557c' , RecommendedItems = 'ec675252-14fa-4fbe-84dd-8d098ed74181' , Wiki = '9479bf85-e257-4318-b5a8-81a180f5faa1' } /**
 * SharePoint search data source property pane properties
 */ export interface ISharePointSearchDataSourceProperties { /**
     * The search query template
     */ queryTemplate : string ; /**
     * SharePoint result source GUID
     */ resultSourceId : string ; /**
     * Flag indicating if the query rules should enabled/disabled
     */ enableQueryRules : boolean ; /**
     * The KQL or FQL refinement filters to apply to the query
     */ refinementFilters : string ; /**
     * Flag indicating if the query should be localized
     */ enableLocalization : boolean ; /**
     * The search query language to use (locale ID)
     */ searchQueryLanguage : number ; /**
     * The search managed properties to retrieve
     */ selectedProperties : string [ ] ; /**
     * The sort fields configuration
     */ sortList : ISortFieldConfiguration [ ] ; /**
     * Flag indicating if the audience targeting should be enabled
     */ enableAudienceTargeting : boolean ; } export class SharePointSearchDataSource extends BaseDataSource < ISharePointSearchDataSourceProperties > { private _availableLanguages : IPropertyPaneDropdownOption [ ] = [ ] ; private _availableManagedProperties : IComboBoxOption [ ] = [ ] ; private _resultSourcesOptions : IComboBoxOption [ ] = [ ] ; private _sharePointSearchService : ISharePointSearchService ; private _pageContext : PageContext ; private _tokenService : ITokenService ; private _taxonomyService : ITaxonomyService ; private _currentLocaleId : number ; private _propertyFieldCollectionData : any = null ; private _customCollectionFieldType : any = null ; private _propertyPaneWebPartInformation : any = null ; /**
     * The data source items count
     */ private _itemsCount : number ; private dateHelper : DateHelper ; /**
    * The moment.js library reference
    */ private moment : any ; public constructor ( serviceScope ) { super ( serviceScope ) ; serviceScope . whenFinished ( ( ) => { this . _sharePointSearchService = serviceScope . consume < ISharePointSearchService > ( SharePointSearchService . ServiceKey ) ; this . _pageContext = serviceScope . consume < PageContext > ( PageContext . serviceKey ) ; this . _tokenService = serviceScope . consume < ITokenService > ( TokenService . ServiceKey ) ; this . _taxonomyService = serviceScope . consume < ITaxonomyService > ( TaxonomyService . ServiceKey ) ; } ) ; } public async onInit ( ) { this . initProperties ( ) ; this . dateHelper = this . serviceScope . consume < DateHelper > ( DateHelper . ServiceKey ) ; this . moment = await this . dateHelper . moment ( ) ; if ( this . editMode ) { const { PropertyFieldCollectionData , CustomCollectionFieldType } = await import ( '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData' ) ; const { PropertyPaneWebPartInformation } = await import ( '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation' ) ; this . _propertyFieldCollectionData = PropertyFieldCollectionData ; this . _customCollectionFieldType = CustomCollectionFieldType ; this . _propertyPaneWebPartInformation = PropertyPaneWebPartInformation ; } let culture = this . getTranslatedCultureFromUrl ( ) ; if ( culture ) { this . _currentLocaleId = LocalizationHelper . getLocaleId ( culture ) ; } if ( ! culture || this . _currentLocaleId === 0 ) { this . _currentLocaleId = LocalizationHelper . getLocaleId ( this . _pageContext . cultureInfo . currentUICultureName ) ; } if ( this . _availableLanguages . length == 0 ) { const languages = await this . _sharePointSearchService . getAvailableQueryLanguages ( ) ; this . _availableLanguages = languages . map ( language => { return { key : language . Lcid , text : `${ language . DisplayName } (${ language . Lcid })` } ; } ) ; } } /**
    * Pick culture from url in translated pages as they are folder names like: "en", "no", "de"
    */ private getTranslatedCultureFromUrl ( ) { const pathParts = window . location . pathname . toLocaleLowerCase ( ) . split ( '/' ) ; const cultureFolderCandidate = pathParts [ pathParts . length - 2 ] ; if ( cultureFolderCandidate . length == 2 ) return cultureFolderCandidate ; return null ; } public async getData ( dataContext ) { const searchQuery = await this . buildSharePointSearchQuery ( dataContext ) ; const results = await this . _sharePointSearchService . search ( searchQuery ) ; let data = { items : results . relevantResults , filters : results . refinementResults , queryModification : results . queryModification , secondaryResults : results . secondaryResults , spellingSuggestion : results . spellingSuggestion , promotedResults : results . promotedResults } ; if ( this . properties . enableLocalization ) { const localizedFilters = await this . _getLocalizedFilters ( data . filters , this . _currentLocaleId ) ; data . filters = localizedFilters ; const localizedResults = await this . _getLocalizedMetadata ( data . items , this . _currentLocaleId ) ; data . items = localizedResults ; } this . _itemsCount = results . totalRows ; return data ; } public getPropertyPaneGroupsConfiguration ( ) { return [ { groupName : commonStrings . DataSources . SharePointSearch . SourceName , groupFields : [ PropertyPaneLabel ( '' , { text : commonStrings . DataSources . SharePointSearch . QueryTextFieldLabel } ) , this . _propertyPaneWebPartInformation ( { description : `<em>${ commonStrings . DataSources . SharePointSearch . QueryTextFieldInfoMessage }</em>` , key : 'queryText' } ) , new PropertyPaneNonReactiveTextField ( 'dataSourceProperties.queryTemplate' , { defaultValue : this . properties . queryTemplate , label : commonStrings . DataSources . SharePointSearch . QueryTemplateFieldLabel , placeholderText : commonStrings . DataSources . SharePointSearch . QueryTemplatePlaceHolderText , multiline : true , description : commonStrings . DataSources . SharePointSearch . QueryTemplateFieldDescription , applyBtnText : commonStrings . DataSources . SharePointSearch . ApplyQueryTemplateBtnText , allowEmptyValue : false , rows : 8 } ) , new PropertyPaneAsyncCombo ( 'dataSourceProperties.resultSourceId' , { availableOptions : this . _resultSourcesOptions , allowMultiSelect : false , allowFreeform : true , description : commonStrings . DataSources . SharePointSearch . ResultSourceIdDescription , label : commonStrings . DataSources . SharePointSearch . ResultSourceIdLabel , onLoadOptions : this . getBuiltinSourceIdOptions . bind ( this ) , searchAsYouType : false , defaultSelectedKey : this . properties . resultSourceId , textDisplayValue : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , this . properties . resultSourceId ) !== null ? EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , this . properties . resultSourceId ) : this . properties . resultSourceId , onGetErrorMessage : this . validateSourceId . bind ( this ) , onPropertyChange : this . onCustomPropertyUpdate . bind ( this ) , onUpdateOptions : ( ( options ) => { this . _resultSourcesOptions = options ; } ) . bind ( this ) } ) , new PropertyPaneAsyncCombo ( 'dataSourceProperties.selectedProperties' , { availableOptions : this . _availableManagedProperties , allowMultiSelect : true , allowFreeform : true , description : commonStrings . DataSources . SharePointSearch . SelectedPropertiesFieldDescription , label : commonStrings . DataSources . SharePointSearch . SelectedPropertiesFieldLabel , placeholder : commonStrings . DataSources . SharePointSearch . SelectedPropertiesPlaceholderLabel , onLoadOptions : this . getAvailableProperties . bind ( this ) , searchAsYouType : false , clearTextOnFocus : true , defaultSelectedKeys : this . properties . selectedProperties , onPropertyChange : this . onCustomPropertyUpdate . bind ( this ) , onUpdateOptions : ( ( options ) => { this . _availableManagedProperties = this . parseAndCleanOptions ( options ) ; } ) . bind ( this ) } ) , this . _propertyFieldCollectionData ( 'dataSourceProperties.sortList' , { manageBtnLabel : commonStrings . DataSources . SearchCommon . Sort . EditSortLabel , key : 'sortList' , enableSorting : true , panelHeader : commonStrings . DataSources . SearchCommon . Sort . EditSortLabel , panelDescription : commonStrings . DataSources . SearchCommon . Sort . SortListDescription , label : commonStrings . DataSources . SearchCommon . Sort . SortPropertyPaneFieldLabel , value : this . properties . sortList , fields : [ { id : 'sortField' , title : commonStrings . DataSources . SearchCommon . Sort . SortFieldColumnLabel , type : this . _customCollectionFieldType . custom , required : true , onCustomRender : ( ( field , value , onUpdate , item , itemId , onError ) => { return React . createElement ( "div" , { key : `${ field . id }-${ itemId }` } , React . createElement ( AsyncCombo , { defaultSelectedKey : item [ field . id ] ? item [ field . id ] : '' , onUpdate : ( option ) => { this . _sharePointSearchService . validateSortableProperty ( option . key as string ) . then ( ( sortable ) => { if ( ! sortable ) { onError ( field . id , commonStrings . DataSources . SearchCommon . Sort . SortInvalidSortableFieldMessage ) ; } else { onUpdate ( field . id , option . key as string ) ; onError ( field . id , '' ) ; } } ) ; } , allowMultiSelect : false , allowFreeform : true , availableOptions : this . _availableManagedProperties , onLoadOptions : this . getAvailableProperties . bind ( this ) , onUpdateOptions : ( ( options ) => { this . _availableManagedProperties = options ; } ) . bind ( this ) , clearTextOnFocus : true , placeholder : commonStrings . DataSources . SearchCommon . Sort . SortFieldColumnPlaceholder , useComboBoxAsMenuWidth : false } as IAsyncComboProps ) ) ; } ) . bind ( this ) } , { id : 'sortDirection' , title : commonStrings . DataSources . SearchCommon . Sort . SortDirectionColumnLabel , type : this . _customCollectionFieldType . dropdown , required : true , options : [ { key : SortFieldDirection . Ascending , text : commonStrings . DataSources . SearchCommon . Sort . SortDirectionAscendingLabel } , { key : SortFieldDirection . Descending , text : commonStrings . DataSources . SearchCommon . Sort . SortDirectionDescendingLabel } ] , defaultValue : SortFieldDirection . Ascending } ] } ) , new PropertyPaneNonReactiveTextField ( 'dataSourceProperties.refinementFilters' , { defaultValue : this . properties . refinementFilters , label : commonStrings . DataSources . SharePointSearch . RefinementFilters , placeholderText : `ex: FileType:equals("docx")` , multiline : true , allowEmptyValue : true , description : commonStrings . DataSources . SharePointSearch . RefinementFiltersDescription , applyBtnText : commonStrings . DataSources . SharePointSearch . ApplyQueryTemplateBtnText , rows : 3 } ) , PropertyPaneDropdown ( 'dataSourceProperties.searchQueryLanguage' , { label : commonStrings . DataSources . SharePointSearch . QueryCultureLabel , options : [ { type : PropertyPaneDropdownOptionType . Normal , key : this . _currentLocaleId , text : commonStrings . DataSources . SharePointSearch . QueryCultureUseUiLanguageLabel } as IPropertyPaneDropdownOption ] . concat ( sortBy ( this . _availableLanguages , [ 'text' ] ) ) , selectedKey : this . properties . searchQueryLanguage ? this . properties . searchQueryLanguage : this . _currentLocaleId } ) , PropertyPaneToggle ( 'dataSourceProperties.enableQueryRules' , { label : commonStrings . DataSources . SharePointSearch . EnableQueryRulesLabel , checked : this . properties . enableQueryRules , } ) , PropertyPaneToggle ( 'dataSourceProperties.enableAudienceTargeting' , { label : commonStrings . DataSources . SharePointSearch . EnableAudienceTargetingTglLabel , checked : this . properties . enableAudienceTargeting , } ) , PropertyPaneToggle ( 'dataSourceProperties.enableLocalization' , { checked : this . properties . enableLocalization , label : commonStrings . DataSources . SharePointSearch . EnableLocalizationLabel , onText : commonStrings . DataSources . SharePointSearch . EnableLocalizationOnLabel , offText : commonStrings . DataSources . SharePointSearch . EnableLocalizationOffLabel } ) ] } ] ; } public onCustomPropertyUpdate ( propertyPath , newValue ) { if ( propertyPath . localeCompare ( 'dataSourceProperties.selectedProperties' ) === 0 ) { let options = this . parseAndCleanOptions ( ( cloneDeep ( newValue ) as IComboBoxOption [ ] ) ) ; this . properties . selectedProperties = options . map ( v => { return v . key as string ; } ) ; this . context . propertyPane . refresh ( ) ; this . render ( ) ; } if ( propertyPath . localeCompare ( 'dataSourceProperties.resultSourceId' ) === 0 ) { this . properties . resultSourceId = ( newValue as IComboBoxOption ) . key as string ; this . context . propertyPane . refresh ( ) ; this . render ( ) ; } } public onPropertyUpdate ( propertyPath , oldValue , newVlue ) { if ( propertyPath . localeCompare ( 'dataSourceProperties.enableLocalization' ) === 0 && this . properties . enableLocalization ) { if ( this . properties . selectedProperties . indexOf ( 'UniqueID' ) === - 1 ) { this . properties . selectedProperties = update ( this . properties . selectedProperties , { $push : [ 'UniqueID' ] } ) ; } } } public getPagingBehavior ( ) { return PagingBehavior . Dynamic ; } public getFilterBehavior ( ) { return FilterBehavior . Dynamic ; } public getItemCount ( ) { return this . _itemsCount ; } public getTemplateSlots ( ) { return [ { slotName : BuiltinTemplateSlots . Title , slotField : 'Title' } , { slotName : BuiltinTemplateSlots . Path , slotField : 'DefaultEncodingURL' } , { slotName : BuiltinTemplateSlots . Summary , slotField : 'HitHighlightedSummary' } , { slotName : BuiltinTemplateSlots . FileType , slotField : 'FileType' } , { slotName : BuiltinTemplateSlots . PreviewImageUrl , slotField : 'AutoPreviewImageUrl' } , { slotName : BuiltinTemplateSlots . PreviewUrl , slotField : 'AutoPreviewUrl' } , { slotName : BuiltinTemplateSlots . Author , slotField : 'AuthorOWSUSER' } , { slotName : BuiltinTemplateSlots . Tags , slotField : 'owstaxidmetadataalltagsinfo' } , { slotName : BuiltinTemplateSlots . Date , slotField : 'Created' } , { slotName : BuiltinTemplateSlots . SiteId , slotField : 'NormSiteID' } , { slotName : BuiltinTemplateSlots . WebId , slotField : 'NormWebID' } , { slotName : BuiltinTemplateSlots . ListId , slotField : 'NormListID' } , { slotName : BuiltinTemplateSlots . ItemId , slotField : 'NormUniqueID' } , { slotName : BuiltinTemplateSlots . IsFolder , slotField : 'ContentTypeId' } , { slotName : BuiltinTemplateSlots . ContentClass , slotField : 'contentclass' } , { slotName : BuiltinTemplateSlots . PersonQuery , slotField : 'UserName' } , { slotName : BuiltinTemplateSlots . UserDisplayName , slotField : 'Title' } , { slotName : BuiltinTemplateSlots . UserEmail , slotField : 'UserName' } ] ; } private initProperties ( ) { this . properties . queryTemplate = this . properties . queryTemplate ? this . properties . queryTemplate : "{searchTerms}" ; this . properties . enableQueryRules = this . properties . enableQueryRules !== undefined ? this . properties . enableQueryRules : false ; this . properties . enableLocalization = this . properties . enableLocalization !== undefined ? this . properties . enableLocalization : false ; this . properties . refinementFilters = this . properties . refinementFilters ? this . properties . refinementFilters : '' ; this . properties . selectedProperties = this . properties . selectedProperties !== undefined ? this . properties . selectedProperties : [ 'Title' , 'Path' , 'DefaultEncodingURL' , 'FileType' , 'HitHighlightedSummary' , 'AuthorOWSUSER' , 'owstaxidmetadataalltagsinfo' , 'Created' , 'UniqueID' , 'NormSiteID' , 'NormWebID' , 'NormListID' , 'NormUniqueID' , 'ContentTypeId' , 'contentclass' , 'UserName' , 'JobTitle' , 'WorkPhone' , 'SPSiteUrl' , 'SiteTitle' , 'CreatedBy' , 'HtmlFileType' , 'SiteLogo' ] ; this . properties . resultSourceId = this . properties . resultSourceId !== undefined ? this . properties . resultSourceId : BuiltinSourceIds . LocalSharePointResults ; this . properties . sortList = this . properties . sortList !== undefined ? this . properties . sortList : [ ] ; } private getBuiltinSourceIdOptions ( ) { this . _resultSourcesOptions = [ { key : BuiltinSourceIds . Documents , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . Documents ) } , { key : BuiltinSourceIds . ItemsMatchingContentType , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . ItemsMatchingContentType ) } , { key : BuiltinSourceIds . ItemsMatchingTag , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . ItemsMatchingTag ) } , { key : BuiltinSourceIds . ItemsRelatedToCurrentUser , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . ItemsRelatedToCurrentUser ) } , { key : BuiltinSourceIds . ItemsWithSameKeywordAsThisItem , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . ItemsWithSameKeywordAsThisItem ) } , { key : BuiltinSourceIds . LocalPeopleResults , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . LocalPeopleResults ) } , { key : BuiltinSourceIds . LocalReportsAndDataResults , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . LocalReportsAndDataResults ) } , { key : BuiltinSourceIds . LocalSharePointResults , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . LocalSharePointResults ) } , { key : BuiltinSourceIds . LocalVideoResults , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . LocalVideoResults ) } , { key : BuiltinSourceIds . Pages , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . Pages ) } , { key : BuiltinSourceIds . Pictures , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . Pictures ) } , { key : BuiltinSourceIds . Popular , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . Popular ) } , { key : BuiltinSourceIds . RecentlyChangedItems , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . RecentlyChangedItems ) } , { key : BuiltinSourceIds . RecommendedItems , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . RecommendedItems ) } , { key : BuiltinSourceIds . Wiki , text : EnumHelper . getEnumKeyByEnumValue ( BuiltinSourceIds , BuiltinSourceIds . Wiki ) } , ] ; return this . _resultSourcesOptions ; } private async getAvailableProperties ( ) { const searchManagedProperties = await this . _sharePointSearchService . getAvailableManagedProperties ( ) ; this . _availableManagedProperties = searchManagedProperties . map ( managedProperty => { return { key : managedProperty . name , text : managedProperty . name , } as IComboBoxOption ; } ) ; return this . _availableManagedProperties ; } private _convertToSortList ( sortList ) { return sortList . map ( e => { let direction ; switch ( e . sortDirection ) { case SortFieldDirection . Ascending : direction = SortDirection . Ascending ; break ; case SortFieldDirection . Descending : direction = SortDirection . Descending ; break ; default : direction = SortDirection . Ascending ; break ; } return { Property : e . sortField , Direction : direction } as ISort ; } ) ; } private async buildSharePointSearchQuery ( dataContext ) { let searchQuery = { } ; searchQuery . ClientType = 'PnPModernSearch' ; searchQuery . Properties = [ { Name : "EnableDynamicGroups" , Value : { BoolVal : true , QueryPropertyValueTypeIndex : 3 } } , { Name : "EnableMultiGeoSearch" , Value : { BoolVal : true , QueryPropertyValueTypeIndex : 3 } } , { Name : "ClientFunction" , Value : { StrVal : "PnPSearchWebPart" , QueryPropertyValueTypeIndex : 1 } } ] ; if ( this . _pageContext . list ) { searchQuery . Properties . push ( { Name : "ListId" , Value : { StrVal : this . _pageContext . list . id . toString ( ) , QueryPropertyValueTypeIndex : 1 } } ) ; } if ( this . _pageContext . listItem ) { searchQuery . Properties . push ( { Name : "ListItemId" , Value : { StrVal : this . _pageContext . listItem . id . toString ( ) , QueryPropertyValueTypeIndex : 1 } } ) ; } searchQuery . Querytext = dataContext . inputQueryText ; searchQuery . EnableQueryRules = this . properties . enableQueryRules ; searchQuery . QueryTemplate = await this . _tokenService . resolveTokens ( this . properties . queryTemplate ) ; if ( this . properties . resultSourceId ) { if ( Guid . isValid ( this . properties . resultSourceId ) ) { searchQuery . SourceId = this . properties . resultSourceId ; if ( searchQuery . SourceId && searchQuery . SourceId . toLocaleLowerCase ( ) === BuiltinSourceIds . LocalPeopleResults ) { searchQuery . EnableNicknames = true ; searchQuery . EnablePhonetic = true ; } else { searchQuery . EnableNicknames = false ; searchQuery . EnablePhonetic = false ; } } else { searchQuery = this . _setResultSourceByName ( this . properties . resultSourceId , searchQuery ) ; } } searchQuery . Culture = this . properties . searchQueryLanguage !== undefined && this . properties . searchQueryLanguage !== null ? this . properties . searchQueryLanguage : this . _currentLocaleId ; let timeZoneBias = { WebBias : this . _pageContext . legacyPageContext . webTimeZoneData . Bias ? this . _pageContext . legacyPageContext . webTimeZoneData . Bias : 0 , WebDST : this . _pageContext . legacyPageContext . webTimeZoneData . DaylightBias ? this . _pageContext . legacyPageContext . webTimeZoneData . DaylightBias : 0 , UserBias : null , UserDST : null , Id : this . _pageContext . legacyPageContext . webTimeZoneData . Id } ; if ( this . _pageContext . legacyPageContext . userTimeZoneData ) { timeZoneBias . UserBias = this . _pageContext . legacyPageContext . userTimeZoneData . Bias ? this . _pageContext . legacyPageContext . userTimeZoneData . Bias : 0 ; timeZoneBias . UserDST = this . _pageContext . legacyPageContext . userTimeZoneData . DaylightBias ? this . _pageContext . legacyPageContext . userTimeZoneData . DaylightBias : 0 ; timeZoneBias . Id = this . _pageContext . legacyPageContext . webTimeZoneData . Id ; } searchQuery [ 'TimeZoneId' ] = timeZoneBias . Id ; let refinementFilters = ! isEmpty ( this . properties . refinementFilters ) ? [ this . properties . refinementFilters ] : [ ] ; if ( ! isEmpty ( dataContext . filters ) ) { searchQuery . Refiners = dataContext . filters . filtersConfiguration . map ( filterConfig => { const regexExpr = "(RefinableDate\\d+)(?=,|$)|" + "(RefinableDateInvariant00\\d+)(?=,|$)|" + "(RefinableDateSingle\\d+)(?=,|$)|" + "(LastModifiedTime)(?=,|$)|" + "(LastModifiedTimeForRetention)(?=,|$)|" + "(Created)(?=,|$)|" + "(Date\\d+)(?=,|$)|" + "(EndDate)(?=,|$)|" + "(.+OWSDATE)(?=,|$)|" + "(EventsRollUpEndDate)(?=,|$)|" + "(EventsRollUpStartDate)(?=,|$)|" + "(FirstPublishedDate)(?=,|$)|" + "(ImageDateCreated)(?=,|$)|" + "(LastAnalyticsUpdateTime)(?=,|$)|" + "(ModifierDates)(?=,|$)|" + "(ClassificationLastScan)(?=,|$)|" + "(ComplianceTagWrittenTime)(?=,|$)|" + "(ContentModifiedTime)(?=,|$)|" + "(DocumentAnalyticsLastActivityTimestamp)(?=,|$)|" + "(ExpirationTime)(?=,|$)|" + "(LastSharedByTime)(?=,|$)|" + "(StartDate)(?=,|$)|" + "(TagEventDate)(?=,|$)|" + "(processingtime)(?=,|$)|" + "(ExtractedDate)(?=,|$)" ; const refinableDateRegex = new RegExp ( regexExpr . replace ( /\s+/gi , '' ) , 'gi' ) ; if ( refinableDateRegex . test ( filterConfig . filterName ) ) { const pastYear = this . moment ( new Date ( ) ) . subtract ( 1 , 'years' ) . subtract ( 'minutes' , 1 ) . toISOString ( ) ; const past3Months = this . moment ( new Date ( ) ) . subtract ( 3 , 'months' ) . subtract ( 'minutes' , 1 ) . toISOString ( ) ; const pastMonth = this . moment ( new Date ( ) ) . subtract ( 1 , 'months' ) . subtract ( 'minutes' , 1 ) . toISOString ( ) ; const pastWeek = this . moment ( new Date ( ) ) . subtract ( 1 , 'week' ) . subtract ( 'minutes' , 1 ) . toISOString ( ) ; const past24hours = this . moment ( new Date ( ) ) . subtract ( 24 , 'hours' ) . subtract ( 'minutes' , 1 ) . toISOString ( ) ; const today = new Date ( ) . toISOString ( ) ; return `${ filterConfig . filterName }(discretize=manual/${ pastYear }/${ past3Months }/${ pastMonth }/${ pastWeek }/${ past24hours }/${ today })` ; } else if ( filterConfig . maxBuckets ) { const sort = filterConfig . sortBy == FilterSortType . ByName ? "name" : "frequency" ; const direction = filterConfig . sortDirection == FilterSortDirection . Ascending ? "ascending" : "descending" ; return `${ filterConfig . filterName }(filter=${ filterConfig . maxBuckets }/0/*,sort=${ sort }/${ direction })` ; } else { return filterConfig . filterName ; } } ) . join ( ',' ) ; if ( dataContext . filters . selectedFilters . length > 0 ) { if ( dataContext . filters . selectedFilters . length > 1 && dataContext . filters . selectedFilters . filter ( selectedFilter => selectedFilter . values . length > 0 ) . length > 1 ) { const refinementString = DataFilterHelper . buildFqlRefinementString ( dataContext . filters . selectedFilters , dataContext . filters . filtersConfiguration , this . moment ) . join ( ',' ) ; if ( ! isEmpty ( refinementString ) ) { refinementFilters = refinementFilters . concat ( [ `${ dataContext . filters . filterOperator }(${ refinementString })` ] ) ; } } else { refinementFilters = refinementFilters . concat ( DataFilterHelper . buildFqlRefinementString ( dataContext . filters . selectedFilters , dataContext . filters . filtersConfiguration , this . moment ) ) ; } } } searchQuery . RefinementFilters = refinementFilters ; searchQuery . RowLimit = dataContext . itemsCountPerPage ? dataContext . itemsCountPerPage : 50 ; if ( dataContext . pageNumber === 1 ) { searchQuery . StartRow = 0 ; } else { searchQuery . StartRow = ( dataContext . pageNumber - 1 ) * searchQuery . RowLimit ; } searchQuery . TrimDuplicates = false ; searchQuery . SortList = this . _convertToSortList ( this . properties . sortList ) ; searchQuery . SelectProperties = this . properties . selectedProperties . filter ( a => a ) ; if ( this . properties . enableAudienceTargeting ) { searchQuery . QueryTemplate = `${ searchQuery . QueryTemplate } (ModernAudienceAadObjectIds:{User.Audiences} OR NOT IsAudienceTargeted:true)` ; } return searchQuery ; } param value private validateSourceId ( value ) { if ( value . length > 0 ) { if ( ! ( /^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/ ) . test ( value ) ) { return this . _validateSourceName ( value ) ; } } return '' ; } param value returns private _validateSourceName ( value ) { const validLevels = [ "SPSiteSubscription" , "SPSite" , "SPWeb" ] ; if ( value . length > 0 ) { const parts = value . split ( "|" ) ; if ( parts . length !== 2 ) return commonStrings . DataSources . SharePointSearch . InvalidResultSourceIdMessage ; const level = parts [ 0 ] ; const resultSourceName = parts [ 1 ] ; if ( validLevels . find ( i => i . toLowerCase ( ) === level . toLowerCase ( ) ) ) { if ( ! resultSourceName ) { return commonStrings . DataSources . SharePointSearch . InvalidResultSourceIdMessage ; } } else { return commonStrings . DataSources . SharePointSearch . InvalidResultSourceIdMessage ; } } return '' ; } param _resultSourceId param searchQuery private _setResultSourceByName ( _resultSourceId , searchQuery ) { const parts = _resultSourceId . split ( "|" ) ; const level = parts [ 0 ] ; const resultSourceName = parts [ 1 ] ; searchQuery . Properties . push ( { Name : "SourceLevel" , Value : { StrVal : level , QueryPropertyValueTypeIndex : 1 } } ) ; searchQuery . Properties . push ( { Name : "SourceName" , Value : { StrVal : resultSourceName , QueryPropertyValueTypeIndex : 1 } } ) ; return searchQuery ; } param rawFilters param lcid private async _getLocalizedFilters ( rawFilters , lcid ) { let termsToLocalize = [ ] ; let updatedFilters = [ ] ; let localizedTerms = [ ] ; updatedFilters = rawFilters . map ( ( filterResult ) => { let updatedValues = [ ] ; filterResult . values . forEach ( ( value ) => { const isTerm = /L0\|#(.+)\|/ . test ( value . name ) ; if ( isTerm ) { const values = value . name . split ( ';' ) ; values . forEach ( ( term ) => { let matches = /L0\|#(.+)\|/ . exec ( value . name ) ; let termId = Guid . isValid ( matches [ 1 ] ) ? matches [ 1 ] : matches [ 1 ] . substr ( 1 ) ; let strippedTermFilter = "GP0|#" + termId . toString ( ) ; let strippedTerm = term . substring ( 0 , term . lastIndexOf ( '|' ) + 1 ) ; const fqlFilterValue = `"ǂǂ${ this . _bytesToHex ( this . _stringToUTF8Bytes ( strippedTermFilter ) ) }"` ; const existingFilterIdx = updatedValues . map ( updatedValue => updatedValue . name ) . indexOf ( strippedTerm ) ; if ( existingFilterIdx === - 1 ) { updatedValues . push ( { count : value . count , name : strippedTerm , value : fqlFilterValue } as IDataFilterResultValue ) ; } else { updatedValues [ existingFilterIdx ] . count = updatedValues [ existingFilterIdx ] . count + 1 ; updatedValues [ existingFilterIdx ] . value = fqlFilterValue ; } } ) ; } else { updatedValues . push ( value ) ; } } ) ; filterResult . values = cloneDeep ( updatedValues ) ; return filterResult ; } ) ; updatedFilters . forEach ( ( filterResult ) => { filterResult . values . forEach ( ( value ) => { const isTerm = /L0\|#(.+)\|/ . test ( value . name ) ; if ( isTerm ) { let matches = /L0\|#(.+)\|/ . exec ( value . name ) ; if ( matches . length > 0 ) { let termId = Guid . isValid ( matches [ 1 ] ) ? matches [ 1 ] : matches [ 1 ] . substr ( 1 ) ; if ( termsToLocalize . map ( t => t . uniqueIdentifier ) . indexOf ( value . value ) === - 1 ) { termsToLocalize . push ( { uniqueIdentifier : value . value , termId : termId , localizedTermLabel : null } ) ; } } } } ) ; } ) ; if ( termsToLocalize . length > 0 ) { const termValues = await this . _taxonomyService . getTermsById ( this . _pageContext . site . absoluteUrl , termsToLocalize . map ( ( t ) => { return t . termId ; } ) ) ; termsToLocalize . forEach ( ( termToLocalize ) => { const termsFromTaxonomy = termValues . filter ( ( taxonomyTerm ) => { const termIdFromTaxonomy = taxonomyTerm . Id . substring ( taxonomyTerm . Id . indexOf ( '(' ) + 1 , taxonomyTerm . Id . indexOf ( ')' ) ) ; return termIdFromTaxonomy === termToLocalize . termId ; } ) ; if ( termsFromTaxonomy . length > 0 ) { const termFromTaxonomy = termsFromTaxonomy [ 0 ] ; let localizedLabel = termFromTaxonomy [ "Labels" ] . _Child_Items_ . filter ( ( label ) => { return label . Language === lcid ; } ) ; if ( localizedLabel . length === 0 ) { localizedLabel = termFromTaxonomy [ "Labels" ] . _Child_Items_ ; } localizedTerms . push ( { uniqueIdentifier : termToLocalize . uniqueIdentifier , termId : termToLocalize . termId , localizedTermLabel : localizedLabel [ 0 ] . Value } ) ; } else { localizedTerms . push ( { uniqueIdentifier : termToLocalize . uniqueIdentifier , termId : termToLocalize . termId , localizedTermLabel : Text . format ( commonStrings . DataSources . SharePointSearch . TermNotFound , termToLocalize . termId ) } ) ; } } ) ; updatedFilters = updatedFilters . map ( ( filter ) => { filter . values = filter . values . map ( ( value ) => { const existingFilters = localizedTerms . filter ( ( e ) => { return e . uniqueIdentifier === value . value ; } ) ; if ( existingFilters . length > 0 ) { const isTerm = /L0\|#(.+)\|/ . test ( value . name ) ; if ( isTerm ) { value . name = existingFilters [ 0 ] . localizedTermLabel ; } } if ( ! ( /(GTSet|GPP|GP0)/i ) . test ( value . name ) ) { return value ; } } ) . filter ( v => v ) ; return filter ; } ) ; } return updatedFilters ; } param rawResults param lcid private async _getLocalizedMetadata ( rawResults , lcid ) { let resultsToLocalize = [ ] ; let updatedResults = [ ] ; let localizedTerms = [ ] ; rawResults . forEach ( ( result ) => { let properties = [ ] ; Object . keys ( result ) . forEach ( ( value ) => { const isTerm = /L0\|#.?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/ . test ( result [ value ] ) ; if ( isTerm ) { let termIds = [ ] ; const regex = /L0\|#.?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/g ; const str = result [ value ] ; let m ; while ( ( m = regex . exec ( str ) ) !== null ) { if ( m . index === regex . lastIndex ) { regex . lastIndex ++ ; } termIds . push ( m [ 1 ] ) ; } properties . push ( { propertyName : value , termIds : termIds } as ILocalizableSearchResultProperty ) ; } } ) ; if ( properties . length > 0 ) { resultsToLocalize . push ( { uniqueIdentifier : result . UniqueID , properties : properties } ) ; } } ) ; if ( resultsToLocalize . length > 0 ) { let allTerms = [ ] ; resultsToLocalize . forEach ( ( result ) => { result . properties . forEach ( ( p ) => { allTerms = allTerms . concat ( p . termIds ) ; } ) ; } ) ; allTerms = uniq < string > ( allTerms ) ; const termValues = await this . _taxonomyService . getTermsById ( this . _pageContext . site . absoluteUrl , allTerms ) ; resultsToLocalize . forEach ( ( resultToLocalize ) => { let updatedProperties = [ ] ; resultToLocalize . properties . forEach ( property => { let termLabels = [ ] ; const termsFromTaxonomy = termValues . filter ( ( taxonomyTerm ) => { const termIdFromTaxonomy = taxonomyTerm . Id . substring ( taxonomyTerm . Id . indexOf ( '(' ) + 1 , taxonomyTerm . Id . indexOf ( ')' ) ) ; return property . termIds . indexOf ( termIdFromTaxonomy ) !== - 1 ; } ) ; if ( termsFromTaxonomy . length > 0 ) { termsFromTaxonomy . forEach ( ( taxonomyTerm ) => { let localizedLabel = taxonomyTerm [ "Labels" ] . _Child_Items_ . filter ( ( label ) => { return label . Language === lcid && label . IsDefaultForLanguage ; } ) ; if ( localizedLabel . length === 0 ) { localizedLabel = taxonomyTerm [ "Labels" ] . _Child_Items_ ; } if ( localizedLabel . length > 0 ) { termLabels . push ( localizedLabel [ 0 ] . Value ) ; } } ) ; updatedProperties . push ( { propertyName : property . propertyName , termLabels : termLabels } ) ; } } ) ; localizedTerms . push ( { uniqueIdentifier : resultToLocalize . uniqueIdentifier , properties : updatedProperties , } ) ; } ) ; updatedResults = rawResults . map ( ( result ) => { const existingResults = localizedTerms . filter ( ( e ) => { return e . uniqueIdentifier === result . UniqueID ; } ) ; if ( existingResults . length > 0 ) { existingResults [ 0 ] . properties . forEach ( ( res ) => { result [ `Auto${ res . propertyName }` ] = res . termLabels . join ( ', ' ) ; } ) ; } return result ; } ) ; return updatedResults ; } else { return rawResults ; } } private _bytesToHex ( bytes ) { return Array . from ( bytes , byte => ( byte as any ) . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" ) ; } private _stringToUTF8Bytes ( string ) { return new TextEncoder ( ) . encode ( string ) ; } private parseAndCleanOptions ( options ) { let optionWithComma = options . find ( o => ( o . key as string ) . indexOf ( "," ) > 0 ) ; if ( optionWithComma ) { return ( optionWithComma . key as string ) . split ( "," ) . map ( k => { return { key : k . trim ( ) , text : k . trim ( ) , selected : true } ; } ) ; } return options ; } }