Automation: Initial support (#2134)

This commit is contained in:
Anna No 2022-06-23 15:54:58 +07:00 committed by GitHub
parent 14de8334ba
commit 3c037e4506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1654 additions and 34 deletions

View File

@ -13,6 +13,9 @@ specifiers:
'@rush-temp/attachment': file:./projects/attachment.tgz
'@rush-temp/attachment-assets': file:./projects/attachment-assets.tgz
'@rush-temp/attachment-resources': file:./projects/attachment-resources.tgz
'@rush-temp/automation': file:./projects/automation.tgz
'@rush-temp/automation-assets': file:./projects/automation-assets.tgz
'@rush-temp/automation-resources': file:./projects/automation-resources.tgz
'@rush-temp/board': file:./projects/board.tgz
'@rush-temp/board-assets': file:./projects/board-assets.tgz
'@rush-temp/board-resources': file:./projects/board-resources.tgz
@ -61,6 +64,7 @@ specifiers:
'@rush-temp/model-activity': file:./projects/model-activity.tgz
'@rush-temp/model-all': file:./projects/model-all.tgz
'@rush-temp/model-attachment': file:./projects/model-attachment.tgz
'@rush-temp/model-automation': file:./projects/model-automation.tgz
'@rush-temp/model-board': file:./projects/model-board.tgz
'@rush-temp/model-calendar': file:./projects/model-calendar.tgz
'@rush-temp/model-chunter': file:./projects/model-chunter.tgz
@ -314,6 +318,9 @@ dependencies:
'@rush-temp/attachment': file:projects/attachment.tgz
'@rush-temp/attachment-assets': file:projects/attachment-assets.tgz_typescript@4.7.2
'@rush-temp/attachment-resources': file:projects/attachment-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c
'@rush-temp/automation': file:projects/automation.tgz
'@rush-temp/automation-assets': file:projects/automation-assets.tgz_typescript@4.7.2
'@rush-temp/automation-resources': file:projects/automation-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c
'@rush-temp/board': file:projects/board.tgz
'@rush-temp/board-assets': file:projects/board-assets.tgz_typescript@4.7.2
'@rush-temp/board-resources': file:projects/board-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c
@ -362,6 +369,7 @@ dependencies:
'@rush-temp/model-activity': file:projects/model-activity.tgz_typescript@4.7.2
'@rush-temp/model-all': file:projects/model-all.tgz_typescript@4.7.2
'@rush-temp/model-attachment': file:projects/model-attachment.tgz_typescript@4.7.2
'@rush-temp/model-automation': file:projects/model-automation.tgz_typescript@4.7.2
'@rush-temp/model-board': file:projects/model-board.tgz_typescript@4.7.2
'@rush-temp/model-calendar': file:projects/model-calendar.tgz_typescript@4.7.2
'@rush-temp/model-chunter': file:projects/model-chunter.tgz_typescript@4.7.2
@ -10202,6 +10210,90 @@ packages:
- supports-color
dev: false
file:projects/automation-assets.tgz_typescript@4.7.2:
resolution: {integrity: sha512-IwQs0Nc95dL+Xacvp+ETxvlN9rlWdGXk24FqpHjQi/wRvH2/VucZZAhqCpB1o9H5THFNpOiPHXSQ4RspNVzwBA==, tarball: file:projects/automation-assets.tgz}
id: file:projects/automation-assets.tgz
name: '@rush-temp/automation-assets'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@types/node': 16.11.38
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: false
file:projects/automation-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
resolution: {integrity: sha512-Ee3vgtig0qsuUIGOvSOXAinzXLSHF0MUTGqbMCqkDt6H5CZhwrqZHfPpgaBozXHOIV3HVnZ1gxQam4cMo2dTNw==, tarball: file:projects/automation-resources.tgz}
id: file:projects/automation-resources.tgz
name: '@rush-temp/automation-resources'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
eslint-plugin-svelte3: 4.0.0_eslint@7.32.0+svelte@3.48.0
prettier: 2.6.2
prettier-plugin-svelte: 2.7.0_prettier@2.6.2+svelte@3.48.0
sass: 1.52.2
svelte: 3.48.0
svelte-check: 2.7.2_c1788f0bf13b393830d6c30602bd01af
svelte-loader: 3.1.3_svelte@3.48.0
svelte-preprocess: 4.10.6_0757fe126296bf9639251bcd13609b29
typescript: 4.7.2
transitivePeerDependencies:
- '@babel/core'
- coffeescript
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- less
- node-sass
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
dev: false
file:projects/automation.tgz:
resolution: {integrity: sha512-qUBX9l8mso6djseOkGoMs1P791OVqOOKKMEV+Mi2U4h4uVQKJKekfMmfZfK6ArRM0yXiB3AGlfu9tdTiNy7QHg==, tarball: file:projects/automation.tgz}
name: '@rush-temp/automation'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
simplytyped: 3.3.0_typescript@4.7.2
typescript: 4.7.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: false
file:projects/board-assets.tgz_typescript@4.7.2:
resolution: {integrity: sha512-cnaqyRiwKUSLi9RgFQpYqIN4PyW85s77OzOovNq1cT0cJqHycMJN1CaXIDIBrlTrFS0TCOeTpUyMamq0b0rBOQ==, tarball: file:projects/board-assets.tgz}
id: file:projects/board-assets.tgz
@ -11466,7 +11558,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.7.2:
resolution: {integrity: sha512-VrR3QpQKo2qizq7xi/uS9Jj+OyrB/mPDLnAc8/pvwEx8jF5PwySSsP63uSNGcO3fwv7ZjZvAiuvEnaIHUZ5Ivg==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-cqTW87WKHFYNzT4PSJJesrbiXrMIUP7byOo6OubKKnXsRltLG/aUWj+v24tg4gOX0vLk+/HsZ/pLS0fqXJ5RuQ==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@ -11515,8 +11607,31 @@ packages:
- typescript
dev: false
file:projects/model-automation.tgz_typescript@4.7.2:
resolution: {integrity: sha512-V/knMbpqayHquNsaGCVgZDR6RVCglOkJFlko9UswT+4t8LeDTN5aBFjupz+XDenoXt3BVNrqyIoPE95u87fYWw==, tarball: file:projects/model-automation.tgz}
id: file:projects/model-automation.tgz
name: '@rush-temp/model-automation'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: false
file:projects/model-board.tgz_typescript@4.7.2:
resolution: {integrity: sha512-BDGUx3OP3/eowFfmRWMZnFcAj9Oa6orfKBNrYh6F2lqKhBeDwBGvdSYurtm2n4/ISaK0bZaSgM0reRKn63oDsA==, tarball: file:projects/model-board.tgz}
resolution: {integrity: sha512-SChn62gAEcqTy7E0oX/2zY2kMaLuE+GzeUHahQMC+Ev1Z2uj5SdkjL3P1wBnKk9+RnN/v54UuFhmw1feVBwRCw==, tarball: file:projects/model-board.tgz}
id: file:projects/model-board.tgz
name: '@rush-temp/model-board'
version: 0.0.0
@ -12873,7 +12988,7 @@ packages:
dev: false
file:projects/prod.tgz_d1c3762ecb2c185353d3f02936f6ec22:
resolution: {integrity: sha512-Ruj8noHdrzO1bGVEYxLSgza5VdloFea3VbtIutq8UnZThqIzyBmJRsEo1lLmN5WAKBW50oEBKsKDZxuXB/ZuYQ==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-lLcDC0hOQAwp7ZoxN75OI6FScb/VuSQTMzGyBTxk1VP46pQnGe3U6vjcNK18oc7Sesuk6kcb7Uot65tWgB01eg==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0

View File

@ -71,6 +71,9 @@
"@anticrm/activity": "~0.6.0",
"@anticrm/activity-assets": "~0.6.0",
"@anticrm/activity-resources": "~0.6.0",
"@anticrm/automation": "~0.6.0",
"@anticrm/automation-assets": "~0.6.0",
"@anticrm/automation-resources": "~0.6.0",
"@anticrm/telegram": "~0.6.2",
"@anticrm/telegram-assets": "~0.6.0",
"@anticrm/telegram-resources": "~0.6.0",

View File

@ -24,6 +24,7 @@ import { contactId } from '@anticrm/contact'
import { chunterId } from '@anticrm/chunter'
import { recruitId } from '@anticrm/recruit'
import { activityId } from '@anticrm/activity'
import { automationId } from '@anticrm/automation'
import { settingId } from '@anticrm/setting'
import { telegramId } from '@anticrm/telegram'
import { attachmentId } from '@anticrm/attachment'
@ -50,6 +51,7 @@ import '@anticrm/attachment-assets'
import '@anticrm/contact-assets'
import '@anticrm/recruit-assets'
import '@anticrm/activity-assets'
import '@anticrm/automation-assets'
import '@anticrm/setting-assets'
import '@anticrm/telegram-assets'
import '@anticrm/lead-assets'
@ -117,6 +119,7 @@ export async function configurePlatform() {
addLocation(trackerId, () => import(/* webpackChunkName: "tracker" */ '@anticrm/tracker-resources'))
addLocation(boardId, () => import(/* webpackChunkName: "board" */ '@anticrm/board-resources'))
addLocation(automationId, () => import(/* webpackChunkName: "automation" */ '@anticrm/automation-resources'))
addLocation(hrId, () => import(/* webpackChunkName: "hr" */ '@anticrm/hr-resources'))
setMetadata(workbench.metadata.PlatformTitle, 'Platform')

View File

@ -31,6 +31,7 @@
},
"dependencies": {
"@anticrm/model": "~0.6.0",
"@anticrm/model-automation": "~0.6.0",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-workbench": "~0.6.1",

View File

@ -19,6 +19,7 @@ import jsonVersion from './version.json'
import { Builder } from '@anticrm/model'
import { createModel as activityModel } from '@anticrm/model-activity'
import { createModel as attachmentModel } from '@anticrm/model-attachment'
import { createModel as automationModel } from '@anticrm/model-automation'
import { createModel as chunterModel } from '@anticrm/model-chunter'
import { createModel as contactModel } from '@anticrm/model-contact'
import { createModel as coreModel } from '@anticrm/model-core'
@ -105,7 +106,8 @@ const builders: [(b: Builder) => void, string][] = [
[serverHrModel, 'server-hr'],
[trackerModel, 'tracker'],
[boardModel, 'board'],
[calendarModel, 'calendar']
[calendarModel, 'calendar'],
[automationModel, 'automation']
]
for (const [b, id] of builders) {

View File

@ -16,6 +16,7 @@
// Import migrate operations.
import { MigrateOperation } from '@anticrm/model'
import { attachmentOperation } from '@anticrm/model-attachment'
import { automationOperation } from '@anticrm/model-automation'
import { chunterOperation } from '@anticrm/model-chunter'
import { contactOperation } from '@anticrm/model-contact'
import { coreOperation } from '@anticrm/model-core'
@ -43,6 +44,7 @@ export const migrateOperations: MigrateOperation[] = [
telegramOperation,
taskOperation,
attachmentOperation,
automationOperation,
leadOperation,
recruitOperation,
viewOperation,

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,38 @@
{
"name": "@anticrm/model-automation",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5"
},
"dependencies": {
"@anticrm/core": "~0.6.16",
"@anticrm/automation": "~0.6.0",
"@anticrm/model": "~0.6.0",
"@anticrm/model-core": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/setting": "~0.6.1",
"@anticrm/platform": "~0.6.6",
"@anticrm/automation-resources": "~0.6.0"
}
}

View File

@ -0,0 +1,93 @@
//
// Copyright © 2022 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.
//
// To help typescript locate view plugin properly
import automation, {
Automation,
AutomationSupport,
AttributeAutomationSupport,
AutomationSortSupport,
AutomationTriggerSupport,
Command,
TriggerType
} from '@anticrm/automation'
import { Class, Doc, Domain, Ref } from '@anticrm/core'
import { Builder, Mixin, Model, Prop, TypeString, UX } from '@anticrm/model'
import core, { TAttachedDoc, TClass } from '@anticrm/model-core'
import setting from '@anticrm/setting'
import view from '@anticrm/view'
import plugin from './plugin'
export const DOMAIN_AUTOMATION = 'automation' as Domain
@Model(automation.class.Automation, core.class.AttachedDoc, DOMAIN_AUTOMATION)
@UX(automation.string.Automation)
export class TAutomation extends TAttachedDoc implements Automation<Doc> {
@Prop(TypeString(), core.string.Name)
name!: string
@Prop(TypeString(), core.string.Description)
description!: string | null
targetClass!: Ref<Class<Doc>> | null
declare trigger: {
type: TriggerType
}
declare commands: Command<Doc>[]
}
@Mixin(automation.mixin.AutomationSupport, core.class.Class)
export class TAutomationSupport extends TClass implements AutomationSupport<Doc> {
declare attributes: AttributeAutomationSupport<Doc>[]
declare trigger: AutomationTriggerSupport<Doc>
sort?: AutomationSortSupport<Doc>
}
export function createModel (builder: Builder): void {
builder.createModel(TAutomation, TAutomationSupport)
builder.createDoc(
core.class.Space,
core.space.Model,
{
archived: false,
description: 'Automation space',
members: [],
name: 'Automation space',
private: true
},
automation.space.Automation
)
builder.createDoc(
setting.class.SettingsCategory,
core.space.Model,
{
name: 'automation',
label: automation.string.Automation,
icon: automation.icon.Automation, // TODO: update icon
component: plugin.component.AutomationSettingsElement,
order: 3600
},
plugin.ids.Automation
)
// TODO: Enable when server triggers are added
builder.mixin(automation.class.Automation, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.Delete]
})
}
export { automationOperation } from './migration'

View File

@ -0,0 +1,21 @@
//
// Copyright © 2022 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 { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
export const automationOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
}

View File

@ -0,0 +1,29 @@
//
// Copyright © 2022 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 { Ref } from '@anticrm/core'
import { automationId } from '@anticrm/automation'
import automation, { PluginType } from '@anticrm/automation-resources/src/plugin'
import { mergeIds } from '@anticrm/platform'
import { SettingsCategory } from '@anticrm/setting'
const pluginData = {
ids: {
Automation: '' as Ref<SettingsCategory>
}
}
const automations: PluginType & typeof pluginData = mergeIds(automationId, automation, pluginData)
export default automations

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -36,6 +36,7 @@
"@anticrm/model-contact": "~0.6.1",
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/automation": "~0.6.0",
"@anticrm/board": "~0.6.0",
"@anticrm/board-resources": "~0.6.0",
"@anticrm/view": "~0.6.0",

View File

@ -14,9 +14,10 @@
//
// To help typescript locate view plugin properly
import automation, { AutomationSupport } from '@anticrm/automation'
import type { Board, Card, MenuPage, CommonBoardPreference, CardCover } from '@anticrm/board'
import type { Employee } from '@anticrm/contact'
import { DOMAIN_MODEL, IndexKind, Markup, Ref, Type } from '@anticrm/core'
import { Class, DOMAIN_MODEL, IndexKind, Markup, Ref, Type } from '@anticrm/core'
import {
ArrOf,
Builder,
@ -443,6 +444,56 @@ export function createModel (builder: Builder): void {
builder.mixin(board.class.Card, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.Delete, task.action.Move]
})
builder.mixin<Class<Card>, AutomationSupport<Card>>(
board.class.Card,
core.class.Class,
automation.mixin.AutomationSupport,
{
attributes: [
{
name: 'isArchived'
},
{
name: 'title'
},
{
name: 'description'
}
],
trigger: {
action: {
mode: ['context', 'editor']
}
}
}
)
builder.mixin<Class<Board>, AutomationSupport<Board>>(
board.class.Board,
core.class.Class,
automation.mixin.AutomationSupport,
{
attributes: [
{
name: 'name'
},
{
name: 'description'
},
{
name: 'private'
},
{
name: 'archived'
}
],
trigger: {
action: {
mode: ['context']
}
}
}
)
// TODO: update query when nested query is available
createAction(

View File

@ -18,7 +18,6 @@ import { IntlString, mergeIds } from '@anticrm/platform'
export default mergeIds(coreId, core, {
string: {
Description: '' as IntlString,
Private: '' as IntlString,
Archived: '' as IntlString,
ClassLabel: '' as IntlString,

View File

@ -13,33 +13,13 @@
// limitations under the License.
//
import { ObjQueryType, Ref } from '@anticrm/core'
import { ObjQueryType } from '@anticrm/core'
import { IntlString, mergeIds, Resource } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
import { Action, Filter, ViewAction, viewId } from '@anticrm/view'
import { Filter, ViewAction, viewId } from '@anticrm/view'
import view from '@anticrm/view-resources/src/plugin'
export default mergeIds(viewId, view, {
action: {
Delete: '' as Ref<Action>,
Move: '' as Ref<Action>,
MoveLeft: '' as Ref<Action>,
MoveRight: '' as Ref<Action>,
MoveUp: '' as Ref<Action>,
MoveDown: '' as Ref<Action>,
SelectItem: '' as Ref<Action>,
SelectItemAll: '' as Ref<Action>,
SelectItemNone: '' as Ref<Action>,
SelectUp: '' as Ref<Action>,
SelectDown: '' as Ref<Action>,
ShowPreview: '' as Ref<Action>,
ShowActions: '' as Ref<Action>,
// Edit document
Open: '' as Ref<Action>
},
actionImpl: {
Delete: '' as ViewAction,
Move: '' as ViewAction,

View File

@ -133,6 +133,7 @@ export default plugin(coreId, {
Array: '' as IntlString,
Bag: '' as IntlString,
Name: '' as IntlString,
Description: '' as IntlString,
Enum: '' as IntlString
}
})

View File

@ -17,7 +17,7 @@ import { addLocation, addStringsLoader } from '@anticrm/platform'
import { SvelteComponent } from 'svelte'
import { readable } from 'svelte/store'
import Root from './components/internal/Root.svelte'
import { uiId } from './plugin'
import { uiId, uis } from './plugin'
export type {
AnyComponent,
@ -171,4 +171,4 @@ addStringsLoader(uiId, async (lang: string) => {
addLocation(uiId, async () => ({ default: async () => ({}) }))
export { default } from './plugin'
export default uis

View File

@ -23,7 +23,7 @@ import { AnyComponent } from './types'
*/
export const uiId = 'ui' as Plugin
export default plugin(uiId, {
export const uis = plugin(uiId, {
string: {
EditBoxPlaceholder: '' as IntlString,
Ok: '' as IntlString,
@ -69,3 +69,5 @@ export default plugin(uiId, {
DefaultApplication: '' as Metadata<AnyComponent>
}
})
export default uis

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/assets/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,6 @@
<!-- !!! Dublicate chunter-assets (cunter, hashtag, lock) !!! -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="automation" viewBox="0 0 16 16">
<path fill-rule="evenodd" clip-rule="evenodd" 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"></path>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@anticrm/platform-rig",
"rigProfile": "assets"
}

View File

@ -0,0 +1,20 @@
{
"string": {
"Automation": "Automation",
"Actions": "Actions",
"Chat": "Chat",
"Content": "Content",
"Dates": "Dates",
"Tracker": "Tracker",
"Trigger": "Trigger",
"Set": "Set",
"To": "to",
"Mode": "Mode",
"AddMenu": "Add menu",
"Menu": "Menu",
"Icon": "Icon",
"SelectClass": "Select class",
"In": "in",
"Update": "Update"
}
}

View File

@ -0,0 +1,20 @@
{
"string": {
"Automation": "Автоматизация",
"Actions": "Действия",
"Chat": "Чат",
"Content": "Контент",
"Dates": "Даты",
"Tracker": "Трекер",
"Trigger": "Триггер",
"Set": "Присвоить",
"To": "значение",
"Mode": "Режим",
"AddMenu": "Добавить меню",
"Menu": "Menu",
"Icon": "Изображение",
"SelectClass": "Выберите класс",
"In": "в",
"Update": "Обновить"
}
}

View File

@ -0,0 +1,34 @@
{
"name": "@anticrm/automation-assets",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"template": "@anticrm/assets-package",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:docs": "",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src",
"build:watch": "tsc"
},
"devDependencies": {
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5",
"@types/node": "~16.11.12",
"@anticrm/platform-rig": "~0.6.0"
},
"dependencies": {
"@anticrm/platform": "~0.6.6",
"@anticrm/automation": "~0.6.0"
}
}

View File

@ -0,0 +1,24 @@
//
// Copyright © 2022 Anticrm Platform Contributors.
//
// 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 { addStringsLoader, loadMetadata } from '@anticrm/platform'
import automation, { automationId } from '@anticrm/automation'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(automation.icon, {
Automation: `${icons}#automation`
})
addStringsLoader(automationId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
}

View File

@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@anticrm/platform-rig",
"rigProfile": "ui"
}

View File

@ -0,0 +1,42 @@
{
"name": "@anticrm/automation-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "tsc --noEmit",
"build:docs": "api-extractor run --local",
"lint": "svelte-check && eslint",
"lint:fix": "eslint --fix src",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint": "^7.32.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.4.1",
"prettier-plugin-svelte": "^2.7.0",
"sass": "^1.37.5",
"svelte-check": "^2.7.0",
"svelte-loader": "^3.1.2",
"svelte-preprocess": "^4.10.5",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/automation": "~0.6.0",
"@anticrm/core": "~0.6.16",
"@anticrm/platform": "~0.6.6",
"@anticrm/presentation": "~0.6.2",
"svelte": "^3.47",
"@anticrm/ui": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,101 @@
<script lang="ts">
import { AutomationSupport } from '@anticrm/automation'
import core, { Class, Doc, Ref } from '@anticrm/core'
import { Asset } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import { Button, Dropdown, EditBox, eventToHTMLElement, IconAdd, Label, ListItem, showPopup } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import { Trigger } from '../models'
import automation from '../plugin'
import ClassSelector from './selectors/ClassSelector.svelte'
import IconChooser from './selectors/IconChooser.svelte'
export let trigger: Trigger | undefined = undefined
let targetClass: Ref<Class<Doc>>
let automationSupport: AutomationSupport<Doc>
const dispatch = createEventDispatcher()
const client = getClient()
const hierarchy = client.getHierarchy()
let selectedActionMode: ListItem | undefined = undefined
let name: string | undefined = undefined
let icon: Asset | undefined = undefined
function addActionTrigger () {
if (!name || !selectedActionMode) {
return
}
trigger = {
action: {
context: selectedActionMode?._id as 'editor' | 'context',
target: targetClass,
label: name!,
icon
}
}
dispatch('targetClass', targetClass)
dispatch('trigger', trigger)
}
function chooseIcon (ev: MouseEvent) {
showPopup(IconChooser, { icon }, eventToHTMLElement(ev), (result) => {
if (result !== undefined && result !== null) {
icon = result
}
})
}
const classes = hierarchy
.getDescendants(core.class.Doc)
.map((p) => hierarchy.getClass(p))
.filter((p) => {
if (!hierarchy.hasMixin(p, automation.mixin.AutomationSupport)) {
return false
}
const support = hierarchy.as(p, automation.mixin.AutomationSupport)
return !!support.trigger.action
})
$: if (targetClass) {
automationSupport = hierarchy.as(hierarchy.getClass(targetClass), automation.mixin.AutomationSupport)
}
</script>
<div class="flex-col">
<Label label={automation.string.AddMenu} />
<div class="flex flex-gap-2 mt-2 mb-2 items-center">
<Label label={automation.string.SelectClass} />
<ClassSelector
{classes}
on:selected={(e) => {
targetClass = e.detail
}}
/>
</div>
{#if automationSupport?.trigger?.action}
<div class="flex-between">
<div class="flex flex-gap-3 mb-2">
<div class="flex-col items-baseline justify-center flex-gap-2">
<Label label={automation.string.Icon} />
<Button icon={icon ?? IconAdd} kind="no-border" size="medium" on:click={chooseIcon} />
</div>
<div class="flex-col items-baseline justify-center flex-gap-2">
<Label label={core.string.Name} />
<div class="h-7">
<EditBox bind:value={name} />
</div>
</div>
<div class="flex-col items-baseline justify-center flex-gap-2">
<Label label={automation.string.Mode} />
<Dropdown
items={automationSupport.trigger.action.mode.map((m) => ({ _id: m, isSelectable: true, label: m }))}
bind:selected={selectedActionMode}
placeholder={view.string.LabelNA}
size="medium"
/>
</div>
</div>
<Button icon={IconAdd} kind="transparent" disabled={!name || !selectedActionMode} on:click={addActionTrigger} />
</div>
{/if}
</div>

View File

@ -0,0 +1,61 @@
<script lang="ts">
import { Command } from '@anticrm/automation'
import core, { Class, Doc, Ref } from '@anticrm/core'
import presentation, { getClient } from '@anticrm/presentation'
import { Button, EditBox, Label } from '@anticrm/ui'
import { Trigger } from '../models'
import { createAutomation } from '../utils'
import AutomationActions from './AutomationActions.svelte'
import AutomationTrigger from './AutomationTrigger.svelte'
export let trigger: Trigger | undefined = undefined
export let commands: Command<Doc>[] = []
const client = getClient()
let targetClass: Ref<Class<Doc>> | undefined = undefined
let name: string | undefined = undefined
let description: string | undefined = undefined
function save () {
if (!name || !trigger || !commands.length) {
return
}
createAutomation(client, name, trigger, commands, { description, targetClass })
}
</script>
<div class="flex-col p-4 w-full">
<div class="flex flex-gap-2 mb-2">
<Label label={core.string.Name} />
<EditBox bind:value={name} />
</div>
<div class="flex flex-gap-2 mb-4">
<Label label={core.string.Description} />
<EditBox bind:value={description} />
</div>
<div class="mb-2">
<AutomationTrigger
{trigger}
on:trigger={(e) => {
trigger = e.detail
}}
on:targetClass={(e) => {
targetClass = e.detail
}}
/>
</div>
{#if trigger}
<div class="mb-2">
<AutomationActions {targetClass} bind:commands />
</div>
{/if}
<Button
label={presentation.string.Save}
disabled={!name || !trigger || !commands.length}
kind="primary"
on:click={save}
/>
</div>

View File

@ -0,0 +1,130 @@
<script lang="ts">
import { Command } from '@anticrm/automation'
import core, { AnyAttribute, Class, Doc, Ref } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { Button, IconDelete, Label } from '@anticrm/ui'
import { ActionTab } from '../models'
import automation from '../plugin'
import ContentActionCreate from './actions/ContentActionCreate.svelte'
import CommandPresenter from './presenters/CommandPresenter.svelte'
export let targetClass: Ref<Class<Doc>> | undefined = undefined
export let commands: Command<Doc>[] = []
let attributes: Map<string, AnyAttribute> = new Map()
const client = getClient()
const hierarchy = client.getHierarchy()
const attributeSupportMap = new Map()
const contentAttributes: string[] = []
const dateAttributes: string[] = []
const collectionAttributes: string[] = []
const arrayAttributes: string[] = []
const refAttributes: string[] = []
let currentTab: ActionTab | undefined = undefined
function addCommand (e: CustomEvent<Command<Doc>>) {
commands.push(e.detail)
commands = commands
}
function removeCommand (command: Command<Doc>) {
commands = commands.filter((c) => c !== command)
}
if (targetClass) {
const classObj = hierarchy.getClass(targetClass)
const automationSupport = hierarchy.as(classObj, automation.mixin.AutomationSupport)
automationSupport?.attributes?.forEach((attr) => {
attributeSupportMap.set(attr.name, attr)
attributes = hierarchy.getAllAttributes(targetClass!)
const classifier = attributes.get(attr.name)
if (classifier) {
const typeClass = classifier.type._class
if (
typeClass === core.class.TypeBoolean ||
typeClass === core.class.TypeNumber ||
typeClass === core.class.TypeString ||
typeClass === core.class.TypeMarkup
) {
contentAttributes.push(attr.name)
} else if (typeClass === core.class.TypeTimestamp || typeClass === core.class.TypeDate) {
dateAttributes.push(attr.name)
} else if (typeClass === core.class.Collection) {
collectionAttributes.push(attr.name) // TODO
} else if (typeClass === core.class.ArrOf) {
arrayAttributes.push(attr.name) // TODO
} else if (typeClass === core.class.RefTo) {
refAttributes.push(attr.name) // TODO
}
}
})
}
</script>
<div class="flex-col">
<div class="fs-title mb-4">
<Label label={automation.string.Actions} />
</div>
{#each commands as command}
<div class="flex-between mb-2">
<CommandPresenter value={command} />
<Button
icon={IconDelete}
kind="transparent"
on:click={() => {
removeCommand(command)
}}
/>
</div>
{/each}
<div class="flex flex-gap-2">
{#if contentAttributes.length > 0}
<Button
label={automation.string.Content}
kind="no-border"
on:click={() => {
currentTab = ActionTab.Content
}}
/>
{/if}
{#if dateAttributes.length > 0}
<Button
label={automation.string.Dates}
kind="no-border"
on:click={() => {
currentTab = ActionTab.Dates
}}
/>
{/if}
<!--
<Button
label={automation.string.Chat}
kind="no-border"
on:click={() => {
currentTab = ActionTab.Chat
}}
/>
<Button
label={automation.string.Tracker}
kind="no-border"
on:click={() => {
currentTab = ActionTab.Tracker
}}
/>
-->
</div>
<div class="mt-4">
{#if currentTab === ActionTab.Content && targetClass}
{#each contentAttributes as attr}
<ContentActionCreate
attribute={attributes.get(attr)}
automationSupport={attributeSupportMap.get(attr)}
{targetClass}
on:add={addCommand}
/>
{/each}
<!-- {:else if currentTab === ActionTab.Dates}{:else if currentTab === ActionTab.Chat}{:else if currentTab === ActionTab.Tracker} -->
{/if}
</div>
</div>

View File

@ -0,0 +1,36 @@
<script lang="ts">
import ui, { Button, Icon, IconAdd, IconBack, Label } from '@anticrm/ui'
import automation from '../plugin'
import AddAutomation from './AddAutomation.svelte'
import Automations from './Automations.svelte'
let isAdding = false
</script>
<div class="antiPanel-component">
<div class="ac-header full withSettings short divide">
<div class="ac-header__wrap-title">
<div class="ac-header__icon"><Icon icon={automation.icon.Automation} size="small" /></div>
<span class="ac-header__title"><Label label={automation.string.Automation} /></span>
</div>
<Button
icon={isAdding ? IconBack : IconAdd}
label={isAdding ? ui.string.Cancel : automation.string.Automation}
kind={isAdding ? 'no-border' : 'primary'}
on:click={() => {
isAdding = !isAdding
}}
/>
</div>
{#if isAdding}
<div class="ac-body columns hScroll">
<AddAutomation
on:close={() => {
isAdding = false
}}
/>
</div>
{:else}
<Automations />
{/if}
</div>

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { Button, IconDelete, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import { Trigger } from '../models'
import automation from '../plugin'
import TriggerPresenter from './presenters/TriggerPresenter.svelte'
import AddActionTrigger from './AddActionTrigger.svelte'
export let trigger: Trigger | undefined = undefined
const dispatch = createEventDispatcher()
function clearTrigger () {
trigger = undefined
dispatch('trigger', trigger)
}
</script>
<div class="flex-col">
<div class="fs-title mb-4">
<Label label={automation.string.Trigger} />
</div>
{#if trigger}
<div class="flex-between">
<TriggerPresenter value={trigger} />
<Button icon={IconDelete} kind="transparent" on:click={clearTrigger} />
</div>
{:else}
<AddActionTrigger on:trigger on:targetClass />
{/if}
</div>

View File

@ -0,0 +1,6 @@
<script lang="ts">
import automation from '@anticrm/automation'
import { TableBrowser } from '@anticrm/view-resources'
</script>
<TableBrowser _class={automation.class.Automation} config={['name', 'description', 'modifiedOn']} query={{}} />

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { CommandType, UpdateDocCommand } from '@anticrm/automation'
import core, { AnyAttribute, Class, Doc, Ref } from '@anticrm/core'
import ui, { Button, EditBox, IconAdd, Label } from '@anticrm/ui'
import { BooleanEditor, NumberEditor } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
import automation from '../../plugin'
export let automationSupport: { name: string }
export let attribute: AnyAttribute | undefined = undefined
export let targetClass: Ref<Class<Doc>>
const dispatch = createEventDispatcher()
const typeClass = attribute?.type._class
let value: string | undefined = undefined
function onChange (v: any) {
value = v
}
function add () {
if (attribute && value !== undefined) {
const command: UpdateDocCommand<any> = {
type: CommandType.UpdateDoc,
targetClass,
update: {
[attribute.name]: value
}
}
dispatch('add', command)
}
}
</script>
{#if attribute && automationSupport}
<div class="flex-between">
<div class="flex flex-gap-1 mr-3 items-center">
<Label label={automation.string.Set} />
<span class="font-semi-bold">
<Label label={attribute.label} />
</span>
<Label label={automation.string.To} />
<div>
{#if typeClass === core.class.TypeString || typeClass === core.class.TypeMarkup}
<EditBox bind:value />
{:else if typeClass === core.class.TypeNumber}
<NumberEditor value={Number(value)} {onChange} focus={true} placeholder={ui.string.EditBoxPlaceholder} />
{:else if typeClass === core.class.TypeBoolean}
<BooleanEditor {value} {onChange} />
{/if}
</div>
</div>
<Button icon={IconAdd} kind="transparent" on:click={add} />
</div>
{/if}

View File

@ -0,0 +1,43 @@
<script lang="ts">
import { Doc } from '@anticrm/core'
import { Command, isUpdateDocCommand, UpdateDocCommand } from '@anticrm/automation'
import { getClient } from '@anticrm/presentation'
import { Label } from '@anticrm/ui'
import { ClassPresenter } from '@anticrm/view-resources'
import automation from '../../plugin'
export let value: Command<Doc>
const client = getClient()
const hierarchy = client.getHierarchy()
function toUpdateDocCommand (command: Command<Doc>): UpdateDocCommand<Doc> {
return command as UpdateDocCommand<Doc>
}
function toObjectValue (obj: any, attr: any): string {
if (!obj || !attr) {
return ''
}
return obj[attr]?.toString()
}
</script>
{#if isUpdateDocCommand(value)}
{@const updateCommand = toUpdateDocCommand(value)}
{@const targetClass = hierarchy.getClass(updateCommand.targetClass)}
{#each Object.keys(updateCommand.update) as attr}
{@const attribute = hierarchy.getAttribute(updateCommand.targetClass, attr)}
<div class="flex flex-gap-1 items-center">
<Label label={automation.string.Update} />
<span class="font-semi-bold">
<ClassPresenter value={targetClass} />
</span>
<Label label={automation.string.Set} />
<span class="font-semi-bold">
<Label label={attribute.label} />
</span>
<Label label={automation.string.To} />
<span>{toObjectValue(updateCommand.update, attr)}</span>
</div>
{/each}
{/if}

View File

@ -0,0 +1,29 @@
<script lang="ts">
import { getClient } from '@anticrm/presentation'
import { Icon, Label } from '@anticrm/ui'
import { ClassPresenter } from '@anticrm/view-resources'
import { Trigger } from '../../models'
import automation from '../../plugin'
export let value: Trigger
const client = getClient()
const hierarchy = client.getHierarchy()
</script>
{#if value.action}
{@const targetClass = hierarchy.getClass(value.action.target)}
<div class="flex flex-gap-1 items-center">
<span class="mr-4">
<Label label={automation.string.Menu} />
</span>
{#if value.action.icon}
<Icon icon={value.action.icon} size="small" />
{/if}
<span class="mr-2">{value.action.label}</span>
<Label label={automation.string.In} />
<span class="font-semi-bold">
<ClassPresenter value={targetClass} />
</span>
</div>
{/if}

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { Class, Obj } from '@anticrm/core'
import { translate } from '@anticrm/platform'
import { DropdownLabels, DropdownTextItem } from '@anticrm/ui'
import automation from '../../plugin'
export let classes: Class<Obj>[] = []
async function getClassItems () {
const classItems: DropdownTextItem[] = []
for (const cl of classes) {
const label = await translate(cl.label, {})
classItems.push({ id: cl._id, label })
}
return classItems
}
</script>
{#await getClassItems() then classItems}
<DropdownLabels label={automation.string.SelectClass} items={classItems} on:selected />
{/await}

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { Metadata } from '@anticrm/platform'
import presentation, { Card } from '@anticrm/presentation'
import { Button } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import automation from '../../plugin'
export let icon: Metadata<string> | undefined = undefined
const dispatch = createEventDispatcher()
const icons = [
view.icon.Archive,
view.icon.ArrowRight,
view.icon.Card,
view.icon.Delete,
view.icon.Model,
view.icon.MoreH,
view.icon.Move,
view.icon.Open,
view.icon.Pin,
view.icon.Setting,
view.icon.Statuses,
view.icon.Table,
view.icon.Views
]
function save () {
dispatch('close', icon)
}
</script>
<Card
label={automation.string.Automation}
okLabel={presentation.string.Save}
okAction={save}
canSave={icon !== undefined}
on:close={() => {
dispatch('close')
}}
>
<div class="float-left-box">
{#each icons as obj}
<div class="float-left p-2">
<Button
icon={obj}
size="medium"
kind={obj === icon ? 'primary' : 'transparent'}
on:click={() => {
icon = obj
}}
/>
</div>
{/each}
</div>
</Card>

View File

@ -0,0 +1,27 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 { Resources } from '@anticrm/platform'
import AutomationSettingsElement from './components/AutomationSettingsElement.svelte'
import { performAutomation } from './utils'
export default async (): Promise<Resources> => ({
component: {
AutomationSettingsElement
},
action: {
PerformAutomation: performAutomation
}
})

View File

@ -0,0 +1,21 @@
import { Class, Doc, Ref } from '@anticrm/core'
import { Asset } from '@anticrm/platform'
export enum ActionTab {
Add = 'Add',
Chat = 'Chat',
Content = 'Content',
Dates = 'Dates',
Move = 'Move',
Sort = 'Sort',
Tracker = 'Tracker'
}
export interface Trigger {
action?: {
context: 'context' | 'editor'
target: Ref<Class<Doc>>
label: string
icon?: Asset
}
}

View File

@ -0,0 +1,28 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 automation, { automationId } from '@anticrm/automation'
import { mergeIds } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
const automations = mergeIds(automationId, automation, {
component: {
AutomationSettingsElement: '' as AnyComponent
}
})
export type PluginType = typeof automations
export default automations

View File

@ -0,0 +1,120 @@
import automation, {
Automation,
Command,
isUpdateDocCommand,
PerformAutomationProps,
TriggerType
} from '@anticrm/automation'
import core, { Class, Doc, Ref, Space, TxOperations } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import view, { Action } from '@anticrm/view'
import { Trigger } from './models'
export async function createAutomation (
client: TxOperations,
name: string,
trigger: Trigger,
commands: Array<Command<Doc>>,
props: { targetClass?: Ref<Class<Doc>>, attachedTo?: Doc, description?: string } = {}
): Promise<Ref<Automation<Doc>>> {
let space: Ref<Space> = automation.space.Automation
let attachedTo: Ref<Doc> = automation.space.Automation
let attachedToClass: Ref<Class<Doc>> = core.class.Space
const collection = 'automations'
if (props.attachedTo !== undefined) {
space = props.attachedTo.space
attachedTo = props.attachedTo._id
attachedToClass = props.attachedTo._class
}
const automationId = await client.addCollection(
automation.class.Automation,
space,
attachedTo,
attachedToClass,
collection,
{
name,
description: null,
targetClass: props.targetClass ?? null,
trigger: {
type: getTriggerType(trigger)
},
commands
}
)
await createTrigger(client, trigger, space, automationId)
return automationId
}
export function getTriggerType (trigger: Trigger): TriggerType {
if (trigger.action !== undefined) {
return TriggerType.Action
} else {
throw new Error('Unknown automation trigger')
}
}
export async function createTrigger (
client: TxOperations,
trigger: Trigger,
space: Ref<Space>,
automationId: Ref<Automation<Doc>>
): Promise<Doc | undefined> {
if (trigger.action !== undefined) {
const triggerId = await client.createDoc<Action<Doc, PerformAutomationProps>>(view.class.Action, core.space.Model, {
action: automation.action.PerformAutomation,
actionProps: {
automationId,
automationClass: automation.class.Automation
},
category: automation.category.Automation,
context: { mode: trigger.action.context },
icon: trigger.action.icon,
input: 'any',
label: trigger.action.label as IntlString,
target: trigger.action.target
})
return await client.findOne<Action<Doc, PerformAutomationProps>>(view.class.Action, { _id: triggerId })
} else {
throw new Error('Unknown automation trigger')
}
}
export async function performAutomation (
doc: Doc | Doc[] | undefined,
evt: Event,
props: PerformAutomationProps | undefined
): Promise<void> {
if (doc === undefined || props?.automationId === undefined) {
throw new Error('Unknown automation action')
}
const client = getClient()
const automationObj = await client.findOne(props.automationClass ?? automation.class.Automation, {
_id: props.automationId
})
if (automationObj === undefined) {
return
}
let objects = []
if (Array.isArray(doc)) {
objects = doc
} else {
objects = [doc]
}
await doPerformAutomation(client, objects, automationObj)
}
async function doPerformAutomation (client: TxOperations, docs: Doc[], automationObj: Automation<Doc>): Promise<void> {
// TODO: move to automation server
const hierarchy = client.getHierarchy()
for (const doc of docs) {
for (const command of automationObj.commands) {
if (isUpdateDocCommand(command) && hierarchy.isDerived(doc._class, command.targetClass)) {
await client.update(doc, command.update)
}
}
}
}

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,35 @@
{
"name": "@anticrm/automation",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"test": "heft test",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"simplytyped": "^3.3.0",
"@rushstack/heft": "^0.45.5",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/platform": "~0.6.6",
"@anticrm/core": "~0.6.16",
"@anticrm/view": "~0.6.0"
}
}

View File

@ -0,0 +1,148 @@
import { AttachedDoc, Class, Doc, DocumentQuery, DocumentUpdate, Mixin, Ref, Space } from '@anticrm/core'
import { Asset, IntlString, Plugin, plugin } from '@anticrm/platform'
import { ActionCategory, ViewAction } from '@anticrm/view'
/**
* @public
*/
export const automationId = 'automation' as Plugin
/**
* @public
*/
export enum CommandType {
UpdateDoc = 'UPDATE_DOC'
}
/**
* @public
*/
export enum TriggerType {
Action = 'ACTION',
Trigger = 'TRIGGER'
}
/**
* @public
*/
export interface AttributeAutomationSupport<T extends Doc> {
name: keyof T
sort?: {
groupBy?: DocumentQuery<Doc>
}
}
/**
* @public
*/
export interface AttributeAutomationTriggerSupport<T extends Doc> {
name: keyof T
}
/**
* @public
*/
export interface AutomationTriggerSupport<T extends Doc> {
action?: {
mode: ('editor' | 'context')[]
}
attributes?: AttributeAutomationTriggerSupport<T>[]
}
/**
* @public
*/
export interface AutomationSortSupport<T extends Doc> {
groupBy?: DocumentQuery<T>
}
/**
* @public
*/
export interface AutomationSupport<T extends Doc> extends Class<Doc> {
attributes: AttributeAutomationSupport<T>[]
trigger: AutomationTriggerSupport<T>
sort?: AutomationSortSupport<T>
}
/**
* @public
*/
export interface Command<T extends Doc> {
fetch?: DocumentQuery<T>
type: CommandType
}
/**
* @public
*/
export interface UpdateDocCommand<T extends Doc> extends Command<T> {
type: CommandType.UpdateDoc
targetClass: Ref<Class<T>>
update: DocumentUpdate<T>
}
/**
* @public
*/
export function isUpdateDocCommand (command: Command<Doc>): command is UpdateDocCommand<Doc> {
return command.type === CommandType.UpdateDoc
}
/**
* @public
*/
export interface Automation<T extends Doc> extends AttachedDoc {
name: string
description: string | null
targetClass: Ref<Class<T>> | null
trigger: {
type: TriggerType
}
commands: Command<T>[]
}
/**
* @public
*/
export interface PerformAutomationProps {
automationId: Ref<Automation<Doc>>
automationClass: Ref<Class<Automation<Doc>>>
}
export default plugin(automationId, {
class: {
Automation: '' as Ref<Class<Automation<Doc>>>
},
action: {
PerformAutomation: '' as ViewAction<PerformAutomationProps>
},
mixin: {
AutomationSupport: '' as Ref<Mixin<AutomationSupport<Doc>>>
},
category: {
Automation: '' as Ref<ActionCategory>
},
string: {
Automation: '' as IntlString,
Actions: '' as IntlString,
Chat: '' as IntlString,
Content: '' as IntlString,
Dates: '' as IntlString,
Tracker: '' as IntlString,
Trigger: '' as IntlString,
Set: '' as IntlString,
To: '' as IntlString,
AddMenu: '' as IntlString,
Menu: '' as IntlString,
Mode: '' as IntlString,
Icon: '' as IntlString,
SelectClass: '' as IntlString,
In: '' as IntlString,
Update: '' as IntlString
},
icon: {
Automation: '' as Asset
},
space: {
Automation: '' as Ref<Space>
}
})

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}

View File

@ -99,7 +99,12 @@ export {
UpDownNavigator,
ViewletSettingButton,
FilterBar,
ClassAttributeBar
ClassAttributeBar,
ClassPresenter,
BooleanEditor,
BooleanPresenter,
NumberEditor,
NumberPresenter
}
export default async (): Promise<Resources> => ({

View File

@ -33,7 +33,6 @@ export default mergeIds(viewId, view, {
Cancel: '' as IntlString,
LabelYes: '' as IntlString,
LabelNo: '' as IntlString,
LabelNA: '' as IntlString,
ChooseAColor: '' as IntlString,
DeleteObject: '' as IntlString,
DeleteObjectConfirm: '' as IntlString,

View File

@ -400,6 +400,26 @@ const view = plugin(viewId, {
LinkPresenter: '' as Ref<Class<LinkPresenter>>,
FilterMode: '' as Ref<Class<FilterMode>>
},
action: {
Delete: '' as Ref<Action>,
Move: '' as Ref<Action>,
MoveLeft: '' as Ref<Action>,
MoveRight: '' as Ref<Action>,
MoveUp: '' as Ref<Action>,
MoveDown: '' as Ref<Action>,
SelectItem: '' as Ref<Action>,
SelectItemAll: '' as Ref<Action>,
SelectItemNone: '' as Ref<Action>,
SelectUp: '' as Ref<Action>,
SelectDown: '' as Ref<Action>,
ShowPreview: '' as Ref<Action>,
ShowActions: '' as Ref<Action>,
// Edit document
Open: '' as Ref<Action>
},
viewlet: {
Table: '' as Ref<ViewletDescriptor>
},
@ -410,7 +430,8 @@ const view = plugin(viewId, {
BooleanTruePresenter: '' as AnyComponent
},
string: {
CustomizeView: '' as IntlString
CustomizeView: '' as IntlString,
LabelNA: '' as IntlString
},
icon: {
Table: '' as Asset,

View File

@ -1328,6 +1328,26 @@
"projectFolder": "pods/backup",
"shouldPublish": false
},
{
"packageName": "@anticrm/automation",
"projectFolder": "plugins/automation",
"shouldPublish": true
},
{
"packageName": "@anticrm/automation-resources",
"projectFolder": "plugins/automation-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/automation-assets",
"projectFolder": "plugins/automation-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-automation",
"projectFolder": "models/automation",
"shouldPublish": true
},
{
"packageName": "@anticrm/hr",
"projectFolder": "plugins/hr",