mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-26 19:44:57 +02:00
UBERF-9732: Use huly id as primary social id (#8499)
* uberf-9732: use huly id as primary social id Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com> * uberf-9732: fix person id filter Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Person, SocialIdentity } from '@hcengineering/contact'
|
||||
import core, { FindResult, getObjectValue, includesAny, PersonId, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import ui, {
|
||||
deviceOptionsStore,
|
||||
EditWithIcon,
|
||||
Icon,
|
||||
IconCheck,
|
||||
IconSearch,
|
||||
Loading,
|
||||
resizeObserver
|
||||
} from '@hcengineering/ui'
|
||||
import view, { Filter } from '@hcengineering/view'
|
||||
import { FILTER_DEBOUNCE_MS, sortFilterValues } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import PersonPresenter from './PersonPresenter.svelte'
|
||||
|
||||
export let filter: Filter
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let onChange: (e: Filter) => void
|
||||
|
||||
const client = getClient()
|
||||
filter.modes = filter.modes === undefined ? [view.filter.FilterObjectIn, view.filter.FilterObjectNin] : filter.modes
|
||||
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
|
||||
|
||||
let socialIdentities: (WithLookup<SocialIdentity> | undefined | null)[] = []
|
||||
let socialIdentitiesPromise: Promise<FindResult<SocialIdentity>> | undefined
|
||||
let personIdToPersonMap: Record<PersonId, Ref<Person>> = {}
|
||||
let personToPersonIdsMap: Record<Ref<Person>, PersonId[]> = {}
|
||||
let persons: Ref<Person>[] = []
|
||||
const targets = new Set<any>()
|
||||
|
||||
let filterUpdateTimeout: any | undefined
|
||||
let search: string = ''
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getValues (search: string): Promise<void> {
|
||||
if (socialIdentitiesPromise !== undefined) {
|
||||
await socialIdentitiesPromise
|
||||
}
|
||||
targets.clear()
|
||||
|
||||
const spaces = (
|
||||
await client.findAll(core.class.Space, { archived: true }, { projection: { _id: 1, archived: 1, _class: 1 } })
|
||||
).map((it) => it._id)
|
||||
|
||||
const baseObjects = await client.findAll(
|
||||
filter.key._class,
|
||||
space !== undefined ? { space } : { space: { $nin: spaces } },
|
||||
{
|
||||
projection: { [filter.key.key]: 1, space: 1 }
|
||||
}
|
||||
)
|
||||
for (const object of baseObjects) {
|
||||
const socialIdentity = getObjectValue(filter.key.key, object) ?? undefined
|
||||
targets.add(socialIdentity)
|
||||
}
|
||||
for (const object of filter.value) {
|
||||
targets.add(object)
|
||||
}
|
||||
|
||||
const resultQuery =
|
||||
search !== ''
|
||||
? {
|
||||
'$lookup.attachedTo.name': { $like: '%' + search + '%' },
|
||||
_id: { $in: Array.from(targets.keys()) }
|
||||
}
|
||||
: {
|
||||
_id: { $in: Array.from(targets.keys()) }
|
||||
}
|
||||
socialIdentitiesPromise = client.findAll(contact.class.SocialIdentity, resultQuery, {
|
||||
lookup: { attachedTo: contact.class.Person }
|
||||
})
|
||||
socialIdentities = await socialIdentitiesPromise
|
||||
if (targets.has(undefined)) {
|
||||
socialIdentities.unshift(undefined)
|
||||
}
|
||||
|
||||
personIdToPersonMap = (socialIdentities ?? []).reduce<Record<string, Ref<Person>>>((acc, sid) => {
|
||||
if (sid == null) return acc
|
||||
|
||||
const person = sid?.$lookup?.attachedTo
|
||||
|
||||
if (person == null) return acc
|
||||
|
||||
acc[sid._id] = person._id
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
personToPersonIdsMap = (socialIdentities ?? []).reduce<Record<Ref<Person>, PersonId[]>>((acc, sid) => {
|
||||
if (sid == null) return acc
|
||||
|
||||
const person = sid?.$lookup?.attachedTo
|
||||
|
||||
if (person == null) return acc
|
||||
|
||||
if (acc[person._id] == null) {
|
||||
acc[person._id] = []
|
||||
}
|
||||
|
||||
acc[person._id].push(sid._id)
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
persons = sortFilterValues(Array.from(new Set(Object.values(personIdToPersonMap))), (p) =>
|
||||
isPersonSelected(p, filter.value)
|
||||
)
|
||||
|
||||
socialIdentitiesPromise = undefined
|
||||
}
|
||||
|
||||
function isPersonSelected (person: Ref<Person>, selectedIds: any[]): boolean {
|
||||
const personSocialIds = personToPersonIdsMap[person] ?? []
|
||||
|
||||
return includesAny(personSocialIds, selectedIds)
|
||||
}
|
||||
|
||||
function handleFilterToggle (person: Ref<Person>): void {
|
||||
const personSocialIds = personToPersonIdsMap[person] ?? []
|
||||
if (isPersonSelected(person, filter.value)) {
|
||||
filter.value = filter.value.filter((p) => !personSocialIds.includes(p))
|
||||
} else {
|
||||
filter.value = [...filter.value, ...personSocialIds]
|
||||
}
|
||||
|
||||
updateFilter()
|
||||
}
|
||||
|
||||
function updateFilter (): void {
|
||||
clearTimeout(filterUpdateTimeout)
|
||||
|
||||
filterUpdateTimeout = setTimeout(() => {
|
||||
onChange(filter)
|
||||
}, FILTER_DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
$: {
|
||||
void getValues(search)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
<div class="header">
|
||||
<EditWithIcon
|
||||
icon={IconSearch}
|
||||
size={'large'}
|
||||
width={'100%'}
|
||||
autoFocus={!$deviceOptionsStore.isMobile}
|
||||
bind:value={search}
|
||||
placeholder={presentation.string.Search}
|
||||
/>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
{#if socialIdentitiesPromise}
|
||||
<Loading />
|
||||
{:else}
|
||||
{#each persons as person}
|
||||
<button
|
||||
class="menu-item no-focus flex-row-center"
|
||||
on:click={() => {
|
||||
handleFilterToggle(person)
|
||||
}}
|
||||
>
|
||||
<div class="clear-mins flex-grow">
|
||||
<PersonPresenter
|
||||
value={person}
|
||||
shouldShowPlaceholder
|
||||
defaultName={ui.string.NotSelected}
|
||||
disabled
|
||||
noUnderline
|
||||
/>
|
||||
</div>
|
||||
<div class="check pointer-events-none">
|
||||
{#if isPersonSelected(person, filter.value)}
|
||||
<Icon icon={IconCheck} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-space" />
|
||||
</div>
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import {
|
||||
AccountRole,
|
||||
SocialIdType,
|
||||
type AccountUuid,
|
||||
type Class,
|
||||
type Client,
|
||||
type Data,
|
||||
@@ -102,6 +103,7 @@ import PersonFilterValuePresenter from './components/PersonFilterValuePresenter.
|
||||
import PersonEditor from './components/PersonEditor.svelte'
|
||||
import PersonIcon from './components/PersonIcon.svelte'
|
||||
import PersonPresenter from './components/PersonPresenter.svelte'
|
||||
import PersonIdFilter from './components/PersonIdFilter.svelte'
|
||||
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
|
||||
import SelectAvatars from './components/SelectAvatars.svelte'
|
||||
import SelectUsersPopup from './components/SelectUsersPopup.svelte'
|
||||
@@ -199,7 +201,8 @@ export {
|
||||
UserDetails,
|
||||
UserInfo,
|
||||
UsersList,
|
||||
UsersPopup
|
||||
UsersPopup,
|
||||
PersonIdFilter
|
||||
}
|
||||
|
||||
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
||||
@@ -299,7 +302,7 @@ async function kickEmployee (doc: Person): Promise<void> {
|
||||
|
||||
if (doc.personUuid != null) {
|
||||
const leaveWorkspace = await getResource(login.function.LeaveWorkspace)
|
||||
await leaveWorkspace(doc.personUuid)
|
||||
await leaveWorkspace(doc.personUuid as AccountUuid)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -390,7 +393,8 @@ export default async (): Promise<Resources> => ({
|
||||
EditOrganizationPanel,
|
||||
ChannelIcon,
|
||||
SpaceMembersEditor,
|
||||
ContactNamePresenter
|
||||
ContactNamePresenter,
|
||||
PersonIdFilter
|
||||
},
|
||||
completion: {
|
||||
EmployeeQuery: async (
|
||||
|
||||
@@ -238,7 +238,8 @@ export const contactPlugin = plugin(contactId, {
|
||||
CreateGuest: '' as AnyComponent,
|
||||
SpaceMembersEditor: '' as AnyComponent,
|
||||
ContactNamePresenter: '' as AnyComponent,
|
||||
PersonFilterValuePresenter: '' as AnyComponent
|
||||
PersonFilterValuePresenter: '' as AnyComponent,
|
||||
PersonIdFilter: '' as AnyComponent
|
||||
},
|
||||
channelProvider: {
|
||||
Email: '' as Ref<ChannelProvider>,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
MeasureContext,
|
||||
notEmpty,
|
||||
PersonId,
|
||||
pickPrimarySocialId,
|
||||
Ref,
|
||||
SocialId,
|
||||
toIdMap,
|
||||
@@ -283,15 +284,6 @@ export function formatContactName (
|
||||
return name
|
||||
}
|
||||
|
||||
// TODO: remove me in favor of the same util in core package
|
||||
export function pickPrimarySocialId (ids: PersonId[]): PersonId {
|
||||
if (ids.length === 0) {
|
||||
throw new Error('No social ids provided')
|
||||
}
|
||||
|
||||
return ids[0]
|
||||
}
|
||||
|
||||
export function includesAny (members: PersonId[], ids: PersonId[]): boolean {
|
||||
return members.some((m) => ids.includes(m))
|
||||
}
|
||||
@@ -337,13 +329,13 @@ export async function getPersonRefsBySocialIds (
|
||||
}
|
||||
|
||||
export async function getPrimarySocialId (client: Client, person: Ref<Person>): Promise<PersonId | undefined> {
|
||||
const socialIds = await client.findAll(contact.class.SocialIdentity, { attachedTo: person })
|
||||
const socialIds = await client.findAll(contact.class.SocialIdentity, { attachedTo: person, verifiedOn: { $gt: 0 } })
|
||||
|
||||
if (socialIds.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
return pickPrimarySocialId(socialIds.map((it) => it._id))
|
||||
return pickPrimarySocialId(socialIds)._id
|
||||
}
|
||||
|
||||
export async function getAllSocialStringsByPersonId (client: Client, personId: PersonId): Promise<PersonId[]> {
|
||||
@@ -393,12 +385,6 @@ export async function getAllAccounts (client: Client): Promise<AccountUuid[]> {
|
||||
return employees.map((it) => it.personUuid).filter(notEmpty)
|
||||
}
|
||||
|
||||
export async function getAllEmployeesPrimarySocialStrings (client: Client): Promise<PersonId[]> {
|
||||
const socialStringsByPerson = getSocialStringsByEmployee(client)
|
||||
|
||||
return Object.values(socialStringsByPerson).map((it) => pickPrimarySocialId(it))
|
||||
}
|
||||
|
||||
export async function getAllUserAccounts (client: Client): Promise<AccountUuid[]> {
|
||||
const employees = await client.findAll(contact.mixin.Employee, { active: true })
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
AccountRole,
|
||||
concatLink,
|
||||
parseSocialIdString,
|
||||
type AccountUuid,
|
||||
type Person,
|
||||
type WorkspaceInfoWithStatus,
|
||||
type WorkspaceUserOperation
|
||||
@@ -661,7 +662,7 @@ export async function changeUsername (first: string, last: string): Promise<void
|
||||
}
|
||||
}
|
||||
|
||||
export async function leaveWorkspace (account: string): Promise<LoginInfo | null> {
|
||||
export async function leaveWorkspace (account: AccountUuid): Promise<LoginInfo | null> {
|
||||
return await getAccountClient().leaveWorkspace(account)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, Space } from '@hcengineering/core'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Filter } from '@hcengineering/view'
|
||||
|
||||
export let filter: Filter
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let onChange: (e: Filter) => void
|
||||
</script>
|
||||
|
||||
<Component is={contact.component.PersonIdFilter} props={{ filter, space, onChange }} />
|
||||
@@ -18,17 +18,20 @@
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import contact, { getPersonRefsBySocialIds, Person } from '@hcengineering/contact'
|
||||
|
||||
export let value: [any, PersonId][]
|
||||
export let value: PersonId[]
|
||||
|
||||
const client = getClient()
|
||||
let persons: Ref<Person>[] = []
|
||||
$: void getPersons(value.map((p) => p[1]).flat())
|
||||
$: void getPersons(value)
|
||||
|
||||
async function getPersons (ids: PersonId[]): Promise<void> {
|
||||
if (ids !== undefined) {
|
||||
const personsMap = await getPersonRefsBySocialIds(client, ids)
|
||||
persons = Object.values(personsMap)
|
||||
console.log(persons)
|
||||
if (ids.length === 0) {
|
||||
persons = []
|
||||
} else {
|
||||
const personsMap = await getPersonRefsBySocialIds(client, ids)
|
||||
persons = Object.values(personsMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -98,6 +98,7 @@ import StatusPresenter from './components/status/StatusPresenter.svelte'
|
||||
import StatusRefPresenter from './components/status/StatusRefPresenter.svelte'
|
||||
import PersonArrayEditor from './components/PersonArrayEditor.svelte'
|
||||
import PersonIdPresenter from './components/PersonIdPresenter.svelte'
|
||||
import PersonIdFilter from './components/filter/PersonIdFilter.svelte'
|
||||
import PersonIdFilterValuePresenter from './components/filter/PersonIdFilterValuePresenter.svelte'
|
||||
import AudioViewer from './components/viewer/AudioViewer.svelte'
|
||||
import ImageViewer from './components/viewer/ImageViewer.svelte'
|
||||
@@ -176,6 +177,7 @@ export { default as NavLink } from './components/navigator/NavLink.svelte'
|
||||
export { default as StatusPresenter } from './components/status/StatusPresenter.svelte'
|
||||
export { default as StatusRefPresenter } from './components/status/StatusRefPresenter.svelte'
|
||||
export { default as PersonIdPresenter } from './components/PersonIdPresenter.svelte'
|
||||
export { default as PersonIdFilter } from './components/filter/PersonIdFilter.svelte'
|
||||
export { default as PersonIdFilterValuePresenter } from './components/filter/PersonIdFilterValuePresenter.svelte'
|
||||
export { default as FoldersBrowser } from './components/folders/FoldersBrowser.svelte'
|
||||
export { default as RelationsEditor } from './components/RelationsEditor.svelte'
|
||||
@@ -309,6 +311,7 @@ export default async (): Promise<Resources> => ({
|
||||
StatusRefPresenter,
|
||||
PersonArrayEditor,
|
||||
PersonIdPresenter,
|
||||
PersonIdFilter,
|
||||
PersonIdFilterValuePresenter,
|
||||
DateFilterPresenter,
|
||||
StringFilterPresenter,
|
||||
|
||||
@@ -175,6 +175,7 @@ const view = plugin(viewId, {
|
||||
SearchSelector: '' as AnyComponent,
|
||||
FoldersBrowser: '' as AnyComponent,
|
||||
PersonIdPresenter: '' as AnyComponent,
|
||||
PersonIdFilter: '' as AnyComponent,
|
||||
RolePresenter: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
|
||||
Reference in New Issue
Block a user