@asd20/ui-next 2.11.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ # [2.12.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.11.1...ui-next-v2.12.0) (2026-05-26)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * add querypeople to public api contract ([a1a3141](https://github.com/academydistrict20/asd20-ui-next/commit/a1a3141ec8b3df29da27e947fecba80baf0169e1))
9
+
10
+
11
+ ### Features
12
+
13
+ * add people to ai search ([241cf12](https://github.com/academydistrict20/asd20-ui-next/commit/241cf1247633f83a47b6fe3cd3bc91bbff275e80))
14
+
3
15
  ## [2.11.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.11.0...ui-next-v2.11.1) (2026-05-26)
4
16
 
5
17
 
package/helpers.js CHANGED
@@ -42,6 +42,7 @@ export { default as queryEvents } from './src/helpers/queryEvents.js'
42
42
  export { default as queryFiles } from './src/helpers/queryFiles.js'
43
43
  export { default as queryMessages } from './src/helpers/queryMessages.js'
44
44
  export { default as queryPages } from './src/helpers/queryPages.js'
45
+ export { default as queryPeople } from './src/helpers/queryPeople.js'
45
46
  export { default as sendAccessibilityIssue } from './src/helpers/sendAccessibilityIssue.js'
46
47
  export { default as sendEmail } from './src/helpers/sendEmail.js'
47
48
  export { default as sortOrgsByLevel } from './src/helpers/sortOrgsByLevel.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.11.1",
3
+ "version": "2.12.0",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -53,12 +53,14 @@ export default {
53
53
  pages: { type: Array, default: () => [] },
54
54
  files: { type: Array, default: () => [] },
55
55
  groups: { type: Array, default: () => [] },
56
+ people: { type: Array, default: () => [] },
56
57
  showContacts: { type: Boolean, default: true },
57
58
  includeDistrictResults: { type: Boolean, default: true },
58
59
  searchLanguageCode: { type: String, default: null },
59
60
  queryPagesHandler: { type: Function, default: null },
60
61
  queryFilesHandler: { type: Function, default: null },
61
62
  queryGroupsHandler: { type: Function, default: null },
63
+ queryPeopleHandler: { type: Function, default: null },
62
64
  queryAiSiteHandler: { type: Function, default: null },
63
65
  organization: { type: Object, default: () => ({}) },
64
66
  organizationOptions: { type: Array, default: () => [] },
@@ -71,11 +71,13 @@
71
71
  :pages="pages"
72
72
  :files="files"
73
73
  :groups="groups"
74
+ :people="people"
74
75
  :include-district-results="includeDistrictResults"
75
76
  :search-language-code="searchLanguageCode"
76
77
  :query-pages-handler="queryPagesHandler"
77
78
  :query-files-handler="queryFilesHandler"
78
79
  :query-groups-handler="queryGroupsHandler"
80
+ :query-people-handler="queryPeopleHandler"
79
81
  :query-ai-site-handler="queryAiSiteHandler"
80
82
  :organization="organization"
81
83
  :organization-options="organizationOptions"
@@ -118,12 +120,14 @@ export default {
118
120
  pages: { type: Array, default: () => [] },
119
121
  files: { type: Array, default: () => [] },
120
122
  groups: { type: Array, default: () => [] },
123
+ people: { type: Array, default: () => [] },
121
124
  showContacts: { type: Boolean, default: true },
122
125
  includeDistrictResults: { type: Boolean, default: true },
123
126
  searchLanguageCode: { type: String, default: null },
124
127
  queryPagesHandler: { type: Function, default: null },
125
128
  queryFilesHandler: { type: Function, default: null },
126
129
  queryGroupsHandler: { type: Function, default: null },
130
+ queryPeopleHandler: { type: Function, default: null },
127
131
  queryAiSiteHandler: { type: Function, default: null },
128
132
  organization: { type: Object, default: null },
129
133
  organizationOptions: { type: Array, default: () => [] },
@@ -324,37 +324,35 @@
324
324
  v-if="
325
325
  hasSubmitted &&
326
326
  showContacts &&
327
- currentGroups.length === 0 &&
328
- !searchingFiles
327
+ contactResultCount === 0 &&
328
+ !searchingFiles &&
329
+ !searchingPeople
329
330
  "
330
331
  title="No Contacts Found"
331
332
  description="Try using different keywords."
332
333
  />
333
334
  <div
334
- v-if="currentGroups.length > 0 && showContacts"
335
+ v-if="contactResultCount > 0 && showContacts"
335
336
  class="asd20-site-search__suggested"
336
337
  >
337
338
  <h3>
338
339
  {{
339
- currentGroups.length > 1
340
+ contactResultCount > 1
340
341
  ? 'Suggested Contacts:'
341
342
  : 'Suggested Contact:'
342
343
  }}
343
344
  </h3>
344
345
  <asd20-list-item
345
- v-for="item in groupListItems"
346
- :key="item.group.id"
347
- :text-avatar="item.textAvatar"
348
- :label="item.label"
349
- :logo-url="item.logoUrl"
350
- :source-title="item.sourceTitle"
351
- :attribution="!!item.logoUrl"
352
- actionable
353
- @click="selectedGroup = item.group"
346
+ v-for="item in contactListItems"
347
+ v-bind="item"
348
+ :key="item.key"
349
+ @click="onContactClick(item)"
354
350
  />
355
351
  </div>
356
352
  <asd20-loader
357
- v-if="searchingFiles && currentGroups.length === 0"
353
+ v-if="
354
+ (searchingFiles || searchingPeople) && contactResultCount === 0
355
+ "
358
356
  size="lg"
359
357
  />
360
358
  </asd20-viewport>
@@ -467,6 +465,12 @@
467
465
  </asd20-viewport>
468
466
  </asd20-modal>
469
467
 
468
+ <asd20-person-modal
469
+ :person="selectedPerson"
470
+ :open="!!selectedPerson"
471
+ @dismiss="selectedPerson = null"
472
+ />
473
+
470
474
  <asd20-modal
471
475
  :open="improvementFeedbackModalOpen"
472
476
  title="Report Missing or Incorrect Information"
@@ -539,6 +543,7 @@ import Asd20Notification from '../../molecules/Asd20Notification'
539
543
  import Asd20Loader from '../../molecules/Asd20Loader'
540
544
  import Asd20Modal from '../../molecules/Asd20Modal'
541
545
  import Asd20DepartmentContactCard from '../../molecules/Asd20DepartmentContactCard'
546
+ import Asd20PersonModal from '../../organisms/Asd20PersonModal'
542
547
  import Asd20Button from '../../atoms/Asd20Button'
543
548
  import Asd20Icon from '../../atoms/Asd20Icon'
544
549
  import Asd20Spinner from '../../atoms/Asd20Spinner'
@@ -548,9 +553,11 @@ import Asd20Spinner from '../../atoms/Asd20Spinner'
548
553
  import debounce from 'lodash/debounce'
549
554
  import mapPagesToListItems from '../../../helpers/mapPagesToListItems'
550
555
  import mapFilesToListItems from '../../../helpers/mapFilesToListItems'
556
+ import mapPeopleToListItems from '../../../helpers/mapPeopleToListItems'
551
557
  import logSearchAnalytics from '../../../helpers/logSearchAnalytics'
552
558
  import logSearchFeedback from '../../../helpers/logSearchFeedback'
553
559
  import queryAiSite from '../../../helpers/queryAiSite'
560
+ import queryPeople from '../../../helpers/queryPeople'
554
561
  import { NO_PAGE_RESULTS_FALLBACKS } from '../../../helpers/queryPages'
555
562
 
556
563
  // Mixins
@@ -604,6 +611,7 @@ export default {
604
611
  Asd20Loader,
605
612
  Asd20Modal,
606
613
  Asd20DepartmentContactCard,
614
+ Asd20PersonModal,
607
615
  Asd20Button,
608
616
  Asd20Icon,
609
617
  Asd20Spinner,
@@ -625,6 +633,11 @@ export default {
625
633
  { type: Array, default: () => [] },
626
634
  'search'
627
635
  ),
636
+ globalPropMixinFactory(
637
+ 'people',
638
+ { type: Array, default: () => [] },
639
+ 'search'
640
+ ),
628
641
  globalPropMixinFactory(
629
642
  'includeDistrictResults',
630
643
  { type: Boolean, default: true },
@@ -642,6 +655,7 @@ export default {
642
655
  queryPagesHandler: { type: Function, default: null },
643
656
  queryFilesHandler: { type: Function, default: null },
644
657
  queryGroupsHandler: { type: Function, default: null },
658
+ queryPeopleHandler: { type: Function, default: null },
645
659
  queryAiSiteHandler: { type: Function, default: null },
646
660
  organization: {
647
661
  type: Object,
@@ -666,12 +680,15 @@ export default {
666
680
  aiTranscript: [],
667
681
 
668
682
  selectedGroup: null,
683
+ selectedPerson: null,
669
684
  searchingPages: false,
670
685
  searchingFiles: false,
686
+ searchingPeople: false,
671
687
 
672
688
  localSearchPages: null,
673
689
  localSearchFiles: null,
674
690
  localSearchGroups: null,
691
+ localSearchPeople: null,
675
692
 
676
693
  keywordsFromAi: false,
677
694
  aiKeywordsDisplayAnswer: '',
@@ -723,6 +740,15 @@ export default {
723
740
  return Array.isArray(this.resolvedGroups) ? this.resolvedGroups : []
724
741
  },
725
742
 
743
+ currentPeople() {
744
+ if (Array.isArray(this.localSearchPeople)) return this.localSearchPeople
745
+ return Array.isArray(this.resolvedPeople) ? this.resolvedPeople : []
746
+ },
747
+
748
+ contactResultCount() {
749
+ return this.currentGroups.length + this.currentPeople.length
750
+ },
751
+
726
752
  shouldUsePageFallbacks() {
727
753
  const hasSearchText =
728
754
  typeof this.keywords === 'string' && this.keywords.trim()
@@ -828,7 +854,7 @@ export default {
828
854
  if (this.showContacts) {
829
855
  assistantTabs.push({
830
856
  label: 'Contacts',
831
- badge: this.currentGroups.length,
857
+ badge: this.contactResultCount,
832
858
  active: this.currentTab === 'Contacts',
833
859
  })
834
860
  }
@@ -851,7 +877,7 @@ export default {
851
877
  if (this.showContacts) {
852
878
  keywordTabs.push({
853
879
  label: 'Contacts',
854
- badge: this.currentGroups.length,
880
+ badge: this.contactResultCount,
855
881
  active: this.currentTab === 'Contacts',
856
882
  })
857
883
  }
@@ -954,6 +980,8 @@ export default {
954
980
  ((g.shortName || g.title || '').match(/\b([A-Z])/g) || []).join('')
955
981
 
956
982
  return {
983
+ key: `group-${g.id || g.title}`,
984
+ contactType: 'group',
957
985
  group: g,
958
986
  label: `${g.title}`,
959
987
  textAvatar,
@@ -962,6 +990,17 @@ export default {
962
990
  }
963
991
  })
964
992
  },
993
+ peopleListItems() {
994
+ return (mapPeopleToListItems(this.currentPeople) || []).map(item => ({
995
+ ...item,
996
+ key: `person-${item.id || item.email || item.label}`,
997
+ contactType: 'person',
998
+ }))
999
+ },
1000
+
1001
+ contactListItems() {
1002
+ return [...this.peopleListItems, ...this.groupListItems]
1003
+ },
965
1004
  feedbackFormUrlRaw() {
966
1005
  if (this.aiSearchFeedbackFormUrl) return this.aiSearchFeedbackFormUrl
967
1006
  const runtimeConfig = this.resolveRuntimeConfig()
@@ -1216,7 +1255,7 @@ export default {
1216
1255
  : 'en'
1217
1256
  },
1218
1257
 
1219
- setSearchResults({ pages, files, groups } = {}) {
1258
+ setSearchResults({ pages, files, groups, people } = {}) {
1220
1259
  if (pages !== undefined) {
1221
1260
  this.localSearchPages = Array.isArray(pages) ? pages : []
1222
1261
  }
@@ -1226,12 +1265,16 @@ export default {
1226
1265
  if (groups !== undefined) {
1227
1266
  this.localSearchGroups = Array.isArray(groups) ? groups : []
1228
1267
  }
1268
+ if (people !== undefined) {
1269
+ this.localSearchPeople = Array.isArray(people) ? people : []
1270
+ }
1229
1271
  },
1230
1272
 
1231
1273
  readSearchResults(target) {
1232
1274
  if (target === 'pages') return this.currentPages
1233
1275
  if (target === 'files') return this.currentFiles
1234
1276
  if (target === 'groups') return this.currentGroups
1277
+ if (target === 'people') return this.currentPeople
1235
1278
  return []
1236
1279
  },
1237
1280
 
@@ -1245,6 +1288,9 @@ export default {
1245
1288
  if (target === 'groups') {
1246
1289
  return Array.isArray(this.resolvedGroups) ? this.resolvedGroups : []
1247
1290
  }
1291
+ if (target === 'people') {
1292
+ return Array.isArray(this.resolvedPeople) ? this.resolvedPeople : []
1293
+ }
1248
1294
  return []
1249
1295
  },
1250
1296
 
@@ -1308,6 +1354,26 @@ export default {
1308
1354
  }
1309
1355
  },
1310
1356
 
1357
+ async queryPeopleWithFallback(payload) {
1358
+ if (typeof this.queryPeopleHandler === 'function') {
1359
+ const result = await this.queryPeopleHandler(payload)
1360
+ if (Array.isArray(result)) return result
1361
+ return result && Array.isArray(result.people) ? result.people : []
1362
+ }
1363
+
1364
+ const searchStore = this.getSearchStore()
1365
+ if (searchStore) {
1366
+ await searchStore.dispatch('search/queryPeople', payload)
1367
+ return Array.isArray(this.resolvedPeople) ? this.resolvedPeople : []
1368
+ }
1369
+
1370
+ const { people } = await queryPeople({
1371
+ ...payload,
1372
+ runtimeConfig: this.resolveRuntimeConfig(),
1373
+ })
1374
+ return Array.isArray(people) ? people : []
1375
+ },
1376
+
1311
1377
  isLocalDevelopmentHost(hostname = '') {
1312
1378
  if (!hostname || typeof hostname !== 'string') return false
1313
1379
  const lower = hostname.toLowerCase()
@@ -1526,6 +1592,7 @@ export default {
1526
1592
  pages: Array.isArray(this.currentPages) ? this.currentPages : [],
1527
1593
  files: Array.isArray(this.currentFiles) ? this.currentFiles : [],
1528
1594
  groups: Array.isArray(this.currentGroups) ? this.currentGroups : [],
1595
+ people: Array.isArray(this.currentPeople) ? this.currentPeople : [],
1529
1596
  aiAnswer: this.aiAnswer || null,
1530
1597
  aiSources: Array.isArray(this.aiSources) ? this.aiSources : [],
1531
1598
  aiErrorMessage: this.aiErrorMessage || '',
@@ -1636,7 +1703,8 @@ export default {
1636
1703
  const pages = Array.isArray(snapshot.pages) ? snapshot.pages : []
1637
1704
  const files = Array.isArray(snapshot.files) ? snapshot.files : []
1638
1705
  const groups = Array.isArray(snapshot.groups) ? snapshot.groups : []
1639
- this.setSearchResults({ pages, files, groups })
1706
+ const people = Array.isArray(snapshot.people) ? snapshot.people : []
1707
+ this.setSearchResults({ pages, files, groups, people })
1640
1708
 
1641
1709
  if (!skipTabUpdate) {
1642
1710
  const preferredTab = SEARCH_ROUTE_TABS.has(targetState.tab)
@@ -2177,6 +2245,39 @@ export default {
2177
2245
  this.$emit('update:active', false)
2178
2246
  },
2179
2247
 
2248
+ onContactClick(item) {
2249
+ if (!item) return
2250
+ if (item.contactType === 'person') {
2251
+ this.selectedPerson = this.normalizePersonForModal(item.person || item)
2252
+ return
2253
+ }
2254
+ if (item.group) {
2255
+ this.selectedGroup = item.group
2256
+ }
2257
+ },
2258
+
2259
+ normalizePersonForModal(person = {}) {
2260
+ const jobTitles = Array.isArray(person.jobTitles)
2261
+ ? person.jobTitles
2262
+ : person.jobTitles
2263
+ ? [person.jobTitles]
2264
+ : person.jobTypes
2265
+ ? Array.isArray(person.jobTypes)
2266
+ ? person.jobTypes
2267
+ : [person.jobTypes]
2268
+ : []
2269
+
2270
+ return {
2271
+ ...person,
2272
+ id: person.id || person.employeeId || person.profileId || person.userId,
2273
+ displayName: person.displayName || person.name || '',
2274
+ jobTitles,
2275
+ workPhone: person.workPhone || person.phone || '',
2276
+ thumbnailUrl:
2277
+ person.thumbnailUrl || person.thumbnail || person.photoUrl || '',
2278
+ }
2279
+ },
2280
+
2180
2281
  resolveRuntimeConfig() {
2181
2282
  const runtimeConfig =
2182
2283
  this.$config ||
@@ -2341,9 +2442,11 @@ export default {
2341
2442
  this.beginSearchRun()
2342
2443
  }
2343
2444
  this.selectedGroup = null
2445
+ this.selectedPerson = null
2344
2446
  this.searchingAi = preserveAiLoading ? this.searchingAi : false
2345
2447
  this.searchingPages = false
2346
2448
  this.searchingFiles = false
2449
+ this.searchingPeople = false
2347
2450
  this.activeAiQuestionKey = preserveAiLoading
2348
2451
  ? this.activeAiQuestionKey
2349
2452
  : ''
@@ -2366,7 +2469,7 @@ export default {
2366
2469
  this.aiTranscript = []
2367
2470
  this.anchoredUserTurnId = null
2368
2471
  }
2369
- this.setSearchResults({ pages: [], files: [], groups: [] })
2472
+ this.setSearchResults({ pages: [], files: [], groups: [], people: [] })
2370
2473
  },
2371
2474
 
2372
2475
  async clearInput() {
@@ -2471,8 +2574,11 @@ export default {
2471
2574
  async searchFilesWithGroupOwners(options = {}) {
2472
2575
  const runId = options.runId || this.searchRunId
2473
2576
  const groupsPromise = this.searchGroups({ runId })
2577
+ const peoplePromise = this.searchPeople({ runId })
2474
2578
  await this.searchFiles({ runId })
2475
2579
  if (!this.isCurrentSearchRun(runId)) return []
2580
+ await peoplePromise
2581
+ if (!this.isCurrentSearchRun(runId)) return []
2476
2582
  const ownerFilters = await groupsPromise
2477
2583
  if (!this.isCurrentSearchRun(runId)) return ownerFilters
2478
2584
  if (ownerFilters.length > 0) {
@@ -2513,6 +2619,32 @@ export default {
2513
2619
  )
2514
2620
  },
2515
2621
 
2622
+ async searchPeople(options = {}) {
2623
+ const runId = options.runId || this.searchRunId
2624
+ const searchInput = {
2625
+ keywords: this.keywords,
2626
+ question: this.searchQuestion || this.inputText,
2627
+ }
2628
+
2629
+ this.searchingPeople = true
2630
+ try {
2631
+ let people = []
2632
+ try {
2633
+ people = await this.queryPeopleWithFallback(searchInput)
2634
+ } catch (error) {
2635
+ people = []
2636
+ }
2637
+ if (!this.isCurrentSearchRun(runId)) return this.currentPeople
2638
+ this.setSearchResults({ people })
2639
+ this.persistSearchCacheSnapshot()
2640
+ return this.currentPeople
2641
+ } finally {
2642
+ if (this.isCurrentSearchRun(runId)) {
2643
+ this.searchingPeople = false
2644
+ }
2645
+ }
2646
+ },
2647
+
2516
2648
  async onSubmitInput() {
2517
2649
  if (this.searchingAi) return
2518
2650
  const text = (this.inputText || '').trim()
@@ -13,11 +13,13 @@
13
13
  :pages="searchPages"
14
14
  :files="searchFiles"
15
15
  :groups="searchGroups"
16
+ :people="searchPeople"
16
17
  :include-district-results="includeDistrictResults"
17
18
  :search-language-code="searchLanguageCode"
18
19
  :query-pages-handler="queryPagesHandler"
19
20
  :query-files-handler="queryFilesHandler"
20
21
  :query-groups-handler="queryGroupsHandler"
22
+ :query-people-handler="queryPeopleHandler"
21
23
  :query-ai-site-handler="queryAiSiteHandler"
22
24
  :organization="organization"
23
25
  :organization-options="organizationOptions"
@@ -65,11 +67,13 @@
65
67
  :pages="searchPages"
66
68
  :files="searchFiles"
67
69
  :groups="searchGroups"
70
+ :people="searchPeople"
68
71
  :include-district-results="includeDistrictResults"
69
72
  :search-language-code="searchLanguageCode"
70
73
  :query-pages-handler="queryPagesHandler"
71
74
  :query-files-handler="queryFilesHandler"
72
75
  :query-groups-handler="queryGroupsHandler"
76
+ :query-people-handler="queryPeopleHandler"
73
77
  :query-ai-site-handler="queryAiSiteHandler"
74
78
  :organization="organization"
75
79
  :organization-options="organizationOptions"
@@ -221,11 +225,13 @@ export default {
221
225
  searchPages: { type: Array, default: () => [] },
222
226
  searchFiles: { type: Array, default: () => [] },
223
227
  searchGroups: { type: Array, default: () => [] },
228
+ searchPeople: { type: Array, default: () => [] },
224
229
  includeDistrictResults: { type: Boolean, default: true },
225
230
  searchLanguageCode: { type: String, default: null },
226
231
  queryPagesHandler: { type: Function, default: null },
227
232
  queryFilesHandler: { type: Function, default: null },
228
233
  queryGroupsHandler: { type: Function, default: null },
234
+ queryPeopleHandler: { type: Function, default: null },
229
235
  queryAiSiteHandler: { type: Function, default: null },
230
236
  aiSearchFeedbackFormUrl: { type: String, default: '' },
231
237
  },
@@ -13,11 +13,13 @@
13
13
  :pages="searchPages"
14
14
  :files="searchFiles"
15
15
  :groups="searchGroups"
16
+ :people="searchPeople"
16
17
  :include-district-results="includeDistrictResults"
17
18
  :search-language-code="searchLanguageCode"
18
19
  :query-pages-handler="queryPagesHandler"
19
20
  :query-files-handler="queryFilesHandler"
20
21
  :query-groups-handler="queryGroupsHandler"
22
+ :query-people-handler="queryPeopleHandler"
21
23
  :query-ai-site-handler="queryAiSiteHandler"
22
24
  :organization-options="organizationOptions"
23
25
  :organization="organization"
@@ -109,11 +111,13 @@
109
111
  :pages="searchPages"
110
112
  :files="searchFiles"
111
113
  :groups="searchGroups"
114
+ :people="searchPeople"
112
115
  :include-district-results="includeDistrictResults"
113
116
  :search-language-code="searchLanguageCode"
114
117
  :query-pages-handler="queryPagesHandler"
115
118
  :query-files-handler="queryFilesHandler"
116
119
  :query-groups-handler="queryGroupsHandler"
120
+ :query-people-handler="queryPeopleHandler"
117
121
  :query-ai-site-handler="queryAiSiteHandler"
118
122
  :organization="organization"
119
123
  :organization-options="organizationOptions"
@@ -234,11 +238,13 @@ export default {
234
238
  searchPages: { type: Array, default: () => [] },
235
239
  searchFiles: { type: Array, default: () => [] },
236
240
  searchGroups: { type: Array, default: () => [] },
241
+ searchPeople: { type: Array, default: () => [] },
237
242
  includeDistrictResults: { type: Boolean, default: true },
238
243
  searchLanguageCode: { type: String, default: null },
239
244
  queryPagesHandler: { type: Function, default: null },
240
245
  queryFilesHandler: { type: Function, default: null },
241
246
  queryGroupsHandler: { type: Function, default: null },
247
+ queryPeopleHandler: { type: Function, default: null },
242
248
  queryAiSiteHandler: { type: Function, default: null },
243
249
  aiSearchFeedbackFormUrl: { type: String, default: '' },
244
250
  },
@@ -13,11 +13,13 @@
13
13
  :pages="searchPages"
14
14
  :files="searchFiles"
15
15
  :groups="searchGroups"
16
+ :people="searchPeople"
16
17
  :include-district-results="includeDistrictResults"
17
18
  :search-language-code="searchLanguageCode"
18
19
  :query-pages-handler="queryPagesHandler"
19
20
  :query-files-handler="queryFilesHandler"
20
21
  :query-groups-handler="queryGroupsHandler"
22
+ :query-people-handler="queryPeopleHandler"
21
23
  :query-ai-site-handler="queryAiSiteHandler"
22
24
  :organization="organization"
23
25
  :organization-options="organizationOptions"
@@ -110,11 +112,13 @@
110
112
  :pages="searchPages"
111
113
  :files="searchFiles"
112
114
  :groups="searchGroups"
115
+ :people="searchPeople"
113
116
  :include-district-results="includeDistrictResults"
114
117
  :search-language-code="searchLanguageCode"
115
118
  :query-pages-handler="queryPagesHandler"
116
119
  :query-files-handler="queryFilesHandler"
117
120
  :query-groups-handler="queryGroupsHandler"
121
+ :query-people-handler="queryPeopleHandler"
118
122
  :query-ai-site-handler="queryAiSiteHandler"
119
123
  :organization="organization"
120
124
  :organization-options="organizationOptions"
@@ -239,11 +243,13 @@ export default {
239
243
  searchPages: { type: Array, default: () => [] },
240
244
  searchFiles: { type: Array, default: () => [] },
241
245
  searchGroups: { type: Array, default: () => [] },
246
+ searchPeople: { type: Array, default: () => [] },
242
247
  includeDistrictResults: { type: Boolean, default: true },
243
248
  searchLanguageCode: { type: String, default: null },
244
249
  queryPagesHandler: { type: Function, default: null },
245
250
  queryFilesHandler: { type: Function, default: null },
246
251
  queryGroupsHandler: { type: Function, default: null },
252
+ queryPeopleHandler: { type: Function, default: null },
247
253
  queryAiSiteHandler: { type: Function, default: null },
248
254
  aiSearchFeedbackFormUrl: { type: String, default: '' },
249
255
  },
@@ -0,0 +1,122 @@
1
+ import axios from 'axios'
2
+
3
+ import { hasSearchProxyRuntime, resolveSearchProxyUrl } from './searchProxyUrl'
4
+ import { normalizeBaseUrl, resolveRuntimeConfigValue } from './runtimeConfig'
5
+
6
+ function searchPayload({ keywords = '', top = 10 }) {
7
+ const trimmedKeywords = keywords.trim()
8
+ const terms = trimmedKeywords.split(/\s+/).filter(Boolean)
9
+ const expandedTerms = terms
10
+ .map(term => `displayName:${term}* OR email:${term}*`)
11
+ .join(' OR ')
12
+
13
+ return {
14
+ count: true,
15
+ queryType: 'full',
16
+ search: trimmedKeywords
17
+ ? `"${trimmedKeywords}"${expandedTerms ? ` OR ${expandedTerms}` : ''}`
18
+ : '*',
19
+ top,
20
+ }
21
+ }
22
+
23
+ export default async function queryPeople({
24
+ keywords = '',
25
+ top = 10,
26
+ limit = 0,
27
+ requireKeywords = true,
28
+ indexName = 'people-index',
29
+ requestUrl = '',
30
+ runtimeConfig = null,
31
+ searchKey = '',
32
+ apiVersion = '',
33
+ searchEndpoint = '',
34
+ endpoint = '',
35
+ }) {
36
+ if (!keywords && requireKeywords) {
37
+ return {
38
+ people: [],
39
+ facets: [],
40
+ }
41
+ }
42
+
43
+ const resolvedLimit = Number.isFinite(Number(limit)) && Number(limit) > 0
44
+ ? Number(limit)
45
+ : top
46
+ const resolvedSearchKey = resolveRuntimeConfigValue({
47
+ explicitValue: searchKey,
48
+ runtimeConfig,
49
+ runtimeConfigKeys: ['azureSearchKey'],
50
+ envKeys: [
51
+ 'AZURE_SEARCH_KEY',
52
+ 'VUE_APP_AZURE_SEARCH_KEY',
53
+ 'GRIDSOME_AZURE_SEARCH_KEY',
54
+ ],
55
+ })
56
+ const resolvedApiVersion = resolveRuntimeConfigValue({
57
+ explicitValue: apiVersion,
58
+ runtimeConfig,
59
+ runtimeConfigKeys: ['azureSearchVersion'],
60
+ envKeys: [
61
+ 'AZURE_SEARCH_VERSION',
62
+ 'VUE_APP_AZURE_SEARCH_API_VERSION',
63
+ 'GRIDSOME_AZURE_SEARCH_API_VERSION',
64
+ ],
65
+ })
66
+ const resolvedSearchEndpoint = resolveRuntimeConfigValue({
67
+ explicitValue: searchEndpoint || endpoint,
68
+ runtimeConfig,
69
+ runtimeConfigKeys: ['azureSearchEndpoint'],
70
+ envKeys: [
71
+ 'AZURE_SEARCH_ENDPOINT',
72
+ 'VUE_APP_AZURE_SEARCH_ENDPOINT',
73
+ 'GRIDSOME_AZURE_SEARCH_ENDPOINT',
74
+ ],
75
+ })
76
+ const resolvedRequestUrl =
77
+ typeof requestUrl === 'string' && requestUrl.trim()
78
+ ? requestUrl.trim()
79
+ : !resolvedSearchEndpoint &&
80
+ hasSearchProxyRuntime(runtimeConfig)
81
+ ? resolveSearchProxyUrl('people', runtimeConfig)
82
+ : ''
83
+
84
+ if (
85
+ !resolvedRequestUrl &&
86
+ (!resolvedSearchEndpoint || !resolvedSearchKey || !resolvedApiVersion)
87
+ ) {
88
+ throw new Error('Azure Search or proxy configuration is required for queryPeople.')
89
+ }
90
+
91
+ const payload = {
92
+ keywords,
93
+ limit: resolvedLimit,
94
+ indexName,
95
+ }
96
+
97
+ const { data } = resolvedRequestUrl
98
+ ? await axios({
99
+ method: 'post',
100
+ url: resolvedRequestUrl,
101
+ data: payload,
102
+ })
103
+ : await axios({
104
+ method: 'post',
105
+ headers: {
106
+ 'api-key': resolvedSearchKey,
107
+ },
108
+ params: {
109
+ 'api-version': resolvedApiVersion,
110
+ },
111
+ url: `${normalizeBaseUrl(resolvedSearchEndpoint)}/indexes/${indexName}/docs/search`,
112
+ data: searchPayload({
113
+ keywords,
114
+ top: resolvedLimit,
115
+ }),
116
+ })
117
+
118
+ return {
119
+ people: Array.isArray(data.value) ? data.value : [],
120
+ facets: data['@search.facets'],
121
+ }
122
+ }
@@ -5,6 +5,7 @@ const SEARCH_PROXY_PATHS = Object.freeze({
5
5
  pages: '/api/search/pages',
6
6
  files: '/api/search/files',
7
7
  events: '/api/search/events',
8
+ people: '/api/search/people',
8
9
  })
9
10
 
10
11
  export function resolveSearchProxyPath(kind) {