From ad976c66fd9a78e6d90224091634b04a25a08f40 Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Tue, 6 Aug 2019 17:40:30 +0200
Subject: Unified workflow management
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/components/Operation.vue | 83 ++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 apps/workflowengine/src/components/Operation.vue

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
new file mode 100644
index 00000000000..f7a8f56cede
--- /dev/null
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -0,0 +1,83 @@
+<template>
+	<div class="actions__item" :class="{'colored': !!color}" :style="{ backgroundColor: color }">
+		<div class="icon" :class="icon"></div>
+		<h3>{{ title }}</h3>
+		<small>{{ description }}</small>
+		<slot />
+	</div>
+</template>
+
+<script>
+	export default {
+		name: "Operation",
+		props: {
+			icon: {
+				type: String,
+				required: true
+			},
+			title: {
+				type: String,
+				required: true
+			},
+			description: {
+				type: String,
+				required: true
+			},
+			color: {
+				type: String,
+				required: false
+			},
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	.actions__item {
+		display: flex;
+		flex-direction: column;
+		flex-grow: 1;
+		margin-left: -1px;
+		padding: 10px;
+		border-radius: var(--border-radius-large);
+		max-width: 230px;
+		margin-right: 20px;
+	}
+	.icon {
+		display: block;
+		width: 100%;
+		height: 50px;
+		background-size: 50px 50px;
+		background-position: center center;
+		margin-top: 10px;
+		margin-bottom: 20px;
+	}
+	h3, small {
+		padding: 6px;
+		text-align: center;
+		display: block;
+	}
+	h3 {
+		margin: 0;
+		padding: 0;
+		font-weight: 500;
+	}
+	small {
+		font-size: 10pt;
+	}
+	.icon-block {
+		background-image: url(/apps-extra/files_accesscontrol/img/app-dark.svg);
+	}
+
+	.icon-convert-pdf {
+		background-image: url(/apps-extra/workflow_pdf_converter/img/app-dark.svg);
+	}
+
+	.colored {
+		.icon {
+			filter: invert(1);
+		}
+		* {
+			color: var(--color-primary-text)
+		}
+	}
+</style>
\ No newline at end of file
-- 
cgit v1.2.3


From 9f8ccf1036ff18d961949bfb644c9d445caa8a23 Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Mon, 19 Aug 2019 17:13:33 +0200
Subject: Use entity/event definitions from backend
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/components/Check.vue     |  16 +--
 apps/workflowengine/src/components/Event.vue     |  24 +++-
 apps/workflowengine/src/components/Operation.vue |  25 ++--
 apps/workflowengine/src/components/Rule.vue      |   8 +-
 apps/workflowengine/src/components/Workflow.vue  |  21 +--
 apps/workflowengine/src/services/Operation.js    | 168 ++++++++++++-----------
 6 files changed, 141 insertions(+), 121 deletions(-)

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue
index c8c7c46aa87..8cfe5a89020 100644
--- a/apps/workflowengine/src/components/Check.vue
+++ b/apps/workflowengine/src/components/Check.vue
@@ -15,6 +15,7 @@
 </template>
 
 <script>
+	import { Checks } from '../services/Operation';
 	import { Multiselect, Actions, ActionButton } from 'nextcloud-vue'
 	import ClickOutside from 'vue-click-outside';
 
@@ -43,13 +44,8 @@
 			}
 		},
 		mounted() {
-			this.options = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => {
-				if (plugin.component) {
-					return {...plugin.getCheck(), component: plugin.component}
-				}
-				return plugin.getCheck()
-			})
-			this.currentOption = this.options.find((option) => option.class === this.check.class)
+			this.options = Object.values(Checks)
+			this.currentOption = Checks[this.check.class]
 			this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
 			this.$nextTick(() => {
 				this.$refs.checkSelector.$el.focus()
@@ -59,12 +55,12 @@
 			operators() {
 				if (!this.currentOption)
 					return []
-				return this.options.find((item) => item.class === this.currentOption.class).operators
+				return Checks[this.currentOption.class].operators
 			},
 			currentComponent() {
 				if (!this.currentOption)
 					return []
-				let currentComponent = this.options.find((item) => item.class === this.currentOption.class).component
+				let currentComponent = Checks[this.currentOption.class].component
 				return currentComponent && currentComponent()
 			}
 		},
@@ -101,4 +97,4 @@
 	::placeholder {
 		font-size: 10px;
 	}
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue
index fd5097cecfc..caf5c7631bd 100644
--- a/apps/workflowengine/src/components/Event.vue
+++ b/apps/workflowengine/src/components/Event.vue
@@ -2,11 +2,11 @@
 	<div>
 	<Multiselect :value="currentEvent" :options="allEvents" label="name" track-by="id" :allow-empty="false" :disabled="allEvents.length <= 1" @input="updateEvent">
 		<template slot="singleLabel" slot-scope="props">
-			<span class="option__icon" :class="props.option.icon"></span>
+			<img class="option__icon" :src="props.option.icon" />
 			<span class="option__title option__title_single">{{ props.option.name }}</span>
 		</template>
 		<template slot="option" slot-scope="props">
-			<span class="option__icon" :class="props.option.icon"></span>
+			<img class="option__icon" :src="props.option.icon" />
 			<span class="option__title">{{ props.option.name }}</span>
 		</template>
 	</Multiselect>
@@ -15,7 +15,7 @@
 
 <script>
 	import { Multiselect } from 'nextcloud-vue'
-	import { eventService, operationService } from '../services/Operation'
+	import { Entities, operationService } from '../services/Operation'
 
 	export default {
 		name: "Event",
@@ -36,7 +36,21 @@
 				return this.allEvents.find(event => event.id === this.rule.event)
 			},
 			allEvents() {
-				return this.operation.events.map((eventName) => eventService.get(eventName))
+				return this.operation.events.map((entityEventName) => {
+					const parts = entityEventName.split('::')
+					const entityId = parts[0]
+					const eventName = parts[1]
+					const Entity = Entities.find((entity) => entity.id === entityId)
+					const Event = Entity.events.find((event) => event.eventName === eventName)
+					return {
+						entity: entityId,
+						id: entityEventName,
+						event: eventName,
+						name: Event.displayName,
+						icon: Entity.icon,
+						checks: Entity.checks,
+					}
+				})
 			},
 			operation() {
 				return operationService.get(this.rule.class)
@@ -81,4 +95,4 @@
 	.option__title_single {
 		font-weight: 900;
 	}
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
index f7a8f56cede..3e5de3198ca 100644
--- a/apps/workflowengine/src/components/Operation.vue
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -39,8 +39,8 @@
 		margin-left: -1px;
 		padding: 10px;
 		border-radius: var(--border-radius-large);
-		max-width: 230px;
 		margin-right: 20px;
+		margin-bottom: 20px;
 	}
 	.icon {
 		display: block;
@@ -64,20 +64,21 @@
 	small {
 		font-size: 10pt;
 	}
-	.icon-block {
-		background-image: url(/apps-extra/files_accesscontrol/img/app-dark.svg);
-	}
-
-	.icon-convert-pdf {
-		background-image: url(/apps-extra/workflow_pdf_converter/img/app-dark.svg);
-	}
 
 	.colored {
-		.icon {
-			filter: invert(1);
-		}
 		* {
 			color: var(--color-primary-text)
 		}
 	}
-</style>
\ No newline at end of file
+
+	/* TODO: those should be provided by the backend, remove once ready */
+	.icon-block {
+		background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' height='32' width='32' version='1.1' viewBox='0 0 32 32'%3E%3Cpath fill='%23fff' d='m10.203 2-8.203 8.203v11.594l8.203 8.203h11.594l8.203-8.203v-11.594l-8.203-8.203h-11.594zm11.097 5.3092 3.345 3.3448-5.346 5.346 5.346 5.346-3.299 3.299-5.346-5.346-5.346 5.346-3.2992-3.299 5.3462-5.346-5.3462-5.346 3.2992-3.2992 5.346 5.3462 5.3-5.3918z'/%3E%3C/svg%3E");
+	}
+	.icon-convert-pdf {
+		background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' version='1.1' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23fff'%3E%3Cpath d='m7.0624 2.9056c-0.20526 0-0.36653 0.14989-0.36653 0.34066v8.8571c0 0.19077 0.16127 0.34066 0.36653 0.34066h8.0637c0.20526 0 0.36653-0.14989 0.36653-0.34066v-7.1538l-2.1992-2.044zm4.2518 2.6571s0.05132 0.64725-0.10996 1.567c0.52414 1.3987 1.0996 1.5875 1.3562 1.7033 0.54247-0.040873 1.1472-0.068129 1.6861 0.2044 0.36653 0.19486 0.65536 1.022-0.21992 1.022-0.39586-0.023161-1.1267-0.23574-1.6494-0.47692-0.78145 0.081762-1.752 0.21802-2.5657 0.54505-0.91633 1.4308-1.3268 1.6352-1.6494 1.6352-0.89067-0.21802-0.41052-1.3149-0.073304-1.4989 0.40319-0.32022 0.87601-0.50417 1.0263-0.54505 0.065969-0.10221 1.0146-1.8327 1.2462-2.5549-0.21992-0.69767-0.27123-1.4349-0.14661-1.8736 0.57179-0.69358 1.0996-0.23846 1.0996 0.27253zm-0.51315 2.1121c-0.19793 0.72015-0.98817 2.1012-0.95299 2.044 0.81004-0.33044 1.5394-0.42923 2.3458-0.54505-0.38559-0.16011-0.84009-0.17033-1.3928-1.4989z' stroke-width='.70672'/%3E%3Cpath d='m16.246-9.7651c-2.05e-4 0.0144-6e-3 0.027629-6e-3 0.042066-0.0044 2.2592 2.0761 3.742 4.0564 3.6477v1.2349l2.3737-2.2265-2.3377-2.3407-3e-3 1.2289c-1.0287 0.1337-1.8811-0.66867-1.8659-1.5414 2.9e-4 -0.016152 0.0083-0.029062 9e-3 -0.045071z' stroke-width='.67694'/%3E%3Cpath d='m3.2734 5.1094v1.4492h-2.7676v2.5h2.7246l-0.0019532 1.4629 3.0996-2.6387-3.0547-2.7734z'/%3E%3Cpath d='m8.334-11.356c-0.78035-0.78051-1.9205-1.0863-2.9866-0.80073a0.51533 0.51533 0 1 0 0.26293 0.99405c0.71208-0.19075 1.4702 0.01747 1.9914 0.53876 0.46076 0.46083 0.65567 1.1026 0.56688 1.7376a0.61838 0.61838 0 1 0-0.87225 0.87442l0.8687 0.86886a0.61838 0.61838 0 0 0 0.86992 7.91e-5l0.86886-0.8687a0.61838 0.61838 0 0 0 0.0011543-0.88702 0.61838 0.61838 0 0 0-0.67634-0.12303c0.04094-0.86013-0.27221-1.7117-0.89472-2.3343zm-3.3067 1.0814a0.61838 0.61838 0 0 0-0.015967-0.01364l-0.86984-0.87a0.61838 0.61838 0 0 0-0.042126-0.04213 0.61838 0.61838 0 0 0-0.82551 0.04205l-0.87 0.86984a0.61838 0.61838 0 0 0 0.66145 1.0237c-0.024276 0.84049 0.29182 1.6675 0.90045 2.2762 0.78035 0.78052 1.9205 1.0863 2.9866 0.80073a0.51533 0.51533 0 1 0-0.27202-0.99408c-0.71208 0.19075-1.4669-0.011716-1.988-0.53306-0.45484-0.45491-0.65183-1.0905-0.57258-1.7183l0.018216 0.018221a0.61843 0.61843 0 0 0 0.88935-0.85959z' stroke-width='.68342'/%3E%3Cpath d='m31.219 0.33675v0.00113h-6.9286v1.3295l6.9286 0.036145c0.0026-1.821e-4 0.0053 2.074e-4 0.0079 0 0.0053-4.166e-4 0.01058-0.00137 0.01581-0.00113 0.65203-0.00106 1.1749 0.44619 1.1867 1.0392 0.0108 0.5673-0.60099 1.0888-1.3381 1.0019l-0.0013-0.79858-1.6753 1.5203 1.7016 1.4481-0.0013-0.8031c1.419 0.06127 2.9112-0.90236 2.9081-2.3709-0.0029-1.3197-1.2547-2.4007-2.7961-2.4014-0.0023-1e-6 -0.0043-0.00113-0.0066-0.00113z' stroke-width='.462'/%3E%3Crect x='31.116' y='-1.6777' width='4.3279' height='7.5909'/%3E%3C/g%3E%3C/svg%3E");
+	}
+	.icon-invert {
+		filter: invert(1);
+	}
+</style>
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
index 818e15610fa..43cbbced209 100644
--- a/apps/workflowengine/src/components/Rule.vue
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -163,10 +163,12 @@
 
 	.rule {
 		display: flex;
+		flex-wrap: wrap;
+
 		.trigger, .action {
 			flex-grow: 1;
 			min-height: 100px;
-			width: 50%;
+			flex-basis: 50%;
 		}
 		.action {
 			position: relative;
@@ -177,6 +179,8 @@
 		}
 		.icon-confirm {
 			background-position: right center;
+			padding-right: 20px;
+			margin-right: 20px;
 		}
 	}
 	.trigger p, .action p {
@@ -206,4 +210,4 @@
 		text-align: left;
 		font-size: 1em;
 	}
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/components/Workflow.vue b/apps/workflowengine/src/components/Workflow.vue
index 9993b328827..918236ead84 100644
--- a/apps/workflowengine/src/components/Workflow.vue
+++ b/apps/workflowengine/src/components/Workflow.vue
@@ -14,12 +14,6 @@
 					{{ showMoreOperations ? t('workflowengine', 'Show less') : t('workflowengine', 'Show more') }}
 				</button>
 			</div>
-			<transition name="slide">
-				<div class="actions" v-if="!hasUnsavedRule && showMoreOperations && hasMoreOperations">
-					<Operation v-for="operation in getMoreOperations" :key="operation.class" :icon="operation.icon" :title="operation.title" :description="operation.description"
-							   @click.native="createNewRule(operation)"></Operation>
-				</div>
-			</transition>
 		</div>
 
 		<transition-group name="slide">
@@ -59,6 +53,9 @@
 				return Object.keys(this.operations.getAll()).length > ACTION_LIMIT
 			},
 			getMainOperations() {
+				if (this.showMoreOperations) {
+					return Object.values(this.operations.getAll())
+				}
 				return Object.values(this.operations.getAll()).slice(0, ACTION_LIMIT)
 			},
 			getMoreOperations() {
@@ -95,10 +92,16 @@
 </script>
 
 <style scoped lang="scss">
+	.section {
+		max-width: 100vw;
+	}
 	.actions {
 		display: flex;
-		.action__item {
-			border: 1px solid var(--color-border);
+		flex-wrap: wrap;
+		.actions__item {
+			max-width: 300px;
+			min-width: 200px;
+			flex-basis: 200px;
 		}
 	}
 
@@ -143,4 +146,4 @@
 		overflow: hidden;
 		max-height: 0;
 	}
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/services/Operation.js b/apps/workflowengine/src/services/Operation.js
index 1de7d2a95ad..a24d97ef226 100644
--- a/apps/workflowengine/src/services/Operation.js
+++ b/apps/workflowengine/src/services/Operation.js
@@ -1,5 +1,41 @@
 import ConvertToPdf from './../components/Operations/ConvertToPdf'
 import Tag from './../components/Operations/Tag'
+
+const ALL_CHECKS = [
+	'OCA\\WorkflowEngine\\Check\\FileMimeType',
+	'OCA\\WorkflowEngine\\Check\\FileName',
+	'OCA\\WorkflowEngine\\Check\\FileSize',
+	'OCA\\WorkflowEngine\\Check\\FileSystemTags',
+	'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
+	'OCA\\WorkflowEngine\\Check\\RequestTime',
+	'OCA\\WorkflowEngine\\Check\\RequestURL',
+	'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
+	'OCA\\WorkflowEngine\\Check\\UserGroupMembership'
+]
+
+const Entities = OCP.InitialState.loadState('workflowengine', 'entities').map(entity => {
+	return {
+		...entity,
+		// TODO: see if we should have this defined in the backend as well
+		checks: [...ALL_CHECKS]
+	}
+})
+
+const Checks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => {
+	if (plugin.component) {
+		return {...plugin.getCheck(), component: plugin.component}
+	}
+	return plugin.getCheck()
+}).reduce((obj, item) => {
+	obj[item.class] = item
+	return obj
+}, {})
+
+/**
+ * Register operations
+ * TODO: should be provided by the backend
+ */
+
 class OperationService {
 
 	constructor() {
@@ -20,92 +56,63 @@ class OperationService {
 	}
 
 }
-
-class EventService {
-
-	constructor() {
-		this.events = {}
-	}
-	registerEvent(event) {
-		this.events[event.id] = event
-	}
-
-	getAll() {
-		return this.events
-	}
-
-	get(id) {
-		return this.events[id]
-	}
-
-}
-
 const operationService = new OperationService()
-const eventService = new EventService()
-
-
-const ALL_CHECKS = [
-	'OCA\\WorkflowEngine\\Check\\FileMimeType',
-	'OCA\\WorkflowEngine\\Check\\FileName',
-	'OCA\\WorkflowEngine\\Check\\FileSize',
-	'OCA\\WorkflowEngine\\Check\\FileSystemTags',
-	'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
-	'OCA\\WorkflowEngine\\Check\\RequestTime',
-	'OCA\\WorkflowEngine\\Check\\RequestURL',
-	'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
-	'OCA\\WorkflowEngine\\Check\\UserGroupMembership'
-]
-
-/**
- * TODO: move to separate apps
- * TODO: fetch from initial state api
- **/
-const EVENT_FILE_ACCESS = 'EVENT_FILE_ACCESS'
-const EVENT_FILE_CHANGED = 'EVENT_FILE_CHANGED'
-const EVENT_FILE_TAGGED = 'EVENT_FILE_TAGGED'
-
-eventService.registerEvent({
-	id: EVENT_FILE_ACCESS,
-	name: 'File is accessed',
-	icon: 'icon-desktop',
-	checks: ALL_CHECKS,
-})
-
-eventService.registerEvent({
-	id: EVENT_FILE_CHANGED,
-	name: 'File was updated',
-	icon: 'icon-folder',
-	checks: ALL_CHECKS,
-})
-
-
-eventService.registerEvent({
-	id: EVENT_FILE_TAGGED,
-	name: 'File was tagged',
-	icon: 'icon-tag',
-	checks: ALL_CHECKS,
-})
 
 operationService.registerOperation({
 	class: 'OCA\\FilesAccessControl\\Operation',
 	title: 'Block access',
-	description: 'todo',
+	description: 'Deny access to files when they are accessed',
 	icon: 'icon-block',
 	color: 'var(--color-error)',
+	entites: [
+		'WorkflowEngine_Entity_File'
+	],
 	events: [
-		EVENT_FILE_ACCESS
+		// TODO: this is probably handled differently since there is no regular event for files access control
+		'WorkflowEngine_Entity_File::postTouch'
 	],
 	operation: 'deny'
 })
 
+operationService.registerOperation({
+	class: 'OCA\\TestExample\\Operation1',
+	title: 'Rename file',
+	description: '🚧 For UI mocking only',
+	icon: 'icon-address-white',
+	color: 'var(--color-success)',
+	entites: [],
+	events: [],
+	operation: 'deny'
+})
+operationService.registerOperation({
+	class: 'OCA\\TestExample\\Operation2',
+	title: 'Notify me',
+	description: '🚧 For UI mocking only',
+	icon: 'icon-comment-white',
+	color: 'var(--color-warning)',
+	entites: [],
+	events: [],
+	operation: 'deny'
+})
+operationService.registerOperation({
+	class: 'OCA\\TestExample\\Operation3',
+	title: 'Call a web hook',
+	description: '🚧 For UI mocking only',
+	icon: 'icon-category-integration icon-invert',
+	color: 'var(--color-primary)',
+	entites: [],
+	events: [],
+	operation: 'deny'
+})
+
 operationService.registerOperation({
 	class: 'OCA\\FilesAutomatedTagging\\Operation',
 	title: 'Tag a file',
-	description: 'todo',
-	icon: 'icon-tag',
+	description: 'Assign a tag to a file',
+	icon: 'icon-tag-white',
 	events: [
-		EVENT_FILE_CHANGED,
-		EVENT_FILE_TAGGED
+		'WorkflowEngine_Entity_File::postWrite',
+		//'WorkflowEngine_Entity_File::postTagged',
 	],
 	options: Tag
 
@@ -114,28 +121,23 @@ operationService.registerOperation({
 operationService.registerOperation({
 	class: 'OCA\\WorkflowPDFConverter\\Operation',
 	title: 'Convert to PDF',
-	description: 'todo',
+	description: 'Generate a PDF file',
 	color: '#dc5047',
 	icon: 'icon-convert-pdf',
 	events: [
-		EVENT_FILE_CHANGED,
+		'WorkflowEngine_Entity_File::postWrite',
 		//EVENT_FILE_TAGGED
 	],
 	options: ConvertToPdf
 })
 
+console.debug('[InitialState] Entities', Entities)
+console.debug('[WorkflowEngine] Checks', Checks)
+console.debug('[WorkflowEngine] Operations', operationService.operations)
 
-const legacyChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => {
-	if (plugin.component) {
-		return {...plugin.getCheck(), component: plugin.component}
-	}
-	return plugin.getCheck()
-}).reduce((obj, item) => {
-	obj[item.class] = item
-	return obj
-}, {})
 
 export {
-	eventService,
+	Entities,
+	Checks,
 	operationService
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3


From aa00f401b39c2b63cba7e5e8f6cdec8528466069 Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Wed, 28 Aug 2019 18:27:37 +0200
Subject: Adjust to new backend URLs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/components/Check.vue     |  2 +-
 apps/workflowengine/src/components/Event.vue     | 16 ++++++----
 apps/workflowengine/src/components/Operation.vue |  6 ++++
 apps/workflowengine/src/components/Rule.vue      | 38 ++++++++++++++----------
 apps/workflowengine/src/components/Workflow.vue  | 33 ++++++++++++--------
 apps/workflowengine/src/services/Operation.js    |  4 +--
 6 files changed, 62 insertions(+), 37 deletions(-)

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue
index 8cfe5a89020..86005dae268 100644
--- a/apps/workflowengine/src/components/Check.vue
+++ b/apps/workflowengine/src/components/Check.vue
@@ -48,7 +48,7 @@
 			this.currentOption = Checks[this.check.class]
 			this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
 			this.$nextTick(() => {
-				this.$refs.checkSelector.$el.focus()
+				//this.$refs.checkSelector.$el.focus()
 			})
 		},
 		computed: {
diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue
index caf5c7631bd..7b39ea571fb 100644
--- a/apps/workflowengine/src/components/Event.vue
+++ b/apps/workflowengine/src/components/Event.vue
@@ -28,12 +28,15 @@
 				required: true
 			}
 		},
+		mounted() {
+			this.updateEvent(this.currentEvent)
+		},
 		computed: {
 			currentEvent() {
-				if (typeof this.rule.event === 'undefined') {
+				if (!this.rule.event) {
 					return this.allEvents.length > 0 ? this.allEvents[0] : null
 				}
-				return this.allEvents.find(event => event.id === this.rule.event)
+				return this.allEvents.find(event => event.entity === this.rule.entity && event.event === this.rule.event)
 			},
 			allEvents() {
 				return this.operation.events.map((entityEventName) => {
@@ -45,7 +48,7 @@
 					return {
 						entity: entityId,
 						id: entityEventName,
-						event: eventName,
+						events: eventName,
 						name: Event.displayName,
 						icon: Entity.icon,
 						checks: Entity.checks,
@@ -58,8 +61,11 @@
 		},
 		methods: {
 			updateEvent(event) {
-				this.$set(this.rule, 'event', event.id)
-				this.$emit('update', this.rule)
+				if (this.rule.entity !== event.entity || this.rule.events !== '["' + event.event + '"]') {
+					this.$set(this.rule, 'entity', event.entity)
+					this.$set(this.rule, 'event', event.event)
+					this.$emit('update', this.rule)
+				}
 			}
 		}
 	}
diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
index 3e5de3198ca..30f26eb6ec4 100644
--- a/apps/workflowengine/src/components/Operation.vue
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -71,6 +71,12 @@
 		}
 	}
 
+	.actions__item:not(.colored) {
+		.icon {
+			filter: invert(1);
+		}
+	}
+
 	/* TODO: those should be provided by the backend, remove once ready */
 	.icon-block {
 		background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' height='32' width='32' version='1.1' viewBox='0 0 32 32'%3E%3Cpath fill='%23fff' d='m10.203 2-8.203 8.203v11.594l8.203 8.203h11.594l8.203-8.203v-11.594l-8.203-8.203h-11.594zm11.097 5.3092 3.345 3.3448-5.346 5.346 5.346 5.346-3.299 3.299-5.346-5.346-5.346 5.346-3.2992-3.299 5.3462-5.346-5.3462-5.346 3.2992-3.2992 5.346 5.3462 5.3-5.3918z'/%3E%3C/svg%3E");
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
index 59d66e43091..d1f5a088f51 100644
--- a/apps/workflowengine/src/components/Rule.vue
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -1,5 +1,5 @@
 <template>
-	<div class="section rule">
+	<div class="section rule" :style="{ borderLeftColor: operation.color }">
 		<!-- TODO: icon-confirm -->
 		<div class="trigger icon-confirm">
 			<p>
@@ -17,11 +17,11 @@
 		</div>
 		<div class="action">
 			<div class="buttons">
-				<button class="status-button icon" :class="ruleStatus.class" v-tooltip="ruleStatus.tooltip" @click="saveRule">{{ ruleStatus.title }}</button>
 				<Actions>
-					<ActionButton v-if="rule.id === -1" icon="icon-close" @click="$emit('cancel')">Cancel</ActionButton>
-					<ActionButton v-else icon="icon-delete" @click="deleteRule">Delete</ActionButton>
+					<ActionButton v-if="rule.id === -1" icon="icon-close" @click="$emit('cancel')">Cancel rule creation</ActionButton>
+					<ActionButton v-else icon="icon-close" @click="deleteRule">Remove rule</ActionButton>
 				</Actions>
+				<button class="status-button icon" :class="ruleStatus.class" v-tooltip="ruleStatus.tooltip" @click="saveRule">{{ ruleStatus.title }}</button>
 			</div>
 			<Operation :icon="operation.icon" :title="operation.title" :description="operation.description">
 				<component v-if="operation.options" :is="operation.options" v-model="operation.operation" @input="updateOperation" />
@@ -38,6 +38,7 @@
 	import { operationService } from '../services/Operation'
 	import axios from 'nextcloud-axios'
 	import confirmPassword from 'nextcloud-password-confirmation'
+	import {getApiUrl} from '../helpers/api';
 
 	export default {
 		name: 'Rule',
@@ -69,7 +70,7 @@
 			},
 			ruleStatus() {
 				if (this.error) {
-					return { title: 'Invalid', class: 'icon-close-white invalid', tooltip: { placement: 'bottom', show: true, content: escapeHTML(this.error.data) } }
+					return { title: 'Invalid', class: 'icon-close-white invalid', tooltip: { placement: 'bottom', show: true, content: escapeHTML(this.error) } }
 				}
 				if (!this.dirty || this.checking) {
 					return { title: 'Active', class: 'icon icon-checkmark' }
@@ -93,12 +94,13 @@
 					this.dirty = true
 				}
 				try {
-					let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule)
+					// TODO: add new verify endpoint
+					//let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule)
 					this.error = null
 					this.checking = false
 				} catch (e) {
-					console.error('Failed to update operation')
-					this.error = e.response
+					console.error('Failed to update operation', e)
+					this.error = e.response.ocs.meta.message
 					this.checking = false
 				}
 			},
@@ -107,23 +109,24 @@
 					await confirmPassword()
 					let result
 					if (this.rule.id === -1) {
-						result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations`), this.rule)
-						this.rule.id = result.id
+						result = await axios.post(getApiUrl(''), {...this.rule, events: [this.rule.event]})
+						this.rule.id = result.data.ocs.data.id
 					} else {
-						result = await axios.put(OC.generateUrl(`/apps/workflowengine/operations/${this.rule.id}`), this.rule)
+						result = await axios.put(getApiUrl(`/${this.rule.id}`), {...this.rule, events: [this.rule.event]})
 					}
-					this.$emit('update', result.data)
+					this.$emit('update', this.rule)
 					this.dirty = false
 					this.error = null
 				} catch (e) {
-					console.error('Failed to update operation')
-					this.error = e.response
+					console.log(e.response.data.ocs.meta.message)
+					console.error('Failed to update operation', e)
+					this.error = e.response.data.ocs.meta.message
 				}
 			},
 			async deleteRule() {
 				try {
 					await confirmPassword()
-					await axios.delete(OC.generateUrl(`/apps/workflowengine/operations/${this.rule.id}`))
+					await axios.delete(getApiUrl(`/${this.rule.id}`))
 					this.$emit('delete')
 				} catch (e) {
 					console.error('Failed to delete operation')
@@ -164,17 +167,20 @@
 	.rule {
 		display: flex;
 		flex-wrap: wrap;
+		border-left: 5px solid #fff;
 
 		.trigger, .action {
 			flex-grow: 1;
 			min-height: 100px;
-			flex-basis: 50%;
+			max-width: 700px;
 		}
 		.action {
 			position: relative;
 			.buttons {
 				position: absolute;
 				right: 0;
+				display: flex;
+				z-index: 1;
 			}
 		}
 		.icon-confirm {
diff --git a/apps/workflowengine/src/components/Workflow.vue b/apps/workflowengine/src/components/Workflow.vue
index 918236ead84..0525722666e 100644
--- a/apps/workflowengine/src/components/Workflow.vue
+++ b/apps/workflowengine/src/components/Workflow.vue
@@ -1,14 +1,15 @@
 <template>
-	<div>
+	<div id="workflowengine">
 		<div class="section">
 			<h2>{{ t('workflowengine', 'Workflows') }}</h2>
 
-			<div class="actions" v-if="!hasUnsavedRule">
+			<transition-group name="slide" tag="div" class="actions">
 				<Operation v-for="operation in getMainOperations" :key="operation.class"
 						   :icon="operation.icon" :title="operation.title" :description="operation.description" :color="operation.color"
 						   @click.native="createNewRule(operation)"></Operation>
-			</div>
-			<div class="actions__more" v-if="!hasUnsavedRule && hasMoreOperations">
+			</transition-group>
+
+			<div class="actions__more" v-if="hasMoreOperations">
 				<button class="icon" :class="showMoreOperations ? 'icon-triangle-n' : 'icon-triangle-s'"
 						@click="showMoreOperations=!showMoreOperations">
 					{{ showMoreOperations ? t('workflowengine', 'Show less') : t('workflowengine', 'Show more') }}
@@ -16,7 +17,7 @@
 			</div>
 		</div>
 
-		<transition-group name="slide">
+		<transition-group name="slide" v-if="rules.length > 0">
 			<Rule v-for="rule in rules" :key="rule.id" :rule="rule" @delete="removeRule(rule)" @cancel="removeRule(rule)" @update="updateRule"></Rule>
 		</transition-group>
 
@@ -28,6 +29,7 @@
 	import Operation from './Operation'
 	import { operationService } from '../services/Operation'
 	import axios from 'nextcloud-axios'
+	import { getApiUrl } from '../helpers/api';
 
 	const ACTION_LIMIT = 3
 
@@ -38,11 +40,12 @@
 			Rule
 		},
 		async mounted() {
-			const { data } = await axios.get(OC.generateUrl('/apps/workflowengine/operations'))
-			this.rules = data
+			const { data } = await axios.get(getApiUrl(''))
+			this.rules = Object.values(data.ocs.data).flat()
 		},
 		data() {
 			return {
+				scope: OCP.InitialState.loadState('workflowengine', 'scope'),
 				operations: operationService,
 				showMoreOperations: false,
 				rules: []
@@ -61,9 +64,6 @@
 			getMoreOperations() {
 				return Object.values(this.operations.getAll()).slice(ACTION_LIMIT)
 
-			},
-			hasUnsavedRule() {
-				return !!this.rules.find((rule) => rule.id === -1)
 			}
 		},
 		methods: {
@@ -82,6 +82,8 @@
 				this.rules.unshift({
 					id: -1,
 					class: operation.class,
+					entity: undefined,
+					event: undefined,
 					name: '', // unused in the new ui, there for legacy reasons
 					checks: [],
 					operation: operation.operation || ''
@@ -92,16 +94,19 @@
 </script>
 
 <style scoped lang="scss">
+	#workflowengine {
+		border-bottom: 1px solid var(--color-border);
+	}
 	.section {
 		max-width: 100vw;
 	}
 	.actions {
 		display: flex;
 		flex-wrap: wrap;
+		max-width: 900px;
 		.actions__item {
-			max-width: 300px;
-			min-width: 200px;
-			flex-basis: 200px;
+			max-width: 290px;
+			flex-basis: 250px;
 		}
 	}
 
@@ -145,5 +150,7 @@
 	.slide-enter, .slide-leave-to {
 		overflow: hidden;
 		max-height: 0;
+		padding-top: 0;
+		padding-bottom: 0;
 	}
 </style>
diff --git a/apps/workflowengine/src/services/Operation.js b/apps/workflowengine/src/services/Operation.js
index a24d97ef226..b2257b5545a 100644
--- a/apps/workflowengine/src/services/Operation.js
+++ b/apps/workflowengine/src/services/Operation.js
@@ -65,11 +65,11 @@ operationService.registerOperation({
 	icon: 'icon-block',
 	color: 'var(--color-error)',
 	entites: [
-		'WorkflowEngine_Entity_File'
+		'OCA\\WorkflowEngine\\Entity\\File'
 	],
 	events: [
 		// TODO: this is probably handled differently since there is no regular event for files access control
-		'WorkflowEngine_Entity_File::postTouch'
+		'OCA\\WorkflowEngine\\Entity\\File::postTouch'
 	],
 	operation: 'deny'
 })
-- 
cgit v1.2.3


From bd281daa47152dc09c0f6c2bbc9e5a308d1748bd Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Thu, 29 Aug 2019 16:50:33 +0200
Subject: Move to vuex store
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/admin.js                   | 385 ---------------------
 apps/workflowengine/src/components/Check.vue       | 134 ++++---
 apps/workflowengine/src/components/Event.vue       | 105 +++---
 apps/workflowengine/src/components/Operation.vue   |  39 +--
 .../src/components/Operations/ConvertToPdf.vue     |  59 ++--
 .../src/components/Operations/Tag.vue              |  28 +-
 apps/workflowengine/src/components/Rule.vue        | 216 ++++++------
 .../src/components/Values/FileMimeType.vue         |  56 +--
 .../src/components/Values/SizeValue.vue            |  37 +-
 apps/workflowengine/src/components/Workflow.vue    | 116 +++----
 apps/workflowengine/src/filemimetypeplugin.js      |  77 -----
 apps/workflowengine/src/filenameplugin.js          |  78 -----
 apps/workflowengine/src/filesizeplugin.js          |  59 ----
 apps/workflowengine/src/filesystemtagsplugin.js    |  78 -----
 .../workflowengine/src/hbs_helpers/getOperators.js |   7 -
 apps/workflowengine/src/hbs_helpers/selectItem.js  |   7 -
 .../src/legacy/filemimetypeplugin.js               |  77 +++++
 apps/workflowengine/src/legacy/filenameplugin.js   |  78 +++++
 apps/workflowengine/src/legacy/filesizeplugin.js   |  59 ++++
 .../src/legacy/filesystemtagsplugin.js             |  78 +++++
 .../src/legacy/requestremoteaddressplugin.js       |  83 +++++
 .../workflowengine/src/legacy/requesttimeplugin.js | 196 +++++++++++
 apps/workflowengine/src/legacy/requesturlplugin.js | 116 +++++++
 .../src/legacy/requestuseragentplugin.js           | 119 +++++++
 .../src/legacy/usergroupmembershipplugin.js        |  75 ++++
 .../src/requestremoteaddressplugin.js              |  83 -----
 apps/workflowengine/src/requesttimeplugin.js       | 196 -----------
 apps/workflowengine/src/requesturlplugin.js        | 117 -------
 apps/workflowengine/src/requestuseragentplugin.js  | 119 -------
 apps/workflowengine/src/services/Operation.js      | 134 ++-----
 .../src/templates/operation.handlebars             |  45 ---
 .../src/templates/operations.handlebars            |   2 -
 .../src/usergroupmembershipplugin.js               |  75 ----
 apps/workflowengine/src/workflowengine.js          |  34 +-
 34 files changed, 1309 insertions(+), 1858 deletions(-)
 delete mode 100644 apps/workflowengine/src/admin.js
 delete mode 100644 apps/workflowengine/src/filemimetypeplugin.js
 delete mode 100644 apps/workflowengine/src/filenameplugin.js
 delete mode 100644 apps/workflowengine/src/filesizeplugin.js
 delete mode 100644 apps/workflowengine/src/filesystemtagsplugin.js
 delete mode 100644 apps/workflowengine/src/hbs_helpers/getOperators.js
 delete mode 100644 apps/workflowengine/src/hbs_helpers/selectItem.js
 create mode 100644 apps/workflowengine/src/legacy/filemimetypeplugin.js
 create mode 100644 apps/workflowengine/src/legacy/filenameplugin.js
 create mode 100644 apps/workflowengine/src/legacy/filesizeplugin.js
 create mode 100644 apps/workflowengine/src/legacy/filesystemtagsplugin.js
 create mode 100644 apps/workflowengine/src/legacy/requestremoteaddressplugin.js
 create mode 100644 apps/workflowengine/src/legacy/requesttimeplugin.js
 create mode 100644 apps/workflowengine/src/legacy/requesturlplugin.js
 create mode 100644 apps/workflowengine/src/legacy/requestuseragentplugin.js
 create mode 100644 apps/workflowengine/src/legacy/usergroupmembershipplugin.js
 delete mode 100644 apps/workflowengine/src/requestremoteaddressplugin.js
 delete mode 100644 apps/workflowengine/src/requesttimeplugin.js
 delete mode 100644 apps/workflowengine/src/requesturlplugin.js
 delete mode 100644 apps/workflowengine/src/requestuseragentplugin.js
 delete mode 100644 apps/workflowengine/src/templates/operation.handlebars
 delete mode 100644 apps/workflowengine/src/templates/operations.handlebars
 delete mode 100644 apps/workflowengine/src/usergroupmembershipplugin.js

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/admin.js b/apps/workflowengine/src/admin.js
deleted file mode 100644
index fb2af941436..00000000000
--- a/apps/workflowengine/src/admin.js
+++ /dev/null
@@ -1,385 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-import OperationTemplate from './templates/operation.handlebars';
-import OperationsTemplate from './templates/operations.handlebars';
-
-(function() {
-	OCA.WorkflowEngine = _.extend(OCA.WorkflowEngine || {}, {
-		availablePlugins: [],
-		availableChecks: [],
-
-		getCheckByClass: function(className) {
-			var length = OCA.WorkflowEngine.availableChecks.length;
-			for (var i = 0; i < length; i++) {
-				if (OCA.WorkflowEngine.availableChecks[i]['class'] === className) {
-					return OCA.WorkflowEngine.availableChecks[i];
-				}
-			}
-			return undefined;
-		}
-	});
-
-	/**
-	 * 888b     d888               888          888
-	 * 8888b   d8888               888          888
-	 * 88888b.d88888               888          888
-	 * 888Y88888P888  .d88b.   .d88888  .d88b.  888 .d8888b
-	 * 888 Y888P 888 d88""88b d88" 888 d8P  Y8b 888 88K
-	 * 888  Y8P  888 888  888 888  888 88888888 888 "Y8888b.
-	 * 888   "   888 Y88..88P Y88b 888 Y8b.     888      X88
-	 * 888       888  "Y88P"   "Y88888  "Y8888  888  88888P'
-	 */
-
-	/**
-	 * @class OCA.WorkflowEngine.Operation
-	 */
-	OCA.WorkflowEngine.Operation =
-		OC.Backbone.Model.extend({
-			defaults: {
-				'class': 'OCA\\WorkflowEngine\\Operation',
-				'name': '',
-				'checks': [],
-				'operation': ''
-			}
-		});
-
-	/**
-	 *  .d8888b.           888 888                   888    d8b
-	 * d88P  Y88b          888 888                   888    Y8P
-	 * 888    888          888 888                   888
-	 * 888         .d88b.  888 888  .d88b.   .d8888b 888888 888  .d88b.  88888b.  .d8888b
-	 * 888        d88""88b 888 888 d8P  Y8b d88P"    888    888 d88""88b 888 "88b 88K
-	 * 888    888 888  888 888 888 88888888 888      888    888 888  888 888  888 "Y8888b.
-	 * Y88b  d88P Y88..88P 888 888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888      X88
-	 *  "Y8888P"   "Y88P"  888 888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888  88888P'
-	 */
-
-	/**
-	 * @class OCA.WorkflowEngine.OperationsCollection
-	 *
-	 * collection for all configurated operations
-	 */
-	OCA.WorkflowEngine.OperationsCollection =
-		OC.Backbone.Collection.extend({
-			model: OCA.WorkflowEngine.Operation,
-			url: OC.generateUrl('apps/workflowengine/operations')
-		});
-
-	/**
-	 * 888     888 d8b
-	 * 888     888 Y8P
-	 * 888     888
-	 * Y88b   d88P 888  .d88b.  888  888  888 .d8888b
-	 *  Y88b d88P  888 d8P  Y8b 888  888  888 88K
-	 *   Y88o88P   888 88888888 888  888  888 "Y8888b.
-	 *    Y888P    888 Y8b.     Y88b 888 d88P      X88
-	 *     Y8P     888  "Y8888   "Y8888888P"   88888P'
-	 */
-
-	/**
-	 * @class OCA.WorkflowEngine.OperationView
-	 *
-	 * this creates the view for a single operation
-	 */
-	OCA.WorkflowEngine.OperationView =
-		OC.Backbone.View.extend({
-			templateId: '#operation-template',
-			events: {
-				'change .check-class': 'checkChanged',
-				'change .check-operator': 'checkChanged',
-				'change .check-value': 'checkChanged',
-				'change .operation-name': 'operationChanged',
-				'change .operation-operation': 'operationChanged',
-				'click .button-reset': 'reset',
-				'click .button-save': 'save',
-				'click .button-add': 'add',
-				'click .button-delete': 'delete',
-				'click .button-delete-check': 'deleteCheck'
-			},
-			originalModel: null,
-			hasChanged: false,
-			message: '',
-			errorMessage: '',
-			saving: false,
-			groups: [],
-			template: function(vars) {
-				return OperationTemplate(_.extend(
-					{
-						shortRuleDescTXT: t('workflowengine', 'Short rule description'),
-						addRuleTXT: t('workflowengine', 'Add rule'),
-						resetTXT: t('workflowengine', 'Reset'),
-						saveTXT: t('workflowengine', 'Save'),
-						savingTXT: t('workflowengine', 'Saving…')
-					},
-					vars
-				));
-			},
-			initialize: function() {
-				// this creates a new copy of the object to definitely have a new reference and being able to reset the model
-				this.originalModel = JSON.parse(JSON.stringify(this.model));
-				this.model.on('change', function() {
-					console.log('model changed');
-					this.hasChanged = true;
-					this.render();
-				}, this);
-
-				if (this.model.get('id') === undefined) {
-					this.hasChanged = true;
-				}
-				var self = this;
-				$.ajax({
-					url: OC.linkToOCS('cloud/groups', 2) + 'details',
-					dataType: 'json',
-					quietMillis: 100,
-				}).success(function(data) {
-					if (data.ocs.data.groups && data.ocs.data.groups.length > 0) {
-
-						data.ocs.data.groups.forEach(function(group) {
-							self.groups.push({ id: group.id, displayname: group.displayname });
-						});
-						self.render();
-
-					} else {
-						OC.Notification.error(t('workflowengine', 'Group list is empty'), { type: 'error' });
-						console.log(data);
-					}
-				}).error(function(data) {
-					OC.Notification.error(t('workflowengine', 'Unable to retrieve the group list'), { type: 'error' });
-					console.log(data);
-				});
-			},
-			delete: function() {
-				if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-					OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.delete, this));
-					return;
-				}
-
-				this.model.destroy();
-				this.remove();
-			},
-			reset: function() {
-				this.hasChanged = false;
-				// silent is need to not trigger the change event which resets the hasChanged attribute
-				this.model.set(this.originalModel, { silent: true });
-				this.render();
-			},
-			save: function() {
-				if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-					OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.save, this));
-					return;
-				}
-
-				var success = function(model, response, options) {
-					this.saving = false;
-					this.originalModel = JSON.parse(JSON.stringify(this.model));
-
-					this.message = t('workflowengine', 'Saved');
-					this.errorMessage = '';
-					this.render();
-				};
-				var error = function(model, response, options) {
-					this.saving = false;
-					this.hasChanged = true;
-
-					this.message = t('workflowengine', 'Saving failed:');
-					this.errorMessage = response.responseText;
-					this.render();
-				};
-				this.hasChanged = false;
-				this.saving = true;
-				this.render();
-				this.model.save(null, { success: success, error: error, context: this });
-			},
-			add: function() {
-				var checks = _.clone(this.model.get('checks')),
-					classname = OCA.WorkflowEngine.availableChecks[0]['class'],
-					operators = OCA.WorkflowEngine.availableChecks[0]['operators'];
-
-				checks.push({
-					'class': classname,
-					'operator': operators[0]['operator'],
-					'value': ''
-				});
-				this.model.set({ 'checks': checks });
-			},
-			checkChanged: function(event) {
-				var value = event.target.value,
-					id = $(event.target.parentElement).data('id'),
-					// this creates a new copy of the object to definitely have a new reference
-					checks = JSON.parse(JSON.stringify(this.model.get('checks'))),
-					key = null;
-
-				for (var i = 0; i < event.target.classList.length; i++) {
-					var className = event.target.classList[i];
-					if (className.substr(0, 'check-'.length) === 'check-') {
-						key = className.substr('check-'.length);
-						break;
-					}
-				}
-
-				if (key === null) {
-					console.warn('checkChanged triggered but element doesn\'t have any "check-" class');
-					return;
-				}
-
-				if (!_.has(checks[id], key)) {
-					console.warn('key "' + key + '" is not available in check', check);
-					return;
-				}
-
-				checks[id][key] = value;
-				// if the class is changed most likely also the operators have changed
-				// with this we set the operator to the first possible operator
-				if (key === 'class') {
-					var check = OCA.WorkflowEngine.getCheckByClass(value);
-					if (!_.isUndefined(check)) {
-						checks[id]['operator'] = check['operators'][0]['operator'];
-						checks[id]['value'] = '';
-					}
-				}
-				// model change will trigger render
-				this.model.set({ 'checks': checks });
-			},
-			deleteCheck: function(event) {
-				console.log(arguments);
-				var id = $(event.target.parentElement).data('id'),
-					checks = JSON.parse(JSON.stringify(this.model.get('checks')));
-
-				// splice removes 1 element at index `id`
-				checks.splice(id, 1);
-				// model change will trigger render
-				this.model.set({ 'checks': checks });
-			},
-			operationChanged: function(event) {
-				var value = event.target.value,
-					key = null;
-
-				for (var i = 0; i < event.target.classList.length; i++) {
-					var className = event.target.classList[i];
-					if (className.substr(0, 'operation-'.length) === 'operation-') {
-						key = className.substr('operation-'.length);
-						break;
-					}
-				}
-
-				if (key === null) {
-					console.warn('operationChanged triggered but element doesn\'t have any "operation-" class');
-					return;
-				}
-
-				if (key !== 'name' && key !== 'operation') {
-					console.warn('key "' + key + '" is no valid attribute');
-					return;
-				}
-
-				// model change will trigger render
-				this.model.set(key, value);
-			},
-			render: function() {
-				this.$el.html(this.template({
-					operation: this.model.toJSON(),
-					classes: OCA.WorkflowEngine.availableChecks,
-					hasChanged: this.hasChanged,
-					message: this.message,
-					errorMessage: this.errorMessage,
-					saving: this.saving
-				}));
-
-				var checks = this.model.get('checks');
-				_.each(this.$el.find('.check'), function(element) {
-					var $element = $(element),
-						id = $element.data('id'),
-						check = checks[id],
-						valueElement = $element.find('.check-value').first();
-					var self = this;
-
-					_.each(OCA.WorkflowEngine.availablePlugins, function(plugin) {
-						if (_.isFunction(plugin.render)) {
-							plugin.render(valueElement, check, self.groups);
-						}
-					});
-				}, this);
-
-				if (this.message !== '') {
-					// hide success messages after some time
-					_.delay(function(elements) {
-						$(elements).css('opacity', 0);
-					}, 7000, this.$el.find('.msg.success'));
-					this.message = '';
-				}
-
-				return this.$el;
-			}
-		});
-
-	/**
-	 * @class OCA.WorkflowEngine.OperationsView
-	 *
-	 * this creates the view for configured operations
-	 */
-	OCA.WorkflowEngine.OperationsView =
-		OC.Backbone.View.extend({
-			templateId: '#operations-template',
-			collection: null,
-			$el: null,
-			events: {
-				'click .button-add-operation': 'add'
-			},
-			template: function(vars) {
-				return OperationsTemplate(_.extend(
-					{
-						addRuleGroupTXT: t('workflowengine', 'Add rule group')
-					},
-					vars
-				));
-			},
-			initialize: function(classname) {
-				if (!OCA.WorkflowEngine.availablePlugins.length) {
-					OCA.WorkflowEngine.availablePlugins = OC.Plugins.getPlugins('OCA.WorkflowEngine.CheckPlugins');
-					_.each(OCA.WorkflowEngine.availablePlugins, function(plugin) {
-						if (_.isFunction(plugin.getCheck)) {
-							OCA.WorkflowEngine.availableChecks.push(plugin.getCheck(classname));
-						}
-					});
-				}
-
-				this.collection.fetch({
-					data: {
-						'class': classname
-					}
-				});
-				this.collection.once('sync', this.render, this);
-			},
-			add: function() {
-				var operation = this.collection.create();
-				this.renderOperation(operation);
-			},
-			renderOperation: function(subView) {
-				var operationsElement = this.$el.find('.operations');
-				operationsElement.append(subView.$el);
-				subView.render();
-			},
-			render: function() {
-				this.$el.html(this.template());
-				this.collection.each(this.renderOperation, this);
-			}
-		});
-})();
\ No newline at end of file
diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue
index 86005dae268..b2e8e13c29b 100644
--- a/apps/workflowengine/src/components/Check.vue
+++ b/apps/workflowengine/src/components/Check.vue
@@ -1,86 +1,82 @@
 <template>
-	<div class="check" @click="showDelete" v-click-outside="hideDelete">
-		<Multiselect v-model="currentOption" :options="options" label="name"
-					 track-by="class" :allow-empty="false" :placeholder="t('workflowengine', 'Select a filter')"
-					 @input="updateCheck" ref="checkSelector"></Multiselect>
-		<Multiselect v-if="currentOption" v-model="currentOperator" @input="updateCheck"
-					 :options="operators" label="name" track-by="operator"
-					 :allow-empty="false" :placeholder="t('workflowengine', 'Select a comparator')"></Multiselect>
-		<component v-if="currentOperator && currentComponent" :is="currentOption.component()" v-model="check.value" />
-		<input v-else-if="currentOperator" type="text" v-model="check.value" @input="updateCheck" />
+	<div v-click-outside="hideDelete" class="check" @click="showDelete">
+		<Multiselect ref="checkSelector" v-model="currentOption" :options="options"
+			label="name" track-by="class" :allow-empty="false"
+			:placeholder="t('workflowengine', 'Select a filter')" @input="updateCheck" />
+		<Multiselect v-if="currentOption" v-model="currentOperator" :options="operators"
+			label="name" track-by="operator" :allow-empty="false"
+			:placeholder="t('workflowengine', 'Select a comparator')" @input="updateCheck" />
+		<component :is="currentOption.component()" v-if="currentOperator && currentComponent" v-model="check.value" />
+		<input v-else-if="currentOperator" v-model="check.value" type="text"
+			@input="updateCheck">
 		<Actions>
-			<ActionButton icon="icon-delete" v-if="deleteVisible || !currentOption" @click="$emit('remove')" />
+			<ActionButton v-if="deleteVisible || !currentOption" icon="icon-delete" @click="$emit('remove')" />
 		</Actions>
 	</div>
 </template>
 
 <script>
-	import { Checks } from '../services/Operation';
-	import { Multiselect, Actions, ActionButton } from 'nextcloud-vue'
-	import ClickOutside from 'vue-click-outside';
+import { Checks } from '../services/Operation'
+import { Multiselect, Actions, ActionButton } from 'nextcloud-vue'
+import ClickOutside from 'vue-click-outside'
 
-	export default {
-		name: 'Check',
-		components: {
-			ActionButton,
-			Actions,
-			Multiselect
-		},
-		directives: {
-			ClickOutside
-		},
-		props: {
-			check: {
-				type: Object,
-				required: true
-			}
-		},
-		data() {
-			return {
-				deleteVisible: false,
-				currentOption: null,
-				currentOperator: null,
-				options: [],
-			}
+export default {
+	name: 'Check',
+	components: {
+		ActionButton,
+		Actions,
+		Multiselect
+	},
+	directives: {
+		ClickOutside
+	},
+	props: {
+		check: {
+			type: Object,
+			required: true
+		}
+	},
+	data() {
+		return {
+			deleteVisible: false,
+			currentOption: null,
+			currentOperator: null,
+			options: []
+		}
+	},
+	computed: {
+		operators() {
+			if (!this.currentOption) { return [] }
+			return Checks[this.currentOption.class].operators
 		},
-		mounted() {
-			this.options = Object.values(Checks)
-			this.currentOption = Checks[this.check.class]
-			this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
-			this.$nextTick(() => {
-				//this.$refs.checkSelector.$el.focus()
-			})
+		currentComponent() {
+			if (!this.currentOption) { return [] }
+			const currentComponent = Checks[this.currentOption.class].component
+			return currentComponent && currentComponent()
+		}
+	},
+	mounted() {
+		this.options = Object.values(Checks)
+		this.currentOption = Checks[this.check.class]
+		this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
+	},
+	methods: {
+		showDelete() {
+			this.deleteVisible = true
 		},
-		computed: {
-			operators() {
-				if (!this.currentOption)
-					return []
-				return Checks[this.currentOption.class].operators
-			},
-			currentComponent() {
-				if (!this.currentOption)
-					return []
-				let currentComponent = Checks[this.currentOption.class].component
-				return currentComponent && currentComponent()
-			}
+		hideDelete() {
+			this.deleteVisible = false
 		},
-		methods: {
-			showDelete() {
-				this.deleteVisible = true
-			},
-			hideDelete() {
-				this.deleteVisible = false
-			},
-			updateCheck() {
-				if (this.check.class !== this.currentOption.class) {
-					this.currentOperator = this.operators[0]
-				}
-				this.check.class = this.currentOption.class
-				this.check.operator = this.currentOperator.operator
-				this.$emit('update', this.check)
+		updateCheck() {
+			if (this.check.class !== this.currentOption.class) {
+				this.currentOperator = this.operators[0]
 			}
+			this.check.class = this.currentOption.class
+			this.check.operator = this.currentOperator.operator
+			this.$emit('update', this.check)
 		}
 	}
+}
 </script>
 
 <style scoped lang="scss">
diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue
index 7b39ea571fb..d05aeb0a07e 100644
--- a/apps/workflowengine/src/components/Event.vue
+++ b/apps/workflowengine/src/components/Event.vue
@@ -1,74 +1,63 @@
 <template>
 	<div>
-	<Multiselect :value="currentEvent" :options="allEvents" label="name" track-by="id" :allow-empty="false" :disabled="allEvents.length <= 1" @input="updateEvent">
-		<template slot="singleLabel" slot-scope="props">
-			<img class="option__icon" :src="props.option.icon" />
-			<span class="option__title option__title_single">{{ props.option.name }}</span>
-		</template>
-		<template slot="option" slot-scope="props">
-			<img class="option__icon" :src="props.option.icon" />
-			<span class="option__title">{{ props.option.name }}</span>
-		</template>
-	</Multiselect>
+		<div v-if="operation.isComplex && operation.fixedEntity !== ''">
+			<img class="option__icon" :src="entity.icon">
+			<span class="option__title option__title_single">{{ entity.name }}</span>
+		</div>
+		<Multiselect v-else :value="currentEvent" :options="allEvents"
+			label="eventName" track-by="id" :allow-empty="false"
+			:disabled="allEvents.length <= 1" @input="updateEvent">
+			<template slot="singleLabel" slot-scope="props">
+				<img class="option__icon" :src="props.option.entity.icon">
+				<span class="option__title option__title_single">{{ props.option.displayName }}</span>
+			</template>
+			<template slot="option" slot-scope="props">
+				<img class="option__icon" :src="props.option.entity.icon">
+				<span class="option__title">{{ props.option.displayName }}</span>
+			</template>
+		</Multiselect>
 	</div>
 </template>
 
 <script>
-	import { Multiselect } from 'nextcloud-vue'
-	import { Entities, operationService } from '../services/Operation'
+import { Multiselect } from 'nextcloud-vue'
 
-	export default {
-		name: "Event",
-		components: {
-			Multiselect
+export default {
+	name: 'Event',
+	components: {
+		Multiselect
+	},
+	props: {
+		rule: {
+			type: Object,
+			required: true
+		}
+	},
+	computed: {
+		entity() {
+			return this.$store.getters.getEntityForOperation(this.operation)
 		},
-		props: {
-			rule: {
-				type: Object,
-				required: true
-			}
+		operation() {
+			return this.$store.getters.getOperationForRule(this.rule)
 		},
-		mounted() {
-			this.updateEvent(this.currentEvent)
+		allEvents() {
+			return this.$store.getters.getEventsForOperation(this.operation)
 		},
-		computed: {
-			currentEvent() {
-				if (!this.rule.event) {
-					return this.allEvents.length > 0 ? this.allEvents[0] : null
-				}
-				return this.allEvents.find(event => event.entity === this.rule.entity && event.event === this.rule.event)
-			},
-			allEvents() {
-				return this.operation.events.map((entityEventName) => {
-					const parts = entityEventName.split('::')
-					const entityId = parts[0]
-					const eventName = parts[1]
-					const Entity = Entities.find((entity) => entity.id === entityId)
-					const Event = Entity.events.find((event) => event.eventName === eventName)
-					return {
-						entity: entityId,
-						id: entityEventName,
-						events: eventName,
-						name: Event.displayName,
-						icon: Entity.icon,
-						checks: Entity.checks,
-					}
-				})
-			},
-			operation() {
-				return operationService.get(this.rule.class)
-			}
-		},
-		methods: {
-			updateEvent(event) {
-				if (this.rule.entity !== event.entity || this.rule.events !== '["' + event.event + '"]') {
-					this.$set(this.rule, 'entity', event.entity)
-					this.$set(this.rule, 'event', event.event)
-					this.$emit('update', this.rule)
-				}
+		currentEvent() {
+			if (!this.rule.events) {
+				return this.allEvents.length > 0 ? this.allEvents[0] : null
 			}
+			return this.allEvents.find(event => event.entity.id === this.rule.entity && this.rule.events.indexOf(event.eventName) !== -1)
+		}
+	},
+	methods: {
+		updateEvent(event) {
+			this.$set(this.rule, 'entity', event.entity.id)
+			this.$set(this.rule, 'events', [event.eventName])
+			this.$store.dispatch('updateRule', this.rule)
 		}
 	}
+}
 </script>
 
 <style scoped>
diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
index 30f26eb6ec4..1ee7a2e4c9d 100644
--- a/apps/workflowengine/src/components/Operation.vue
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -1,34 +1,26 @@
 <template>
-	<div class="actions__item" :class="{'colored': !!color}" :style="{ backgroundColor: color }">
-		<div class="icon" :class="icon"></div>
-		<h3>{{ title }}</h3>
-		<small>{{ description }}</small>
+	<div class="actions__item" :class="{'colored': colored}" :style="{ backgroundColor: colored ? operation.color : 'transparent' }">
+		<div class="icon" :class="operation.iconClass" :style="{ backgroundImage: operation.iconClass ? '' : `url(${operation.icon})` }" />
+		<h3>{{ operation.name }}</h3>
+		<small>{{ operation.description }}</small>
 		<slot />
 	</div>
 </template>
 
 <script>
-	export default {
-		name: "Operation",
-		props: {
-			icon: {
-				type: String,
-				required: true
-			},
-			title: {
-				type: String,
-				required: true
-			},
-			description: {
-				type: String,
-				required: true
-			},
-			color: {
-				type: String,
-				required: false
-			},
+export default {
+	name: 'Operation',
+	props: {
+		operation: {
+			type: Object,
+			required: true
+		},
+		colored: {
+			type: Boolean,
+			default: true
 		}
 	}
+}
 </script>
 
 <style scoped lang="scss">
@@ -50,6 +42,7 @@
 		background-position: center center;
 		margin-top: 10px;
 		margin-bottom: 20px;
+		background-repeat: no-repeat;
 	}
 	h3, small {
 		padding: 6px;
diff --git a/apps/workflowengine/src/components/Operations/ConvertToPdf.vue b/apps/workflowengine/src/components/Operations/ConvertToPdf.vue
index 62c53a4ee6c..5fa1676e092 100644
--- a/apps/workflowengine/src/components/Operations/ConvertToPdf.vue
+++ b/apps/workflowengine/src/components/Operations/ConvertToPdf.vue
@@ -1,38 +1,39 @@
 <template>
-	<multiselect :options="options" track-by="id" label="text" v-model="value"></multiselect>
+	<Multiselect v-model="value" :options="options" track-by="id"
+		label="text" @input="$emit('input', value.id)" />
 </template>
 
 <script>
-	const pdfConvertOptions = [
-		{
-			id: 'keep;preserve',
-			text: t('workflow_pdf_converter', 'Keep original, preserve existing PDFs'),
-		},
-		{
-			id: 'keep;overwrite',
-			text: t('workflow_pdf_converter', 'Keep original, overwrite existing PDF'),
-		},
-		{
-			id: 'delete;preserve',
-			text: t('workflow_pdf_converter', 'Delete original, preserve existing PDFs'),
-		},
-		{
-			id: 'delete;overwrite',
-			text: t('workflow_pdf_converter', 'Delete original, overwrite existing PDF'),
-		},
-	]
+import { Multiselect } from 'nextcloud-vue'
 
-	import { Multiselect } from 'nextcloud-vue'
-	export default {
-		name: "ConvertToPdf",
-		components: {Multiselect},
-		data() {
-			return {
-				options: pdfConvertOptions,
-				value: pdfConvertOptions[0]
-			}
+const pdfConvertOptions = [
+	{
+		id: 'keep;preserve',
+		text: t('workflow_pdf_converter', 'Keep original, preserve existing PDFs')
+	},
+	{
+		id: 'keep;overwrite',
+		text: t('workflow_pdf_converter', 'Keep original, overwrite existing PDF')
+	},
+	{
+		id: 'delete;preserve',
+		text: t('workflow_pdf_converter', 'Delete original, preserve existing PDFs')
+	},
+	{
+		id: 'delete;overwrite',
+		text: t('workflow_pdf_converter', 'Delete original, overwrite existing PDF')
+	}
+]
+export default {
+	name: 'ConvertToPdf',
+	components: { Multiselect },
+	data() {
+		return {
+			options: pdfConvertOptions,
+			value: pdfConvertOptions[0]
 		}
 	}
+}
 </script>
 
 <style scoped>
@@ -42,4 +43,4 @@
 		margin: auto;
 		text-align: center;
 	}
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/components/Operations/Tag.vue b/apps/workflowengine/src/components/Operations/Tag.vue
index 74e4e4f977b..7a27e2b572d 100644
--- a/apps/workflowengine/src/components/Operations/Tag.vue
+++ b/apps/workflowengine/src/components/Operations/Tag.vue
@@ -1,22 +1,24 @@
 <template>
-	<multiselect :options="options" v-model="value" track-by="id" label="title" :multiple="true" :tagging="true" @input="$emit('input', value.map(item => item.id))"></multiselect>
+	<Multiselect v-model="value" :options="options" track-by="id"
+		label="title" :multiple="true" :tagging="true"
+		@input="$emit('input', value.map(item => item.id))" />
 </template>
 
 <script>
-	// TODO: fetch tags from endpoint
-	const tags = [{id: 3, title: 'foo'}, {id: 4, title: 'bar'}]
+// TODO: fetch tags from endpoint
+import { Multiselect } from 'nextcloud-vue'
 
-	import { Multiselect } from 'nextcloud-vue'
-	export default {
-		name: "Tag",
-		components: {Multiselect},
-		data() {
-			return {
-				options: tags,
-				value: null
-			}
+const tags = [{ id: 3, title: 'foo' }, { id: 4, title: 'bar' }]
+export default {
+	name: 'Tag',
+	components: { Multiselect },
+	data() {
+		return {
+			options: tags,
+			value: null
 		}
 	}
+}
 </script>
 
 <style scoped>
@@ -26,4 +28,4 @@
 		margin: auto;
 		text-align: center;
 	}
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
index d1f5a088f51..8a446dd7ae9 100644
--- a/apps/workflowengine/src/components/Rule.vue
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -4,143 +4,144 @@
 		<div class="trigger icon-confirm">
 			<p>
 				<span>{{ t('workflowengine', 'When') }}</span>
-				<Event :rule="rule" @update="updateRule"></Event>
+				<Event :rule="rule" @update="updateRule" />
 			</p>
 			<p v-for="check in rule.checks">
 				<span>{{ t('workflowengine', 'and') }}</span>
-				<Check :check="check" @update="updateRule" @remove="removeCheck(check)"></Check>
+				<Check :check="check" @update="updateRule" @remove="removeCheck(check)" />
 			</p>
 			<p>
-				<span> </span>
-				<input v-if="lastCheckComplete" type="button" class="check--add" @click="rule.checks.push({class: null, operator: null, value: null})" value="Add a new filter"/>
+				<span />
+				<input v-if="lastCheckComplete" type="button" class="check--add"
+					value="Add a new filter" @click="rule.checks.push({class: null, operator: null, value: null})">
 			</p>
 		</div>
 		<div class="action">
 			<div class="buttons">
 				<Actions>
-					<ActionButton v-if="rule.id === -1" icon="icon-close" @click="$emit('cancel')">Cancel rule creation</ActionButton>
-					<ActionButton v-else icon="icon-close" @click="deleteRule">Remove rule</ActionButton>
+					<ActionButton v-if="rule.id < -1" icon="icon-close" @click="cancelRule">
+						Cancel rule creation
+					</ActionButton>
+					<ActionButton v-else icon="icon-close" @click="deleteRule">
+						Remove rule
+					</ActionButton>
 				</Actions>
-				<button class="status-button icon" :class="ruleStatus.class" v-tooltip="ruleStatus.tooltip" @click="saveRule">{{ ruleStatus.title }}</button>
+				<button v-tooltip="ruleStatus.tooltip" class="status-button icon" :class="ruleStatus.class"
+					@click="saveRule">
+					{{ ruleStatus.title }}
+				</button>
 			</div>
-			<Operation :icon="operation.icon" :title="operation.title" :description="operation.description">
-				<component v-if="operation.options" :is="operation.options" v-model="operation.operation" @input="updateOperation" />
+			<Operation :operation="operation" :colored="false">
+				<component :is="operation.options" v-if="operation.options" v-model="rule.operation"
+					@input="updateOperation" />
 			</Operation>
 		</div>
 	</div>
 </template>
 
 <script>
-	import { Actions, ActionButton, Tooltip } from 'nextcloud-vue'
-	import Event from './Event'
-	import Check from './Check'
-	import Operation from './Operation'
-	import { operationService } from '../services/Operation'
-	import axios from 'nextcloud-axios'
-	import confirmPassword from 'nextcloud-password-confirmation'
-	import {getApiUrl} from '../helpers/api';
+import { Actions, ActionButton, Tooltip } from 'nextcloud-vue'
+import Event from './Event'
+import Check from './Check'
+import Operation from './Operation'
+
+export default {
+	name: 'Rule',
+	components: {
+		Operation, Check, Event, Actions, ActionButton
+	},
+	directives: {
+		Tooltip
+	},
+	props: {
+		rule: {
+			type: Object,
+			required: true
+		}
+	},
+	data() {
+		return {
+			editing: false,
+			checks: [],
+			error: null,
+			dirty: this.rule.id < 0,
+			checking: false
+		}
+	},
+	computed: {
+		operation() {
+			return this.$store.getters.getOperationForRule(this.rule)
+		},
+		ruleStatus() {
+			if (this.error) {
+				return {
+					title: t('workflowengine', 'The configuration is invalid'),
+					class: 'icon-close-white invalid',
+					tooltip: { placement: 'bottom', show: true, content: this.error }
+				}
+			}
+			if (!this.dirty || this.checking) {
+				return { title: 'Active', class: 'icon icon-checkmark' }
+			}
+			return { title: 'Save', class: 'icon-confirm-white primary' }
 
-	export default {
-		name: 'Rule',
-		components: {
-			Operation, Check, Event, Actions, ActionButton
 		},
-		directives: {
-			Tooltip
+		lastCheckComplete() {
+			const lastCheck = this.rule.checks[this.rule.checks.length - 1]
+			return typeof lastCheck === 'undefined' || lastCheck.class !== null
+		}
+	},
+	methods: {
+		async updateOperation(operation) {
+			this.$set(this.rule, 'operation', operation)
+			await this.updateRule()
 		},
-		props: {
-			rule: {
-				type: Object,
-				required: true,
+		async updateRule() {
+			this.checking = true
+			if (!this.dirty) {
+				this.dirty = true
+			}
+			try {
+				// TODO: add new verify endpoint
+				// let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule)
+				this.error = null
+				this.checking = false
+				this.$store.dispatch('updateRule', this.rule)
+			} catch (e) {
+				console.error('Failed to update operation', e)
+				this.error = e.response.ocs.meta.message
+				this.checking = false
 			}
 		},
-		data () {
-			return {
-				editing: false,
-				operationService,
-				checks: [],
-				error: null,
-				dirty: this.rule.id === -1,
-				checking: false
+		async saveRule() {
+			try {
+				await this.$store.dispatch('pushUpdateRule', this.rule)
+				this.dirty = false
+				this.error = null
+			} catch (e) {
+				console.error('Failed to save operation')
+				this.error = e.response.data.ocs.meta.message
 			}
 		},
-		computed: {
-			operation() {
-				return this.operationService.get(this.rule.class)
-			},
-			ruleStatus() {
-				if (this.error) {
-					return { title: 'Invalid', class: 'icon-close-white invalid', tooltip: { placement: 'bottom', show: true, content: escapeHTML(this.error) } }
-				}
-				if (!this.dirty || this.checking) {
-					return { title: 'Active', class: 'icon icon-checkmark' }
-				}
-				return { title: 'Save', class: 'icon-confirm-white primary' }
-
-
-			},
-			lastCheckComplete() {
-				const lastCheck = this.rule.checks[this.rule.checks.length-1]
-				return typeof lastCheck === 'undefined' || lastCheck.class !== null
+		async deleteRule() {
+			try {
+				await this.$store.dispatch('deleteRule', this.rule)
+			} catch (e) {
+				console.error('Failed to delete operation')
+				this.error = e.response.data.ocs.meta.message
 			}
 		},
-		methods: {
-			updateOperation(operation) {
-				this.$set(this.rule, 'operation', operation)
-			},
-			async updateRule() {
-				this.checking = true
-				if (!this.dirty) {
-					this.dirty = true
-				}
-				try {
-					// TODO: add new verify endpoint
-					//let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule)
-					this.error = null
-					this.checking = false
-				} catch (e) {
-					console.error('Failed to update operation', e)
-					this.error = e.response.ocs.meta.message
-					this.checking = false
-				}
-			},
-			async saveRule() {
-				try {
-					await confirmPassword()
-					let result
-					if (this.rule.id === -1) {
-						result = await axios.post(getApiUrl(''), {...this.rule, events: [this.rule.event]})
-						this.rule.id = result.data.ocs.data.id
-					} else {
-						result = await axios.put(getApiUrl(`/${this.rule.id}`), {...this.rule, events: [this.rule.event]})
-					}
-					this.$emit('update', this.rule)
-					this.dirty = false
-					this.error = null
-				} catch (e) {
-					console.log(e.response.data.ocs.meta.message)
-					console.error('Failed to update operation', e)
-					this.error = e.response.data.ocs.meta.message
-				}
-			},
-			async deleteRule() {
-				try {
-					await confirmPassword()
-					await axios.delete(getApiUrl(`/${this.rule.id}`))
-					this.$emit('delete')
-				} catch (e) {
-					console.error('Failed to delete operation')
-					this.error = e.response
-				}
-			},
-			removeCheck(check) {
-				const index = this.rule.checks.findIndex(item => item === check)
-				if (index !== -1) {
-					this.rule.checks.splice(index, 1)
-				}
+		cancelRule() {
+			this.$store.dispatch('removeRule', this.rule)
+		},
+		removeCheck(check) {
+			const index = this.rule.checks.findIndex(item => item === check)
+			if (index < 0) {
+				this.rule.checks.splice(index, 1)
 			}
 		}
 	}
+}
 </script>
 
 <style scoped lang="scss">
@@ -162,6 +163,7 @@
 	.status-button.invalid {
 		background-color: var(--color-warning);
 		color: #fff;
+		border: none;
 	}
 
 	.rule {
diff --git a/apps/workflowengine/src/components/Values/FileMimeType.vue b/apps/workflowengine/src/components/Values/FileMimeType.vue
index 70b8f0d984b..9913dc1e858 100644
--- a/apps/workflowengine/src/components/Values/FileMimeType.vue
+++ b/apps/workflowengine/src/components/Values/FileMimeType.vue
@@ -1,39 +1,39 @@
 <template>
-	<input type="text" />
+	<input type="text">
 </template>
 
 <script>
-	import { Multiselect } from 'nextcloud-vue'
+import { Multiselect } from 'nextcloud-vue'
 
-	export default {
-		name: 'SizeValue',
-		components: {
-			Multiselect
-		},
-		data() {
-			return {
-				predefinedTypes: [
-					{
-						icon: 'icon-picture',
-						label: 'Images',
-						pattern: '/image\\/.*/'
-					},
-					{
-						icon: 'icon-category-office',
-						label: 'Office documents',
-						pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/'
-					},
-					{
-						icon: 'icon-filetype-file',
-						label: 'PDF documents',
-						pattern: 'application/pdf'
-					}
-				]
-			}
+export default {
+	name: 'SizeValue',
+	components: {
+		Multiselect
+	},
+	data() {
+		return {
+			predefinedTypes: [
+				{
+					icon: 'icon-picture',
+					label: 'Images',
+					pattern: '/image\\/.*/'
+				},
+				{
+					icon: 'icon-category-office',
+					label: 'Office documents',
+					pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/'
+				},
+				{
+					icon: 'icon-filetype-file',
+					label: 'PDF documents',
+					pattern: 'application/pdf'
+				}
+			]
 		}
 	}
+}
 </script>
 
 <style scoped>
 
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/components/Values/SizeValue.vue b/apps/workflowengine/src/components/Values/SizeValue.vue
index bc4378702e1..03b28f18947 100644
--- a/apps/workflowengine/src/components/Values/SizeValue.vue
+++ b/apps/workflowengine/src/components/Values/SizeValue.vue
@@ -1,28 +1,29 @@
 <template>
-	<input type="text" placeholder="1 MB" @input="$emit('input', newValue)" v-model="newValue">
+	<input v-model="newValue" type="text" placeholder="1 MB"
+		@input="$emit('input', newValue)">
 </template>
 
 <script>
-	export default {
-		name: "SizeValue",
-		props: {
-			value: {
-				type: String
-			}
-		},
-		data() {
-			return {
-				newValue: this.value
-			}
-		},
-		watch: {
-			value() {
-				this.newValue = this.value
-			}
+export default {
+	name: 'SizeValue',
+	props: {
+		value: {
+			type: String
+		}
+	},
+	data() {
+		return {
+			newValue: this.value
+		}
+	},
+	watch: {
+		value() {
+			this.newValue = this.value
 		}
 	}
+}
 </script>
 
 <style scoped>
 
-</style>
\ No newline at end of file
+</style>
diff --git a/apps/workflowengine/src/components/Workflow.vue b/apps/workflowengine/src/components/Workflow.vue
index 0525722666e..986b6f5197c 100644
--- a/apps/workflowengine/src/components/Workflow.vue
+++ b/apps/workflowengine/src/components/Workflow.vue
@@ -4,93 +4,68 @@
 			<h2>{{ t('workflowengine', 'Workflows') }}</h2>
 
 			<transition-group name="slide" tag="div" class="actions">
-				<Operation v-for="operation in getMainOperations" :key="operation.class"
-						   :icon="operation.icon" :title="operation.title" :description="operation.description" :color="operation.color"
-						   @click.native="createNewRule(operation)"></Operation>
+				<Operation v-for="operation in getMainOperations" :key="operation.id" :operation="operation"
+					@click.native="createNewRule(operation)" />
 			</transition-group>
 
-			<div class="actions__more" v-if="hasMoreOperations">
+			<div v-if="hasMoreOperations" class="actions__more">
 				<button class="icon" :class="showMoreOperations ? 'icon-triangle-n' : 'icon-triangle-s'"
-						@click="showMoreOperations=!showMoreOperations">
+					@click="showMoreOperations=!showMoreOperations">
 					{{ showMoreOperations ? t('workflowengine', 'Show less') : t('workflowengine', 'Show more') }}
 				</button>
 			</div>
 		</div>
 
-		<transition-group name="slide" v-if="rules.length > 0">
-			<Rule v-for="rule in rules" :key="rule.id" :rule="rule" @delete="removeRule(rule)" @cancel="removeRule(rule)" @update="updateRule"></Rule>
+		<transition-group v-if="rules.length > 0" name="slide">
+			<Rule v-for="rule in rules" :key="rule.id" :rule="rule" />
 		</transition-group>
-
 	</div>
 </template>
 
 <script>
-	import Rule from './Rule'
-	import Operation from './Operation'
-	import { operationService } from '../services/Operation'
-	import axios from 'nextcloud-axios'
-	import { getApiUrl } from '../helpers/api';
-
-	const ACTION_LIMIT = 3
-
-	export default {
-		name: 'Workflow',
-		components: {
-			Operation,
-			Rule
-		},
-		async mounted() {
-			const { data } = await axios.get(getApiUrl(''))
-			this.rules = Object.values(data.ocs.data).flat()
-		},
-		data() {
-			return {
-				scope: OCP.InitialState.loadState('workflowengine', 'scope'),
-				operations: operationService,
-				showMoreOperations: false,
-				rules: []
-			}
-		},
-		computed: {
-			hasMoreOperations() {
-				return Object.keys(this.operations.getAll()).length > ACTION_LIMIT
-			},
-			getMainOperations() {
-				if (this.showMoreOperations) {
-					return Object.values(this.operations.getAll())
-				}
-				return Object.values(this.operations.getAll()).slice(0, ACTION_LIMIT)
-			},
-			getMoreOperations() {
-				return Object.values(this.operations.getAll()).slice(ACTION_LIMIT)
-
-			}
+import Rule from './Rule'
+import Operation from './Operation'
+import { mapGetters, mapState } from 'vuex'
+
+const ACTION_LIMIT = 3
+
+export default {
+	name: 'Workflow',
+	components: {
+		Operation,
+		Rule
+	},
+	data() {
+		return {
+			showMoreOperations: false
+		}
+	},
+	computed: {
+		...mapGetters({
+			rules: 'getRules'
+		}),
+		...mapState({
+			operations: 'operations'
+		}),
+		hasMoreOperations() {
+			return Object.keys(this.operations).length > ACTION_LIMIT
 		},
-		methods: {
-			updateRule(rule) {
-				let index = this.rules.findIndex((item) => rule === item)
-				this.$set(this.rules, index, rule)
-			},
-			removeRule(rule) {
-				let index = this.rules.findIndex((item) => rule === item)
-				this.rules.splice(index, 1)
-			},
-			showAllOperations() {
-
-			},
-			createNewRule(operation) {
-				this.rules.unshift({
-					id: -1,
-					class: operation.class,
-					entity: undefined,
-					event: undefined,
-					name: '', // unused in the new ui, there for legacy reasons
-					checks: [],
-					operation: operation.operation || ''
-				})
+		getMainOperations() {
+			if (this.showMoreOperations) {
+				return Object.values(this.operations)
 			}
+			return Object.values(this.operations).slice(0, ACTION_LIMIT)
+		}
+	},
+	mounted() {
+		this.$store.dispatch('fetchRules')
+	},
+	methods: {
+		createNewRule(operation) {
+			this.$store.dispatch('createNewRule', operation)
 		}
 	}
+}
 </script>
 
 <style scoped lang="scss">
@@ -119,7 +94,6 @@
 		background-position: 10px center;
 	}
 
-
 	.slide-enter-active {
 		-moz-transition-duration: 0.3s;
 		-webkit-transition-duration: 0.3s;
diff --git a/apps/workflowengine/src/filemimetypeplugin.js b/apps/workflowengine/src/filemimetypeplugin.js
deleted file mode 100644
index 6b929b2aad5..00000000000
--- a/apps/workflowengine/src/filemimetypeplugin.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-import FileMimeType from './components/Values/FileMimeType'
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.FileMimeTypePlugin = {
-		getCheck: function() {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\FileMimeType',
-				'name': t('workflowengine', 'File MIME type'),
-				'operators': [
-					{'operator': 'is', 'name': t('workflowengine', 'is')},
-					{'operator': '!is', 'name': t('workflowengine', 'is not')},
-					{'operator': 'matches', 'name': t('workflowengine', 'matches')},
-					{'operator': '!matches', 'name': t('workflowengine', 'does not match')}
-				]
-			};
-		},
-		render: function(element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileMimeType') {
-				return;
-			}
-
-			var placeholder = 'text/plain';
-			if (check['operator'] === 'matches' || check['operator'] === '!matches') {
-				placeholder = '/^text\\/(plain|html)$/i';
-
-				if (this._validateRegex(check['value'])) {
-					$(element).removeClass('invalid-input');
-				} else {
-					$(element).addClass('invalid-input');
-				}
-			}
-
-			$(element).css('width', '250px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				});
-		},
-
-		_validateRegex: function(string) {
-			var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
-				result = regexRegex.exec(string);
-			return result !== null;
-		},
-
-		component: function () {
-			return FileMimeType
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileMimeTypePlugin);
diff --git a/apps/workflowengine/src/filenameplugin.js b/apps/workflowengine/src/filenameplugin.js
deleted file mode 100644
index 7d8018c29cd..00000000000
--- a/apps/workflowengine/src/filenameplugin.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * @copyright Copyright (c) 2018 Daniel Kesselberg <mail@danielkesselberg.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function () {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.FileNamePlugin = {
-		getCheck: function () {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\FileName',
-				'name': t('workflowengine', 'File name'),
-				'operators': [
-					{'operator': 'is', 'name': t('workflowengine', 'is')},
-					{'operator': '!is', 'name': t('workflowengine', 'is not')},
-					{
-						'operator': 'matches',
-						'name': t('workflowengine', 'matches')
-					},
-					{
-						'operator': '!matches',
-						'name': t('workflowengine', 'does not match')
-					}
-				]
-			};
-		},
-		render: function (element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileName') {
-				return;
-			}
-
-			var placeholder = 'dummy.jpg';
-			if (check['operator'] === 'matches' || check['operator'] === '!matches') {
-				placeholder = '/^dummy-.+$/i';
-
-				if (this._validateRegex(check['value'])) {
-					$(element).removeClass('invalid-input');
-				} else {
-					$(element).addClass('invalid-input');
-				}
-			}
-
-			$(element).css('width', '250px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				});
-		},
-
-		_validateRegex: function (string) {
-			var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
-				result = regexRegex.exec(string);
-			return result !== null;
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileNamePlugin);
\ No newline at end of file
diff --git a/apps/workflowengine/src/filesizeplugin.js b/apps/workflowengine/src/filesizeplugin.js
deleted file mode 100644
index ebd72dcd2f4..00000000000
--- a/apps/workflowengine/src/filesizeplugin.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-import SizeValue from './components/Values/SizeValue'
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.FileSizePlugin = {
-		getCheck: function() {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\FileSize',
-				'name': t('workflowengine', 'File size (upload)'),
-				'operators': [
-					{'operator': 'less', 'name': t('workflowengine', 'less')},
-					{'operator': '!greater', 'name': t('workflowengine', 'less or equals')},
-					{'operator': '!less', 'name': t('workflowengine', 'greater or equals')},
-					{'operator': 'greater', 'name': t('workflowengine', 'greater')}
-				]
-			};
-		},
-		render: function(element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileSize') {
-				return;
-			}
-
-			var placeholder = '12 MB'; // Do not translate!!!
-			$(element).css('width', '250px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				});
-		},
-		component: function () {
-			return SizeValue
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSizePlugin);
diff --git a/apps/workflowengine/src/filesystemtagsplugin.js b/apps/workflowengine/src/filesystemtagsplugin.js
deleted file mode 100644
index e66a35b73b9..00000000000
--- a/apps/workflowengine/src/filesystemtagsplugin.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin = {
-		getCheck: function() {
-			this.collection = OC.SystemTags.collection;
-
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\FileSystemTags',
-				'name': t('workflowengine', 'File system tag'),
-				'operators': [
-					{'operator': 'is', 'name': t('workflowengine', 'is tagged with')},
-					{'operator': '!is', 'name': t('workflowengine', 'is not tagged with')}
-				]
-			};
-		},
-		render: function(element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileSystemTags') {
-				return;
-			}
-
-			$(element).css('width', '400px');
-
-			$(element).select2({
-				allowClear: false,
-				multiple: false,
-				placeholder: t('workflowengine', 'Select tag…'),
-				query: _.debounce(function(query) {
-					query.callback({
-						results: OC.SystemTags.collection.filterByName(query.term)
-					});
-				}, 100, true),
-				id: function(element) {
-					return element.get('id');
-				},
-				initSelection: function(element, callback) {
-					callback($(element).val());
-				},
-				formatResult: function (tag) {
-					return OC.SystemTags.getDescriptiveTag(tag);
-				},
-				formatSelection: function (tagId) {
-					var tag = OC.SystemTags.collection.get(tagId);
-					if (!_.isUndefined(tag)) {
-						return OC.SystemTags.getDescriptiveTag(tag);
-					}
-				},
-				escapeMarkup: function(m) {
-					return m;
-				}
-			});
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin);
diff --git a/apps/workflowengine/src/hbs_helpers/getOperators.js b/apps/workflowengine/src/hbs_helpers/getOperators.js
deleted file mode 100644
index 4e3606fe82c..00000000000
--- a/apps/workflowengine/src/hbs_helpers/getOperators.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = function(classname) {
-	var check = OCA.WorkflowEngine.getCheckByClass(classname);
-	if (!_.isUndefined(check)) {
-		return check['operators'];
-	}
-	return [];
-}
diff --git a/apps/workflowengine/src/hbs_helpers/selectItem.js b/apps/workflowengine/src/hbs_helpers/selectItem.js
deleted file mode 100644
index 594e3debadd..00000000000
--- a/apps/workflowengine/src/hbs_helpers/selectItem.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = function(currentValue, itemValue) {
-	if (currentValue === itemValue) {
-		return 'selected="selected"';
-	}
-
-	return "";
-}
diff --git a/apps/workflowengine/src/legacy/filemimetypeplugin.js b/apps/workflowengine/src/legacy/filemimetypeplugin.js
new file mode 100644
index 00000000000..2b29c4fcbb8
--- /dev/null
+++ b/apps/workflowengine/src/legacy/filemimetypeplugin.js
@@ -0,0 +1,77 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import FileMimeType from './../components/Values/FileMimeType'
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.FileMimeTypePlugin = {
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\FileMimeType',
+				name: t('workflowengine', 'File MIME type'),
+				operators: [
+					{ operator: 'is', name: t('workflowengine', 'is') },
+					{ operator: '!is', name: t('workflowengine', 'is not') },
+					{ operator: 'matches', name: t('workflowengine', 'matches') },
+					{ operator: '!matches', name: t('workflowengine', 'does not match') }
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\FileMimeType') {
+				return
+			}
+
+			var placeholder = 'text/plain'
+			if (check.operator === 'matches' || check.operator === '!matches') {
+				placeholder = '/^text\\/(plain|html)$/i'
+
+				if (this._validateRegex(check.value)) {
+					$(element).removeClass('invalid-input')
+				} else {
+					$(element).addClass('invalid-input')
+				}
+			}
+
+			$(element).css('width', '250px')
+				.attr('placeholder', placeholder)
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+		},
+
+		_validateRegex: function(string) {
+			var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+			var result = regexRegex.exec(string)
+			return result !== null
+		},
+
+		component: function() {
+			return FileMimeType
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileMimeTypePlugin)
diff --git a/apps/workflowengine/src/legacy/filenameplugin.js b/apps/workflowengine/src/legacy/filenameplugin.js
new file mode 100644
index 00000000000..637e08ccbea
--- /dev/null
+++ b/apps/workflowengine/src/legacy/filenameplugin.js
@@ -0,0 +1,78 @@
+/**
+ * @copyright Copyright (c) 2018 Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.FileNamePlugin = {
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\FileName',
+				name: t('workflowengine', 'File name'),
+				operators: [
+					{ operator: 'is', name: t('workflowengine', 'is') },
+					{ operator: '!is', name: t('workflowengine', 'is not') },
+					{
+						operator: 'matches',
+						name: t('workflowengine', 'matches')
+					},
+					{
+						operator: '!matches',
+						name: t('workflowengine', 'does not match')
+					}
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\FileName') {
+				return
+			}
+
+			var placeholder = 'dummy.jpg'
+			if (check.operator === 'matches' || check.operator === '!matches') {
+				placeholder = '/^dummy-.+$/i'
+
+				if (this._validateRegex(check.value)) {
+					$(element).removeClass('invalid-input')
+				} else {
+					$(element).addClass('invalid-input')
+				}
+			}
+
+			$(element).css('width', '250px')
+				.attr('placeholder', placeholder)
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+		},
+
+		_validateRegex: function(string) {
+			var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+			var result = regexRegex.exec(string)
+			return result !== null
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileNamePlugin)
diff --git a/apps/workflowengine/src/legacy/filesizeplugin.js b/apps/workflowengine/src/legacy/filesizeplugin.js
new file mode 100644
index 00000000000..fcb81bd3397
--- /dev/null
+++ b/apps/workflowengine/src/legacy/filesizeplugin.js
@@ -0,0 +1,59 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import SizeValue from './../components/Values/SizeValue'
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.FileSizePlugin = {
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\FileSize',
+				name: t('workflowengine', 'File size (upload)'),
+				operators: [
+					{ operator: 'less', name: t('workflowengine', 'less') },
+					{ operator: '!greater', name: t('workflowengine', 'less or equals') },
+					{ operator: '!less', name: t('workflowengine', 'greater or equals') },
+					{ operator: 'greater', name: t('workflowengine', 'greater') }
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\FileSize') {
+				return
+			}
+
+			var placeholder = '12 MB' // Do not translate!!!
+			$(element).css('width', '250px')
+				.attr('placeholder', placeholder)
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+		},
+		component: function() {
+			return SizeValue
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSizePlugin)
diff --git a/apps/workflowengine/src/legacy/filesystemtagsplugin.js b/apps/workflowengine/src/legacy/filesystemtagsplugin.js
new file mode 100644
index 00000000000..e945c79195c
--- /dev/null
+++ b/apps/workflowengine/src/legacy/filesystemtagsplugin.js
@@ -0,0 +1,78 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin = {
+		getCheck: function() {
+			this.collection = OC.SystemTags.collection
+
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\FileSystemTags',
+				name: t('workflowengine', 'File system tag'),
+				operators: [
+					{ operator: 'is', name: t('workflowengine', 'is tagged with') },
+					{ operator: '!is', name: t('workflowengine', 'is not tagged with') }
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\FileSystemTags') {
+				return
+			}
+
+			$(element).css('width', '400px')
+
+			$(element).select2({
+				allowClear: false,
+				multiple: false,
+				placeholder: t('workflowengine', 'Select tag…'),
+				query: _.debounce(function(query) {
+					query.callback({
+						results: OC.SystemTags.collection.filterByName(query.term)
+					})
+				}, 100, true),
+				id: function(element) {
+					return element.get('id')
+				},
+				initSelection: function(element, callback) {
+					callback($(element).val())
+				},
+				formatResult: function(tag) {
+					return OC.SystemTags.getDescriptiveTag(tag)
+				},
+				formatSelection: function(tagId) {
+					var tag = OC.SystemTags.collection.get(tagId)
+					if (!_.isUndefined(tag)) {
+						return OC.SystemTags.getDescriptiveTag(tag)
+					}
+				},
+				escapeMarkup: function(m) {
+					return m
+				}
+			})
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin)
diff --git a/apps/workflowengine/src/legacy/requestremoteaddressplugin.js b/apps/workflowengine/src/legacy/requestremoteaddressplugin.js
new file mode 100644
index 00000000000..b6f7d02dd46
--- /dev/null
+++ b/apps/workflowengine/src/legacy/requestremoteaddressplugin.js
@@ -0,0 +1,83 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.RequestRemoteAddressPlugin = {
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
+				name: t('workflowengine', 'Request remote address'),
+				operators: [
+					{ operator: 'matchesIPv4', name: t('workflowengine', 'matches IPv4') },
+					{ operator: '!matchesIPv4', name: t('workflowengine', 'does not match IPv4') },
+					{ operator: 'matchesIPv6', name: t('workflowengine', 'matches IPv6') },
+					{ operator: '!matchesIPv6', name: t('workflowengine', 'does not match IPv6') }
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress') {
+				return
+			}
+
+			var placeholder = '127.0.0.1/32' // Do not translate!!!
+			if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') {
+				placeholder = '::1/128' // Do not translate!!!
+				if (this._validateIPv6(check.value)) {
+					$(element).removeClass('invalid-input')
+				} else {
+					$(element).addClass('invalid-input')
+				}
+			} else {
+				if (this._validateIPv4(check.value)) {
+					$(element).removeClass('invalid-input')
+				} else {
+					$(element).addClass('invalid-input')
+				}
+			}
+
+			$(element).css('width', '300px')
+				.attr('placeholder', placeholder)
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+		},
+
+		_validateIPv4: function(string) {
+			var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/
+			var result = regexRegex.exec(string)
+			return result !== null
+		},
+
+		_validateIPv6: function(string) {
+			var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/
+			var result = regexRegex.exec(string)
+			return result !== null
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestRemoteAddressPlugin)
diff --git a/apps/workflowengine/src/legacy/requesttimeplugin.js b/apps/workflowengine/src/legacy/requesttimeplugin.js
new file mode 100644
index 00000000000..eaeae287da4
--- /dev/null
+++ b/apps/workflowengine/src/legacy/requesttimeplugin.js
@@ -0,0 +1,196 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.RequestTimePlugin = {
+		timezones: [
+			'Europe/Berlin',
+			'Europe/London'
+		],
+		_$element: null,
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\RequestTime',
+				name: t('workflowengine', 'Request time'),
+				operators: [
+					{ operator: 'in', name: t('workflowengine', 'between') },
+					{ operator: '!in', name: t('workflowengine', 'not between') }
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\RequestTime') {
+				return
+			}
+
+			var startTime = '09:00'
+			var endTime = '18:00'
+			var timezone = jstz.determine().name()
+			var $element = $(element)
+
+			if (_.isString(check.value) && check.value !== '') {
+				var value = JSON.parse(check.value)
+				var splittedStart = value[0].split(' ', 2)
+				var splittedEnd = value[1].split(' ', 2)
+
+				startTime = splittedStart[0]
+				endTime = splittedEnd[0]
+				timezone = splittedStart[1]
+			}
+
+			var valueJSON = JSON.stringify([startTime + ' ' + timezone, endTime + ' ' + timezone])
+			if (check.value !== valueJSON) {
+				check.value = valueJSON
+				$element.val(valueJSON)
+			}
+
+			$element.css('display', 'none')
+
+			$('<input>')
+				.attr('type', 'text')
+				.attr('placeholder', t('workflowengine', 'Start'))
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: '16:00' }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+				.addClass('start')
+				.val(startTime)
+				.insertBefore($element)
+			$('<input>')
+				.attr('type', 'text')
+				.attr('placeholder', t('workflowengine', 'End'))
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: '16:00' }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+				.addClass('end')
+				.val(endTime)
+				.insertBefore($element)
+
+			var timezoneInput = $('<input>')
+				.attr('type', 'hidden')
+				.css('width', '250px')
+				.insertBefore($element)
+				.val(timezone)
+
+			timezoneInput.select2({
+				allowClear: false,
+				multiple: false,
+				placeholder: t('workflowengine', 'Select timezone…'),
+				ajax: {
+					url: OC.generateUrl('apps/workflowengine/timezones'),
+					dataType: 'json',
+					quietMillis: 100,
+					data: function(term) {
+						if (term === '') {
+							// Default search in the same continent...
+							term = jstz.determine().name().split('/')
+							term = term[0]
+						}
+						return {
+							search: term
+						}
+					},
+					results: function(response) {
+						var results = []
+						$.each(response, function(timezone) {
+							results.push({ id: timezone })
+						})
+
+						return {
+							results: results,
+							more: false
+						}
+					}
+				},
+				initSelection: function(element, callback) {
+					callback(element.val())
+				},
+				formatResult: function(element) {
+					return '<span>' + element.id + '</span>'
+				},
+				formatSelection: function(element) {
+					if (!_.isUndefined(element.id)) {
+						element = element.id
+					}
+					return '<span>' + element + '</span>'
+				}
+			})
+
+			// Has to be added after select2 for `event.target.classList`
+			timezoneInput.addClass('timezone')
+
+			$element.parent()
+				.on('change', '.start', _.bind(this.update, this))
+				.on('change', '.end', _.bind(this.update, this))
+				.on('change', '.timezone', _.bind(this.update, this))
+
+			this._$element = $element
+		},
+		update: function(event) {
+			var value = event.target.value
+			var key = null
+
+			for (var i = 0; i < event.target.classList.length; i++) {
+				key = event.target.classList[i]
+			}
+
+			if (key === null) {
+				console.warn('update triggered but element doesn\'t have any class')
+				return
+			}
+
+			var data = JSON.parse(this._$element.val())
+			var startTime = moment(data[0].split(' ', 2)[0], 'H:m Z')
+			var endTime = moment(data[1].split(' ', 2)[0], 'H:m Z')
+			var timezone = data[0].split(' ', 2)[1]
+
+			if (key === 'start' || key === 'end') {
+				var parsedDate = moment(value, ['H:m', 'h:m a'], true).format('HH:mm')
+
+				if (parsedDate === 'Invalid date') {
+					return
+				}
+
+				var indexValue = 0
+				if (key === 'end') {
+					indexValue = 1
+				}
+				data[indexValue] = parsedDate + ' ' + timezone
+			}
+
+			if (key === 'timezone') {
+				data[0] = startTime.format('HH:mm') + ' ' + value
+				data[1] = endTime.format('HH:mm') + ' ' + value
+			}
+
+			this._$element.val(JSON.stringify(data))
+			this._$element.trigger('change')
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestTimePlugin)
diff --git a/apps/workflowengine/src/legacy/requesturlplugin.js b/apps/workflowengine/src/legacy/requesturlplugin.js
new file mode 100644
index 00000000000..7e13422387f
--- /dev/null
+++ b/apps/workflowengine/src/legacy/requesturlplugin.js
@@ -0,0 +1,116 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.RequestURLPlugin = {
+		predefinedValues: ['webdav'],
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\RequestURL',
+				name: t('workflowengine', 'Request URL'),
+				operators: [
+					{ operator: 'is', name: t('workflowengine', 'is') },
+					{ operator: '!is', name: t('workflowengine', 'is not') },
+					{ operator: 'matches', name: t('workflowengine', 'matches') },
+					{ operator: '!matches', name: t('workflowengine', 'does not match') }
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\RequestURL') {
+				return
+			}
+
+			var placeholder = 'https://localhost/index.php'
+
+			if (check.operator === 'matches' || check.operator === '!matches') {
+				placeholder = '/^https\\:\\/\\/localhost\\/index\\.php$/i'
+			}
+
+			$(element).css('width', '250px')
+				.attr('placeholder', placeholder)
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+
+			if (check.operator === 'matches' || check.operator === '!matches') {
+				if (this._validateRegex(check.value)) {
+					$(element).removeClass('invalid-input')
+				} else {
+					$(element).addClass('invalid-input')
+				}
+			} else {
+				var self = this
+				var data = [
+					{
+						text: t('workflowengine', 'Predefined URLs'),
+						children: [
+							{ id: 'webdav', text: t('workflowengine', 'Files WebDAV') }
+						]
+					}
+				]
+				if (this.predefinedValues.indexOf(check.value) === -1) {
+					data.unshift({
+						id: check.value,
+						text: check.value
+					})
+				}
+
+				$(element).select2({
+					data: data,
+					createSearchChoice: function(term) {
+						if (self.predefinedValues.indexOf(check.value) === -1) {
+							return {
+								id: term,
+								text: term
+							}
+						}
+					},
+					id: function(element) {
+						return element.id
+					},
+					formatResult: function(tag) {
+						return tag.text
+					},
+					formatSelection: function(tag) {
+						return tag.text
+					},
+					escapeMarkup: function(m) {
+						return m
+					}
+				})
+			}
+		},
+
+		_validateRegex: function(string) {
+			var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+			var result = regexRegex.exec(string)
+			return result !== null
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestURLPlugin)
diff --git a/apps/workflowengine/src/legacy/requestuseragentplugin.js b/apps/workflowengine/src/legacy/requestuseragentplugin.js
new file mode 100644
index 00000000000..5480e4ccc8b
--- /dev/null
+++ b/apps/workflowengine/src/legacy/requestuseragentplugin.js
@@ -0,0 +1,119 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin = {
+		predefinedValues: ['android', 'ios', 'desktop'],
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
+				name: t('workflowengine', 'Request user agent'),
+				operators: [
+					{ operator: 'is', name: t('workflowengine', 'is') },
+					{ operator: '!is', name: t('workflowengine', 'is not') },
+					{ operator: 'matches', name: t('workflowengine', 'matches') },
+					{ operator: '!matches', name: t('workflowengine', 'does not match') }
+				]
+			}
+		},
+		render: function(element, check) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\RequestUserAgent') {
+				return
+			}
+
+			var placeholder = 'Mozilla/5.0 User Agent'
+
+			if (check.operator === 'matches' || check.operator === '!matches') {
+				placeholder = '/^Mozilla\\/5\\.0 (.*)$/i'
+			}
+
+			$(element).css('width', '250px')
+				.attr('placeholder', placeholder)
+				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
+				.addClass('has-tooltip')
+				.tooltip({
+					placement: 'bottom'
+				})
+
+			if (check.operator === 'matches' || check.operator === '!matches') {
+				if (this._validateRegex(check.value)) {
+					$(element).removeClass('invalid-input')
+				} else {
+					$(element).addClass('invalid-input')
+				}
+			} else {
+				var self = this
+				var data = [
+					{
+						text: t('workflowengine', 'Sync clients'),
+						children: [
+							{ id: 'android', text: t('workflowengine', 'Android client') },
+							{ id: 'ios', text: t('workflowengine', 'iOS client') },
+							{ id: 'desktop', text: t('workflowengine', 'Desktop client') },
+							{ id: 'mail', text: t('workflowengine', 'Thunderbird & Outlook addons') }
+						]
+					}
+				]
+				if (this.predefinedValues.indexOf(check.value) === -1) {
+					data.unshift({
+						id: check.value,
+						text: check.value
+					})
+				}
+
+				$(element).select2({
+					data: data,
+					createSearchChoice: function(term) {
+						if (self.predefinedValues.indexOf(check.value) === -1) {
+							return {
+								id: term,
+								text: term
+							}
+						}
+					},
+					id: function(element) {
+						return element.id
+					},
+					formatResult: function(tag) {
+						return tag.text
+					},
+					formatSelection: function(tag) {
+						return tag.text
+					},
+					escapeMarkup: function(m) {
+						return m
+					}
+				})
+			}
+		},
+
+		_validateRegex: function(string) {
+			var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+			var result = regexRegex.exec(string)
+			return result !== null
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin)
diff --git a/apps/workflowengine/src/legacy/usergroupmembershipplugin.js b/apps/workflowengine/src/legacy/usergroupmembershipplugin.js
new file mode 100644
index 00000000000..6d60e9e4d66
--- /dev/null
+++ b/apps/workflowengine/src/legacy/usergroupmembershipplugin.js
@@ -0,0 +1,75 @@
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
+	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
+
+	OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin = {
+		getCheck: function() {
+			return {
+				class: 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
+				name: t('workflowengine', 'User group membership'),
+				operators: [
+					{ operator: 'is', name: t('workflowengine', 'is member of') },
+					{ operator: '!is', name: t('workflowengine', 'is not member of') }
+				]
+			}
+		},
+		render: function(element, check, groups) {
+			if (check.class !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') {
+				return
+			}
+
+			$(element).css('width', '400px')
+
+			$(element).select2({
+				data: { results: groups, text: 'displayname' },
+				initSelection: function(element, callback) {
+					var groupId = element.val()
+					if (groupId && groups.length > 0) {
+						callback({
+							id: groupId,
+							displayname: groups.find(function(group) {
+								return group.id === groupId
+							}).displayname
+						})
+					} else if (groupId) {
+						callback({
+							id: groupId,
+							displayname: groupId
+						})
+					} else {
+						callback()
+					}
+				},
+				formatResult: function(element) {
+					return '<span>' + escapeHTML(element.displayname) + '</span>'
+				},
+				formatSelection: function(element) {
+					return '<span title="' + escapeHTML(element.id) + '">' + escapeHTML(element.displayname) + '</span>'
+				}
+			})
+		}
+	}
+})()
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin)
diff --git a/apps/workflowengine/src/requestremoteaddressplugin.js b/apps/workflowengine/src/requestremoteaddressplugin.js
deleted file mode 100644
index a66d6f51f0f..00000000000
--- a/apps/workflowengine/src/requestremoteaddressplugin.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.RequestRemoteAddressPlugin = {
-		getCheck: function() {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
-				'name': t('workflowengine', 'Request remote address'),
-				'operators': [
-					{'operator': 'matchesIPv4', 'name': t('workflowengine', 'matches IPv4')},
-					{'operator': '!matchesIPv4', 'name': t('workflowengine', 'does not match IPv4')},
-					{'operator': 'matchesIPv6', 'name': t('workflowengine', 'matches IPv6')},
-					{'operator': '!matchesIPv6', 'name': t('workflowengine', 'does not match IPv6')}
-				]
-			};
-		},
-		render: function(element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress') {
-				return;
-			}
-
-			var placeholder = '127.0.0.1/32'; // Do not translate!!!
-			if (check['operator'] === 'matchesIPv6' || check['operator'] === '!matchesIPv6') {
-				placeholder = '::1/128'; // Do not translate!!!
-				if (this._validateIPv6(check['value'])) {
-					$(element).removeClass('invalid-input');
-				} else {
-					$(element).addClass('invalid-input');
-				}
-			} else {
-				if (this._validateIPv4(check['value'])) {
-					$(element).removeClass('invalid-input');
-				} else {
-					$(element).addClass('invalid-input');
-				}
-			}
-
-			$(element).css('width', '300px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				});
-		},
-
-		_validateIPv4: function(string) {
-			var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/,
-				result = regexRegex.exec(string);
-			return result !== null;
-		},
-
-		_validateIPv6: function(string) {
-			var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/,
-				result = regexRegex.exec(string);
-			return result !== null;
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestRemoteAddressPlugin);
diff --git a/apps/workflowengine/src/requesttimeplugin.js b/apps/workflowengine/src/requesttimeplugin.js
deleted file mode 100644
index 111b2bb7437..00000000000
--- a/apps/workflowengine/src/requesttimeplugin.js
+++ /dev/null
@@ -1,196 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.RequestTimePlugin = {
-		timezones: [
-			"Europe/Berlin",
-			"Europe/London"
-		],
-		_$element: null,
-		getCheck: function() {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\RequestTime',
-				'name': t('workflowengine', 'Request time'),
-				'operators': [
-					{'operator': 'in', 'name': t('workflowengine', 'between')},
-					{'operator': '!in', 'name': t('workflowengine', 'not between')}
-				]
-			};
-		},
-		render: function(element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestTime') {
-				return;
-			}
-
-			var startTime = '09:00',
-				endTime = '18:00',
-				timezone = jstz.determine().name(),
-				$element = $(element);
-
-			if (_.isString(check['value']) && check['value'] !== '') {
-				var value = JSON.parse(check['value']),
-					splittedStart = value[0].split(' ', 2),
-					splittedEnd = value[1].split(' ', 2);
-
-				startTime = splittedStart[0];
-				endTime = splittedEnd[0];
-				timezone = splittedStart[1];
-			}
-
-			var valueJSON = JSON.stringify([startTime + ' ' + timezone, endTime + ' ' + timezone]);
-			if (check['value'] !== valueJSON) {
-				check['value'] = valueJSON;
-				$element.val(valueJSON);
-			}
-
-			$element.css('display', 'none');
-
-			$('<input>')
-				.attr('type', 'text')
-				.attr('placeholder', t('workflowengine', 'Start'))
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: '16:00'}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				})
-				.addClass('start')
-				.val(startTime)
-				.insertBefore($element);
-			$('<input>')
-				.attr('type', 'text')
-				.attr('placeholder', t('workflowengine', 'End'))
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: '16:00'}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				})
-				.addClass('end')
-				.val(endTime)
-				.insertBefore($element);
-
-			var timezoneInput = $('<input>')
-				.attr('type', 'hidden')
-				.css('width', '250px')
-				.insertBefore($element)
-				.val(timezone);
-
-			timezoneInput.select2({
-				allowClear: false,
-				multiple: false,
-				placeholder: t('workflowengine', 'Select timezone…'),
-				ajax: {
-					url: OC.generateUrl('apps/workflowengine/timezones'),
-					dataType: 'json',
-					quietMillis: 100,
-					data: function (term) {
-						if (term === '') {
-							// Default search in the same continent...
-							term = jstz.determine().name().split('/');
-							term = term[0];
-						}
-						return {
-							search: term
-						};
-					},
-					results: function (response) {
-						var results = [];
-						$.each(response, function(timezone) {
-							results.push({ id: timezone });
-						});
-
-						return {
-							results: results,
-							more: false
-						};
-					}
-				},
-				initSelection: function (element, callback) {
-					callback(element.val());
-				},
-				formatResult: function (element) {
-					return '<span>' + element.id + '</span>';
-				},
-				formatSelection: function (element) {
-					if (!_.isUndefined(element.id)) {
-						element = element.id;
-					}
-					return '<span>' + element + '</span>';
-				}
-			});
-
-			// Has to be added after select2 for `event.target.classList`
-			timezoneInput.addClass('timezone');
-
-			$element.parent()
-				.on('change', '.start', _.bind(this.update, this))
-				.on('change', '.end', _.bind(this.update, this))
-				.on('change', '.timezone', _.bind(this.update, this));
-
-			this._$element = $element;
-		},
-		update: function(event) {
-			var value = event.target.value,
-				key = null;
-
-			for (var i = 0; i < event.target.classList.length; i++) {
-				key = event.target.classList[i];
-			}
-
-			if (key === null) {
-				console.warn('update triggered but element doesn\'t have any class');
-				return;
-			}
-
-			var data = JSON.parse(this._$element.val()),
-				startTime = moment(data[0].split(' ', 2)[0], 'H:m Z'),
-				endTime = moment(data[1].split(' ', 2)[0], 'H:m Z'),
-				timezone = data[0].split(' ', 2)[1];
-
-			if (key === 'start' || key === 'end') {
-				var parsedDate = moment(value, ['H:m', 'h:m a'], true).format('HH:mm');
-
-				if (parsedDate === 'Invalid date') {
-					return;
-				}
-
-				var indexValue = 0;
-				if (key === 'end') {
-					indexValue = 1;
-				}
-				data[indexValue] = parsedDate + ' ' + timezone;
-			}
-
-			if (key === 'timezone') {
-				data[0] = startTime.format('HH:mm') + ' ' + value;
-				data[1] = endTime.format('HH:mm') + ' ' + value;
-			}
-
-			this._$element.val(JSON.stringify(data));
-			this._$element.trigger('change');
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestTimePlugin);
diff --git a/apps/workflowengine/src/requesturlplugin.js b/apps/workflowengine/src/requesturlplugin.js
deleted file mode 100644
index 7c81deaaf33..00000000000
--- a/apps/workflowengine/src/requesturlplugin.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.RequestURLPlugin = {
-		predefinedValues: ['webdav'],
-		getCheck: function() {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\RequestURL',
-				'name': t('workflowengine', 'Request URL'),
-				'operators': [
-					{'operator': 'is', 'name': t('workflowengine', 'is')},
-					{'operator': '!is', 'name': t('workflowengine', 'is not')},
-					{'operator': 'matches', 'name': t('workflowengine', 'matches')},
-					{'operator': '!matches', 'name': t('workflowengine', 'does not match')}
-				]
-			};
-		},
-		render: function(element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestURL') {
-				return;
-			}
-
-			var placeholder = 'https://localhost/index.php';
-
-			if (check['operator'] === 'matches' || check['operator'] === '!matches') {
-				placeholder = '/^https\\:\\/\\/localhost\\/index\\.php$/i';
-			}
-
-			$(element).css('width', '250px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				});
-
-			if (check['operator'] === 'matches' || check['operator'] === '!matches') {
-				if (this._validateRegex(check['value'])) {
-					$(element).removeClass('invalid-input');
-				} else {
-					$(element).addClass('invalid-input');
-				}
-			} else {
-				var self = this,
-					data = [
-					{
-						text: t('workflowengine', 'Predefined URLs'),
-						children: [
-							{id: 'webdav', text: t('workflowengine', 'Files WebDAV')}
-						]
-					}
-				];
-				if (this.predefinedValues.indexOf(check['value']) === -1) {
-					data.unshift({
-						id: check['value'],
-						text: check['value']
-					})
-				}
-
-
-				$(element).select2({
-					data: data,
-					createSearchChoice: function(term) {
-						if (self.predefinedValues.indexOf(check['value']) === -1) {
-							return {
-								id: term,
-								text: term
-							};
-						}
-					},
-					id: function(element) {
-						return element.id;
-					},
-					formatResult: function (tag) {
-						return tag.text;
-					},
-					formatSelection: function (tag) {
-						return tag.text;
-					},
-					escapeMarkup: function(m) {
-						return m;
-					}
-				})
-			}
-		},
-
-		_validateRegex: function(string) {
-			var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
-				result = regexRegex.exec(string);
-			return result !== null;
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestURLPlugin);
diff --git a/apps/workflowengine/src/requestuseragentplugin.js b/apps/workflowengine/src/requestuseragentplugin.js
deleted file mode 100644
index 881ea4b8ac7..00000000000
--- a/apps/workflowengine/src/requestuseragentplugin.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin = {
-		predefinedValues: ['android', 'ios', 'desktop'],
-		getCheck: function() {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
-				'name': t('workflowengine', 'Request user agent'),
-				'operators': [
-					{'operator': 'is', 'name': t('workflowengine', 'is')},
-					{'operator': '!is', 'name': t('workflowengine', 'is not')},
-					{'operator': 'matches', 'name': t('workflowengine', 'matches')},
-					{'operator': '!matches', 'name': t('workflowengine', 'does not match')}
-				]
-			};
-		},
-		render: function(element, check) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestUserAgent') {
-				return;
-			}
-
-			var placeholder = 'Mozilla/5.0 User Agent';
-
-			if (check.operator === 'matches' || check.operator === '!matches') {
-				placeholder = '/^Mozilla\\/5\\.0 (.*)$/i';
-			}
-
-			$(element).css('width', '250px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				});
-
-			if (check.operator === 'matches' || check.operator === '!matches') {
-				if (this._validateRegex(check.value)) {
-					$(element).removeClass('invalid-input');
-				} else {
-					$(element).addClass('invalid-input');
-				}
-			} else {
-				var self = this,
-					data = [
-					{
-						text: t('workflowengine', 'Sync clients'),
-						children: [
-							{id: 'android', text: t('workflowengine', 'Android client')},
-							{id: 'ios', text: t('workflowengine', 'iOS client')},
-							{id: 'desktop', text: t('workflowengine', 'Desktop client')},
-							{id: 'mail', text: t('workflowengine', 'Thunderbird & Outlook addons')}
-						]
-					}
-				];
-				if (this.predefinedValues.indexOf(check.value) === -1) {
-					data.unshift({
-						id: check.value,
-						text: check.value
-					});
-				}
-
-				$(element).select2({
-					data: data,
-					createSearchChoice: function(term) {
-						if (self.predefinedValues.indexOf(check.value) === -1) {
-							return {
-								id: term,
-								text: term
-							};
-						}
-					},
-					id: function(element) {
-						return element.id;
-					},
-					formatResult: function (tag) {
-						return tag.text;
-					},
-					formatSelection: function (tag) {
-						return tag.text;
-					},
-					escapeMarkup: function(m) {
-						return m;
-					}
-				})
-			}
-		},
-
-		_validateRegex: function(string) {
-			var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
-				result = regexRegex.exec(string);
-			return result !== null;
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin);
diff --git a/apps/workflowengine/src/services/Operation.js b/apps/workflowengine/src/services/Operation.js
index b2257b5545a..99dca212f4c 100644
--- a/apps/workflowengine/src/services/Operation.js
+++ b/apps/workflowengine/src/services/Operation.js
@@ -13,17 +13,9 @@ const ALL_CHECKS = [
 	'OCA\\WorkflowEngine\\Check\\UserGroupMembership'
 ]
 
-const Entities = OCP.InitialState.loadState('workflowengine', 'entities').map(entity => {
-	return {
-		...entity,
-		// TODO: see if we should have this defined in the backend as well
-		checks: [...ALL_CHECKS]
-	}
-})
-
 const Checks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => {
 	if (plugin.component) {
-		return {...plugin.getCheck(), component: plugin.component}
+		return { ...plugin.getCheck(), component: plugin.component }
 	}
 	return plugin.getCheck()
 }).reduce((obj, item) => {
@@ -31,113 +23,55 @@ const Checks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => {
 	return obj
 }, {})
 
+const Operators = OCP.InitialState.loadState('workflowengine', 'operators')
+
 /**
- * Register operations
- * TODO: should be provided by the backend
+ * Extend operators for testing
  */
-
-class OperationService {
-
-	constructor() {
-		this.operations = {}
-	}
-	registerOperation (operation) {
-		this.operations[operation.class] = Object.assign({
-			color: 'var(--color-primary)'
-		}, operation)
-	}
-
-	getAll() {
-		return this.operations
-	}
-
-	get(className) {
-		return this.operations[className]
-	}
-
-}
-const operationService = new OperationService()
-
-operationService.registerOperation({
-	class: 'OCA\\FilesAccessControl\\Operation',
-	title: 'Block access',
-	description: 'Deny access to files when they are accessed',
-	icon: 'icon-block',
+Operators['OCA\\FilesAccessControl\\Operation'] = {
+	...Operators['OCA\\FilesAccessControl\\Operation'],
 	color: 'var(--color-error)',
-	entites: [
+	entities: [
 		'OCA\\WorkflowEngine\\Entity\\File'
 	],
-	events: [
-		// TODO: this is probably handled differently since there is no regular event for files access control
-		'OCA\\WorkflowEngine\\Entity\\File::postTouch'
-	],
 	operation: 'deny'
-})
+}
+Operators['OCA\\WorkflowPDFConverter\\Operation'] = {
+	id: 'OCA\\WorkflowPDFConverter\\Operation',
+	name: 'Convert to PDF',
+	description: 'Generate a PDF file',
+	color: '#dc5047',
+	iconClass: 'icon-convert-pdf',
+	options: ConvertToPdf
+}
 
-operationService.registerOperation({
-	class: 'OCA\\TestExample\\Operation1',
-	title: 'Rename file',
+Operators['OCA\\TestExample\\Operation1'] = {
+	id: 'OCA\\TestExample\\Operation1',
+	name: 'Rename file',
 	description: '🚧 For UI mocking only',
-	icon: 'icon-address-white',
+	iconClass: 'icon-address-white',
 	color: 'var(--color-success)',
-	entites: [],
-	events: [],
 	operation: 'deny'
-})
-operationService.registerOperation({
-	class: 'OCA\\TestExample\\Operation2',
-	title: 'Notify me',
+}
+Operators['OCA\\TestExample\\Operation2'] = {
+	id: 'OCA\\TestExample\\Operation2',
+	name: 'Notify me',
 	description: '🚧 For UI mocking only',
-	icon: 'icon-comment-white',
+	iconClass: 'icon-comment-white',
 	color: 'var(--color-warning)',
-	entites: [],
-	events: [],
-	operation: 'deny'
-})
-operationService.registerOperation({
-	class: 'OCA\\TestExample\\Operation3',
-	title: 'Call a web hook',
-	description: '🚧 For UI mocking only',
-	icon: 'icon-category-integration icon-invert',
-	color: 'var(--color-primary)',
-	entites: [],
-	events: [],
 	operation: 'deny'
-})
-
-operationService.registerOperation({
-	class: 'OCA\\FilesAutomatedTagging\\Operation',
-	title: 'Tag a file',
+}
+Operators['OCA\\FilesAutomatedTagging\\Operation'] = {
+	id: 'OCA\\FilesAutomatedTagging\\Operation',
+	name: 'Tag a file',
 	description: 'Assign a tag to a file',
-	icon: 'icon-tag-white',
-	events: [
-		'WorkflowEngine_Entity_File::postWrite',
-		//'WorkflowEngine_Entity_File::postTagged',
-	],
+	iconClass: 'icon-tag-white',
+	color: 'var(--color-primary)',
 	options: Tag
-
-})
-
-operationService.registerOperation({
-	class: 'OCA\\WorkflowPDFConverter\\Operation',
-	title: 'Convert to PDF',
-	description: 'Generate a PDF file',
-	color: '#dc5047',
-	icon: 'icon-convert-pdf',
-	events: [
-		'WorkflowEngine_Entity_File::postWrite',
-		//EVENT_FILE_TAGGED
-	],
-	options: ConvertToPdf
-})
-
-console.debug('[InitialState] Entities', Entities)
-console.debug('[WorkflowEngine] Checks', Checks)
-console.debug('[WorkflowEngine] Operations', operationService.operations)
-
+}
 
 export {
-	Entities,
 	Checks,
-	operationService
+	Operators,
+	ALL_CHECKS
 }
diff --git a/apps/workflowengine/src/templates/operation.handlebars b/apps/workflowengine/src/templates/operation.handlebars
deleted file mode 100644
index 0899890cef2..00000000000
--- a/apps/workflowengine/src/templates/operation.handlebars
+++ /dev/null
@@ -1,45 +0,0 @@
-<div class="operation{{#if hasChanged}} modified{{/if}}">
-	<div class="operation-header">
-		<input type="text" class="operation-name" placeholder="{{shortRuleDescTXT}}" value="{{operation.name}}" />
-		<input type="text" class="operation-operation" value="{{operation.operation}}" />
-		{{! delete only makes sense if the operation is already saved }}
-		{{#if operation.id}}
-			<span class="button-delete icon-delete"></span>
-		{{/if}}
-	</div>
-
-	<div class="checks">
-		{{#each operation.checks}}
-			<div class="check" data-id="{{@index}}">
-				<select class="check-class">
-					{{#each ../classes}}
-						<option value="{{class}}" {{{selectItem class ../class}}}>{{name}}</option>
-					{{/each}}
-				</select>
-				<select class="check-operator">
-					{{#each (getOperators class)}}
-						<option value="{{operator}}" {{{selectItem operator ../operator}}}>{{name}}</option>
-					{{/each}}
-				</select>
-				<input type="text" class="check-value" value="{{value}}">
-				<span class="button-delete-check icon-delete"></span>
-			</div>
-		{{/each}}
-	</div>
-	<button class="button-add">{{addRuleTXT}}</button>
-	{{#if hasChanged}}
-	{{! reset only makes sense if the operation is already saved }}
-		{{#if operation.id}}
-			<button class="button-reset pull-right">{{resetTXT}}</button>
-		{{/if}}
-		<button class="button-save pull-right">{{saveTXT}}</button>
-	{{/if}}
-	{{#if saving}}
-		<span class="icon-loading-small pull-right"></span>
-		<span class="pull-right">{{savingTXT}}</span>
-	{{else}}{{#if message}}
-		<span class="msg pull-right {{#if errorMessage}}error{{else}}success{{/if}}">
-					{{message}}{{#if errorMessage}} {{errorMessage}}{{/if}}
-				</span>
-	{{/if}}{{/if}}
-</div>
diff --git a/apps/workflowengine/src/templates/operations.handlebars b/apps/workflowengine/src/templates/operations.handlebars
deleted file mode 100644
index 14b62ee79a6..00000000000
--- a/apps/workflowengine/src/templates/operations.handlebars
+++ /dev/null
@@ -1,2 +0,0 @@
-<div class="operations"></div>
-<button class="button-add-operation">{{addRuleGroupTXT}}</button>
diff --git a/apps/workflowengine/src/usergroupmembershipplugin.js b/apps/workflowengine/src/usergroupmembershipplugin.js
deleted file mode 100644
index 53f35fedf2d..00000000000
--- a/apps/workflowengine/src/usergroupmembershipplugin.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {};
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
-	OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin = {
-		getCheck: function() {
-			return {
-				'class': 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
-				'name': t('workflowengine', 'User group membership'),
-				'operators': [
-					{'operator': 'is', 'name': t('workflowengine', 'is member of')},
-					{'operator': '!is', 'name': t('workflowengine', 'is not member of')}
-				]
-			};
-		},
-		render: function(element, check, groups) {
-			if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') {
-				return;
-			}
-
-			$(element).css('width', '400px');
-
-			$(element).select2({
-				data: { results: groups, text: 'displayname' },
-				initSelection: function (element, callback) {
-					var groupId = element.val();
-					if (groupId && groups.length > 0) {
-						callback({
-							id: groupId,
-							displayname: groups.find(function (group) {
-								return group.id === groupId;
-							}).displayname
-						});
-					} else if (groupId) {
-						callback({
-							id: groupId,
-							displayname: groupId
-						});
-					} else {
-						callback();
-					}
-				},
-				formatResult: function (element) {
-					return '<span>' + escapeHTML(element.displayname) + '</span>';
-				},
-				formatSelection: function (element) {
-					return '<span title="'+escapeHTML(element.id)+'">'+escapeHTML(element.displayname)+'</span>';
-				}
-			});
-		}
-	};
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin);
diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js
index 6c7af2a446e..866f049b0bf 100644
--- a/apps/workflowengine/src/workflowengine.js
+++ b/apps/workflowengine/src/workflowengine.js
@@ -1,19 +1,25 @@
-import './filemimetypeplugin'
-import './filenameplugin'
-import './filesizeplugin'
-import './filesystemtagsplugin'
-import './requestremoteaddressplugin'
-import './requesttimeplugin'
-import './requesturlplugin'
-import './requestuseragentplugin'
-import './usergroupmembershipplugin'
+import './legacy/filemimetypeplugin'
+import './legacy/filenameplugin'
+import './legacy/filesizeplugin'
+import './legacy/filesystemtagsplugin'
+import './legacy/requestremoteaddressplugin'
+import './legacy/requesttimeplugin'
+import './legacy/requesturlplugin'
+import './legacy/requestuseragentplugin'
+import './legacy/usergroupmembershipplugin'
 
-window.OCA.WorkflowEngine = OCA.WorkflowEngine
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+import store from './store'
 
-import Vue from 'vue';
+import Settings from './components/Workflow'
 
-Vue.prototype.t = t;
+window.OCA.WorkflowEngine = OCA.WorkflowEngine
+Vue.use(Vuex)
 
-import Settings from './components/Workflow';
+Vue.prototype.t = t
 const View = Vue.extend(Settings)
-new View({}).$mount('#workflowengine')
\ No newline at end of file
+new View({
+	store
+}).$mount('#workflowengine')
-- 
cgit v1.2.3


From 28a7721b2b485e2ad842d91001601e5e35f71b70 Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Sat, 31 Aug 2019 11:53:15 +0200
Subject: Handle operator registration properly
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/components/Check.vue       |  3 +-
 apps/workflowengine/src/components/Event.vue       | 16 +++++++--
 apps/workflowengine/src/components/Operation.vue   |  1 +
 apps/workflowengine/src/components/Rule.vue        | 39 +++++++++++++++++-----
 .../src/components/Values/SizeValue.vue            | 19 +++++++++--
 apps/workflowengine/src/services/Operation.js      | 30 +----------------
 apps/workflowengine/src/store.js                   | 13 +++++---
 apps/workflowengine/src/workflowengine.js          |  6 +++-
 8 files changed, 78 insertions(+), 49 deletions(-)

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue
index 5f8140f2222..8583288016f 100644
--- a/apps/workflowengine/src/components/Check.vue
+++ b/apps/workflowengine/src/components/Check.vue
@@ -89,11 +89,12 @@ export default {
 		width: 100%;
 		padding-right: 20px;
 		& > *:not(.icon-delete) {
-			width: 200px;
+			width: 180px;
 		}
 		& > .multiselect,
 		& > input[type=text] {
 			margin-right: 5px;
+			margin-bottom: 5px;
 		}
 	}
 	input[type=text] {
diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue
index 2621b8ca3ba..5ff59882b9a 100644
--- a/apps/workflowengine/src/components/Event.vue
+++ b/apps/workflowengine/src/components/Event.vue
@@ -1,6 +1,6 @@
 <template>
 	<div>
-		<div v-if="operation.isComplex && operation.fixedEntity !== ''">
+		<div v-if="operation.isComplex && operation.fixedEntity !== ''" class="isComplex">
 			<img class="option__icon" :src="entity.icon">
 			<span class="option__title option__title_single">{{ operation.triggerHint }}</span>
 		</div>
@@ -60,7 +60,19 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+	.isComplex {
+		img {
+			vertical-align: top;
+			padding-top: 4px;
+			padding-bottom: 4px;
+			padding-left: 4px;
+		}
+		span {
+			padding-top: 2px;
+			display: inline-block;
+		}
+	}
 	.multiselect::v-deep .multiselect__single {
 		display: flex;
 	}
diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
index 1ee7a2e4c9d..346b28e6b9d 100644
--- a/apps/workflowengine/src/components/Operation.vue
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -59,6 +59,7 @@ export default {
 	}
 
 	.colored {
+		background-color: var(--color-primary-element);
 		* {
 			color: var(--color-primary-text)
 		}
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
index 6a8757c5b3f..f1551d985ca 100644
--- a/apps/workflowengine/src/components/Rule.vue
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -1,7 +1,7 @@
 <template>
 	<div class="section rule" :style="{ borderLeftColor: operation.color }">
 		<!-- TODO: icon-confirm -->
-		<div class="trigger icon-confirm">
+		<div class="trigger">
 			<p>
 				<span>{{ t('workflowengine', 'When') }}</span>
 				<Event :rule="rule" @update="updateRule" />
@@ -12,10 +12,11 @@
 			</p>
 			<p>
 				<span />
-				<di<input v-if="lastCheckComplete" type="button" class="check--add"
+				<input v-if="lastCheckComplete" type="button" class="check--add"
 					value="Add a new filter" @click="rule.checks.push({class: null, operator: null, value: null})">
 			</p>
 		</div>
+		<div class="flow-icon icon-confirm"></div>
 		<div class="action">
 			<div class="buttons">
 				<Actions>
@@ -26,15 +27,15 @@
 						Remove rule
 					</ActionButton>
 				</Actions>
-				<button v-tooltip="ruleStatus.tooltip" class="status-button icon" :class="ruleStatus.class"
-					@click="saveRule">
-					{{ ruleStatus.title }}
-				</button>
 			</div>
 			<Operation :operation="operation" :colored="false">
 				<component :is="operation.options" v-if="operation.options" v-model="rule.operation"
 					@input="updateOperation" />
 			</Operation>
+			<button v-tooltip="ruleStatus.tooltip" class="status-button icon" :class="ruleStatus.class"
+					@click="saveRule">
+				{{ ruleStatus.title }}
+			</button>
 		</div>
 	</div>
 </template>
@@ -152,6 +153,8 @@ export default {
 
 	.status-button {
 		transition: 0.5s ease all;
+		display: block;
+		margin: auto;
 	}
 	.status-button.primary {
 		padding-left: 32px;
@@ -166,15 +169,19 @@ export default {
 		border: none;
 	}
 
+	.flow-icon {
+		width: 44px;
+	}
+
 	.rule {
 		display: flex;
 		flex-wrap: wrap;
-		border-left: 5px solid #fff;
+		border-left: 5px solid var(--color-primary-element);
 
 		.trigger, .action {
 			flex-grow: 1;
 			min-height: 100px;
-			max-width: 900px;
+			max-width: 700px;
 		}
 		.action {
 			max-width: 400px;
@@ -214,10 +221,24 @@ export default {
 		background-color: transparent;
 		padding-left: 6px;
 		margin: 0;
-		width: 200px;
+		width: 180px;
 		border-radius: var(--border-radius);
 		font-weight: normal;
 		text-align: left;
 		font-size: 1em;
 	}
+
+	@media (max-width:1400px) {
+		.rule {
+			&, .trigger, .action {
+				width: 100%;
+				max-width: 100%;
+			}
+			.flow-icon {
+				display: none;
+			}
+		}
+	}
+
+
 </style>
diff --git a/apps/workflowengine/src/components/Values/SizeValue.vue b/apps/workflowengine/src/components/Values/SizeValue.vue
index 03b28f18947..3e59b70453f 100644
--- a/apps/workflowengine/src/components/Values/SizeValue.vue
+++ b/apps/workflowengine/src/components/Values/SizeValue.vue
@@ -1,6 +1,6 @@
 <template>
 	<input v-model="newValue" type="text" placeholder="1 MB"
-		@input="$emit('input', newValue)">
+		@input="update">
 </template>
 
 <script>
@@ -8,11 +8,13 @@ export default {
 	name: 'SizeValue',
 	props: {
 		value: {
-			type: String
+			type: String,
+			default: '1 MB'
 		}
 	},
 	data() {
 		return {
+			valid: false,
 			newValue: this.value
 		}
 	},
@@ -20,6 +22,19 @@ export default {
 		value() {
 			this.newValue = this.value
 		}
+	},
+	methods: {
+		validate() {
+			return this.newValue.match(/^[0-9]+[ ]?[kmgt]?b$/i) !== null
+		},
+		update() {
+			if (this.validate()) {
+				this.$emit('input', this.newValue)
+				this.valid = false
+			} else {
+				this.valid = false
+			}
+		}
 	}
 }
 </script>
diff --git a/apps/workflowengine/src/services/Operation.js b/apps/workflowengine/src/services/Operation.js
index ed996593cb4..58c20d6db14 100644
--- a/apps/workflowengine/src/services/Operation.js
+++ b/apps/workflowengine/src/services/Operation.js
@@ -1,6 +1,3 @@
-import ConvertToPdf from './../components/Operations/ConvertToPdf'
-import Tag from './../components/Operations/Tag'
-
 const ALL_CHECKS = [
 	'OCA\\WorkflowEngine\\Check\\FileMimeType',
 	'OCA\\WorkflowEngine\\Check\\FileName',
@@ -13,27 +10,10 @@ const ALL_CHECKS = [
 	'OCA\\WorkflowEngine\\Check\\UserGroupMembership'
 ]
 
-const Operators = OCP.InitialState.loadState('workflowengine', 'operators')
-
+const Operators = {}
 /**
  * Extend operators for testing
  */
-Operators['OCA\\FilesAccessControl\\Operation'] = {
-	...Operators['OCA\\FilesAccessControl\\Operation'],
-	color: 'var(--color-error)',
-	entities: [
-		'OCA\\WorkflowEngine\\Entity\\File'
-	],
-	operation: 'deny'
-}
-Operators['OCA\\WorkflowPDFConverter\\Operation'] = {
-	id: 'OCA\\WorkflowPDFConverter\\Operation',
-	name: 'Convert to PDF',
-	description: 'Generate a PDF file',
-	color: '#dc5047',
-	iconClass: 'icon-convert-pdf',
-	options: ConvertToPdf
-}
 
 Operators['OCA\\TestExample\\Operation1'] = {
 	id: 'OCA\\TestExample\\Operation1',
@@ -51,14 +31,6 @@ Operators['OCA\\TestExample\\Operation2'] = {
 	color: 'var(--color-warning)',
 	operation: 'deny'
 }
-Operators['OCA\\FilesAutomatedTagging\\Operation'] = {
-	id: 'OCA\\FilesAutomatedTagging\\Operation',
-	name: 'Tag a file',
-	description: 'Assign a tag to a file',
-	iconClass: 'icon-tag-white',
-	color: 'var(--color-primary)',
-	options: Tag
-}
 
 export {
 	Operators,
diff --git a/apps/workflowengine/src/store.js b/apps/workflowengine/src/store.js
index 34cee256757..b38bef0e6ea 100644
--- a/apps/workflowengine/src/store.js
+++ b/apps/workflowengine/src/store.js
@@ -24,7 +24,7 @@ import Vue from 'vue'
 import Vuex from 'vuex'
 import axios from 'nextcloud-axios'
 import { getApiUrl } from './helpers/api'
-import { ALL_CHECKS, Operators } from './services/Operation'
+import { ALL_CHECKS } from './services/Operation'
 import confirmPassword from 'nextcloud-password-confirmation'
 
 Vue.use(Vuex)
@@ -33,8 +33,7 @@ const store = new Vuex.Store({
 	state: {
 		rules: [],
 		scope: OCP.InitialState.loadState('workflowengine', 'scope'),
-		// TODO: move to backend data
-		operations: Operators,
+		operations: OCP.InitialState.loadState('workflowengine', 'operators'),
 
 		plugins: Vue.observable({
 			checks: {},
@@ -74,7 +73,10 @@ const store = new Vuex.Store({
 			Vue.set(state.plugins.checks, plugin.class, plugin)
 		},
 		addPluginOperator(state, plugin) {
-			Vue.set(state.plugins.operators, plugin.class, plugin)
+			plugin = Object.assign(
+				{ color: 'var(--color-primary-element)' },
+				plugin, state.operations[plugin.id] || {})
+			Vue.set(state.operations, plugin.id, plugin)
 		}
 	},
 	actions: {
@@ -88,7 +90,8 @@ const store = new Vuex.Store({
 			let entity = null
 			let events = []
 			if (rule.isComplex === false && rule.fixedEntity === '') {
-				entity = context.state.entities.find((item) => rule.entities[0] === item.id)
+				entity = context.state.entities.find((item) => rule.entities && rule.entities[0] === item.id)
+				entity = entity ? entity : Object.values(context.state.entities)[0]
 				events = [entity.events[0].eventName]
 			}
 
diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js
index 6e22852b829..cae85c3c24a 100644
--- a/apps/workflowengine/src/workflowengine.js
+++ b/apps/workflowengine/src/workflowengine.js
@@ -4,6 +4,7 @@ import store from './store'
 
 import Settings from './components/Workflow'
 import FileValues from './components/Values/file'
+import {Operators} from './services/Operation';
 
 /**
  * A plugin for displaying a custom value field for checks
@@ -21,7 +22,7 @@ import FileValues from './components/Values/file'
  * A plugin for extending the admin page repesentation of a operator
  *
  * @typedef {Object} OperatorPlugin
- * @property {string} class - The PHP class name of the check
+ * @property {string} id - The PHP class name of the check
  * @property {string} operation - Default value for the operation field
  * @property {string} color - Custom color code to be applied for the operator selector
  * @property {Vue} component - A vue component to handle the rendering of options
@@ -40,6 +41,8 @@ import FileValues from './components/Values/file'
  * Public javascript api for apps to register custom plugins
  */
 window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, {
+
+
 	/**
 	 *
 	 * @param {CheckPlugin} Plugin
@@ -58,6 +61,7 @@ window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, {
 
 // Register shipped checks for file entity
 FileValues.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(checkPlugin))
+Object.values(Operators).forEach((operatorPlugin) => window.OCA.WorkflowEngine.registerOperator(operatorPlugin))
 
 Vue.use(Vuex)
 Vue.prototype.t = t
-- 
cgit v1.2.3


From 2364fc84d78df931c5fc020f7fd052d980f8a7bb Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Mon, 2 Sep 2019 12:11:19 +0200
Subject: Make rule listing more compact
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/components/Operation.vue | 20 ++++++++++++++++++--
 apps/workflowengine/src/components/Rule.vue      |  4 ++--
 2 files changed, 20 insertions(+), 4 deletions(-)

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
index 346b28e6b9d..45b0f24223d 100644
--- a/apps/workflowengine/src/components/Operation.vue
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -1,8 +1,10 @@
 <template>
 	<div class="actions__item" :class="{'colored': colored}" :style="{ backgroundColor: colored ? operation.color : 'transparent' }">
 		<div class="icon" :class="operation.iconClass" :style="{ backgroundImage: operation.iconClass ? '' : `url(${operation.icon})` }" />
+		<div class="actions__item__description">
 		<h3>{{ operation.name }}</h3>
 		<small>{{ operation.description }}</small>
+		</div>
 		<slot />
 	</div>
 </template>
@@ -32,7 +34,7 @@ export default {
 		padding: 10px;
 		border-radius: var(--border-radius-large);
 		margin-right: 20px;
-		margin-bottom: 20px;
+		margin-bottom: 5px;
 	}
 	.icon {
 		display: block;
@@ -44,9 +46,11 @@ export default {
 		margin-bottom: 20px;
 		background-repeat: no-repeat;
 	}
+	.actions__item__description {
+		text-align: center;
+	}
 	h3, small {
 		padding: 6px;
-		text-align: center;
 		display: block;
 	}
 	h3 {
@@ -66,7 +70,19 @@ export default {
 	}
 
 	.actions__item:not(.colored) {
+		flex-direction: row;
+
+		.actions__item__description {
+			padding-top: 5px;
+			text-align: left;
+			small {
+				padding: 0;
+			}
+		}
 		.icon {
+			width: 50px;
+			margin: 0;
+			margin-right: 10px;
 			filter: invert(1);
 		}
 	}
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
index 88129525eb3..d3cf7291af4 100644
--- a/apps/workflowengine/src/components/Rule.vue
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -155,6 +155,7 @@ export default {
 		transition: 0.5s ease all;
 		display: block;
 		margin: auto;
+		margin-right: 0;
 	}
 	.status-button.primary {
 		padding-left: 32px;
@@ -194,7 +195,7 @@ export default {
 			}
 		}
 		.icon-confirm {
-			background-position: right center;
+			background-position: right 27px;
 			padding-right: 20px;
 			margin-right: 20px;
 		}
@@ -202,7 +203,6 @@ export default {
 	.trigger p, .action p {
 		display: flex;
 		align-items: center;
-		margin-bottom: 5px;
 
 		& > span {
 			min-width: 50px;
-- 
cgit v1.2.3


From 24aec9b9d27378ab19ebe028f46f26bcf0a1b901 Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Mon, 9 Sep 2019 13:53:03 +0200
Subject: Frontend polishing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/components/Check.vue       |  4 ++
 .../src/components/Checks/RequestTime.vue          |  2 +-
 .../src/components/Checks/request.js               |  4 +-
 apps/workflowengine/src/components/Operation.vue   | 17 ++++++--
 .../src/components/Operations/ConvertToPdf.vue     | 46 ----------------------
 .../src/components/Operations/Tag.vue              | 31 ---------------
 apps/workflowengine/src/components/Rule.vue        | 10 +++--
 apps/workflowengine/src/components/Workflow.vue    |  6 +--
 apps/workflowengine/src/workflowengine.js          | 23 -----------
 apps/workflowengine/webpack.js                     | 12 ------
 10 files changed, 27 insertions(+), 128 deletions(-)
 delete mode 100644 apps/workflowengine/src/components/Operations/ConvertToPdf.vue
 delete mode 100644 apps/workflowengine/src/components/Operations/Tag.vue

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue
index 508b8a4a1f4..8f7fbca2865 100644
--- a/apps/workflowengine/src/components/Check.vue
+++ b/apps/workflowengine/src/components/Check.vue
@@ -138,6 +138,10 @@ export default {
 		margin-top: -5px;
 		margin-bottom: -5px;
 	}
+	button.action-item.action-item--single.icon-delete {
+		height: 34px;
+		width: 34px;
+	}
 	.invalid {
 		border: 1px solid var(--color-error) !important;
 	}
diff --git a/apps/workflowengine/src/components/Checks/RequestTime.vue b/apps/workflowengine/src/components/Checks/RequestTime.vue
index 2f09693232a..9ea211874fe 100644
--- a/apps/workflowengine/src/components/Checks/RequestTime.vue
+++ b/apps/workflowengine/src/components/Checks/RequestTime.vue
@@ -74,7 +74,7 @@ export default {
 		display: flex;
 		flex-grow: 1;
 		flex-wrap: wrap;
-		max-width: 200px;
+		max-width: 180px;
 
 		.multiselect {
 			width: 100%;
diff --git a/apps/workflowengine/src/components/Checks/request.js b/apps/workflowengine/src/components/Checks/request.js
index d2e4eaa3565..8b36b89a9e8 100644
--- a/apps/workflowengine/src/components/Checks/request.js
+++ b/apps/workflowengine/src/components/Checks/request.js
@@ -42,7 +42,6 @@ const RequestChecks = [
 			{ operator: 'in', name: t('workflowengine', 'between') },
 			{ operator: '!in', name: t('workflowengine', 'not between') }
 		],
-		// TODO: implement component
 		component: RequestTime
 	},
 	{
@@ -54,7 +53,8 @@ const RequestChecks = [
 			{ operator: 'matches', name: t('workflowengine', 'matches') },
 			{ operator: '!matches', name: t('workflowengine', 'does not match') }
 		],
-		component: RequestUserAgent
+		// TODO: implement component
+		// component: RequestUserAgent
 	},
 	{
 		class: 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
index 45b0f24223d..3cd7378f0df 100644
--- a/apps/workflowengine/src/components/Operation.vue
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -5,7 +5,9 @@
 		<h3>{{ operation.name }}</h3>
 		<small>{{ operation.description }}</small>
 		</div>
-		<slot />
+		<div class="actions__item_options">
+			<slot />
+		</div>
 	</div>
 </template>
 
@@ -28,13 +30,14 @@ export default {
 <style scoped lang="scss">
 	.actions__item {
 		display: flex;
+		flex-wrap: wrap;
 		flex-direction: column;
 		flex-grow: 1;
 		margin-left: -1px;
 		padding: 10px;
 		border-radius: var(--border-radius-large);
 		margin-right: 20px;
-		margin-bottom: 5px;
+		margin-bottom: 20px;
 	}
 	.icon {
 		display: block;
@@ -49,6 +52,10 @@ export default {
 	.actions__item__description {
 		text-align: center;
 	}
+	.actions__item_options {
+		width: 100%;
+		margin-top: 10px;
+	}
 	h3, small {
 		padding: 6px;
 		display: block;
@@ -83,7 +90,9 @@ export default {
 			width: 50px;
 			margin: 0;
 			margin-right: 10px;
-			filter: invert(1);
+			&:not(.icon-invert) {
+				filter: invert(1);
+			}
 		}
 	}
 
@@ -94,7 +103,7 @@ export default {
 	.icon-convert-pdf {
 		background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' version='1.1' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23fff'%3E%3Cpath d='m7.0624 2.9056c-0.20526 0-0.36653 0.14989-0.36653 0.34066v8.8571c0 0.19077 0.16127 0.34066 0.36653 0.34066h8.0637c0.20526 0 0.36653-0.14989 0.36653-0.34066v-7.1538l-2.1992-2.044zm4.2518 2.6571s0.05132 0.64725-0.10996 1.567c0.52414 1.3987 1.0996 1.5875 1.3562 1.7033 0.54247-0.040873 1.1472-0.068129 1.6861 0.2044 0.36653 0.19486 0.65536 1.022-0.21992 1.022-0.39586-0.023161-1.1267-0.23574-1.6494-0.47692-0.78145 0.081762-1.752 0.21802-2.5657 0.54505-0.91633 1.4308-1.3268 1.6352-1.6494 1.6352-0.89067-0.21802-0.41052-1.3149-0.073304-1.4989 0.40319-0.32022 0.87601-0.50417 1.0263-0.54505 0.065969-0.10221 1.0146-1.8327 1.2462-2.5549-0.21992-0.69767-0.27123-1.4349-0.14661-1.8736 0.57179-0.69358 1.0996-0.23846 1.0996 0.27253zm-0.51315 2.1121c-0.19793 0.72015-0.98817 2.1012-0.95299 2.044 0.81004-0.33044 1.5394-0.42923 2.3458-0.54505-0.38559-0.16011-0.84009-0.17033-1.3928-1.4989z' stroke-width='.70672'/%3E%3Cpath d='m16.246-9.7651c-2.05e-4 0.0144-6e-3 0.027629-6e-3 0.042066-0.0044 2.2592 2.0761 3.742 4.0564 3.6477v1.2349l2.3737-2.2265-2.3377-2.3407-3e-3 1.2289c-1.0287 0.1337-1.8811-0.66867-1.8659-1.5414 2.9e-4 -0.016152 0.0083-0.029062 9e-3 -0.045071z' stroke-width='.67694'/%3E%3Cpath d='m3.2734 5.1094v1.4492h-2.7676v2.5h2.7246l-0.0019532 1.4629 3.0996-2.6387-3.0547-2.7734z'/%3E%3Cpath d='m8.334-11.356c-0.78035-0.78051-1.9205-1.0863-2.9866-0.80073a0.51533 0.51533 0 1 0 0.26293 0.99405c0.71208-0.19075 1.4702 0.01747 1.9914 0.53876 0.46076 0.46083 0.65567 1.1026 0.56688 1.7376a0.61838 0.61838 0 1 0-0.87225 0.87442l0.8687 0.86886a0.61838 0.61838 0 0 0 0.86992 7.91e-5l0.86886-0.8687a0.61838 0.61838 0 0 0 0.0011543-0.88702 0.61838 0.61838 0 0 0-0.67634-0.12303c0.04094-0.86013-0.27221-1.7117-0.89472-2.3343zm-3.3067 1.0814a0.61838 0.61838 0 0 0-0.015967-0.01364l-0.86984-0.87a0.61838 0.61838 0 0 0-0.042126-0.04213 0.61838 0.61838 0 0 0-0.82551 0.04205l-0.87 0.86984a0.61838 0.61838 0 0 0 0.66145 1.0237c-0.024276 0.84049 0.29182 1.6675 0.90045 2.2762 0.78035 0.78052 1.9205 1.0863 2.9866 0.80073a0.51533 0.51533 0 1 0-0.27202-0.99408c-0.71208 0.19075-1.4669-0.011716-1.988-0.53306-0.45484-0.45491-0.65183-1.0905-0.57258-1.7183l0.018216 0.018221a0.61843 0.61843 0 0 0 0.88935-0.85959z' stroke-width='.68342'/%3E%3Cpath d='m31.219 0.33675v0.00113h-6.9286v1.3295l6.9286 0.036145c0.0026-1.821e-4 0.0053 2.074e-4 0.0079 0 0.0053-4.166e-4 0.01058-0.00137 0.01581-0.00113 0.65203-0.00106 1.1749 0.44619 1.1867 1.0392 0.0108 0.5673-0.60099 1.0888-1.3381 1.0019l-0.0013-0.79858-1.6753 1.5203 1.7016 1.4481-0.0013-0.8031c1.419 0.06127 2.9112-0.90236 2.9081-2.3709-0.0029-1.3197-1.2547-2.4007-2.7961-2.4014-0.0023-1e-6 -0.0043-0.00113-0.0066-0.00113z' stroke-width='.462'/%3E%3Crect x='31.116' y='-1.6777' width='4.3279' height='7.5909'/%3E%3C/g%3E%3C/svg%3E");
 	}
-	.icon-invert {
+	.colored .icon-invert {
 		filter: invert(1);
 	}
 </style>
diff --git a/apps/workflowengine/src/components/Operations/ConvertToPdf.vue b/apps/workflowengine/src/components/Operations/ConvertToPdf.vue
deleted file mode 100644
index 5fa1676e092..00000000000
--- a/apps/workflowengine/src/components/Operations/ConvertToPdf.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<template>
-	<Multiselect v-model="value" :options="options" track-by="id"
-		label="text" @input="$emit('input', value.id)" />
-</template>
-
-<script>
-import { Multiselect } from 'nextcloud-vue'
-
-const pdfConvertOptions = [
-	{
-		id: 'keep;preserve',
-		text: t('workflow_pdf_converter', 'Keep original, preserve existing PDFs')
-	},
-	{
-		id: 'keep;overwrite',
-		text: t('workflow_pdf_converter', 'Keep original, overwrite existing PDF')
-	},
-	{
-		id: 'delete;preserve',
-		text: t('workflow_pdf_converter', 'Delete original, preserve existing PDFs')
-	},
-	{
-		id: 'delete;overwrite',
-		text: t('workflow_pdf_converter', 'Delete original, overwrite existing PDF')
-	}
-]
-export default {
-	name: 'ConvertToPdf',
-	components: { Multiselect },
-	data() {
-		return {
-			options: pdfConvertOptions,
-			value: pdfConvertOptions[0]
-		}
-	}
-}
-</script>
-
-<style scoped>
-	.multiselect {
-		width: 100%;
-		max-width: 300px;
-		margin: auto;
-		text-align: center;
-	}
-</style>
diff --git a/apps/workflowengine/src/components/Operations/Tag.vue b/apps/workflowengine/src/components/Operations/Tag.vue
deleted file mode 100644
index 7a27e2b572d..00000000000
--- a/apps/workflowengine/src/components/Operations/Tag.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<template>
-	<Multiselect v-model="value" :options="options" track-by="id"
-		label="title" :multiple="true" :tagging="true"
-		@input="$emit('input', value.map(item => item.id))" />
-</template>
-
-<script>
-// TODO: fetch tags from endpoint
-import { Multiselect } from 'nextcloud-vue'
-
-const tags = [{ id: 3, title: 'foo' }, { id: 4, title: 'bar' }]
-export default {
-	name: 'Tag',
-	components: { Multiselect },
-	data() {
-		return {
-			options: tags,
-			value: null
-		}
-	}
-}
-</script>
-
-<style scoped>
-	.multiselect {
-		width: 100%;
-		max-width: 300px;
-		margin: auto;
-		text-align: center;
-	}
-</style>
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
index 82e19dbe82b..c5c6094879a 100644
--- a/apps/workflowengine/src/components/Rule.vue
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -1,6 +1,5 @@
 <template>
 	<div class="section rule" :style="{ borderLeftColor: operation.color || '' }">
-		<!-- TODO: icon-confirm -->
 		<div class="trigger">
 			<p>
 				<span>{{ t('workflowengine', 'When') }}</span>
@@ -21,10 +20,10 @@
 			<div class="buttons">
 				<Actions>
 					<ActionButton v-if="rule.id < -1" icon="icon-close" @click="cancelRule">
-						Cancel rule creation
+						{{ t('workflowengine', 'Cancel rule creation') }}
 					</ActionButton>
 					<ActionButton v-else icon="icon-close" @click="deleteRule">
-						Remove rule
+						{{ t('workflowengine', 'Remove rule') }}
 					</ActionButton>
 				</Actions>
 			</div>
@@ -204,6 +203,7 @@ export default {
 		}
 	}
 	.trigger p, .action p {
+		min-height: 34px;
 		display: flex;
 		align-items: center;
 
@@ -211,7 +211,9 @@ export default {
 			min-width: 50px;
 			text-align: right;
 			color: var(--color-text-maxcontrast);
-			padding-right: 5px;
+			padding-right: 10px;
+			padding-top: 7px;
+			margin-bottom: auto;
 		}
 		.multiselect {
 			flex-grow: 1;
diff --git a/apps/workflowengine/src/components/Workflow.vue b/apps/workflowengine/src/components/Workflow.vue
index 986b6f5197c..b4fab5a058c 100644
--- a/apps/workflowengine/src/components/Workflow.vue
+++ b/apps/workflowengine/src/components/Workflow.vue
@@ -80,15 +80,11 @@ export default {
 		flex-wrap: wrap;
 		max-width: 900px;
 		.actions__item {
-			max-width: 290px;
+			max-width: 280px;
 			flex-basis: 250px;
 		}
 	}
 
-	.actions__more {
-		text-align: center;
-	}
-
 	button.icon {
 		padding-left: 32px;
 		background-position: 10px center;
diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js
index 149f9d4aa85..2eb8161fc87 100644
--- a/apps/workflowengine/src/workflowengine.js
+++ b/apps/workflowengine/src/workflowengine.js
@@ -61,29 +61,6 @@ window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, {
 // Register shipped checks
 ShippedChecks.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(checkPlugin))
 
-/**
- * FIXME: remove before merge as this is for UI testing only
- */
-const demo = [
-	{
-		id: 'OCA\\TestExample\\Operation1',
-		name: 'Convert to PDF',
-		description: 'Convert a file to PDF using Libreoffice',
-		iconClass: 'icon-convert-pdf',
-		color: 'var(--color-success)',
-		operation: 'deny'
-	},
-	{
-		id: 'OCA\\TestExample\\Operation2',
-		name: 'Notify me',
-		description: 'Send a Nextcloud Notification',
-		iconClass: 'icon-comment-white',
-		color: 'var(--color-warning)',
-		operation: 'deny'
-	}
-]
-demo.forEach((operatorPlugin) => window.OCA.WorkflowEngine.registerOperator(operatorPlugin))
-
 Vue.use(Vuex)
 Vue.prototype.t = t
 
diff --git a/apps/workflowengine/webpack.js b/apps/workflowengine/webpack.js
index 76e46261f93..bbd5efa9799 100644
--- a/apps/workflowengine/webpack.js
+++ b/apps/workflowengine/webpack.js
@@ -7,17 +7,5 @@ module.exports = {
 		publicPath: '/js/',
 		filename: 'workflowengine.js',
 		jsonpFunction: 'webpackJsonpWorkflowengine'
-	},
-	module: {
-		rules: [
-			{
-				test: /\.handlebars/,
-				loader: "handlebars-loader",
-				query: {
-					extensions: '.handlebars',
-					helperDirs: path.join(__dirname, 'src/hbs_helpers'),
-				}
-			}
-		]
 	}
 }
-- 
cgit v1.2.3


From e17a6665177f6c62ce5a067d0738f11f33021a8f Mon Sep 17 00:00:00 2001
From: Julius Härtl <jus@bitgrid.net>
Date: Mon, 9 Sep 2019 15:04:51 +0200
Subject: Implement custom check components and fix linting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 apps/workflowengine/src/components/Check.vue       |  28 +--
 .../src/components/Checks/FileMimeType.vue         |  60 ++++---
 .../src/components/Checks/FileSystemTag.vue        |  68 -------
 .../src/components/Checks/RequestTime.vue          |  28 +--
 .../src/components/Checks/RequestUserAgent.vue     |  85 +++++----
 apps/workflowengine/src/components/Checks/file.js  |   6 +-
 .../src/components/Checks/request.js               |  11 +-
 apps/workflowengine/src/components/Operation.vue   |   4 +-
 apps/workflowengine/src/components/Rule.vue        |  10 +-
 apps/workflowengine/src/helpers/validators.js      |   1 -
 .../src/legacy/filesystemtagsplugin.js             |  78 --------
 .../workflowengine/src/legacy/requesttimeplugin.js | 196 ---------------------
 apps/workflowengine/src/legacy/requesturlplugin.js | 116 ------------
 .../src/legacy/requestuseragentplugin.js           | 119 -------------
 .../src/legacy/usergroupmembershipplugin.js        |  75 --------
 apps/workflowengine/src/store.js                   |   4 +-
 16 files changed, 129 insertions(+), 760 deletions(-)
 delete mode 100644 apps/workflowengine/src/components/Checks/FileSystemTag.vue
 delete mode 100644 apps/workflowengine/src/legacy/filesystemtagsplugin.js
 delete mode 100644 apps/workflowengine/src/legacy/requesttimeplugin.js
 delete mode 100644 apps/workflowengine/src/legacy/requesturlplugin.js
 delete mode 100644 apps/workflowengine/src/legacy/requestuseragentplugin.js
 delete mode 100644 apps/workflowengine/src/legacy/usergroupmembershipplugin.js

(limited to 'apps/workflowengine/src/components/Operation.vue')

diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue
index 8f7fbca2865..905c2c989b1 100644
--- a/apps/workflowengine/src/components/Check.vue
+++ b/apps/workflowengine/src/components/Check.vue
@@ -3,12 +3,17 @@
 		<Multiselect ref="checkSelector" v-model="currentOption" :options="options"
 			label="name" track-by="class" :allow-empty="false"
 			:placeholder="t('workflowengine', 'Select a filter')" @input="updateCheck" />
-		<Multiselect :disabled="!currentOption" v-model="currentOperator" :options="operators"
+		<Multiselect v-model="currentOperator" :disabled="!currentOption" :options="operators"
 			label="name" track-by="operator" :allow-empty="false"
 			:placeholder="t('workflowengine', 'Select a comparator')" @input="updateCheck" />
-		<component :is="currentOption.component" v-if="currentOperator && currentComponent" v-model="check.value" :disabled="!currentOption" :check="check" @valid="valid=true && validate()" @invalid="valid=false && validate()" />
-		<input v-else v-model="check.value" type="text" :class="{ invalid: !valid }"
-			@input="updateCheck" :disabled="!currentOption" :placeholder="valuePlaceholder">
+		<component :is="currentOption.component" v-if="currentOperator && currentComponent" v-model="check.value"
+			:disabled="!currentOption" :check="check"
+			@input="updateCheck"
+			@valid="(valid=true) && validate()"
+			@invalid="(valid=false) && validate()" />
+		<input v-else v-model="check.value" type="text"
+			:class="{ invalid: !valid }"
+			:disabled="!currentOption" :placeholder="valuePlaceholder" @input="updateCheck">
 		<Actions>
 			<ActionButton v-if="deleteVisible || !currentOption" icon="icon-delete" @click="$emit('remove')" />
 		</Actions>
@@ -47,7 +52,7 @@ export default {
 			currentOption: null,
 			currentOperator: null,
 			options: [],
-			valid: true,
+			valid: true
 		}
 	},
 	computed: {
@@ -67,6 +72,12 @@ export default {
 			if (this.currentOption && this.currentOption.placeholder) {
 				return this.currentOption.placeholder(this.check)
 			}
+			return ''
+		}
+	},
+	watch: {
+		'check.operator': function() {
+			this.validate()
 		}
 	},
 	mounted() {
@@ -74,11 +85,6 @@ export default {
 		this.currentOption = this.Checks[this.check.class]
 		this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
 	},
-	watch: {
-		'check.operator': function () {
-			this.validate()
-		}
-	},
 	methods: {
 		showDelete() {
 			this.deleteVisible = true
@@ -88,7 +94,7 @@ export default {
 		},
 		validate() {
 			if (this.currentOption && this.currentOption.validate) {
-				if(this.currentOption.validate(this.check)) {
+				if (this.currentOption.validate(this.check)) {
 					this.valid = true
 				} else {
 					this.valid = false
diff --git a/apps/workflowengine/src/components/Checks/FileMimeType.vue b/apps/workflowengine/src/components/Checks/FileMimeType.vue
index 02e68db1783..a3e3798304d 100644
--- a/apps/workflowengine/src/components/Checks/FileMimeType.vue
+++ b/apps/workflowengine/src/components/Checks/FileMimeType.vue
@@ -1,35 +1,39 @@
 <template>
 	<div>
-		<multiselect
+		<Multiselect
 			:value="currentValue"
-			placeholder="Select a user agent"
+			:placeholder="t('workflowengine', 'Select a file type')"
 			label="label"
 			track-by="pattern"
-			:options="options" :multiple="false" :tagging="false" @input="setValue">
+			:options="options" :multiple="false" :tagging="false"
+			@input="setValue">
 			<template slot="singleLabel" slot-scope="props">
-				<span class="option__icon" :class="props.option.icon"></span>
+				<span class="option__icon" :class="props.option.icon" />
 				<span class="option__title option__title_single">{{ props.option.label }}</span>
 			</template>
 			<template slot="option" slot-scope="props">
-				<span class="option__icon" :class="props.option.icon"></span>
+				<span class="option__icon" :class="props.option.icon" />
 				<span class="option__title">{{ props.option.label }}</span>
 			</template>
-		</multiselect>
-		<input type="text" :value="currentValue.pattern" @input="updateCustom"/>
+		</Multiselect>
+		<input type="text" :value="currentValue.pattern" @input="updateCustom" v-if="!isPredefined">
 	</div>
 </template>
 
 <script>
-	import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import valueMixin from './../../mixins/valueMixin'
 
 export default {
 	name: 'FileMimeType',
 	components: {
 		Multiselect
 	},
+	mixins: [
+		valueMixin
+	],
 	data() {
 		return {
-			value: '',
 			predefinedTypes: [
 				{
 					icon: 'icon-picture',
@@ -53,23 +57,29 @@ export default {
 		options() {
 			return [...this.predefinedTypes, this.customValue]
 		},
+		isPredefined() {
+			const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern)
+			if (matchingPredefined) {
+				return true
+			}
+			return false
+		},
 		customValue() {
-			const matchingPredefined = this.predefinedTypes.find((type) => this.value.pattern === type.pattern)
 			return {
 				icon: 'icon-settings-dark',
-				label: t('workflowengine', 'Custom pattern'),
-				pattern: '',
+				label: t('workflowengine', 'Custom mimetype'),
+				pattern: ''
 			}
 		},
 		currentValue() {
-			const matchingPredefined = this.predefinedTypes.find((type) => this.value === type.pattern)
+			const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern)
 			if (matchingPredefined) {
 				return matchingPredefined
 			}
 			return {
 				icon: 'icon-settings-dark',
-				label: t('workflowengine', 'Custom pattern'),
-				pattern: this.value,
+				label: t('workflowengine', 'Custom mimetype'),
+				pattern: this.newValue
 			}
 		}
 	},
@@ -79,25 +89,19 @@ export default {
 			var result = regexRegex.exec(string)
 			return result !== null
 		},
-		setValue (value) {
+		setValue(value) {
 			// TODO: check if value requires a regex and set the check operator according to that
 			if (value !== null) {
-				this.value = value.pattern
+				this.newValue = value.pattern
+				this.$emit('input', this.newValue)
 			}
 		},
-		updateCustom (event) {
-			console.log(event)
-			this.value = event.target.value
+		updateCustom(event) {
+			this.newValue = event.target.value
+			this.$emit('input', this.newValue)
 		}
 	}
 }
 </script>
 
-<style scoped>
-	.multiselect::v-deep .multiselect__single {
-		display: flex;
-	}
-	input, .multiselect {
-		width: 100%;
-	}
-</style>
+<style scoped src="./../../css/multiselect.css"></style>
diff --git a/apps/workflowengine/src/components/Checks/FileSystemTag.vue b/apps/workflowengine/src/components/Checks/FileSystemTag.vue
deleted file mode 100644
index b8c24bf118a..00000000000
--- a/apps/workflowengine/src/components/Checks/FileSystemTag.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-<!--
-  - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
-  -
-  - @author Julius Härtl <jus@bitgrid.net>
-  -
-  - @license GNU AGPL version 3 or any later version
-  -
-  - This program is free software: you can redistribute it and/or modify
-  - it under the terms of the GNU Affero General Public License as
-  - published by the Free Software Foundation, either version 3 of the
-  - License, or (at your option) any later version.
-  -
-  - This program is distributed in the hope that it will be useful,
-  - but WITHOUT ANY WARRANTY; without even the implied warranty of
-  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-  - GNU Affero General Public License for more details.
-  -
-  - You should have received a copy of the GNU Affero General Public License
-  - along with this program. If not, see <http://www.gnu.org/licenses/>.
-  -
-  -->
-
-<template>
-	<MultiselectTag v-model="newValue" :multiple="false" type="text" placeholder="1 MB"
-					@input="update"/>
-</template>
-
-<script>
-import { MultiselectTag } from 'nextcloud-vue'
-
-export default {
-	name: 'SizeValue',
-	components: {
-		MultiselectTag
-	},
-	props: {
-		value: {
-			type: String,
-			default: ''
-		}
-	},
-	data() {
-		return {
-			valid: false,
-			newValue: this.value
-		}
-	},
-	watch: {
-		value() {
-			this.newValue = this.value
-		}
-	},
-	methods: {
-		update() {
-			if (this.validate()) {
-				this.$emit('input', this.newValue)
-				this.valid = false
-			} else {
-				this.valid = false
-			}
-		}
-	}
-}
-</script>
-
-<style scoped>
-
-</style>
diff --git a/apps/workflowengine/src/components/Checks/RequestTime.vue b/apps/workflowengine/src/components/Checks/RequestTime.vue
index 9ea211874fe..b150e67b004 100644
--- a/apps/workflowengine/src/components/Checks/RequestTime.vue
+++ b/apps/workflowengine/src/components/Checks/RequestTime.vue
@@ -1,14 +1,17 @@
 <template>
 	<div class="timeslot">
-		<multiselect v-model="newValue.timezone" :options="timezones"></multiselect>
-		<input type="text" class="timeslot--start" v-model="newValue.startTime" placeholder="08:00" @input="update"/>
-		<input type="text" v-model="newValue.endTime" placeholder="18:00" @input="update"/>
+		<Multiselect v-model="newValue.timezone" :options="timezones" />
+		<input v-model="newValue.startTime" type="text" class="timeslot--start"
+			placeholder="08:00" @input="update">
+		<input v-model="newValue.endTime" type="text" placeholder="18:00"
+			@input="update">
 	</div>
 </template>
 
 <script>
 import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
 import moment from 'moment-timezone'
+import valueMixin from '../../mixins/valueMixin';
 
 const zones = moment.tz.names()
 export default {
@@ -16,6 +19,9 @@ export default {
 	components: {
 		Multiselect
 	},
+	mixins: [
+		valueMixin
+	],
 	props: {
 		value: {
 			type: String,
@@ -29,7 +35,7 @@ export default {
 				startTime: null,
 				endTime: null,
 				timezone: moment.tz.guess()
-			},
+			}
 		}
 	},
 	computed: {
@@ -37,8 +43,8 @@ export default {
 			return zones
 		}
 	},
-	watch: {
-		'value': function(value) {
+	methods: {
+		updateInternalValue(value) {
 			var data = JSON.parse(value)
 			var startTime = data[0].split(' ', 2)[0]
 			var endTime = data[1].split(' ', 2)[0]
@@ -48,13 +54,11 @@ export default {
 				endTime: endTime,
 				timezone: timezone
 			}
-		}
-	},
-	methods: {
+		},
 		validate() {
-			return this.newValue.startTime && this.newValue.startTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null &&
-				this.newValue.endTime && this.newValue.endTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null &&
-				moment.tz.zone(this.newValue.timezone) !== null
+			return this.newValue.startTime && this.newValue.startTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null
+				&& this.newValue.endTime && this.newValue.endTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null
+				&& moment.tz.zone(this.newValue.timezone) !== null
 		},
 		update() {
 			if (this.validate()) {
diff --git a/apps/workflowengine/src/components/Checks/RequestUserAgent.vue b/apps/workflowengine/src/components/Checks/RequestUserAgent.vue
index 676a4c23178..c5ea6bd4eec 100644
--- a/apps/workflowengine/src/components/Checks/RequestUserAgent.vue
+++ b/apps/workflowengine/src/components/Checks/RequestUserAgent.vue
@@ -22,47 +22,51 @@
 
 <template>
 	<div>
-
-		<multiselect
+		<Multiselect
 			:value="currentValue"
-			placeholder="Select a file type"
+			:placeholder="t('workflowengine', 'Select a user agent')"
 			label="label"
 			track-by="pattern"
-			group-values="children",
-			group-label="text",
-			:options="options" :multiple="false" :tagging="false" @input="setValue">
+			group-values="children"
+			group-label="label"
+			:options="options" :multiple="false" :tagging="false"
+			@input="setValue">
 			<template slot="singleLabel" slot-scope="props">
-				<span class="option__icon" :class="props.option.icon"></span>
+				<span class="option__icon" :class="props.option.icon" />
 				<span class="option__title option__title_single">{{ props.option.label }}</span>
 			</template>
 			<template slot="option" slot-scope="props">
-				<span class="option__icon" :class="props.option.icon"></span>
-				<span class="option__title">{{ props.option.label }}</span>
+				<span class="option__icon" :class="props.option.icon" />
+				<span class="option__title">{{ props.option.label }} {{ props.option.$groupLabel }}</span>
 			</template>
-		</multiselect>
-		<input type="text" :value="currentValue.pattern" @input="updateCustom"/>
+		</Multiselect>
+		<input type="text" :value="currentValue.pattern" @input="updateCustom" v-if="!isPredefined">
 	</div>
 </template>
 
 <script>
 import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import valueMixin from '../../mixins/valueMixin';
 
 export default {
-	name: 'UserAgent',
+	name: 'RequestUserAgent',
 	components: {
 		Multiselect
 	},
+	mixins: [
+		valueMixin
+	],
 	data() {
 		return {
-			value: '',
+			newValue: '',
 			predefinedTypes: [
 				{
-					text: t('workflowengine', 'Sync clients'),
+					label: t('workflowengine', 'Sync clients'),
 					children: [
-						{ id: 'android', text: t('workflowengine', 'Android client') },
-						{ id: 'ios', text: t('workflowengine', 'iOS client') },
-						{ id: 'desktop', text: t('workflowengine', 'Desktop client') },
-						{ id: 'mail', text: t('workflowengine', 'Thunderbird & Outlook addons') }
+						{ pattern: 'android', label: t('workflowengine', 'Android client'), icon: 'icon-phone' },
+						{ pattern: 'ios', label: t('workflowengine', 'iOS client'), icon: 'icon-phone' },
+						{ pattern: 'desktop', label: t('workflowengine', 'Desktop client'), icon: 'icon-desktop' },
+						{ pattern: 'mail', label: t('workflowengine', 'Thunderbird & Outlook addons'), icon: 'icon-mail' }
 					]
 				}
 			]
@@ -72,23 +76,34 @@ export default {
 		options() {
 			return [...this.predefinedTypes, this.customValue]
 		},
+		isPredefined() {
+			const matchingPredefined = this.predefinedTypes.map(groups => groups.children).flat().find((type) => this.newValue === type.pattern)
+			if (matchingPredefined) {
+				return true
+			}
+			return false
+		},
 		customValue() {
-			const matchingPredefined = this.predefinedTypes.find((type) => this.value.pattern === type.pattern)
 			return {
-				icon: 'icon-settings-dark',
-				label: t('workflowengine', 'Custom pattern'),
-				pattern: '',
+				label: t('workflowengine', 'Others'),
+				children: [
+					{
+						icon: 'icon-settings-dark',
+						label: t('workflowengine', 'Custom user agent'),
+						pattern: ''
+					}
+				]
 			}
 		},
 		currentValue() {
-			const matchingPredefined = this.predefinedTypes.find((type) => this.value === type.pattern)
+			const matchingPredefined = this.predefinedTypes.map(groups => groups.children).flat().find((type) => this.newValue === type.pattern)
 			if (matchingPredefined) {
 				return matchingPredefined
 			}
 			return {
 				icon: 'icon-settings-dark',
-				label: t('workflowengine', 'Custom pattern'),
-				pattern: this.value,
+				label: t('workflowengine', 'Custom user agent'),
+				pattern: this.newValue,
 			}
 		}
 	},
@@ -98,25 +113,19 @@ export default {
 			var result = regexRegex.exec(string)
 			return result !== null
 		},
-		setValue (value) {
+		setValue(value) {
 			// TODO: check if value requires a regex and set the check operator according to that
 			if (value !== null) {
-				this.value = value.pattern
+				this.newValue = value.pattern
+				this.$emit('input', this.newValue)
 			}
 		},
-		updateCustom (event) {
-			console.log(event)
-			this.value = event.target.value
+		updateCustom(event) {
+			this.newValue = event.target.value
+			this.$emit('input', this.newValue)
 		}
 	}
 }
 </script>
 
-<style scoped>
-	.multiselect::v-deep .multiselect__single {
-		display: flex;
-	}
-	input, .multiselect {
-		width: 100%;
-	}
-</style>
+<style scoped src="./../../css/multiselect.css"></style>
diff --git a/apps/workflowengine/src/components/Checks/file.js b/apps/workflowengine/src/components/Checks/file.js
index 146ac045cee..816aaa73a65 100644
--- a/apps/workflowengine/src/components/Checks/file.js
+++ b/apps/workflowengine/src/components/Checks/file.js
@@ -20,8 +20,8 @@
  *
  */
 
-import FileMimeType from './FileMimeType';
-import { stringValidator, validateIPv4, validateIPv6} from './../../helpers/validators'
+import FileMimeType from './FileMimeType'
+import { stringValidator, validateIPv4, validateIPv6 } from './../../helpers/validators'
 const FileChecks = [
 	{
 		class: 'OCA\\WorkflowEngine\\Check\\FileName',
@@ -77,7 +77,7 @@ const FileChecks = [
 		],
 		placeholder: (check) => {
 			if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') {
-				return '::1/128';
+				return '::1/128'
 			}
 			return '127.0.0.1/32'
 		},
diff --git a/apps/workflowengine/src/components/Checks/request.js b/apps/workflowengine/src/components/Checks/request.js
index 8b36b89a9e8..5550555cf02 100644
--- a/apps/workflowengine/src/components/Checks/request.js
+++ b/apps/workflowengine/src/components/Checks/request.js
@@ -20,8 +20,8 @@
  *
  */
 
-import RequestUserAgent from './RequestUserAgent';
-import RequestTime from './RequestTime';
+import RequestUserAgent from './RequestUserAgent'
+import RequestTime from './RequestTime'
 
 const RequestChecks = [
 	{
@@ -32,7 +32,7 @@ const RequestChecks = [
 			{ operator: '!is', name: t('workflowengine', 'is not') },
 			{ operator: 'matches', name: t('workflowengine', 'matches') },
 			{ operator: '!matches', name: t('workflowengine', 'does not match') }
-		],
+		]
 		// TODO: implement component
 	},
 	{
@@ -53,8 +53,7 @@ const RequestChecks = [
 			{ operator: 'matches', name: t('workflowengine', 'matches') },
 			{ operator: '!matches', name: t('workflowengine', 'does not match') }
 		],
-		// TODO: implement component
-		// component: RequestUserAgent
+		component: RequestUserAgent
 	},
 	{
 		class: 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
@@ -62,7 +61,7 @@ const RequestChecks = [
 		operators: [
 			{ operator: 'is', name: t('workflowengine', 'is member of') },
 			{ operator: '!is', name: t('workflowengine', 'is not member of') }
-		],
+		]
 		// TODO: implement component
 	}
 ]
diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
index 3cd7378f0df..ad44d376934 100644
--- a/apps/workflowengine/src/components/Operation.vue
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -2,8 +2,8 @@
 	<div class="actions__item" :class="{'colored': colored}" :style="{ backgroundColor: colored ? operation.color : 'transparent' }">
 		<div class="icon" :class="operation.iconClass" :style="{ backgroundImage: operation.iconClass ? '' : `url(${operation.icon})` }" />
 		<div class="actions__item__description">
-		<h3>{{ operation.name }}</h3>
-		<small>{{ operation.description }}</small>
+			<h3>{{ operation.name }}</h3>
+			<small>{{ operation.description }}</small>
 		</div>
 		<div class="actions__item_options">
 			<slot />
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
index c5c6094879a..76d332ac414 100644
--- a/apps/workflowengine/src/components/Rule.vue
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -5,9 +5,10 @@
 				<span>{{ t('workflowengine', 'When') }}</span>
 				<Event :rule="rule" @update="updateRule" />
 			</p>
-			<p v-for="check in rule.checks">
+			<p v-for="(check, index) in rule.checks" :key="index">
 				<span>{{ t('workflowengine', 'and') }}</span>
-				<Check :check="check" :rule="rule" @update="updateRule" @remove="removeCheck(check)" />
+				<Check :check="check" :rule="rule" @update="updateRule"
+					@remove="removeCheck(check)" />
 			</p>
 			<p>
 				<span />
@@ -15,7 +16,7 @@
 					value="Add a new filter" @click="rule.checks.push({class: null, operator: null, value: null})">
 			</p>
 		</div>
-		<div class="flow-icon icon-confirm"></div>
+		<div class="flow-icon icon-confirm" />
 		<div class="action">
 			<div class="buttons">
 				<Actions>
@@ -32,7 +33,7 @@
 					@input="updateOperation" />
 			</Operation>
 			<button v-tooltip="ruleStatus.tooltip" class="status-button icon" :class="ruleStatus.class"
-					@click="saveRule">
+				@click="saveRule">
 				{{ ruleStatus.title }}
 			</button>
 		</div>
@@ -245,5 +246,4 @@ export default {
 		}
 	}
 
-
 </style>
diff --git a/apps/workflowengine/src/helpers/validators.js b/apps/workflowengine/src/helpers/validators.js
index 033ce2ec7fe..5fb94f66ecf 100644
--- a/apps/workflowengine/src/helpers/validators.js
+++ b/apps/workflowengine/src/helpers/validators.js
@@ -20,7 +20,6 @@
  *
  */
 
-
 const validateRegex = function(string) {
 	var regexRegex = /^\/(.*)\/([gui]{0,3})$/
 	var result = regexRegex.exec(string)
diff --git a/apps/workflowengine/src/legacy/filesystemtagsplugin.js b/apps/workflowengine/src/legacy/filesystemtagsplugin.js
deleted file mode 100644
index e945c79195c..00000000000
--- a/apps/workflowengine/src/legacy/filesystemtagsplugin.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
-
-	OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin = {
-		getCheck: function() {
-			this.collection = OC.SystemTags.collection
-
-			return {
-				class: 'OCA\\WorkflowEngine\\Check\\FileSystemTags',
-				name: t('workflowengine', 'File system tag'),
-				operators: [
-					{ operator: 'is', name: t('workflowengine', 'is tagged with') },
-					{ operator: '!is', name: t('workflowengine', 'is not tagged with') }
-				]
-			}
-		},
-		render: function(element, check) {
-			if (check.class !== 'OCA\\WorkflowEngine\\Check\\FileSystemTags') {
-				return
-			}
-
-			$(element).css('width', '400px')
-
-			$(element).select2({
-				allowClear: false,
-				multiple: false,
-				placeholder: t('workflowengine', 'Select tag…'),
-				query: _.debounce(function(query) {
-					query.callback({
-						results: OC.SystemTags.collection.filterByName(query.term)
-					})
-				}, 100, true),
-				id: function(element) {
-					return element.get('id')
-				},
-				initSelection: function(element, callback) {
-					callback($(element).val())
-				},
-				formatResult: function(tag) {
-					return OC.SystemTags.getDescriptiveTag(tag)
-				},
-				formatSelection: function(tagId) {
-					var tag = OC.SystemTags.collection.get(tagId)
-					if (!_.isUndefined(tag)) {
-						return OC.SystemTags.getDescriptiveTag(tag)
-					}
-				},
-				escapeMarkup: function(m) {
-					return m
-				}
-			})
-		}
-	}
-})()
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin)
diff --git a/apps/workflowengine/src/legacy/requesttimeplugin.js b/apps/workflowengine/src/legacy/requesttimeplugin.js
deleted file mode 100644
index eaeae287da4..00000000000
--- a/apps/workflowengine/src/legacy/requesttimeplugin.js
+++ /dev/null
@@ -1,196 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
-
-	OCA.WorkflowEngine.Plugins.RequestTimePlugin = {
-		timezones: [
-			'Europe/Berlin',
-			'Europe/London'
-		],
-		_$element: null,
-		getCheck: function() {
-			return {
-				class: 'OCA\\WorkflowEngine\\Check\\RequestTime',
-				name: t('workflowengine', 'Request time'),
-				operators: [
-					{ operator: 'in', name: t('workflowengine', 'between') },
-					{ operator: '!in', name: t('workflowengine', 'not between') }
-				]
-			}
-		},
-		render: function(element, check) {
-			if (check.class !== 'OCA\\WorkflowEngine\\Check\\RequestTime') {
-				return
-			}
-
-			var startTime = '09:00'
-			var endTime = '18:00'
-			var timezone = jstz.determine().name()
-			var $element = $(element)
-
-			if (_.isString(check.value) && check.value !== '') {
-				var value = JSON.parse(check.value)
-				var splittedStart = value[0].split(' ', 2)
-				var splittedEnd = value[1].split(' ', 2)
-
-				startTime = splittedStart[0]
-				endTime = splittedEnd[0]
-				timezone = splittedStart[1]
-			}
-
-			var valueJSON = JSON.stringify([startTime + ' ' + timezone, endTime + ' ' + timezone])
-			if (check.value !== valueJSON) {
-				check.value = valueJSON
-				$element.val(valueJSON)
-			}
-
-			$element.css('display', 'none')
-
-			$('<input>')
-				.attr('type', 'text')
-				.attr('placeholder', t('workflowengine', 'Start'))
-				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: '16:00' }))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				})
-				.addClass('start')
-				.val(startTime)
-				.insertBefore($element)
-			$('<input>')
-				.attr('type', 'text')
-				.attr('placeholder', t('workflowengine', 'End'))
-				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: '16:00' }))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				})
-				.addClass('end')
-				.val(endTime)
-				.insertBefore($element)
-
-			var timezoneInput = $('<input>')
-				.attr('type', 'hidden')
-				.css('width', '250px')
-				.insertBefore($element)
-				.val(timezone)
-
-			timezoneInput.select2({
-				allowClear: false,
-				multiple: false,
-				placeholder: t('workflowengine', 'Select timezone…'),
-				ajax: {
-					url: OC.generateUrl('apps/workflowengine/timezones'),
-					dataType: 'json',
-					quietMillis: 100,
-					data: function(term) {
-						if (term === '') {
-							// Default search in the same continent...
-							term = jstz.determine().name().split('/')
-							term = term[0]
-						}
-						return {
-							search: term
-						}
-					},
-					results: function(response) {
-						var results = []
-						$.each(response, function(timezone) {
-							results.push({ id: timezone })
-						})
-
-						return {
-							results: results,
-							more: false
-						}
-					}
-				},
-				initSelection: function(element, callback) {
-					callback(element.val())
-				},
-				formatResult: function(element) {
-					return '<span>' + element.id + '</span>'
-				},
-				formatSelection: function(element) {
-					if (!_.isUndefined(element.id)) {
-						element = element.id
-					}
-					return '<span>' + element + '</span>'
-				}
-			})
-
-			// Has to be added after select2 for `event.target.classList`
-			timezoneInput.addClass('timezone')
-
-			$element.parent()
-				.on('change', '.start', _.bind(this.update, this))
-				.on('change', '.end', _.bind(this.update, this))
-				.on('change', '.timezone', _.bind(this.update, this))
-
-			this._$element = $element
-		},
-		update: function(event) {
-			var value = event.target.value
-			var key = null
-
-			for (var i = 0; i < event.target.classList.length; i++) {
-				key = event.target.classList[i]
-			}
-
-			if (key === null) {
-				console.warn('update triggered but element doesn\'t have any class')
-				return
-			}
-
-			var data = JSON.parse(this._$element.val())
-			var startTime = moment(data[0].split(' ', 2)[0], 'H:m Z')
-			var endTime = moment(data[1].split(' ', 2)[0], 'H:m Z')
-			var timezone = data[0].split(' ', 2)[1]
-
-			if (key === 'start' || key === 'end') {
-				var parsedDate = moment(value, ['H:m', 'h:m a'], true).format('HH:mm')
-
-				if (parsedDate === 'Invalid date') {
-					return
-				}
-
-				var indexValue = 0
-				if (key === 'end') {
-					indexValue = 1
-				}
-				data[indexValue] = parsedDate + ' ' + timezone
-			}
-
-			if (key === 'timezone') {
-				data[0] = startTime.format('HH:mm') + ' ' + value
-				data[1] = endTime.format('HH:mm') + ' ' + value
-			}
-
-			this._$element.val(JSON.stringify(data))
-			this._$element.trigger('change')
-		}
-	}
-})()
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestTimePlugin)
diff --git a/apps/workflowengine/src/legacy/requesturlplugin.js b/apps/workflowengine/src/legacy/requesturlplugin.js
deleted file mode 100644
index 7e13422387f..00000000000
--- a/apps/workflowengine/src/legacy/requesturlplugin.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
-
-	OCA.WorkflowEngine.Plugins.RequestURLPlugin = {
-		predefinedValues: ['webdav'],
-		getCheck: function() {
-			return {
-				class: 'OCA\\WorkflowEngine\\Check\\RequestURL',
-				name: t('workflowengine', 'Request URL'),
-				operators: [
-					{ operator: 'is', name: t('workflowengine', 'is') },
-					{ operator: '!is', name: t('workflowengine', 'is not') },
-					{ operator: 'matches', name: t('workflowengine', 'matches') },
-					{ operator: '!matches', name: t('workflowengine', 'does not match') }
-				]
-			}
-		},
-		render: function(element, check) {
-			if (check.class !== 'OCA\\WorkflowEngine\\Check\\RequestURL') {
-				return
-			}
-
-			var placeholder = 'https://localhost/index.php'
-
-			if (check.operator === 'matches' || check.operator === '!matches') {
-				placeholder = '/^https\\:\\/\\/localhost\\/index\\.php$/i'
-			}
-
-			$(element).css('width', '250px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				})
-
-			if (check.operator === 'matches' || check.operator === '!matches') {
-				if (this._validateRegex(check.value)) {
-					$(element).removeClass('invalid-input')
-				} else {
-					$(element).addClass('invalid-input')
-				}
-			} else {
-				var self = this
-				var data = [
-					{
-						text: t('workflowengine', 'Predefined URLs'),
-						children: [
-							{ id: 'webdav', text: t('workflowengine', 'Files WebDAV') }
-						]
-					}
-				]
-				if (this.predefinedValues.indexOf(check.value) === -1) {
-					data.unshift({
-						id: check.value,
-						text: check.value
-					})
-				}
-
-				$(element).select2({
-					data: data,
-					createSearchChoice: function(term) {
-						if (self.predefinedValues.indexOf(check.value) === -1) {
-							return {
-								id: term,
-								text: term
-							}
-						}
-					},
-					id: function(element) {
-						return element.id
-					},
-					formatResult: function(tag) {
-						return tag.text
-					},
-					formatSelection: function(tag) {
-						return tag.text
-					},
-					escapeMarkup: function(m) {
-						return m
-					}
-				})
-			}
-		},
-
-		_validateRegex: function(string) {
-			var regexRegex = /^\/(.*)\/([gui]{0,3})$/
-			var result = regexRegex.exec(string)
-			return result !== null
-		}
-	}
-})()
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestURLPlugin)
diff --git a/apps/workflowengine/src/legacy/requestuseragentplugin.js b/apps/workflowengine/src/legacy/requestuseragentplugin.js
deleted file mode 100644
index 5480e4ccc8b..00000000000
--- a/apps/workflowengine/src/legacy/requestuseragentplugin.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
-
-	OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin = {
-		predefinedValues: ['android', 'ios', 'desktop'],
-		getCheck: function() {
-			return {
-				class: 'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
-				name: t('workflowengine', 'Request user agent'),
-				operators: [
-					{ operator: 'is', name: t('workflowengine', 'is') },
-					{ operator: '!is', name: t('workflowengine', 'is not') },
-					{ operator: 'matches', name: t('workflowengine', 'matches') },
-					{ operator: '!matches', name: t('workflowengine', 'does not match') }
-				]
-			}
-		},
-		render: function(element, check) {
-			if (check.class !== 'OCA\\WorkflowEngine\\Check\\RequestUserAgent') {
-				return
-			}
-
-			var placeholder = 'Mozilla/5.0 User Agent'
-
-			if (check.operator === 'matches' || check.operator === '!matches') {
-				placeholder = '/^Mozilla\\/5\\.0 (.*)$/i'
-			}
-
-			$(element).css('width', '250px')
-				.attr('placeholder', placeholder)
-				.attr('title', t('workflowengine', 'Example: {placeholder}', { placeholder: placeholder }))
-				.addClass('has-tooltip')
-				.tooltip({
-					placement: 'bottom'
-				})
-
-			if (check.operator === 'matches' || check.operator === '!matches') {
-				if (this._validateRegex(check.value)) {
-					$(element).removeClass('invalid-input')
-				} else {
-					$(element).addClass('invalid-input')
-				}
-			} else {
-				var self = this
-				var data = [
-					{
-						text: t('workflowengine', 'Sync clients'),
-						children: [
-							{ id: 'android', text: t('workflowengine', 'Android client') },
-							{ id: 'ios', text: t('workflowengine', 'iOS client') },
-							{ id: 'desktop', text: t('workflowengine', 'Desktop client') },
-							{ id: 'mail', text: t('workflowengine', 'Thunderbird & Outlook addons') }
-						]
-					}
-				]
-				if (this.predefinedValues.indexOf(check.value) === -1) {
-					data.unshift({
-						id: check.value,
-						text: check.value
-					})
-				}
-
-				$(element).select2({
-					data: data,
-					createSearchChoice: function(term) {
-						if (self.predefinedValues.indexOf(check.value) === -1) {
-							return {
-								id: term,
-								text: term
-							}
-						}
-					},
-					id: function(element) {
-						return element.id
-					},
-					formatResult: function(tag) {
-						return tag.text
-					},
-					formatSelection: function(tag) {
-						return tag.text
-					},
-					escapeMarkup: function(m) {
-						return m
-					}
-				})
-			}
-		},
-
-		_validateRegex: function(string) {
-			var regexRegex = /^\/(.*)\/([gui]{0,3})$/
-			var result = regexRegex.exec(string)
-			return result !== null
-		}
-	}
-})()
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin)
diff --git a/apps/workflowengine/src/legacy/usergroupmembershipplugin.js b/apps/workflowengine/src/legacy/usergroupmembershipplugin.js
deleted file mode 100644
index 6d60e9e4d66..00000000000
--- a/apps/workflowengine/src/legacy/usergroupmembershipplugin.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
-	OCA.WorkflowEngine = OCA.WorkflowEngine || {}
-	OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {}
-
-	OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin = {
-		getCheck: function() {
-			return {
-				class: 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
-				name: t('workflowengine', 'User group membership'),
-				operators: [
-					{ operator: 'is', name: t('workflowengine', 'is member of') },
-					{ operator: '!is', name: t('workflowengine', 'is not member of') }
-				]
-			}
-		},
-		render: function(element, check, groups) {
-			if (check.class !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') {
-				return
-			}
-
-			$(element).css('width', '400px')
-
-			$(element).select2({
-				data: { results: groups, text: 'displayname' },
-				initSelection: function(element, callback) {
-					var groupId = element.val()
-					if (groupId && groups.length > 0) {
-						callback({
-							id: groupId,
-							displayname: groups.find(function(group) {
-								return group.id === groupId
-							}).displayname
-						})
-					} else if (groupId) {
-						callback({
-							id: groupId,
-							displayname: groupId
-						})
-					} else {
-						callback()
-					}
-				},
-				formatResult: function(element) {
-					return '<span>' + escapeHTML(element.displayname) + '</span>'
-				},
-				formatSelection: function(element) {
-					return '<span title="' + escapeHTML(element.id) + '">' + escapeHTML(element.displayname) + '</span>'
-				}
-			})
-		}
-	}
-})()
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin)
diff --git a/apps/workflowengine/src/store.js b/apps/workflowengine/src/store.js
index 75e53149ea2..a322c7fb3ea 100644
--- a/apps/workflowengine/src/store.js
+++ b/apps/workflowengine/src/store.js
@@ -85,7 +85,7 @@ const store = new Vuex.Store({
 			let events = []
 			if (rule.isComplex === false && rule.fixedEntity === '') {
 				entity = context.state.entities.find((item) => rule.entities && rule.entities[0] === item.id)
-				entity = entity ? entity : Object.values(context.state.entities)[0]
+				entity = entity || Object.values(context.state.entities)[0]
 				events = [entity.events[0].eventName]
 			}
 
@@ -124,7 +124,7 @@ const store = new Vuex.Store({
 			await axios.delete(getApiUrl(`/${rule.id}`))
 			context.commit('removeRule', rule)
 		},
-		setValid (context, { rule, valid }) {
+		setValid(context, { rule, valid }) {
 			rule.valid = valid
 			context.commit('updateRule', rule)
 		}
-- 
cgit v1.2.3

