Fix role migration, remove extra rosamunds (#2190)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
Co-authored-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Denis Bykhov 2022-07-03 00:49:22 +06:00 committed by GitHub
parent a4b3cb4a44
commit a5b1d5c278
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 102 additions and 52 deletions

View File

@ -88,7 +88,8 @@ async function setRole (client: MigrationClient): Promise<void> {
DOMAIN_TX,
{
_class: core.class.TxCreateDoc,
objectClass: contact.class.Employee
objectClass: contact.class.EmployeeAccount,
'attributes.role': { $exists: false }
},
{
'attributes.role': AccountRole.User

View File

@ -13,10 +13,11 @@
// limitations under the License.
//
import core, { AccountRole, TxOperations } from '@anticrm/core'
import core, { AccountRole, DOMAIN_TX, TxCreateDoc, TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
import contact, { EmployeeAccount } from '@anticrm/contact'
import recruit from '@anticrm/model-recruit'
import { DOMAIN_CONTACT } from '@anticrm/model-contact'
async function createCandidate (
tx: TxOperations,
@ -45,20 +46,37 @@ async function createCandidate (
}
export const demoOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async migrate (client: MigrationClient): Promise<void> {
const rosamunds = await client.find<TxCreateDoc<EmployeeAccount>>(DOMAIN_TX, {
_class: core.class.TxCreateDoc,
objectClass: contact.class.EmployeeAccount,
'attributes.email': 'rosamund@hc.engineering'
})
const docs = await client.find(DOMAIN_CONTACT, {
_id: { $in: rosamunds.map((p) => p.attributes.employee) }
})
const currentEmployees = new Set(docs.map((p) => p._id))
for (const rosamund of rosamunds) {
if (!currentEmployees.has(rosamund.attributes.employee)) await client.delete(DOMAIN_TX, rosamund._id)
}
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
const current = await tx.findOne(contact.class.EmployeeAccount, {
const ops = new TxOperations(client, core.account.System)
const tx = await ops.findOne(core.class.TxCreateDoc, {
objectClass: contact.class.EmployeeAccount,
'attributes.email': 'rosamund@hc.engineering'
})
const current = await ops.findOne(contact.class.EmployeeAccount, {
email: 'rosamund@hc.engineering'
})
if (current === undefined) {
const employee = await tx.createDoc(contact.class.Employee, contact.space.Employee, {
if (tx === undefined && current === undefined) {
const employee = await ops.createDoc(contact.class.Employee, contact.space.Employee, {
name: 'Chen,Rosamund',
city: 'Mountain View',
active: true
})
await tx.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, {
await ops.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, {
email: 'rosamund@hc.engineering',
employee,
name: 'Chen,Rosamund',
@ -66,8 +84,8 @@ export const demoOperation: MigrateOperation = {
})
}
await createCandidate(tx, 'P.,Andrey', 'Monte Carlo', 'andrey@hc.engineering', 'Chief Architect')
await createCandidate(tx, 'M.,Marina', 'Los Angeles', 'marina@hc.engineering', 'Chief Designer')
await createCandidate(tx, 'P.,Alex', 'Krasnodar, Russia', 'alex@hc.engineering', 'Frontend Engineer')
await createCandidate(ops, 'P.,Andrey', 'Monte Carlo', 'andrey@hc.engineering', 'Chief Architect')
await createCandidate(ops, 'M.,Marina', 'Los Angeles', 'marina@hc.engineering', 'Chief Designer')
await createCandidate(ops, 'P.,Alex', 'Krasnodar, Russia', 'alex@hc.engineering', 'Frontend Engineer')
}
}

View File

@ -170,6 +170,7 @@ export function createModel (builder: Builder): void {
icon: notification.icon.Notifications,
component: notification.component.NotificationSettings,
group: 'settings',
secured: false,
order: 2500
},
notification.ids.NotificationSettings

View File

@ -84,7 +84,7 @@
}
okProcessing = true
const r = okAction()
if (r instanceof Promise && !createMore) {
if (r instanceof Promise && createMore) {
r.then(() => {
okProcessing = false
dispatch('close')

View File

@ -67,6 +67,7 @@
"KickEmployee": "Kick an employee",
"KickEmployeeDescr": "Are you sure you want to kick the employee out of the workspace? This action cannot be undone",
"Email": "Email",
"CreateEmployee": "Create an employee"
"CreateEmployee": "Create an employee",
"Inactive": "Inactive"
}
}

View File

@ -67,6 +67,7 @@
"KickEmployee": "Исключить сотрудника",
"KickEmployeeDescr": "Вы действительно хотите выгнать сотрудника из рабочего пространства? Это действие нельзя отменить",
"Email": "Email",
"CreateEmployee": "Создать сотрудника"
"CreateEmployee": "Создать сотрудника",
"Inactive": "Не активный"
}
}

View File

@ -14,8 +14,10 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Contact, Organization } from '@anticrm/contact'
import { Contact, Employee, Organization } from '@anticrm/contact'
import { getClient } from '@anticrm/presentation'
import { Label } from '@anticrm/ui'
import contact from '../plugin'
import OrganizationPresenter from './OrganizationPresenter.svelte'
import PersonPresenter from './PersonPresenter.svelte'
@ -28,11 +30,22 @@
const hierarchy = client.getHierarchy()
return hierarchy.isDerived(value._class, contact.class.Person)
}
function isEmployee (value: Contact): boolean {
const client = getClient()
const hierarchy = client.getHierarchy()
return hierarchy.isDerived(value._class, contact.class.Employee)
}
const toOrg = (contact: Contact) => contact as Organization
const toEmployee = (contact: Contact) => contact as Employee
</script>
{#if isPerson(value)}
<PersonPresenter {isInteractive} {value} />
{#if isEmployee(value) && toEmployee(value)?.active === false}
<div class="ml-1">
(<Label label={contact.string.Inactive} />)
</div>
{/if}
{:else}
<OrganizationPresenter value={toOrg(value)} />
{/if}

View File

@ -1,12 +1,12 @@
<script lang="ts">
import { Employee } from '@anticrm/contact'
import EmployeeStatusPresenter from './EmployeeStatusPresenter.svelte'
import PersonPresenter from '../components/PersonPresenter.svelte'
import { showPopup } from '@anticrm/ui'
import EmployeePreviewPopup from './EmployeePreviewPopup.svelte'
import { WithLookup } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
import { showPopup } from '@anticrm/ui'
import PersonPresenter from '../components/PersonPresenter.svelte'
import EmployeePreviewPopup from './EmployeePreviewPopup.svelte'
import EmployeeStatusPresenter from './EmployeeStatusPresenter.svelte'
export let value: WithLookup<Employee> | null | undefined
export let tooltipLabels:

View File

@ -60,6 +60,7 @@ export default mergeIds(contactId, contact, {
KickEmployee: '' as IntlString,
KickEmployeeDescr: '' as IntlString,
Email: '' as IntlString,
CreateEmployee: '' as IntlString
CreateEmployee: '' as IntlString,
Inactive: '' as IntlString
}
})

View File

@ -49,7 +49,7 @@
<span class="antiSection-header__title">
<Label label={recruit.string.Applications} />
</span>
<Button icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
<Button id="appls.add" icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
</div>
{#if applications > 0}
<Table

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import { PersonPresenter } from '@anticrm/contact-resources'
import { AccountRole, getCurrentAccount, Ref, SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
@ -71,8 +71,13 @@
<div class="ac-body columns">
<div class="ac-column max">
{#each accounts as account (account._id)}
{@const employee = employees.get(account.employee)}
<div class="flex-between">
<PersonPresenter value={employees.get(account.employee)} isInteractive={false} />
{#if employee}
<PersonPresenter value={employee} isInteractive={false} />
{:else}
{formatName(account.name)}
{/if}
<DropdownLabelsIntl
label={setting.string.Role}
disabled={account.role > currentRole || (account.role === AccountRole.Owner && owners.length === 1)}

View File

@ -17,7 +17,8 @@
import { AccountRole, getCurrentAccount } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import setting, { SettingsCategory } from '@anticrm/setting'
import { Component, Label } from '@anticrm/ui'
import { Component, getCurrentLocation, Label, location, navigate } from '@anticrm/ui'
import { onDestroy } from 'svelte'
import CategoryElement from './CategoryElement.svelte'
let category: SettingsCategory | undefined
@ -41,9 +42,18 @@
return categories.find((x) => x.name === name)
}
function selectCategory (value: SettingsCategory) {
categoryId = value.name
category = value
onDestroy(
location.subscribe(async (loc) => {
categoryId = loc.path[3]
category = findCategory(categoryId)
})
)
function selectCategory (id: string): void {
const loc = getCurrentLocation()
loc.path[3] = id
loc.path.length = 4
navigate(loc)
}
</script>
@ -60,7 +70,7 @@
label={category.label}
selected={category.name === categoryId}
on:click={() => {
selectCategory(category)
selectCategory(category.name)
}}
/>
{/each}

View File

@ -37,9 +37,11 @@
}
async function newIssue (): Promise<void> {
if (space) {
showPopup(CreateIssue, { space }, 'top')
if (!space) {
const team = await client.findOne(tracker.class.Team, {})
space = team?._id
}
showPopup(CreateIssue, { space }, 'top')
}
</script>

View File

@ -36,39 +36,32 @@
let defaultProjectLabel = ''
const query = createQuery()
let projects: Map<Ref<Project>, Project> = new Map<Ref<Project>, Project>()
let rawProjects: Project[] = []
query.query(
tracker.class.Project,
{},
(res) => {
projects = new Map(
res.map((p) => {
return [p._id, p]
})
)
rawProjects = res
},
{
sort: { modifiedOn: SortingOrder.Ascending }
}
)
$: handleSelectedProjectIdUpdated(value, projects)
$: handleSelectedProjectIdUpdated(value, rawProjects)
$: translate(tracker.string.Project, {}).then((result) => (defaultProjectLabel = result))
$: projectIcon = selectedProject?.icon ?? tracker.icon.Projects
$: projectText = shouldShowLabel ? selectedProject?.label ?? defaultProjectLabel : undefined
const handleSelectedProjectIdUpdated = async (
newProjectId: Ref<Project> | null | undefined,
projects: Map<Ref<Project>, Project>
) => {
const handleSelectedProjectIdUpdated = async (newProjectId: Ref<Project> | null | undefined, projects: Project[]) => {
if (newProjectId === null || newProjectId === undefined) {
selectedProject = undefined
return
}
selectedProject = projects.get(newProjectId)
selectedProject = projects.find((it) => it._id === newProjectId)
}
const handleProjectEditorOpened = async (event: MouseEvent): Promise<void> => {
@ -79,7 +72,7 @@
const projectsInfo = [
{ id: null, icon: tracker.icon.Projects, label: tracker.string.NoProject },
...Array.from(projects.values()).map((p) => ({
...rawProjects.map((p) => ({
id: p._id,
icon: p.icon,
text: p.label

View File

@ -66,8 +66,9 @@
ev.stopPropagation()
const loc = getCurrentLocation()
loc.path[1] = settingId
loc.path[2] = 'classes'
loc.path.length = 3
loc.path[2] = 'setting'
loc.path[3] = 'classes'
loc.path.length = 4
loc.query = { _class }
loc.fragment = undefined
navigate(loc)

View File

@ -457,8 +457,9 @@ export async function setRole (email: string, workspace: string, role: AccountRo
const existingAccount = await ops.findOne(contact.class.EmployeeAccount, { email })
if (existingAccount !== undefined) {
const value = isNaN(Number(role)) ? 0 : Number(role)
await ops.update(existingAccount, {
role
role: value
})
}
} finally {

View File

@ -78,7 +78,7 @@ test.describe('recruit tests', () => {
// Click on Add button
// await page.click('.applications-container .flex-row-center .flex-center')
await page.click('text=Applications There are no applications for this talent. New Application >> button')
await page.click('button[id="appls.add"]')
await page.click('button:has-text("Vacancy")')

View File

@ -44,13 +44,13 @@ test.describe('contact tests', () => {
// Click button:has-text("Settings")
await page.hover('button:has-text("Settings")')
await page.click('button:has-text("Settings")')
// Click text=Workspace Integrations >> button
await page.click('text=Workspace Integrations >> button')
// Click text=Workspace Notifications >> button
await page.click('text=Workspace Notifications >> button')
await page.click('text=Templates')
// Click .flex-center.icon-button
await page.click('#create-template >> .flex-center.icon-button')
// Click [placeholder="New\ template"]
await page.click('[placeholder="New\\ template"]')
// await page.click('[placeholder="New\\ template"]')
// Fill [placeholder="New\ template"]
await page.fill('[placeholder="New\\ template"]', 't1')
@ -77,7 +77,8 @@ test.describe('contact tests', () => {
// await page.click('text=Workspace')
await page.hover('button:has-text("Settings")')
await page.click('button:has-text("Settings")')
await page.click('text=Workspace Integrations >> button')
// Click text=Workspace Notifications >> button
await page.click('text=Workspace Notifications >> button')
// Click button:has-text("Manage Statuses")
await page.click('text="Manage Statuses"')
// Click text=Vacancies

View File

@ -49,6 +49,7 @@ async function fillIssueForm (
}
async function createIssue (page: Page, props: IssueProps): Promise<void> {
await page.waitForSelector('span:has-text("Default")')
await page.click('button:has-text("New issue")')
await fillIssueForm(page, props)
await page.click('button:has-text("Save issue")')

View File

@ -12,7 +12,7 @@ function toHex (value: number, chars: number): string {
return result
}
let counter = (Math.random() * (1 << 24)) | 0
let counter = 0
const random = toHex((Math.random() * (1 << 24)) | 0, 6) + toHex((Math.random() * (1 << 16)) | 0, 4)
function timestamp (): string {