mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
EZQMS-729: Restrict spaces operations (#5500)
This commit is contained in:
parent
ad59463057
commit
4bc1534ee0
@ -505,7 +505,11 @@ export function createModel (builder: Builder): void {
|
||||
description: board.string.ManageBoardStatuses,
|
||||
icon: board.icon.Board,
|
||||
baseClass: board.class.Board,
|
||||
availablePermissions: [core.permission.ForbidDeleteObject],
|
||||
availablePermissions: [
|
||||
core.permission.UpdateSpace,
|
||||
core.permission.ArchiveSpace,
|
||||
core.permission.ForbidDeleteObject
|
||||
],
|
||||
allowedTaskTypeDescriptors: [board.descriptors.Card]
|
||||
},
|
||||
board.descriptors.BoardType
|
||||
|
@ -103,6 +103,7 @@ import {
|
||||
TTxUpdateDoc,
|
||||
TTxWorkspaceEvent
|
||||
} from './tx'
|
||||
import { defineSpaceType } from './spaceType'
|
||||
|
||||
export { coreId } from '@hcengineering/core'
|
||||
export * from './core'
|
||||
@ -328,4 +329,5 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
definePermissions(builder)
|
||||
defineSpaceType(builder)
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ import core, {
|
||||
TxOperations,
|
||||
generateId,
|
||||
DOMAIN_TX,
|
||||
type TxCreateDoc
|
||||
type TxCreateDoc,
|
||||
type Space
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
tryMigrate,
|
||||
@ -31,6 +32,7 @@ import {
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import { DOMAIN_SPACE } from './security'
|
||||
|
||||
async function migrateStatusesToModel (client: MigrationClient): Promise<void> {
|
||||
// Move statuses to model:
|
||||
@ -75,6 +77,44 @@ async function migrateStatusesToModel (client: MigrationClient): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateAllSpaceToTyped (client: MigrationClient): Promise<void> {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{
|
||||
_id: core.space.Space,
|
||||
_class: core.class.Space
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
_class: core.class.TypedSpace,
|
||||
type: core.spaceType.SpacesType
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function migrateSpacesOwner (client: MigrationClient): Promise<void> {
|
||||
const targetClasses = client.hierarchy.getDescendants(core.class.Space)
|
||||
const targetSpaces = await client.find<Space>(DOMAIN_SPACE, {
|
||||
_class: { $in: targetClasses },
|
||||
owners: { $exists: false }
|
||||
})
|
||||
|
||||
for (const space of targetSpaces) {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{
|
||||
_id: space._id
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
owners: [space.createdBy]
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const coreOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
// We need to delete all documents in doc index state for missing classes
|
||||
@ -95,6 +135,14 @@ export const coreOperation: MigrateOperation = {
|
||||
{
|
||||
state: 'statuses-to-model',
|
||||
func: migrateStatusesToModel
|
||||
},
|
||||
{
|
||||
state: 'all-space-to-typed',
|
||||
func: migrateAllSpaceToTyped
|
||||
},
|
||||
{
|
||||
state: 'add-spaces-owner',
|
||||
func: migrateSpacesOwner
|
||||
}
|
||||
])
|
||||
},
|
||||
@ -110,14 +158,15 @@ export const coreOperation: MigrateOperation = {
|
||||
})
|
||||
if (spaceSpace === undefined) {
|
||||
await tx.createDoc(
|
||||
core.class.Space,
|
||||
core.class.TypedSpace,
|
||||
core.space.Space,
|
||||
{
|
||||
name: 'Space for all spaces',
|
||||
description: 'Spaces',
|
||||
private: false,
|
||||
archived: false,
|
||||
members: []
|
||||
members: [],
|
||||
type: core.spaceType.SpacesType
|
||||
},
|
||||
core.space.Space
|
||||
)
|
||||
|
@ -22,7 +22,8 @@ export function definePermissions (builder: Builder): void {
|
||||
core.class.Permission,
|
||||
core.space.Model,
|
||||
{
|
||||
label: core.string.CreateObject
|
||||
label: core.string.CreateObject,
|
||||
description: core.string.CreateObjectDescription
|
||||
},
|
||||
core.permission.CreateObject
|
||||
)
|
||||
@ -31,7 +32,8 @@ export function definePermissions (builder: Builder): void {
|
||||
core.class.Permission,
|
||||
core.space.Model,
|
||||
{
|
||||
label: core.string.UpdateObject
|
||||
label: core.string.UpdateObject,
|
||||
description: core.string.UpdateObjectDescription
|
||||
},
|
||||
core.permission.UpdateObject
|
||||
)
|
||||
@ -55,4 +57,44 @@ export function definePermissions (builder: Builder): void {
|
||||
},
|
||||
core.permission.ForbidDeleteObject
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.Permission,
|
||||
core.space.Model,
|
||||
{
|
||||
label: core.string.UpdateObject,
|
||||
description: core.string.UpdateObjectDescription
|
||||
},
|
||||
core.permission.UpdateObject
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.Permission,
|
||||
core.space.Model,
|
||||
{
|
||||
label: core.string.DeleteObject,
|
||||
description: core.string.DeleteObjectDescription
|
||||
},
|
||||
core.permission.DeleteObject
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.Permission,
|
||||
core.space.Model,
|
||||
{
|
||||
label: core.string.UpdateSpace,
|
||||
description: core.string.UpdateSpaceDescription
|
||||
},
|
||||
core.permission.UpdateSpace
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.Permission,
|
||||
core.space.Model,
|
||||
{
|
||||
label: core.string.ArchiveSpace,
|
||||
description: core.string.ArchiveSpaceDescription
|
||||
},
|
||||
core.permission.ArchiveSpace
|
||||
)
|
||||
}
|
||||
|
@ -28,13 +28,15 @@ import {
|
||||
type Role,
|
||||
type Class,
|
||||
type Permission,
|
||||
type CollectionSize
|
||||
type CollectionSize,
|
||||
type RolesAssignment
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
ArrOf,
|
||||
Collection,
|
||||
Hidden,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
TypeBoolean,
|
||||
@ -42,7 +44,7 @@ import {
|
||||
TypeString,
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { getEmbeddedLabel, type Asset, type IntlString } from '@hcengineering/platform'
|
||||
import core from './component'
|
||||
import { TDoc, TAttachedDoc } from './core'
|
||||
|
||||
@ -70,6 +72,9 @@ export class TSpace extends TDoc implements Space {
|
||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)
|
||||
@Hidden()
|
||||
members!: Arr<Ref<Account>>
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Owners)
|
||||
owners?: Ref<Account>[]
|
||||
}
|
||||
|
||||
@Model(core.class.TypedSpace, core.class.Space)
|
||||
@ -86,6 +91,7 @@ export class TSpaceTypeDescriptor extends TDoc implements SpaceTypeDescriptor {
|
||||
icon!: Asset
|
||||
baseClass!: Ref<Class<Space>>
|
||||
availablePermissions!: Ref<Permission>[]
|
||||
system?: boolean
|
||||
}
|
||||
|
||||
@Model(core.class.SpaceType, core.class.Doc, DOMAIN_MODEL)
|
||||
@ -141,6 +147,12 @@ export class TPermission extends TDoc implements Permission {
|
||||
icon?: Asset
|
||||
}
|
||||
|
||||
@Mixin(core.mixin.SpacesTypeData, core.class.Space)
|
||||
@UX(getEmbeddedLabel("All spaces' type")) // TODO: add icon?
|
||||
export class TSpacesTypeData extends TSpace implements RolesAssignment {
|
||||
[key: Ref<Role>]: Ref<Account>[]
|
||||
}
|
||||
|
||||
@Model(core.class.Account, core.class.Doc, DOMAIN_MODEL)
|
||||
@UX(core.string.Account)
|
||||
export class TAccount extends TDoc implements Account {
|
||||
|
81
models/core/src/spaceType.ts
Normal file
81
models/core/src/spaceType.ts
Normal file
@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright © 2024 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.
|
||||
//
|
||||
|
||||
import { ArrOf, Prop, TypeRef, type Builder } from '@hcengineering/model'
|
||||
import { type Asset } from '@hcengineering/platform'
|
||||
import { getRoleAttributeBaseProps } from '@hcengineering/core'
|
||||
|
||||
import { TSpacesTypeData } from './security'
|
||||
import core from './component'
|
||||
|
||||
const roles = [
|
||||
{
|
||||
_id: core.role.Admin,
|
||||
name: 'Admin',
|
||||
permissions: [core.permission.UpdateObject, core.permission.DeleteObject]
|
||||
}
|
||||
]
|
||||
|
||||
export function defineSpaceType (builder: Builder): void {
|
||||
for (const role of roles) {
|
||||
const { label, id } = getRoleAttributeBaseProps(role, role._id)
|
||||
const roleAssgtType = ArrOf(TypeRef(core.class.Account))
|
||||
|
||||
Prop(roleAssgtType, label)(TSpacesTypeData.prototype, id)
|
||||
}
|
||||
|
||||
builder.createModel(TSpacesTypeData)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.SpaceTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
name: core.string.Spaces,
|
||||
description: core.string.SpacesDescription,
|
||||
icon: '' as Asset, // FIXME
|
||||
baseClass: core.class.Space,
|
||||
availablePermissions: [core.permission.UpdateObject, core.permission.DeleteObject],
|
||||
system: true
|
||||
},
|
||||
core.descriptor.SpacesType
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.SpaceType,
|
||||
core.space.Model,
|
||||
{
|
||||
name: "All spaces' space type",
|
||||
descriptor: core.descriptor.SpacesType,
|
||||
roles: roles.length,
|
||||
targetClass: core.mixin.SpacesTypeData
|
||||
},
|
||||
core.spaceType.SpacesType
|
||||
)
|
||||
|
||||
for (const role of roles) {
|
||||
builder.createDoc(
|
||||
core.class.Role,
|
||||
core.space.Model,
|
||||
{
|
||||
attachedTo: core.spaceType.SpacesType,
|
||||
attachedToClass: core.class.SpaceType,
|
||||
collection: 'roles',
|
||||
name: role.name,
|
||||
permissions: role.permissions
|
||||
},
|
||||
role._id
|
||||
)
|
||||
}
|
||||
}
|
@ -180,7 +180,11 @@ function defineTeamspace (builder: Builder): void {
|
||||
description: document.string.Description,
|
||||
icon: document.icon.Document,
|
||||
baseClass: document.class.Teamspace,
|
||||
availablePermissions: [core.permission.ForbidDeleteObject]
|
||||
availablePermissions: [
|
||||
core.permission.UpdateSpace,
|
||||
core.permission.ArchiveSpace,
|
||||
core.permission.ForbidDeleteObject
|
||||
]
|
||||
},
|
||||
document.descriptor.TeamspaceType
|
||||
)
|
||||
@ -218,6 +222,7 @@ function defineTeamspace (builder: Builder): void {
|
||||
input: 'focus',
|
||||
category: document.category.Document,
|
||||
target: document.class.Teamspace,
|
||||
visibilityTester: view.function.CanEditSpace,
|
||||
query: {},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
|
@ -599,6 +599,7 @@ export function createModel (builder: Builder): void {
|
||||
input: 'focus',
|
||||
category: lead.category.Lead,
|
||||
target: lead.class.Funnel,
|
||||
visibilityTester: view.function.CanEditSpace,
|
||||
override: [view.action.Open],
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { DOMAIN_TX, type Ref, type Status, TxOperations } from '@hcengineering/core'
|
||||
import { AccountRole, DOMAIN_TX, type Ref, type Status, TxOperations } from '@hcengineering/core'
|
||||
import { type Lead, leadId } from '@hcengineering/lead'
|
||||
import {
|
||||
type ModelLogger,
|
||||
@ -24,7 +24,9 @@ import {
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
|
||||
import task, { DOMAIN_TASK, createSequence, migrateDefaultStatusesBase } from '@hcengineering/model-task'
|
||||
import contact from '@hcengineering/model-contact'
|
||||
|
||||
import lead from './plugin'
|
||||
import { defaultLeadStatuses } from './spaceType'
|
||||
@ -147,6 +149,24 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise<void>
|
||||
)
|
||||
}
|
||||
|
||||
async function migrateDefaultProjectOwners (client: MigrationClient): Promise<void> {
|
||||
const workspaceOwners = await client.model.findAll(contact.class.PersonAccount, {
|
||||
role: AccountRole.Owner
|
||||
})
|
||||
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{
|
||||
_id: lead.space.DefaultFunnel
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
owners: workspaceOwners.map((it) => it._id)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const leadOperation: MigrateOperation = {
|
||||
async preMigrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
|
||||
await tryMigrate(client, leadId, [
|
||||
@ -167,6 +187,10 @@ export const leadOperation: MigrateOperation = {
|
||||
func: async (client) => {
|
||||
await migrateDefaultTypeMixins(client)
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'migrateDefaultProjectOwners',
|
||||
func: migrateDefaultProjectOwners
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import { type ChatMessageViewlet } from '@hcengineering/chunter'
|
||||
import type { Doc, Ref, Status } from '@hcengineering/core'
|
||||
import { type Funnel, leadId } from '@hcengineering/lead'
|
||||
import { leadId } from '@hcengineering/lead'
|
||||
import lead from '@hcengineering/lead-resources/src/plugin'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
@ -45,9 +45,6 @@ export default mergeIds(leadId, lead, {
|
||||
Leads: '' as AnyComponent,
|
||||
NewItemsHeader: '' as AnyComponent
|
||||
},
|
||||
space: {
|
||||
DefaultFunnel: '' as Ref<Funnel>
|
||||
},
|
||||
viewlet: {
|
||||
TableCustomer: '' as Ref<Viewlet>,
|
||||
TableLead: '' as Ref<Viewlet>,
|
||||
|
@ -88,7 +88,11 @@ export function defineSpaceType (builder: Builder): void {
|
||||
description: plugin.string.ManageFunnelStatuses,
|
||||
icon: plugin.icon.LeadApplication,
|
||||
baseClass: plugin.class.Funnel,
|
||||
availablePermissions: [core.permission.ForbidDeleteObject],
|
||||
availablePermissions: [
|
||||
core.permission.UpdateSpace,
|
||||
core.permission.ArchiveSpace,
|
||||
core.permission.ForbidDeleteObject
|
||||
],
|
||||
allowedTaskTypeDescriptors: [plugin.descriptors.Lead]
|
||||
},
|
||||
plugin.descriptors.FunnelType
|
||||
|
@ -82,7 +82,7 @@ export function defineSpaceType (builder: Builder): void {
|
||||
icon: plugin.icon.RecruitApplication,
|
||||
editor: plugin.component.VacancyTemplateEditor,
|
||||
baseClass: plugin.class.Vacancy,
|
||||
availablePermissions: [core.permission.ForbidDeleteObject],
|
||||
availablePermissions: [core.permission.ArchiveSpace, core.permission.ForbidDeleteObject],
|
||||
allowedTaskTypeDescriptors: [plugin.descriptors.Application]
|
||||
},
|
||||
plugin.descriptors.VacancyType
|
||||
|
@ -36,6 +36,7 @@
|
||||
"@hcengineering/lead": "^0.6.0",
|
||||
"@hcengineering/model-lead": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/server-notification": "^0.6.1"
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/contact": "^0.6.20"
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,13 @@
|
||||
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
|
||||
import core from '@hcengineering/core'
|
||||
import core, { AccountRole } from '@hcengineering/core'
|
||||
import lead from '@hcengineering/model-lead'
|
||||
import notification from '@hcengineering/notification'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import serverLead from '@hcengineering/server-lead'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import contact from '@hcengineering/contact'
|
||||
|
||||
export { serverLeadId } from '@hcengineering/server-lead'
|
||||
|
||||
@ -40,4 +42,13 @@ export function createModel (builder: Builder): void {
|
||||
func: serverNotification.function.IsUserEmployeeInFieldValue
|
||||
}
|
||||
)
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverLead.trigger.OnWorkspaceOwnerAdded,
|
||||
txMatch: {
|
||||
_class: core.class.TxUpdateDoc,
|
||||
objectClass: contact.class.PersonAccount,
|
||||
'operations.role': AccountRole.Owner
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -35,6 +35,7 @@
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/model-tracker": "^0.6.0",
|
||||
"@hcengineering/server-tracker": "^0.6.0"
|
||||
"@hcengineering/server-tracker": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.20"
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,14 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core from '@hcengineering/core'
|
||||
import core, { AccountRole } from '@hcengineering/core'
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import notification from '@hcengineering/notification'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import serverTracker from '@hcengineering/server-tracker'
|
||||
import contact from '@hcengineering/contact'
|
||||
|
||||
export { serverTrackerId } from '@hcengineering/server-tracker'
|
||||
|
||||
@ -59,6 +60,15 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTracker.trigger.OnWorkspaceOwnerAdded,
|
||||
txMatch: {
|
||||
_class: core.class.TxUpdateDoc,
|
||||
objectClass: contact.class.PersonAccount,
|
||||
'operations.role': AccountRole.Owner
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(
|
||||
tracker.ids.AssigneeNotification,
|
||||
notification.class.NotificationType,
|
||||
|
@ -214,6 +214,19 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
setting.ids.Owners
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'allSpaces',
|
||||
label: setting.string.Spaces,
|
||||
icon: setting.icon.Views,
|
||||
component: setting.component.Spaces,
|
||||
order: 1100,
|
||||
secured: true
|
||||
},
|
||||
setting.ids.Spaces
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
@ -222,7 +235,7 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.Configure,
|
||||
icon: setting.icon.Setting,
|
||||
component: setting.component.Configure,
|
||||
order: 1001,
|
||||
order: 1200,
|
||||
secured: true,
|
||||
adminOnly: true
|
||||
},
|
||||
@ -236,7 +249,7 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.Branding,
|
||||
icon: setting.icon.AccountSettings,
|
||||
component: setting.component.WorkspaceSetting,
|
||||
order: 1002,
|
||||
order: 1300,
|
||||
secured: true
|
||||
},
|
||||
setting.ids.WorkspaceSetting
|
||||
|
@ -266,6 +266,7 @@ export const actionTemplates = template({
|
||||
label: task.string.Archive,
|
||||
message: task.string.ArchiveConfirm
|
||||
},
|
||||
visibilityTester: view.function.CanArchiveSpace,
|
||||
input: 'any',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
@ -288,6 +289,7 @@ export const actionTemplates = template({
|
||||
label: task.string.Unarchive,
|
||||
message: task.string.UnarchiveConfirm
|
||||
},
|
||||
visibilityTester: view.function.CanArchiveSpace,
|
||||
input: 'any',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
|
@ -48,6 +48,7 @@ import {
|
||||
taskId,
|
||||
type ProjectStatus
|
||||
} from '@hcengineering/task'
|
||||
|
||||
import task from './plugin'
|
||||
import { DOMAIN_TASK } from '.'
|
||||
|
||||
|
@ -97,6 +97,7 @@ export function createActions (builder: Builder, issuesId: string, componentsId:
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Project,
|
||||
visibilityTester: view.function.CanEditSpace,
|
||||
query: {},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
@ -115,6 +116,7 @@ export function createActions (builder: Builder, issuesId: string, componentsId:
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Project,
|
||||
visibilityTester: view.function.CanArchiveSpace,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
@ -135,6 +137,7 @@ export function createActions (builder: Builder, issuesId: string, componentsId:
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Project,
|
||||
visibilityTester: view.function.CanDeleteSpace,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
@ -159,6 +162,7 @@ export function createActions (builder: Builder, issuesId: string, componentsId:
|
||||
},
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
visibilityTester: view.function.CanArchiveSpace,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
|
@ -721,7 +721,11 @@ function defineSpaceType (builder: Builder): void {
|
||||
description: tracker.string.ManageWorkflowStatuses,
|
||||
icon: task.icon.Task,
|
||||
baseClass: tracker.class.Project,
|
||||
availablePermissions: [core.permission.ForbidDeleteObject],
|
||||
availablePermissions: [
|
||||
core.permission.UpdateSpace,
|
||||
core.permission.ArchiveSpace,
|
||||
core.permission.ForbidDeleteObject
|
||||
],
|
||||
allowedClassic: true,
|
||||
allowedTaskTypeDescriptors: [tracker.descriptors.Issue]
|
||||
},
|
||||
|
@ -21,7 +21,8 @@ import core, {
|
||||
toIdMap,
|
||||
DOMAIN_TX,
|
||||
type Status,
|
||||
type Ref
|
||||
type Ref,
|
||||
AccountRole
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
type ModelLogger,
|
||||
@ -46,7 +47,9 @@ import {
|
||||
type Project,
|
||||
classicIssueTaskStatuses
|
||||
} from '@hcengineering/tracker'
|
||||
|
||||
import tracker from './plugin'
|
||||
import contact from '@hcengineering/model-contact'
|
||||
|
||||
async function createDefaultProject (tx: TxOperations): Promise<void> {
|
||||
const current = await tx.findOne(tracker.class.Project, {
|
||||
@ -332,6 +335,24 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise<void>
|
||||
)
|
||||
}
|
||||
|
||||
async function migrateDefaultProjectOwners (client: MigrationClient): Promise<void> {
|
||||
const workspaceOwners = await client.model.findAll(contact.class.PersonAccount, {
|
||||
role: AccountRole.Owner
|
||||
})
|
||||
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{
|
||||
_id: tracker.project.DefaultProject
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
owners: workspaceOwners.map((it) => it._id)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const trackerOperation: MigrateOperation = {
|
||||
async preMigrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
|
||||
await tryMigrate(client, trackerId, [
|
||||
@ -358,6 +379,10 @@ export const trackerOperation: MigrateOperation = {
|
||||
{
|
||||
state: 'migrateDefaultTypeMixins',
|
||||
func: migrateDefaultTypeMixins
|
||||
},
|
||||
{
|
||||
state: 'migrateDefaultProjectOwners',
|
||||
func: migrateDefaultProjectOwners
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -642,6 +642,7 @@ export function createModel (builder: Builder): void {
|
||||
archived: false
|
||||
},
|
||||
target: core.class.Space,
|
||||
visibilityTester: view.function.CanArchiveSpace,
|
||||
context: { mode: ['context', 'browser'], group: 'tools' },
|
||||
override: [view.action.Delete]
|
||||
},
|
||||
|
@ -125,7 +125,10 @@ export default mergeIds(viewId, view, {
|
||||
FilterDateNotSpecified: '' as FilterFunction,
|
||||
FilterDateCustom: '' as FilterFunction,
|
||||
ShowEmptyGroups: '' as ViewCategoryAction,
|
||||
CanDeleteObject: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>
|
||||
CanDeleteObject: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
CanEditSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
CanArchiveSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
CanDeleteSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>
|
||||
},
|
||||
pipeline: {
|
||||
PresentationMiddleware: '' as Ref<PresentationMiddlewareFactory>,
|
||||
|
@ -2,6 +2,8 @@
|
||||
"string": {
|
||||
"Id": "Id",
|
||||
"Space": "Space",
|
||||
"Spaces": "Spaces",
|
||||
"SpacesDescription": "Manage all spaces' space type",
|
||||
"TypedSpace": "Typed space",
|
||||
"SpaceType": "Space type",
|
||||
"Modified": "Modified",
|
||||
@ -43,12 +45,19 @@
|
||||
"StatusCategory": "Status category",
|
||||
"Account": "Account",
|
||||
"Rank": "Rank",
|
||||
"Owners": "Owners",
|
||||
"Permission": "Permission",
|
||||
"CreateObject": "Create object",
|
||||
"UpdateObject": "Update object",
|
||||
"DeleteObject": "Delete object",
|
||||
"DeleteObjectDescription": "Grants users ability to delete objects in the space",
|
||||
"ForbidDeleteObject": "Forbid delete object",
|
||||
"ForbidDeleteObjectDescription": "Forbid users deleting objects in the space"
|
||||
"UpdateSpace": "Update space",
|
||||
"ArchiveSpace": "Archive space",
|
||||
"CreateObjectDescription": "Grants users ability to create objects in the space",
|
||||
"UpdateObjectDescription": "Grants users ability to update objects in the space",
|
||||
"DeleteObjectDescription": "Grants users ability to delete objects in the space",
|
||||
"ForbidDeleteObjectDescription": "Forbid users deleting objects in the space",
|
||||
"UpdateSpaceDescription": "Grants users ability to update the space",
|
||||
"ArchiveSpaceDescription": "Grants users ability to archive the space"
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
"string": {
|
||||
"Id": "Id.",
|
||||
"Space": "Espacio",
|
||||
"Spaces": "Espacios",
|
||||
"SpacesDescription": "Gestionar el tipo de espacio de todos los espacios",
|
||||
"Modified": "Modificado",
|
||||
"ModifiedDate": "Fecha de modificación",
|
||||
"ModifiedBy": "Modificado por",
|
||||
@ -35,6 +37,7 @@
|
||||
"Status": "Estado",
|
||||
"StatusCategory": "Categoría de estado",
|
||||
"Account": "Cuenta",
|
||||
"Rank": "Rango"
|
||||
"Rank": "Rango",
|
||||
"Owners": "Propietarios"
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
"string": {
|
||||
"Id": "Id",
|
||||
"Space": "Espaço",
|
||||
"Spaces": "Espaços",
|
||||
"SpacesDescription": "Gestão do tipo de espaço para todas as espaços",
|
||||
"Modified": "Modificado",
|
||||
"ModifiedDate": "Data de modificação",
|
||||
"ModifiedBy": "Modificado por",
|
||||
@ -35,6 +37,7 @@
|
||||
"Status": "Estado",
|
||||
"StatusCategory": "Categoria de estado",
|
||||
"Account": "Conta",
|
||||
"Rank": "Ranking"
|
||||
"Rank": "Ranking",
|
||||
"Owners": "Proprietários"
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
"string": {
|
||||
"Id": "Id",
|
||||
"Space": "Пространство",
|
||||
"Spaces": "Пространства",
|
||||
"SpacesDescription": "Управлять типом пространства всех пространств",
|
||||
"TypedSpace": "Типизированное пространство",
|
||||
"SpaceType": "Тип пространства",
|
||||
"Modified": "Изменено",
|
||||
@ -43,12 +45,19 @@
|
||||
"StatusCategory": "Категория статуса",
|
||||
"Account": "Аккаунт",
|
||||
"Rank": "Ранг",
|
||||
"Owners": "Владельцы",
|
||||
"Permission": "Разрешение",
|
||||
"CreateObject": "Создавать объект",
|
||||
"UpdateObject": "Обновлять объект",
|
||||
"DeleteObject": "Удалять объект",
|
||||
"DeleteObjectDescription": "Дает пользователям разрешение удалять объекты в пространстве",
|
||||
"ForbidDeleteObject": "Запретить удалять объект",
|
||||
"ForbidDeleteObjectDescription": "Запрещает пользователям удалять объекты в пространстве"
|
||||
"UpdateSpace": "Обновлять пространство",
|
||||
"ArchiveSpace": "Архивировать пространство",
|
||||
"CreateObjectDescription": "Дает пользователям разрешение создавать объекты в пространстве",
|
||||
"UpdateObjectDescription": "Дает пользователям разрешение обновлять объекты в пространстве",
|
||||
"DeleteObjectDescription": "Дает пользователям разрешение удалять объекты в пространстве",
|
||||
"ForbidDeleteObjectDescription": "Запрещает пользователям удалять объекты в пространстве",
|
||||
"UpdateSpaceDescription": "Дает пользователям разрешение обновлять пространство",
|
||||
"ArchiveSpaceDescription": "Дает пользователям разрешение архивировать пространство"
|
||||
}
|
||||
}
|
||||
|
@ -366,6 +366,7 @@ export interface Space extends Doc {
|
||||
private: boolean
|
||||
members: Arr<Ref<Account>>
|
||||
archived: boolean
|
||||
owners?: Ref<Account>[]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -388,6 +389,7 @@ export interface SpaceTypeDescriptor extends Doc {
|
||||
icon: Asset
|
||||
baseClass: Ref<Class<Space>> // Child class of Space for which the space type can be defined
|
||||
availablePermissions: Ref<Permission>[]
|
||||
system?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,13 +150,14 @@ export default plugin(coreId, {
|
||||
mixin: {
|
||||
FullTextSearchContext: '' as Ref<Mixin<FullTextSearchContext>>,
|
||||
ConfigurationElement: '' as Ref<Mixin<ConfigurationElement>>,
|
||||
IndexConfiguration: '' as Ref<Mixin<IndexingConfiguration<Doc>>>
|
||||
IndexConfiguration: '' as Ref<Mixin<IndexingConfiguration<Doc>>>,
|
||||
SpacesTypeData: '' as Ref<Mixin<Space>>
|
||||
},
|
||||
space: {
|
||||
Tx: '' as Ref<Space>,
|
||||
DerivedTx: '' as Ref<Space>,
|
||||
Model: '' as Ref<Space>,
|
||||
Space: '' as Ref<Space>,
|
||||
Space: '' as Ref<TypedSpace>,
|
||||
Configuration: '' as Ref<Space>
|
||||
},
|
||||
account: {
|
||||
@ -174,6 +175,8 @@ export default plugin(coreId, {
|
||||
string: {
|
||||
Id: '' as IntlString,
|
||||
Space: '' as IntlString,
|
||||
Spaces: '' as IntlString,
|
||||
SpacesDescription: '' as IntlString,
|
||||
TypedSpace: '' as IntlString,
|
||||
SpaceType: '' as IntlString,
|
||||
Modified: '' as IntlString,
|
||||
@ -214,18 +217,36 @@ export default plugin(coreId, {
|
||||
Account: '' as IntlString,
|
||||
StatusCategory: '' as IntlString,
|
||||
Rank: '' as IntlString,
|
||||
Owners: '' as IntlString,
|
||||
Permission: '' as IntlString,
|
||||
CreateObject: '' as IntlString,
|
||||
UpdateObject: '' as IntlString,
|
||||
DeleteObject: '' as IntlString,
|
||||
DeleteObjectDescription: '' as IntlString,
|
||||
ForbidDeleteObject: '' as IntlString,
|
||||
ForbidDeleteObjectDescription: '' as IntlString
|
||||
UpdateSpace: '' as IntlString,
|
||||
ArchiveSpace: '' as IntlString,
|
||||
CreateObjectDescription: '' as IntlString,
|
||||
UpdateObjectDescription: '' as IntlString,
|
||||
DeleteObjectDescription: '' as IntlString,
|
||||
ForbidDeleteObjectDescription: '' as IntlString,
|
||||
UpdateSpaceDescription: '' as IntlString,
|
||||
ArchiveSpaceDescription: '' as IntlString
|
||||
},
|
||||
descriptor: {
|
||||
SpacesType: '' as Ref<SpaceTypeDescriptor>
|
||||
},
|
||||
spaceType: {
|
||||
SpacesType: '' as Ref<SpaceType>
|
||||
},
|
||||
permission: {
|
||||
CreateObject: '' as Ref<Permission>,
|
||||
UpdateObject: '' as Ref<Permission>,
|
||||
DeleteObject: '' as Ref<Permission>,
|
||||
ForbidDeleteObject: '' as Ref<Permission>
|
||||
ForbidDeleteObject: '' as Ref<Permission>,
|
||||
UpdateSpace: '' as Ref<Permission>,
|
||||
ArchiveSpace: '' as Ref<Permission>
|
||||
},
|
||||
role: {
|
||||
Admin: '' as Ref<Role>
|
||||
}
|
||||
})
|
||||
|
@ -17,7 +17,9 @@ import { deepEqual } from 'fast-equals'
|
||||
import {
|
||||
Account,
|
||||
AnyAttribute,
|
||||
AttachedData,
|
||||
AttachedDoc,
|
||||
Attribute,
|
||||
Class,
|
||||
ClassifierKind,
|
||||
Collection,
|
||||
@ -33,6 +35,7 @@ import {
|
||||
IndexKind,
|
||||
Obj,
|
||||
Permission,
|
||||
PropertyType,
|
||||
Ref,
|
||||
Role,
|
||||
Space,
|
||||
@ -44,6 +47,7 @@ import { TxOperations } from './operations'
|
||||
import { isPredicate } from './predicate'
|
||||
import { DocumentQuery, FindResult } from './storage'
|
||||
import { DOMAIN_TX } from './tx'
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
|
||||
function toHex (value: number, chars: number): string {
|
||||
const result = value.toString(16)
|
||||
@ -601,6 +605,25 @@ export async function checkPermission (
|
||||
return myPermissions.has(_id)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface RoleAttributeBaseProps {
|
||||
label: IntlString
|
||||
id: Ref<Attribute<PropertyType>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getRoleAttributeBaseProps (data: AttachedData<Role>, roleId: Ref<Role>): RoleAttributeBaseProps {
|
||||
const name = data.name.trim()
|
||||
const label = getEmbeddedLabel(`Role: ${name}`)
|
||||
const id = `role-${roleId}` as Ref<Attribute<PropertyType>>
|
||||
|
||||
return { label, id }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -42,6 +42,7 @@
|
||||
export let disabled: boolean = false
|
||||
export let loading: boolean = false
|
||||
export let focusIndex: number | undefined = undefined
|
||||
export let hasDropdown: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -61,7 +62,7 @@
|
||||
{size}
|
||||
{kind}
|
||||
disabled={disabled || loading}
|
||||
shape="rectangle-right"
|
||||
shape={hasDropdown ? 'rectangle-right' : undefined}
|
||||
{justify}
|
||||
borderStyle="none"
|
||||
on:click
|
||||
@ -79,23 +80,25 @@
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
width="1.75rem"
|
||||
{kind}
|
||||
shape="rectangle-left"
|
||||
justify="center"
|
||||
borderStyle="none"
|
||||
on:click={openDropdown}
|
||||
{size}
|
||||
{disabled}
|
||||
{loading}
|
||||
>
|
||||
<div slot="icon">
|
||||
{#if dropdownIcon}
|
||||
<Icon icon={dropdownIcon} size="small" />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
{#if hasDropdown}
|
||||
<Button
|
||||
width="1.75rem"
|
||||
{kind}
|
||||
shape="rectangle-left"
|
||||
justify="center"
|
||||
borderStyle="none"
|
||||
on:click={openDropdown}
|
||||
{size}
|
||||
{disabled}
|
||||
{loading}
|
||||
>
|
||||
<div slot="icon">
|
||||
{#if dropdownIcon}
|
||||
<Icon icon={dropdownIcon} size="small" />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -64,6 +64,8 @@
|
||||
let isColorSelected = false
|
||||
let members: Ref<Account>[] =
|
||||
teamspace?.members !== undefined ? hierarchy.clone(teamspace.members) : [getCurrentAccount()._id]
|
||||
let owners: Ref<Account>[] =
|
||||
teamspace?.owners !== undefined ? hierarchy.clone(teamspace.owners) : [getCurrentAccount()._id]
|
||||
let rolesAssignment: RolesAssignment = {}
|
||||
|
||||
$: isNew = teamspace === undefined
|
||||
@ -115,6 +117,7 @@
|
||||
description,
|
||||
private: isPrivate,
|
||||
members,
|
||||
owners,
|
||||
archived: false,
|
||||
icon,
|
||||
color
|
||||
@ -153,6 +156,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (teamspaceData.owners?.length !== teamspace?.owners?.length) {
|
||||
update.owners = teamspaceData.owners
|
||||
} else {
|
||||
for (const owner of teamspaceData.owners ?? []) {
|
||||
if (teamspace.owners?.findIndex((p) => p === owner) === -1) {
|
||||
update.owners = teamspaceData.owners
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(update).length > 0) {
|
||||
await client.update(teamspace, update)
|
||||
@ -215,6 +228,13 @@
|
||||
|
||||
$: roles = (spaceType?.$lookup?.roles ?? []) as Role[]
|
||||
|
||||
function handleOwnersChanged (newOwners: Ref<Account>[]): void {
|
||||
owners = newOwners
|
||||
|
||||
const newMembersSet = new Set([...members, ...newOwners])
|
||||
members = Array.from(newMembersSet)
|
||||
}
|
||||
|
||||
function handleMembersChanged (newMembers: Ref<Account>[]): void {
|
||||
// If a member was removed we need to remove it from any roles assignments as well
|
||||
const newMembersSet = new Set(newMembers)
|
||||
@ -237,16 +257,21 @@
|
||||
|
||||
rolesAssignment[roleId] = newMembers
|
||||
}
|
||||
|
||||
$: canSave =
|
||||
name.trim().length > 0 &&
|
||||
!(members.length === 0 && isPrivate) &&
|
||||
typeId !== undefined &&
|
||||
spaceType?.targetClass !== undefined &&
|
||||
owners.length > 0 &&
|
||||
(!isPrivate || owners.some((o) => members.includes(o)))
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={isNew ? documentRes.string.NewTeamspace : documentRes.string.EditTeamspace}
|
||||
okLabel={isNew ? presentation.string.Create : presentation.string.Save}
|
||||
okAction={handleSave}
|
||||
canSave={name.trim().length > 0 &&
|
||||
!(members.length === 0 && isPrivate) &&
|
||||
typeId !== undefined &&
|
||||
spaceType?.targetClass !== undefined}
|
||||
{canSave}
|
||||
accentHeader
|
||||
width={'medium'}
|
||||
gap={'gapV-6'}
|
||||
@ -324,6 +349,19 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={core.string.Owners} />
|
||||
</div>
|
||||
<AccountArrayEditor
|
||||
value={owners}
|
||||
label={core.string.Owners}
|
||||
onChange={handleOwnersChanged}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header withDesciption">
|
||||
<Label label={presentation.string.MakePrivate} />
|
||||
|
@ -51,6 +51,7 @@
|
||||
|
||||
let members: Ref<Account>[] =
|
||||
funnel?.members !== undefined ? hierarchy.clone(funnel.members) : [getCurrentAccount()._id]
|
||||
let owners: Ref<Account>[] = funnel?.owners !== undefined ? hierarchy.clone(funnel.owners) : [getCurrentAccount()._id]
|
||||
|
||||
$: void loadSpaceType(typeId)
|
||||
async function loadSpaceType (id: typeof typeId): Promise<void> {
|
||||
@ -99,6 +100,7 @@
|
||||
private: isPrivate,
|
||||
archived: false,
|
||||
members,
|
||||
owners,
|
||||
type: typeId
|
||||
})
|
||||
|
||||
@ -110,7 +112,7 @@
|
||||
if (isNew) {
|
||||
await createFunnel()
|
||||
} else if (funnel !== undefined && spaceType?.targetClass !== undefined) {
|
||||
await client.diffUpdate<Funnel>(funnel, { name, description, members, private: isPrivate }, Date.now())
|
||||
await client.diffUpdate<Funnel>(funnel, { name, description, members, owners, private: isPrivate }, Date.now())
|
||||
|
||||
if (!deepEqual(rolesAssignment, getRolesAssignment())) {
|
||||
await client.updateMixin(
|
||||
@ -124,6 +126,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleOwnersChanged (newOwners: Ref<Account>[]): void {
|
||||
owners = newOwners
|
||||
|
||||
const newMembersSet = new Set([...members, ...newOwners])
|
||||
members = Array.from(newMembersSet)
|
||||
}
|
||||
|
||||
function handleMembersChanged (newMembers: Ref<Account>[]): void {
|
||||
// If a member was removed we need to remove it from any roles assignments as well
|
||||
const newMembersSet = new Set(newMembers)
|
||||
@ -146,13 +155,19 @@
|
||||
|
||||
rolesAssignment[roleId] = newMembers
|
||||
}
|
||||
|
||||
$: canSave =
|
||||
name.trim().length > 0 &&
|
||||
!(members.length === 0 && isPrivate) &&
|
||||
owners.length > 0 &&
|
||||
(!isPrivate || owners.some((o) => members.includes(o)))
|
||||
</script>
|
||||
|
||||
<SpaceCreateCard
|
||||
label={leadRes.string.CreateFunnel}
|
||||
okAction={save}
|
||||
okLabel={!isNew ? ui.string.Save : undefined}
|
||||
canSave={name.trim().length > 0}
|
||||
{canSave}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
@ -178,6 +193,19 @@
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={core.string.Owners} />
|
||||
</div>
|
||||
<AccountArrayEditor
|
||||
value={owners}
|
||||
label={core.string.Owners}
|
||||
onChange={handleOwnersChanged}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row mt-4">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={leadRes.string.Members} />
|
||||
|
@ -92,6 +92,9 @@ const lead = plugin(leadId, {
|
||||
},
|
||||
template: {
|
||||
DefaultFunnel: '' as Ref<ProjectType>
|
||||
},
|
||||
space: {
|
||||
DefaultFunnel: '' as Ref<Funnel>
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -18,11 +18,11 @@
|
||||
import { AccountArrayEditor, UserBox } from '@hcengineering/contact-resources'
|
||||
import core, {
|
||||
Account,
|
||||
Class,
|
||||
Data,
|
||||
fillDefaults,
|
||||
FindResult,
|
||||
generateId,
|
||||
getCurrentAccount,
|
||||
Ref,
|
||||
Role,
|
||||
RolesAssignment,
|
||||
@ -217,6 +217,7 @@
|
||||
number: (incResult as any).object.sequence,
|
||||
company,
|
||||
members: [],
|
||||
owners: [getCurrentAccount()._id],
|
||||
type: typeId
|
||||
},
|
||||
objectId
|
||||
|
@ -137,7 +137,11 @@
|
||||
|
||||
<svelte:fragment slot="attributes" let:direction={dir}>
|
||||
{#if dir === 'column'}
|
||||
<DocAttributeBar {object} {mixins} ignoreKeys={['name', 'fullDescription', 'private', 'archived', 'type']} />
|
||||
<DocAttributeBar
|
||||
{object}
|
||||
{mixins}
|
||||
ignoreKeys={['name', 'fullDescription', 'private', 'archived', 'type', 'owners']}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
|
@ -61,4 +61,7 @@
|
||||
<path d="M24 21C24 20.4477 24.4477 20 25 20C25.5523 20 26 20.4477 26 21V24H29C29.5523 24 30 24.4477 30 25C30 25.5523 29.5523 26 29 26H26V29C26 29.5523 25.5523 30 25 30C24.4477 30 24 29.5523 24 29V26H21C20.4477 26 20 25.5523 20 25C20 24.4477 20.4477 24 21 24H24V21Z" />
|
||||
<path d="M13 20C9.13401 20 6 23.134 6 27V29C6 29.5523 6.44772 30 7 30C7.55228 30 8 29.5523 8 29V27C8 24.2386 10.2386 22 13 22H17C17.5523 22 18 21.5523 18 21C18 20.4477 17.5523 20 17 20H13Z" />
|
||||
</symbol>
|
||||
<symbol id="views" viewBox="0 0 16 16">
|
||||
<path d="M12.6541 10.7952L14.7544 11.6213C14.8576 11.6618 14.9394 11.7434 14.9801 11.8466C15.0511 12.0264 14.9828 12.2268 14.827 12.3284L14.755 12.3656L8.35645 14.8924C8.15935 14.9703 7.94372 14.9831 7.74052 14.9309L7.62035 14.8918L1.25259 12.3653C1.1499 12.3246 1.06864 12.2432 1.02806 12.1404C0.957068 11.9607 1.02536 11.7603 1.1812 11.6587L1.25319 11.6215L3.34307 10.7962L7.06917 12.2751C7.65895 12.5091 8.31525 12.5097 8.9054 12.2766L12.6541 10.7952ZM12.6541 6.77688L14.7544 7.60289C14.8576 7.64346 14.9394 7.72508 14.9801 7.82824C15.0511 8.00803 14.9828 8.20839 14.827 8.31004L14.755 8.3472L10.6001 9.98825L9.619 10.375L8.35645 10.8741L8.317 10.886L8.23566 10.9132C8.20301 10.9215 8.17004 10.9282 8.13688 10.9331C8.12585 10.9346 8.11547 10.936 8.10507 10.9372C8.02541 10.9468 7.94422 10.9464 7.86397 10.9363L7.74052 10.9126L7.62035 10.8735L6.391 10.385L5.38907 9.98825L1.25259 8.34697C1.1499 8.30623 1.06864 8.22483 1.02806 8.12208C0.957068 7.94229 1.02536 7.74192 1.1812 7.64029L1.25319 7.60312L3.34307 6.77788L7.06917 8.25677C7.65895 8.49078 8.31525 8.4913 8.9054 8.25824L12.6541 6.77688ZM7.62186 1.06989C7.85734 0.976906 8.11932 0.976697 8.35494 1.06931L14.7544 3.58452C14.8576 3.62509 14.9394 3.70671 14.9801 3.80987C15.0612 4.01534 14.9605 4.24769 14.755 4.32884L10.6001 5.96988L8.35565 6.856L8.27468 6.88396C8.25405 6.8901 8.23326 6.89557 8.21236 6.90036C8.09824 6.92674 7.98013 6.93258 7.86397 6.91788L7.74052 6.89419L7.62035 6.8551L1.25259 4.3286C1.1499 4.28786 1.06864 4.20646 1.02806 4.10371C0.946925 3.89823 1.04772 3.66589 1.25319 3.58475L7.62186 1.06989Z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"Setting": "Setting",
|
||||
"Setting": "Setting",
|
||||
"Spaces": "Spaces",
|
||||
"Integrations": "Integrations",
|
||||
"Support": "Support",
|
||||
"Privacy": "Privacy",
|
||||
@ -109,6 +110,7 @@
|
||||
"CountSpaces": "{count, plural, =0 {No spaces} =1 {# space} other {# spaces}}",
|
||||
"Roles": "Roles",
|
||||
"RoleName": "Role name",
|
||||
"Permissions": "Permissions"
|
||||
"Permissions": "Permissions",
|
||||
"Assignees": "Assignees"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"Setting": "Configuración",
|
||||
"Spaces": "Espacios",
|
||||
"Integrations": "Integraciones",
|
||||
"Support": "Soporte",
|
||||
"Privacy": "Privacidad",
|
||||
@ -100,6 +101,7 @@
|
||||
"Automations": "Automatizaciones",
|
||||
"Collections": "Colecciones",
|
||||
"ClassColon": "Clase:",
|
||||
"Branding": "Marca"
|
||||
"Branding": "Marca",
|
||||
"Assignees": "Atribuídos"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"Setting": "Configuração",
|
||||
"Spaces": "Espaços",
|
||||
"Integrations": "Integrações",
|
||||
"Support": "Suporte",
|
||||
"Privacy": "Privacidade",
|
||||
@ -100,6 +101,7 @@
|
||||
"Automations": "Automações",
|
||||
"Collections": "Coleções",
|
||||
"ClassColon": "Classe:",
|
||||
"Branding": "Marca"
|
||||
"Branding": "Marca",
|
||||
"Assignees": ""
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"Setting": "Настройки",
|
||||
"Spaces": "Пространства",
|
||||
"Integrations": "Интеграции",
|
||||
"Support": "Поддержка",
|
||||
"Privacy": "Конфиденциальность",
|
||||
@ -110,6 +111,7 @@
|
||||
"CountSpaces": "{count, plural, =0 {Нет пространств} =1 {# пространство} =2 {# пространства} =3 {# пространства} =4 {# пространства} other {# пространств}}",
|
||||
"Roles": "Роли",
|
||||
"RoleName": "Название роли",
|
||||
"Permissions": "Разрешения"
|
||||
"Permissions": "Разрешения",
|
||||
"Assignees": "Назначенные"
|
||||
}
|
||||
}
|
||||
|
@ -31,5 +31,6 @@ loadMetadata(setting.icon, {
|
||||
Clazz: `${icons}#clazz`,
|
||||
Enums: `${icons}#enums`,
|
||||
InviteSettings: `${icons}#inviteSettings`,
|
||||
InviteWorkspace: `${icons}#inviteWorkspace`
|
||||
InviteWorkspace: `${icons}#inviteWorkspace`,
|
||||
Views: `${icons}#views`
|
||||
})
|
||||
|
111
plugins/setting-resources/src/components/Spaces.svelte
Normal file
111
plugins/setting-resources/src/components/Spaces.svelte
Normal file
@ -0,0 +1,111 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { createEventDispatcher } from 'svelte'
|
||||
import { Header, Breadcrumb, Label } from '@hcengineering/ui'
|
||||
import core, { Account, Ref, Role, RolesAssignment, SpaceType, TypedSpace, WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { AccountArrayEditor } from '@hcengineering/contact-resources'
|
||||
|
||||
import setting from '../plugin'
|
||||
|
||||
export let visibleNav: boolean = true
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let space: TypedSpace
|
||||
let spaceType: WithLookup<SpaceType>
|
||||
|
||||
const spaceQuery = createQuery()
|
||||
spaceQuery.query(
|
||||
core.class.TypedSpace,
|
||||
{
|
||||
_id: core.space.Space
|
||||
},
|
||||
(res) => {
|
||||
space = res[0]
|
||||
}
|
||||
)
|
||||
|
||||
const typeQuery = createQuery()
|
||||
$: if (space?.type !== undefined) {
|
||||
typeQuery.query(
|
||||
core.class.SpaceType,
|
||||
{
|
||||
_id: core.spaceType.SpacesType
|
||||
},
|
||||
(res) => {
|
||||
spaceType = res[0]
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
_id: { roles: core.class.Role }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
$: roles = (spaceType?.$lookup?.roles ?? []) as Role[]
|
||||
|
||||
let rolesAssignment: RolesAssignment = {}
|
||||
$: {
|
||||
if (space !== undefined && spaceType?.targetClass !== undefined) {
|
||||
const asMixin = hierarchy.as(space, spaceType?.targetClass)
|
||||
|
||||
rolesAssignment = roles.reduce<RolesAssignment>((prev, { _id }) => {
|
||||
prev[_id] = (asMixin as any)[_id] ?? []
|
||||
|
||||
return prev
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRoleAssignmentChanged (roleId: Ref<Role>, newMembers: Ref<Account>[]): Promise<void> {
|
||||
await client.updateMixin(space._id, space._class, core.space.Space, spaceType.targetClass, {
|
||||
[roleId]: newMembers
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent">
|
||||
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
|
||||
<Breadcrumb icon={setting.icon.Views} label={setting.string.Spaces} size="large" isCurrent />
|
||||
</Header>
|
||||
<div class="hulyComponent-content__column content">
|
||||
{#each roles as role}
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
{role.name}
|
||||
</div>
|
||||
<AccountArrayEditor
|
||||
value={rolesAssignment?.[role._id] ?? []}
|
||||
label={setting.string.Assignees}
|
||||
onChange={(refs) => {
|
||||
handleRoleAssignmentChanged(role._id, refs)
|
||||
}}
|
||||
kind="regular"
|
||||
size="large"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.content {
|
||||
margin: 2rem 3.25rem;
|
||||
}
|
||||
</style>
|
@ -54,7 +54,7 @@
|
||||
|
||||
const descriptors = client
|
||||
.getModel()
|
||||
.findAllSync(core.class.SpaceTypeDescriptor, {})
|
||||
.findAllSync(core.class.SpaceTypeDescriptor, { system: { $ne: true } })
|
||||
.filter((descriptor) => hasResource(descriptor._id as any as Resource<any>))
|
||||
|
||||
descriptor = descriptors[0]
|
||||
@ -101,6 +101,7 @@
|
||||
<ObjectBox
|
||||
_class={core.class.SpaceTypeDescriptor}
|
||||
value={descriptor?._id}
|
||||
docQuery={{ system: { $ne: true } }}
|
||||
on:change={handleDescriptorSelected}
|
||||
kind="regular"
|
||||
size="small"
|
||||
|
@ -44,6 +44,7 @@ import WorkspaceSetting from './components/WorkspaceSetting.svelte'
|
||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
import InviteSetting from './components/InviteSetting.svelte'
|
||||
import Configure from './components/Configure.svelte'
|
||||
import Spaces from './components/Spaces.svelte'
|
||||
import setting from './plugin'
|
||||
import IntegrationPanel from './components/IntegrationPanel.svelte'
|
||||
import { getOwnerFirstName, getOwnerLastName, getOwnerPosition, getValue, filterDescendants } from './utils'
|
||||
@ -93,6 +94,7 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
component: {
|
||||
Settings,
|
||||
Spaces,
|
||||
Profile,
|
||||
Password,
|
||||
WorkspaceSetting,
|
||||
|
@ -23,7 +23,8 @@ export default mergeIds(settingId, setting, {
|
||||
EditEnum: '' as AnyComponent,
|
||||
ManageSpaceTypes: '' as AnyComponent,
|
||||
ManageSpaceTypesTools: '' as AnyComponent,
|
||||
ManageSpaceTypeContent: '' as AnyComponent
|
||||
ManageSpaceTypeContent: '' as AnyComponent,
|
||||
Spaces: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
IntegrationDisabled: '' as IntlString,
|
||||
@ -97,6 +98,7 @@ export default mergeIds(settingId, setting, {
|
||||
Description: '' as IntlString,
|
||||
CountSpaces: '' as IntlString,
|
||||
RoleName: '' as IntlString,
|
||||
Permissions: '' as IntlString
|
||||
Permissions: '' as IntlString,
|
||||
Assignees: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -128,7 +128,8 @@ export default plugin(settingId, {
|
||||
Owners: '' as Ref<Doc>,
|
||||
InviteSettings: '' as Ref<Doc>,
|
||||
WorkspaceSetting: '' as Ref<Doc>,
|
||||
ManageSpaces: '' as Ref<Doc>
|
||||
ManageSpaces: '' as Ref<Doc>,
|
||||
Spaces: '' as Ref<Doc>
|
||||
},
|
||||
mixin: {
|
||||
Editable: '' as Ref<Mixin<Editable>>,
|
||||
@ -169,6 +170,7 @@ export default plugin(settingId, {
|
||||
string: {
|
||||
Settings: '' as IntlString,
|
||||
Setting: '' as IntlString,
|
||||
Spaces: '' as IntlString,
|
||||
WorkspaceSettings: '' as IntlString,
|
||||
Branding: '' as IntlString,
|
||||
Integrations: '' as IntlString,
|
||||
@ -219,7 +221,8 @@ export default plugin(settingId, {
|
||||
Clazz: '' as Asset,
|
||||
Enums: '' as Asset,
|
||||
InviteSettings: '' as Asset,
|
||||
InviteWorkspace: '' as Asset
|
||||
InviteWorkspace: '' as Asset,
|
||||
Views: '' as Asset
|
||||
},
|
||||
templateFieldCategory: {
|
||||
Integration: '' as Ref<TemplateFieldCategory>
|
||||
|
@ -25,7 +25,8 @@ import core, {
|
||||
Space,
|
||||
SpaceType,
|
||||
TxOperations,
|
||||
TypeAny as TypeAnyType
|
||||
TypeAny as TypeAnyType,
|
||||
getRoleAttributeBaseProps
|
||||
} from '@hcengineering/core'
|
||||
import { TypeAny } from '@hcengineering/model'
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
@ -74,12 +75,14 @@ export interface RoleAttributeProps {
|
||||
}
|
||||
|
||||
export function getRoleAttributeProps (data: AttachedData<Role>, roleId: Ref<Role>): RoleAttributeProps {
|
||||
const name = data.name.trim()
|
||||
const label = getEmbeddedLabel(`Role: ${name}`)
|
||||
const roleType = TypeAny(setting.component.RoleAssignmentEditor, label, setting.component.RoleAssignmentEditor)
|
||||
const id = `role-${roleId}` as Ref<Attribute<PropertyType>>
|
||||
const baseProps = getRoleAttributeBaseProps(data, roleId)
|
||||
const roleType = TypeAny(
|
||||
setting.component.RoleAssignmentEditor,
|
||||
baseProps.label,
|
||||
setting.component.RoleAssignmentEditor
|
||||
)
|
||||
|
||||
return { label, roleType, id }
|
||||
return { ...baseProps, roleType }
|
||||
}
|
||||
|
||||
export async function createSpaceTypeRole (
|
||||
|
@ -69,6 +69,8 @@
|
||||
let defaultAssignee: Ref<Employee> | null | undefined = project?.defaultAssignee ?? null
|
||||
let members: Ref<Account>[] =
|
||||
project?.members !== undefined ? hierarchy.clone(project.members) : [getCurrentAccount()._id]
|
||||
let owners: Ref<Account>[] =
|
||||
project?.owners !== undefined ? hierarchy.clone(project.owners) : [getCurrentAccount()._id]
|
||||
let projectsIdentifiers = new Set<string>()
|
||||
let isSaving = false
|
||||
let defaultStatus: Ref<IssueStatus> | undefined = project?.defaultIssueStatus
|
||||
@ -96,6 +98,7 @@
|
||||
description,
|
||||
private: isPrivate,
|
||||
members,
|
||||
owners,
|
||||
archived: false,
|
||||
identifier: identifier.toUpperCase(),
|
||||
sequence: 0,
|
||||
@ -162,6 +165,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (projectData.owners?.length !== project?.owners?.length) {
|
||||
update.owners = projectData.owners
|
||||
} else {
|
||||
for (const owner of projectData.owners || []) {
|
||||
if (project.owners?.findIndex((p) => p === owner) === -1) {
|
||||
update.owners = projectData.owners
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(update).length > 0) {
|
||||
isSaving = true
|
||||
@ -275,6 +288,13 @@
|
||||
rolesQuery.unsubscribe()
|
||||
}
|
||||
|
||||
function handleOwnersChanged (newOwners: Ref<Account>[]): void {
|
||||
owners = newOwners
|
||||
|
||||
const newMembersSet = new Set([...members, ...newOwners])
|
||||
members = Array.from(newMembersSet)
|
||||
}
|
||||
|
||||
function handleMembersChanged (newMembers: Ref<Account>[]): void {
|
||||
// If a member was removed we need to remove it from any roles assignments as well
|
||||
const newMembersSet = new Set(newMembers)
|
||||
@ -297,16 +317,21 @@
|
||||
|
||||
rolesAssignment[roleId] = newMembers
|
||||
}
|
||||
|
||||
$: canSave =
|
||||
name.trim().length > 0 &&
|
||||
identifier.trim().length > 0 &&
|
||||
!projectsIdentifiers.has(identifier.toUpperCase()) &&
|
||||
!(members.length === 0 && isPrivate) &&
|
||||
owners.length > 0 &&
|
||||
(!isPrivate || owners.some((o) => members.includes(o)))
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={isNew ? tracker.string.NewProject : tracker.string.EditProject}
|
||||
okLabel={isNew ? presentation.string.Create : presentation.string.Save}
|
||||
okAction={handleSave}
|
||||
canSave={name.trim().length > 0 &&
|
||||
identifier.trim().length > 0 &&
|
||||
!projectsIdentifiers.has(identifier.toUpperCase()) &&
|
||||
!(members.length === 0 && isPrivate)}
|
||||
{canSave}
|
||||
accentHeader
|
||||
width={'medium'}
|
||||
gap={'gapV-6'}
|
||||
@ -409,14 +434,6 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header withDesciption">
|
||||
<Label label={presentation.string.MakePrivate} />
|
||||
<span><Label label={presentation.string.MakePrivateDescription} /></span>
|
||||
</div>
|
||||
<Toggle bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={tracker.string.DefaultAssignee} />
|
||||
@ -448,6 +465,27 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={core.string.Owners} />
|
||||
</div>
|
||||
<AccountArrayEditor
|
||||
value={owners}
|
||||
label={core.string.Owners}
|
||||
onChange={handleOwnersChanged}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header withDesciption">
|
||||
<Label label={presentation.string.MakePrivate} />
|
||||
<span><Label label={presentation.string.MakePrivateDescription} /></span>
|
||||
</div>
|
||||
<Toggle bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={tracker.string.Members} />
|
||||
|
@ -120,7 +120,7 @@ import {
|
||||
import { IndexedDocumentPreview } from '@hcengineering/presentation'
|
||||
import { AggregationMiddleware, AnalyticsMiddleware } from './middleware'
|
||||
import { showEmptyGroups } from './viewOptions'
|
||||
import { canDeleteObject } from './visibilityTester'
|
||||
import { canArchiveSpace, canDeleteObject, canDeleteSpace, canEditSpace } from './visibilityTester'
|
||||
export { getActions, getContextActions, invokeAction, showMenu } from './actions'
|
||||
export { default as ActionButton } from './components/ActionButton.svelte'
|
||||
export { default as ActionHandler } from './components/ActionHandler.svelte'
|
||||
@ -303,6 +303,9 @@ export default async (): Promise<Resources> => ({
|
||||
CreateDocMiddleware: AggregationMiddleware.create,
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
AnalyticsMiddleware: AnalyticsMiddleware.create,
|
||||
CanDeleteObject: canDeleteObject
|
||||
CanDeleteObject: canDeleteObject,
|
||||
CanEditSpace: canEditSpace,
|
||||
CanArchiveSpace: canArchiveSpace,
|
||||
CanDeleteSpace: canDeleteSpace
|
||||
}
|
||||
})
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { checkPermission, type Space, type Doc, type TypedSpace } from '@hcengineering/core'
|
||||
import core, { checkPermission, type Space, type Doc, type TypedSpace, getCurrentAccount } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
function isTypedSpace (space: Space): space is TypedSpace {
|
||||
@ -40,3 +40,71 @@ export async function canDeleteObject (doc?: Doc | Doc[]): Promise<boolean> {
|
||||
)
|
||||
).some((r) => r)
|
||||
}
|
||||
|
||||
export async function canEditSpace (doc?: Doc | Doc[]): Promise<boolean> {
|
||||
if (doc === undefined || Array.isArray(doc)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const space = doc as Space
|
||||
|
||||
if (space.owners?.includes(getCurrentAccount()._id) ?? false) {
|
||||
return true
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
if (await checkPermission(client, core.permission.UpdateObject, core.space.Space)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (isTypedSpace(space) && (await checkPermission(client, core.permission.UpdateSpace, space._id))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export async function canArchiveSpace (doc?: Doc | Doc[]): Promise<boolean> {
|
||||
if (doc === undefined || Array.isArray(doc)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const space = doc as Space
|
||||
|
||||
if (space.owners?.includes(getCurrentAccount()._id) ?? false) {
|
||||
return true
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
if (await checkPermission(client, core.permission.DeleteObject, core.space.Space)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (isTypedSpace(space) && (await checkPermission(client, core.permission.ArchiveSpace, space._id))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export async function canDeleteSpace (doc?: Doc | Doc[]): Promise<boolean> {
|
||||
if (doc === undefined || Array.isArray(doc)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const space = doc as Space
|
||||
|
||||
if (space.owners?.includes(getCurrentAccount()._id) ?? false) {
|
||||
return true
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
if (await checkPermission(client, core.permission.DeleteObject, core.space.Space)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -13,8 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { concatLink, Doc } from '@hcengineering/core'
|
||||
import { Lead, leadId } from '@hcengineering/lead'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import core, { concatLink, Doc, Tx, TxUpdateDoc } from '@hcengineering/core'
|
||||
import lead, { Lead, leadId } from '@hcengineering/lead'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import view from '@hcengineering/view'
|
||||
@ -39,10 +40,44 @@ export async function leadTextPresenter (doc: Doc): Promise<string> {
|
||||
return `LEAD-${lead.number}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const targetFunnel = (
|
||||
await control.findAll(lead.class.Funnel, {
|
||||
_id: lead.space.DefaultFunnel
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (targetFunnel === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
const actualTx = tx as TxUpdateDoc<PersonAccount>
|
||||
|
||||
if (
|
||||
targetFunnel.owners === undefined ||
|
||||
targetFunnel.owners.length === 0 ||
|
||||
targetFunnel.owners[0] === core.account.System
|
||||
) {
|
||||
const updTx = control.txFactory.createTxUpdateDoc(lead.class.Funnel, targetFunnel.space, targetFunnel._id, {
|
||||
owners: [actualTx.objectId]
|
||||
})
|
||||
res.push(updTx)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
LeadHTMLPresenter: leadHTMLPresenter,
|
||||
LeadTextPresenter: leadTextPresenter
|
||||
},
|
||||
trigger: {
|
||||
OnWorkspaceOwnerAdded
|
||||
}
|
||||
})
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { TriggerFunc } from '@hcengineering/server-core'
|
||||
import { Presenter } from '@hcengineering/server-notification'
|
||||
|
||||
/**
|
||||
@ -29,5 +30,8 @@ export default plugin(serverLeadId, {
|
||||
function: {
|
||||
LeadHTMLPresenter: '' as Resource<Presenter>,
|
||||
LeadTextPresenter: '' as Resource<Presenter>
|
||||
},
|
||||
trigger: {
|
||||
OnWorkspaceOwnerAdded: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
|
@ -179,6 +179,37 @@ export async function OnComponentRemove (tx: Tx, control: TriggerControl): Promi
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const targetProject = (
|
||||
await control.findAll(tracker.class.Project, {
|
||||
_id: tracker.project.DefaultProject
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (targetProject === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
const actualTx = tx as TxUpdateDoc<PersonAccount>
|
||||
|
||||
if (
|
||||
targetProject.owners === undefined ||
|
||||
targetProject.owners.length === 0 ||
|
||||
targetProject.owners[0] === core.account.System
|
||||
) {
|
||||
const updTx = control.txFactory.createTxUpdateDoc(tracker.class.Project, targetProject.space, targetProject._id, {
|
||||
owners: [actualTx.objectId]
|
||||
})
|
||||
res.push(updTx)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -245,19 +276,6 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
|
||||
return []
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
IssueHTMLPresenter: issueHTMLPresenter,
|
||||
IssueTextPresenter: issueTextPresenter,
|
||||
IssueNotificationContentProvider: getIssueNotificationContent
|
||||
},
|
||||
trigger: {
|
||||
OnIssueUpdate,
|
||||
OnComponentRemove
|
||||
}
|
||||
})
|
||||
|
||||
async function doTimeReportUpdate (cud: TxCUD<TimeSpendReport>, tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const parentTx = tx as TxCollectionCUD<Issue, TimeSpendReport>
|
||||
switch (cud._class) {
|
||||
@ -465,3 +483,17 @@ function updateIssueParentEstimations (
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
IssueHTMLPresenter: issueHTMLPresenter,
|
||||
IssueTextPresenter: issueTextPresenter,
|
||||
IssueNotificationContentProvider: getIssueNotificationContent
|
||||
},
|
||||
trigger: {
|
||||
OnIssueUpdate,
|
||||
OnComponentRemove,
|
||||
OnWorkspaceOwnerAdded
|
||||
}
|
||||
})
|
||||
|
@ -34,6 +34,7 @@ export default plugin(serverTrackerId, {
|
||||
},
|
||||
trigger: {
|
||||
OnIssueUpdate: '' as Resource<TriggerFunc>,
|
||||
OnComponentRemove: '' as Resource<TriggerFunc>
|
||||
OnComponentRemove: '' as Resource<TriggerFunc>,
|
||||
OnWorkspaceOwnerAdded: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
|
@ -25,10 +25,10 @@ export class TalentsPage extends CommonRecruitingPage {
|
||||
inputSearchTalent = (): Locator => this.page.locator('div[class*="header"] input')
|
||||
andreyTalet = (): Locator => this.page.locator('text=P. Andrey')
|
||||
|
||||
addApplicationButton = (): Locator => this.page.locator('button[id="appls.add"]')
|
||||
spaceSelector = (): Locator => this.page.locator('[id="space.selector"]')
|
||||
searchInput = (): Locator => this.page.locator('[placeholder="Search..."]')
|
||||
hrInterviewButton = (): Locator =>
|
||||
readonly addApplicationButton = (): Locator => this.page.locator('button[id="appls.add"]')
|
||||
readonly spaceSelector = (): Locator => this.page.locator('[id="space.selector"]')
|
||||
readonly searchInput = (): Locator => this.page.locator('[placeholder="Search..."]')
|
||||
readonly hrInterviewButton = (): Locator =>
|
||||
this.page.locator('[id="recruit:string:CreateApplication"] button:has-text("HR Interview")')
|
||||
|
||||
createButton = (): Locator => this.page.locator('button:has-text("Create")')
|
||||
|
Loading…
Reference in New Issue
Block a user