diff --git a/projects/workflows-creator/src/lib/builder/builder.component.html b/projects/workflows-creator/src/lib/builder/builder.component.html index 4fc1553..7a01dda 100644 --- a/projects/workflows-creator/src/lib/builder/builder.component.html +++ b/projects/workflows-creator/src/lib/builder/builder.component.html @@ -11,7 +11,7 @@ (add)="openPopup(types.GROUP)" (remove)="onGroupRemove(i)" (eventAdded)="onEventAdded($event)" - (eventRemoved)="onEventRemoved()" + (nodeRemoved)="onNodeRemoved()" (actionAdded)="onActionAdded($event)" (itemChanged)="onItemChanged($event)" > @@ -25,10 +25,12 @@ [isLast]="true" [isFirst]="true" [nodeType]="types.GROUP" + [eventGroups]="eventGroups" [popupTemplate]="nodePopup" [templateMap]="templateMap" [allColumns]="allColumns" (eventAdded)="onEventAdded($event)" + (nodeRemoved)="onNodeRemoved()" (actionAdded)="onActionAdded($event)" (itemChanged)="onItemChanged($event)" > diff --git a/projects/workflows-creator/src/lib/builder/builder.component.scss b/projects/workflows-creator/src/lib/builder/builder.component.scss index b5e1b4d..495d2d9 100644 --- a/projects/workflows-creator/src/lib/builder/builder.component.scss +++ b/projects/workflows-creator/src/lib/builder/builder.component.scss @@ -25,7 +25,7 @@ } } .container { - min-width: 46%; + min-width: 52%; max-width: max-content; color: rgb(50, 51, 56); margin: auto; diff --git a/projects/workflows-creator/src/lib/builder/builder.component.ts b/projects/workflows-creator/src/lib/builder/builder.component.ts index d346069..bbe56bd 100644 --- a/projects/workflows-creator/src/lib/builder/builder.component.ts +++ b/projects/workflows-creator/src/lib/builder/builder.component.ts @@ -20,7 +20,16 @@ import { } from '../classes'; import {AbstractBaseGroup} from '../classes/nodes'; import {BuilderService, ElementService, NodeService} from '../classes/services'; -import {EventTypes, LocalizedStringKeys, NodeTypes, ValueTypes} from '../enum'; +import { + ActionTypes, + ConditionTypes, + EventTypes, + LocalizedStringKeys, + NUMBER, + NodeTypes, + NotificationRecipientTypesEnum, + ValueTypes, +} from '../enum'; import {InvalidEntityError} from '../errors/base.error'; import { ActionAddition, @@ -38,6 +47,11 @@ import { WorkflowNode, } from '../types'; import {LocalizationProviderService} from '../services/localization-provider.service'; +import { + ReadColumnValue, + TriggerWhenColumnChanges, +} from '../services/bpmn/elements/tasks'; +import {GatewayElement} from '../services/bpmn/elements/gateways'; @Component({ selector: 'workflow-builder', @@ -78,7 +92,10 @@ export class BuilderComponent implements OnInit, OnChanges { stateChange = new EventEmitter>(); @Output() - diagramChange = new EventEmitter(); + actionGroupsAdded = new EventEmitter[]>(); + + @Output() + diagramChange = new EventEmitter(); @Output() eventAdded = new EventEmitter>(); @@ -99,6 +116,20 @@ export class BuilderComponent implements OnInit, OnChanges { nodeList: AbstractBaseGroup[] = []; processId: string; + + // Current selected event identifier for action filtering + get currentSelectedEvent(): string | undefined { + // Look through event groups to find the first selected event + for (const eventGroup of this.eventGroups) { + if (eventGroup?.children?.length > 0) { + const firstChild = eventGroup.children[0]; + if (firstChild?.node?.type === NodeTypes.EVENT) { + return firstChild.node.getIdentifier(); + } + } + } + return undefined; + } // sonarignore:start // TODO: Refactor this code to be more flexible // sonarignore:start @@ -172,6 +203,8 @@ export class BuilderComponent implements OnInit, OnChanges { action: action.node as WorkflowAction, }); }); + this.actionGroupsAdded.emit(this.actionGroups); + this.hideElseBlockIfRequired(); this.updateDiagram(); } } @@ -210,6 +243,9 @@ export class BuilderComponent implements OnInit, OnChanges { * @param event - ElementsWithInput */ onEventAdded(event: ElementsWithInput) { + // Check if we need to clear existing actions when event changes + this.clearIncompatibleActions(event.node.getIdentifier()); + this.eventAdded.emit({ name: event.node.getIdentifier(), event: event.newNode.node as WorkflowEvent, @@ -217,25 +253,56 @@ export class BuilderComponent implements OnInit, OnChanges { this.updateDiagram(); this.updateState(event.node, event.newNode.inputs); this.elseBlockHidden = - !this.eventGroups[0]?.children?.length && + this.eventGroups[0]?.children?.length === 1 && (event.node.getIdentifier() === EventTypes.OnIntervalEvent || event.node.getIdentifier() === EventTypes.OnAddItemEvent); } + /** + * Clears actions that are not compatible with the selected event + * @param selectedEvent - The identifier of the selected event + */ + private clearIncompatibleActions(selectedEvent: string) { + if (this.actionGroups[0]?.children?.length > 0) { + // Get list of actions that should remain (compatible with new event) + const compatibleActions = this.nodes.getActions(selectedEvent); + const compatibleActionIds = new Set( + compatibleActions.map(action => action.getIdentifier()), + ); + + // Filter out incompatible actions + const currentActions = [...this.actionGroups[0].children]; + const actionsToRemove: number[] = []; + + currentActions.forEach((action, index) => { + const actionId = action.node.getIdentifier(); + if (!compatibleActionIds.has(actionId)) { + actionsToRemove.push(index); + } + }); + + // Remove incompatible actions (reverse order to maintain indexes) + actionsToRemove.reverse().forEach(index => { + this.actionGroups[0].children.splice(index, 1); + }); + + // Clear state for removed actions + actionsToRemove.forEach(index => { + const removedAction = currentActions[index]; + if (removedAction) { + this.updateState(removedAction.node, removedAction.inputs, true); + } + }); + } + } + /** * The function is called when an event is removed from the workflow. * Hides the else block when it is not needed. */ - onEventRemoved() { - const events = this.eventGroups[0].children; - - this.elseBlockHidden = - events.length === 1 && - (events[0].node.getIdentifier() === EventTypes.OnIntervalEvent || - events[0].node.getIdentifier() === EventTypes.OnAddItemEvent || - (events[0].node.getIdentifier() === EventTypes.OnChangeEvent && - (events[0].node.state.get('value') === ValueTypes.AnyValue || - events[0].node.state.get('valueType') === ValueTypes.AnyValue))); + onNodeRemoved() { + this.updateDiagram(); + if (this.eventGroups[0]?.children?.length) this.hideElseBlockIfRequired(); } /** @@ -264,19 +331,32 @@ export class BuilderComponent implements OnInit, OnChanges { item: item.element.node, }); this.updateState(item.element.node, item.element.inputs); - // TODO: to be refactored - // to hide else block when anything is selected in ValueInput or ValueTypeInput - this.elseBlockHidden = - this.eventGroups[0].children?.length === 1 && - this.eventGroups[0].children[0].node.getIdentifier() === - EventTypes.OnChangeEvent && - (this.eventGroups[0].children[0].node.state.get('value') === - ValueTypes.AnyValue || - this.eventGroups[0].children[0].node.state.get('valueType') === - ValueTypes.AnyValue); + this.hideElseBlockIfRequired(); this.updateDiagram(); } + /** + * This function checks if the else block should be hidden based on the type and number of events in + * the event group. + */ + hideElseBlockIfRequired() { + const events = this.eventGroups[0].children; + let value = events[0].node.state.get('value'); + if (typeof value === 'object') { + value = value.value; + } + if (events.length !== 1) { + this.elseBlockHidden = false; + } else { + this.elseBlockHidden = + events[0].node.getIdentifier() === EventTypes.OnIntervalEvent || + events[0].node.getIdentifier() === EventTypes.OnAddItemEvent || + (events[0].node.getIdentifier() === EventTypes.OnChangeEvent && + (value === ValueTypes.AnyValue || + events[0].node.state.get('valueType') === ValueTypes.AnyValue)); + } + } + /** * "If the type is a group, then get the groups, otherwise throw an error." * @@ -346,6 +426,31 @@ export class BuilderComponent implements OnInit, OnChanges { value: AllowedValues | AllowedValuesMap, select = false, ) { + if ( + (input.getIdentifier() === 'ValueTypeInput' || + input.getIdentifier() === 'ValueInput') && + element.node.getIdentifier() === 'OnChangeEvent' + ) { + if ( + ((value as AllowedValuesMap)?.value as AllowedValuesMap)?.value === + ValueTypes.AnyValue || + (value as AllowedValuesMap)?.value === ValueTypes.AnyValue + ) { + /** + * Remove node on changes event + */ + element.node.elements.splice(-NUMBER.TWO, NUMBER.TWO); + // element.inputs[1].prefix = ''; + //this.enableActionIcon = false; + } else { + element.node.elements = [ + TriggerWhenColumnChanges.identifier, + ReadColumnValue.identifier, + GatewayElement.identifier, + ]; + } + } + if (select && isSelectInput(input)) { element.node.state.change( `${input.inputKey}Name`, @@ -429,8 +534,58 @@ export class BuilderComponent implements OnInit, OnChanges { * It builds a new diagram, emits the new diagram, and then tells Angular to update the view */ async updateDiagram() { + const nodes = [ + ...this.eventGroups[0].children, + ...this.actionGroups[0].children, + ...this.elseActionGroups[0].children, + ]; + let isValid = + !!this.eventGroups[0].children.length && + (!!this.actionGroups[0].children.length || + !!this.elseActionGroups[0].children.length); + if (isValid) { + for (const node of nodes) { + switch (node.node.getIdentifier()) { + case EventTypes.OnChangeEvent: + case EventTypes.OnValueEvent: + case ActionTypes.ChangeColumnValueAction: + const columnExists = !!node.node.state.get('column'); + const valueExists = + node.node.state.get('condition') === ConditionTypes.PastToday + ? true + : !!node.node.state.get('value'); + const valueTypeIsSufficient = [ + ValueTypes.AnyValue, + ValueTypes.Today, + ValueTypes.PastToday, + ].includes(node.node.state.get('valueType')); + isValid = columnExists && (valueExists || valueTypeIsSufficient); + break; + case EventTypes.OnIntervalEvent: + const intervalExists = !!node.node.state.get('interval'); + const intervalValueExists = !!node.node.state.get('value'); + isValid = intervalValueExists && intervalExists; + break; + case ActionTypes.SendEmailAction: + const email = !!node.node.state.get('email'); + const emailTo = !!node.node.state.get('emailTo'); + const specificRecipientsRequired = [ + NotificationRecipientTypesEnum.NotifySpecificColumn, + NotificationRecipientTypesEnum.NotifySpecificPeople, + ].includes(node.node.state.get('emailTo')); + const recipients = !!node.node.state.get('specificRecepient'); + isValid = specificRecipientsRequired + ? email && emailTo && recipients + : email && emailTo; + break; + } + if (!isValid) { + break; // exit the loop since we found an invalid input + } + } + } this.diagram = await this.build(); - this.diagramChange.emit(this.diagram); + this.diagramChange.emit({diagram: this.diagram, isValid: isValid}); this.cdr.detectChanges(); } diff --git a/projects/workflows-creator/src/lib/builder/group/group.component.html b/projects/workflows-creator/src/lib/builder/group/group.component.html index 46758f9..9bf27ec 100644 --- a/projects/workflows-creator/src/lib/builder/group/group.component.html +++ b/projects/workflows-creator/src/lib/builder/group/group.component.html @@ -50,11 +50,15 @@ - +
- +
- {{ - (input.prefix.state - ? nodeWithInput.node.state.get(input.prefix.state) - : input.prefix) || '' - }} -
- {{ input.getValueName(nodeWithInput.node.state) }} - - {{ - nodeWithInput.node.state.get(input.customPlaceholder?.state) ?? - input.placeholder - }} - -
- {{ - (input.suffix?.state - ? nodeWithInput.node.state.get(input.suffix?.state) - : input.suffix) || '' - }} + +
+ +
+
+ + + + + @@ -153,6 +146,55 @@ + + {{ + (input.prefix?.state + ? nodeWithInput.node.state.get(input.prefix.state) + : input.prefix) || '' + }} +
+ + {{ input.getValueName(nodeWithInput.node.state) }} + + + {{ + nodeWithInput.node.state.get(input.customPlaceholder?.state) ?? + input.placeholder + }} + +
+ {{ + (input.suffix?.state + ? nodeWithInput.node.state.get(input.suffix?.state) + : input.suffix) || '' + }} +
+ diff --git a/projects/workflows-creator/src/lib/builder/group/group.component.scss b/projects/workflows-creator/src/lib/builder/group/group.component.scss index 42f2acc..70f4871 100644 --- a/projects/workflows-creator/src/lib/builder/group/group.component.scss +++ b/projects/workflows-creator/src/lib/builder/group/group.component.scss @@ -58,6 +58,11 @@ } } } + .input-next-line { + display: block; + height: 5px; + text-decoration: inherit; + } .input-text { cursor: pointer; @@ -66,12 +71,11 @@ text-decoration: inherit; .placeholder-text { color: $placeholder-color; - text-transform: lowercase; } .value-text { display: inline-block; overflow: hidden; - max-width: 45%; + max-width: 15rem; text-decoration: inherit; text-overflow: ellipsis; vertical-align: top; diff --git a/projects/workflows-creator/src/lib/builder/group/group.component.ts b/projects/workflows-creator/src/lib/builder/group/group.component.ts index b9e5cdb..51bf406 100644 --- a/projects/workflows-creator/src/lib/builder/group/group.component.ts +++ b/projects/workflows-creator/src/lib/builder/group/group.component.ts @@ -86,7 +86,7 @@ export class GroupComponent implements OnInit, AfterViewInit { eventAdded = new EventEmitter(); @Output() - eventRemoved = new EventEmitter(); + nodeRemoved = new EventEmitter(); @Output() actionAdded = new EventEmitter(); @@ -103,6 +103,7 @@ export class GroupComponent implements OnInit, AfterViewInit { subject: '', body: '', focusKey: '', + caretPos: 0, }; dropdownSettings: IDropdownSettings = { singleSelection: false, @@ -134,6 +135,9 @@ export class GroupComponent implements OnInit, AfterViewInit { typeSubjectPlaceholder = ''; typeEmailPlaceholder = ''; + doThisLbl = ''; + whenThisHappensLbl = ''; + setLbl = ''; localizedStringKeys = LocalizedStringKeys; @@ -172,7 +176,8 @@ export class GroupComponent implements OnInit, AfterViewInit { ngOnInit(): void { this.events = this.nodes.getEvents(); this.triggerEvents = this.nodes.getEvents(true); - this.actions = this.nodes.getActions(); + this.actions = []; + this.typeSubjectPlaceholder = this.localizationSvc.getLocalizedString( LocalizedStringKeys.TypeSubject, ); @@ -186,28 +191,29 @@ export class GroupComponent implements OnInit, AfterViewInit { * use the default template */ ngAfterViewInit() { + // Create a base template map with defaults + const baseTemplateMap = { + [InputTypes.Boolean]: this.listTemplate, + [InputTypes.List]: this.listTemplate, + [InputTypes.Text]: this.textTemplate, + [InputTypes.Number]: this.numberTemplate, + [InputTypes.Percentage]: this.numberTemplate, + [InputTypes.Date]: this.dateTemplate, + [InputTypes.DateTime]: this.dateTimeTemplate, + [InputTypes.People]: this.searchableDropdownTemplate, + [InputTypes.Interval]: this.listTemplate, + [InputTypes.Email]: this.emailTemplate, + [InputTypes.OptionList]: this.listTemplate, + [InputTypes.Stepper]: this.listTemplate, + [InputTypes.IntervalDate]: this.listTemplate, + [InputTypes.IntervalTime]: this.listTemplate, + }; + + // Merge consumer's custom templates with base templates + // Consumer templates take priority, base templates are fallbacks this.templateMap = { - [InputTypes.Boolean]: - this.templateMap?.[InputTypes.Boolean] || this.listTemplate, - [InputTypes.List]: - this.templateMap?.[InputTypes.List] || this.listTemplate, - [InputTypes.Text]: - this.templateMap?.[InputTypes.Text] || this.textTemplate, - [InputTypes.Number]: - this.templateMap?.[InputTypes.Number] || this.numberTemplate, - [InputTypes.Percentage]: - this.templateMap?.[InputTypes.Percentage] || this.numberTemplate, - [InputTypes.Date]: - this.templateMap?.[InputTypes.Date] || this.dateTemplate, - [InputTypes.DateTime]: - this.templateMap?.[InputTypes.DateTime] || this.dateTimeTemplate, - [InputTypes.People]: - this.templateMap?.[InputTypes.People] || - this.searchableDropdownTemplate, - [InputTypes.Interval]: - this.templateMap?.[InputTypes.Interval] || this.listTemplate, - [InputTypes.Email]: - this.templateMap?.[InputTypes.Email] || this.emailTemplate, + ...baseTemplateMap, + ...this.templateMap, // Consumer's custom templates override defaults }; } @@ -221,6 +227,13 @@ export class GroupComponent implements OnInit, AfterViewInit { if (allowedInputs.includes(input.getIdentifier())) { const value = input.getModelValue(nodeWithInput.node.state); if (nodeWithInput.node.state.get('email')) { + (value as AllowedValuesMap).body = ( + (value as AllowedValuesMap).body as string + ).replace(/\\"/g, '"'); + (value as AllowedValuesMap).subject = ( + (value as AllowedValuesMap).subject as string + ).replace(/\\"/g, '"'); + this.emailInput = value; } else { switch (nodeWithInput.node.state.get('valueInputType')) { @@ -261,11 +274,21 @@ export class GroupComponent implements OnInit, AfterViewInit { */ appendEmailBody(item: Select, emailInput: EmailInput) { if (emailInput.focusKey === 'subject') { - emailInput.subject += ` ${item.value}`; + emailInput.subject = [ + emailInput.subject.slice(0, emailInput.caretPos), + `${item.value}`, + emailInput.subject.slice(emailInput.caretPos), + ].join(''); } if (emailInput.focusKey === 'body') { - emailInput.body += ` ${item.value}`; + emailInput.body = [ + emailInput.body.slice(0, emailInput.caretPos), + `${item.value}`, + emailInput.body.slice(emailInput.caretPos), + ].join(''); } + + emailInput.caretPos += `${item.value}`.length; } /** @@ -278,6 +301,14 @@ export class GroupComponent implements OnInit, AfterViewInit { emailInput.focusKey = key; } + /** + * @emailInput this is the object that contains the email input + * @caretPosition pos caret position + */ + setFocusOutPos(emailInput: EmailInput, caretPosition: number) { + emailInput.caretPos = caretPosition; + } + /** * If the type is an action, set the node list to the actions, otherwise if the type is an event, set * the node list to the trigger events if there is only one event group and no children, otherwise @@ -286,15 +317,44 @@ export class GroupComponent implements OnInit, AfterViewInit { */ openPopup(type: NodeTypes) { if (type === NodeTypes.ACTION) { - this.nodeList = this.actions; - } else if (type === NodeTypes.EVENT) { + const selectedEvent = this.fetchSelectedEvent(); + this.nodeList = this.nodes.getActions(selectedEvent); + return; + } + if (type === NodeTypes.EVENT) { this.nodeList = - this.eventGroups.length === 1 && !this.group.children.length + this.eventGroups?.length === 1 && !this.group.children.length ? this.triggerEvents : this.events; - } else { - throw new InvalidEntityError('' + type); + return; + } + throw new InvalidEntityError(String(type)); + } + + private fetchSelectedEvent() { + let selectedEvent: string | undefined; + + // Method 1: find first EVENT in eventGroups + if (this.eventGroups?.length) { + for (const group of this.eventGroups) { + const firstChild = group?.children?.[0]; + if (firstChild?.node?.type === NodeTypes.EVENT) { + selectedEvent = firstChild.node.getIdentifier(); + break; + } + } + } + + // Method 2: fallback - find first EVENT in current group + if (!selectedEvent) { + for (const child of this.group.children ?? []) { + if (child?.node?.type === NodeTypes.EVENT) { + selectedEvent = child.node.getIdentifier(); + break; + } + } } + return selectedEvent; } /** @@ -322,20 +382,21 @@ export class GroupComponent implements OnInit, AfterViewInit { inputs: this.nodes.mapInputs(node), }; if (node.type === NodeTypes.EVENT) { - this.eventAdded.emit({ - node: node, - newNode: newNode, - }); if (newNode.node.getIdentifier() === 'OnIntervalEvent') { newNode.node.state.change('valueInputType', 'number'); } + this.actions = this.nodes.getActions(); // Get all actions initially this.group.children.push(newNode as EventWithInput); + this.eventAdded.emit({ + node: node, + newNode: newNode, + }); } else if (node.type === NodeTypes.ACTION) { + this.group.children.push(newNode as ActionWithInput); this.actionAdded.emit({ node: node, newNode: newNode, }); - this.group.children.push(newNode as ActionWithInput); } else { throw new InvalidEntityError('Node'); } @@ -347,7 +408,7 @@ export class GroupComponent implements OnInit, AfterViewInit { */ onNodeRemove(index: number) { this.group.children.splice(index, 1); - this.eventRemoved.emit(); + this.nodeRemoved.emit(); } /** @@ -369,13 +430,24 @@ export class GroupComponent implements OnInit, AfterViewInit { element, input, input.setValue(element.node.state, value), - input.typeFunction(element.node.state) === InputTypes.List, + input.typeFunction(element.node.state) === InputTypes.List || + input.typeFunction(element.node.state) === InputTypes.OptionList, ); + this.clearValues(); } popper.hide(); }; } + private clearValues() { + this.emailInput = { + subject: '', + body: '', + focusKey: '', + caretPos: 0, + }; + } + /** * It hides the previous popper and shows the current popper. * @param {MouseEvent} event - MouseEvent - The event that triggered the popper to show. @@ -470,32 +542,24 @@ export class GroupComponent implements OnInit, AfterViewInit { } } if (select && isSelectInput(input)) { - if ( - element.node.state.get('columnName') === 'Priority' && - input.inputKey !== 'condition' - ) { - element.node.state.change( - `${input.inputKey}Name`, - value as AllowedValuesMap, - ); - this.itemChanged.emit({ - field: input.getIdentifier(), - value: value as AllowedValuesMap, - element: element, - }); - value = value as AllowedValuesMap; - } else { - element.node.state.change( - `${input.inputKey}Name`, - (value as AllowedValuesMap)[input.listNameField], - ); - this.itemChanged.emit({ - field: input.getIdentifier(), - value: (value as AllowedValuesMap)[input.listValueField], - element: element, - }); - value = (value as AllowedValuesMap)[input.listValueField]; - } + element.node.state.change( + `${input.inputKey}Name`, + (value as AllowedValuesMap)[input.listNameField], + ); + value = + input.inputKey === 'value' + ? value + : (value as AllowedValuesMap)[input.listValueField]; + element.node.state.change(input.inputKey, value); + this.handleSubsequentInputs(element, input); + this.itemChanged.emit({ + field: input.getIdentifier(), + value: + input.inputKey === 'value' + ? (value as AllowedValuesMap)[input.listValueField] + : value, + element: element, + }); } element.node.state.change(input.inputKey, value); this.handleSubsequentInputs(element, input); @@ -517,6 +581,9 @@ export class GroupComponent implements OnInit, AfterViewInit { element: NodeWithInput, input: WorkflowPrompt, ) { + if (input.inputKey === 'email') { + return; + } const currentIndex = element.inputs.findIndex( i => i.getIdentifier() === input.getIdentifier(), ); diff --git a/projects/workflows-creator/src/lib/builder/node/node.component.scss b/projects/workflows-creator/src/lib/builder/node/node.component.scss index 4d0acaf..e5435d5 100644 --- a/projects/workflows-creator/src/lib/builder/node/node.component.scss +++ b/projects/workflows-creator/src/lib/builder/node/node.component.scss @@ -6,6 +6,7 @@ position: relative; .workflow-content { flex-grow: 1; + height: 3rem; } .action-icons { cursor: pointer; diff --git a/projects/workflows-creator/src/lib/builder/tooltip-render/tooltip-render.component.ts b/projects/workflows-creator/src/lib/builder/tooltip-render/tooltip-render.component.ts index 0f74090..1d90794 100644 --- a/projects/workflows-creator/src/lib/builder/tooltip-render/tooltip-render.component.ts +++ b/projects/workflows-creator/src/lib/builder/tooltip-render/tooltip-render.component.ts @@ -1,17 +1,13 @@ -import {Component, Input, OnInit} from '@angular/core'; +import {Component, Input} from '@angular/core'; @Component({ selector: 'workflow-tooltip-render', templateUrl: './tooltip-render.component.html', styleUrls: ['./tooltip-render.component.scss'], }) -export class TooltipRenderComponent implements OnInit { +export class TooltipRenderComponent { @Input() showsTooltip = true; @Input() tooltipText = 'Default tooltip text'; @Input() topPosition = 215; @Input() leftPosition = 400; - - constructor() {} - - ngOnInit(): void {} } diff --git a/projects/workflows-creator/src/lib/classes/nodes/abstract-prompt.class.ts b/projects/workflows-creator/src/lib/classes/nodes/abstract-prompt.class.ts index 67afaec..30dda6e 100644 --- a/projects/workflows-creator/src/lib/classes/nodes/abstract-prompt.class.ts +++ b/projects/workflows-creator/src/lib/classes/nodes/abstract-prompt.class.ts @@ -54,6 +54,7 @@ export abstract class WorkflowPrompt { value: AllowedValues | AllowedValuesMap, ) { switch (this.typeFunction(state)) { + case InputTypes.OptionList: case InputTypes.List: return value; case InputTypes.People: { @@ -94,12 +95,19 @@ export abstract class WorkflowPrompt { const dateTime = `${this.onDateSelect(date)} ${hours}:${min}`; return moment(dateTime.toString(), 'DD-MM-YYYY hh:mm').format(); case InputTypes.Email: + (value as AllowedValuesMap).body = ( + (value as AllowedValuesMap).body as string + ).replace(/"/g, '\\"'); + (value as AllowedValuesMap).subject = ( + (value as AllowedValuesMap).subject as string + ).replace(/"/g, '\\"'); (value as AllowedValuesMap).displayValue = 'email'; return value; case InputTypes.Number: case InputTypes.Text: case InputTypes.Boolean: case InputTypes.Percentage: + case InputTypes.Stepper: default: if (value) { return (value as HTMLInputElement).value; @@ -140,6 +148,7 @@ export abstract class WorkflowPrompt { */ getValueName(state: State) { switch (this.typeFunction(state)) { + case InputTypes.OptionList: case InputTypes.List: if (typeof state.get(`${this.inputKey}Name`) === 'object') { return state.get(`${this.inputKey}Name`)?.displayValue; @@ -161,10 +170,14 @@ export abstract class WorkflowPrompt { .utc(state.get(this.inputKey), 'YYYY-MM-DD hh:mm') .format('MMM DD, YYYY hh:mm A') : ''; + case InputTypes.IntervalDate: + case InputTypes.IntervalTime: + return state.get(this.inputKey)?.value; case InputTypes.Number: case InputTypes.Text: case InputTypes.Boolean: case InputTypes.Percentage: + case InputTypes.Stepper: default: return state.get(this.inputKey); } @@ -178,6 +191,7 @@ export abstract class WorkflowPrompt { */ setValueName(state: State) { switch (this.typeFunction(state)) { + case InputTypes.OptionList: case InputTypes.List: if ( typeof state.get(this.inputKey) === 'object' && @@ -200,6 +214,7 @@ export abstract class WorkflowPrompt { case InputTypes.Text: case InputTypes.Boolean: case InputTypes.Percentage: + case InputTypes.Stepper: default: return state.get(this.inputKey); } diff --git a/projects/workflows-creator/src/lib/classes/services/abstract-node-service.class.ts b/projects/workflows-creator/src/lib/classes/services/abstract-node-service.class.ts index 0ae7224..c7f2805 100644 --- a/projects/workflows-creator/src/lib/classes/services/abstract-node-service.class.ts +++ b/projects/workflows-creator/src/lib/classes/services/abstract-node-service.class.ts @@ -4,7 +4,7 @@ import {WorkflowNode} from '../../types/base.types'; import {AbstractBaseGroup} from '../nodes'; export abstract class NodeService { - abstract getActions(): WorkflowNode[]; + abstract getActions(selectedEvent?: string): WorkflowNode[]; abstract getEvents(trigger?: boolean): WorkflowNode[]; abstract getGroups( trigger?: boolean, diff --git a/projects/workflows-creator/src/lib/const.ts b/projects/workflows-creator/src/lib/const.ts index 65c943d..092f4ce 100644 --- a/projects/workflows-creator/src/lib/const.ts +++ b/projects/workflows-creator/src/lib/const.ts @@ -14,14 +14,14 @@ xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1aj5pzu" targetNamespace="http://bpmn.io/schema/bpmn" -exporter="Camunda Modeler" exporterVersion="4.12.0" +exporter="Camunda Modeler" exporterVersion="5.21.0" modeler:executionPlatform="Camunda Platform" -modeler:executionPlatformVersion="7.15.0"> +modeler:executionPlatformVersion="7.21.0"> `; -export const JSON_SCRIPT_START = `var json = S(\"{}\");\n`; -export const JSON_SCRIPT_END = `\n json`; +export const JSON_SCRIPT_START = `var json = {};\n`; +export const JSON_SCRIPT_END = `\n JSON.stringify(json)`; export const BASE_XML = new InjectionToken('diagram.bpmn.base'); export const MODDLE = new InjectionToken( diff --git a/projects/workflows-creator/src/lib/enum.ts b/projects/workflows-creator/src/lib/enum.ts index 9914cfb..0e9d3da 100644 --- a/projects/workflows-creator/src/lib/enum.ts +++ b/projects/workflows-creator/src/lib/enum.ts @@ -20,6 +20,11 @@ export enum InputTypes { People = 'people', Percentage = 'percentage', Text = 'text', + OptionList = 'optionList', + Item = 'Item', + Stepper = 'Stepper', + IntervalDate = 'IntervalDate', + IntervalTime = 'IntervalTime', } /* Defining the types of conditions that can be used in the application. */ @@ -79,6 +84,11 @@ export enum EventTypes { OnValueEvent = 'OnValueEvent', } +export enum ActionTypes { + ChangeColumnValueAction = 'ChangeColumnValueAction', + SendEmailAction = 'SendEmailAction', +} + export enum StartElementTypes { BasicStartElement = 'StartElement', StartOnIntervalElement = 'StartOnIntervalElement', @@ -101,3 +111,12 @@ export enum LocalizedStringKeys { SelectColumnTooltip = 'selectColumnTooltip', SetLbl = 'setLbl', } + +export enum IntervalType { + Day = 'day', + Days = 'days', + Weeks = 'weeks', + Months = 'months', + Week = 'week', + Month = 'month', +} diff --git a/projects/workflows-creator/src/lib/services/bpmn/builder.service.ts b/projects/workflows-creator/src/lib/services/bpmn/builder.service.ts index 3b9f009..388da48 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/builder.service.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/builder.service.ts @@ -347,7 +347,10 @@ export class BpmnBuilderService extends BuilderService< const [id, key] = property['name'].split('_'); if (state[id]) { try { - state[id][key] = JSON.parse(property['value']); + state[id][key] = + typeof JSON.parse(property['value']) === 'object' + ? JSON.parse(property['value']) + : property['value']; } catch (error) { state[id][key] = property['value']; } diff --git a/projects/workflows-creator/src/lib/services/bpmn/elements/gateways/gateway.element.ts b/projects/workflows-creator/src/lib/services/bpmn/elements/gateways/gateway.element.ts index 6c178e5..74b653f 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/elements/gateways/gateway.element.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/elements/gateways/gateway.element.ts @@ -21,7 +21,7 @@ export class GatewayElement extends BpmnElement { ) { super(); } - tag = 'bpmn:ExclusiveGateway'; + tag = 'bpmn:InclusiveGateway'; name = 'gateway'; properties = {}; statement: string | undefined; diff --git a/projects/workflows-creator/src/lib/services/bpmn/elements/process/process.element.ts b/projects/workflows-creator/src/lib/services/bpmn/elements/process/process.element.ts index 059f7ba..0c30ab4 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/elements/process/process.element.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/elements/process/process.element.ts @@ -30,6 +30,7 @@ export class ProcessElement extends BpmnElement { static identifier = 'ProcessElement'; attributes = { isExecutable: true, + historyTimeToLive: 'P3650D', }; getIdentifier(): string { diff --git a/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/change-column-value.task.ts b/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/change-column-value.task.ts index ce89120..26d9262 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/change-column-value.task.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/change-column-value.task.ts @@ -8,6 +8,7 @@ import {CREATE_TASK_STRATEGY} from '../../strategies/create'; import {LINK_BASIC_STRATEGY} from '../../strategies/link'; import {ServiceTaskElement} from './service-task.task'; import {ENV_TOKEN} from '../../../../token'; +import {InputTypes} from '../../../../enum'; @Injectable() export class ChangeColumnValue extends ServiceTaskElement { @@ -41,12 +42,25 @@ export class ChangeColumnValue extends ServiceTaskElement { }, changedValue: { formatter: (state: State) => { - if (typeof state.get('value') === 'object') { - return `'${JSON.stringify(state.get('value'))}'`; + switch (state.get('valueInputType')) { + case InputTypes.People: + return `'${JSON.stringify(state.get('value'))}'`; + case InputTypes.OptionList: + case InputTypes.List: + if (!state.get('value')) return ''; + return `'${JSON.stringify({ + displayValue: state.get('value').text?.split('"').join(''), + value: state.get('value').value, + iconClass: state.get('value').iconClass, + color: state.get('value').color, + bgColor: state.get('value').bgColor, + })}'`; + default: + return `'{"displayValue": "${ + state.get('valueName')?.split('"').join('') ?? + state.get('value') + }", "value": "${state.get('value')}"}'`; } - return `'{"displayValue": "${ - state.get('valueName') ?? state.get('value') - }", "value": "${state.get('value')}"}'`; }, }, }, diff --git a/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/read-column.task.ts b/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/read-column.task.ts index 5d7d7fd..64c116c 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/read-column.task.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/read-column.task.ts @@ -32,6 +32,9 @@ export class ReadColumnValue extends ServiceTaskElement { taskIds: { from: 'taskIds', }, + metaData: { + state: 'metaData', + }, groupColumnId: { state: 'column', }, diff --git a/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/send-email.task.ts b/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/send-email.task.ts index fb309e8..f52ef29 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/send-email.task.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/elements/tasks/send-email.task.ts @@ -54,7 +54,8 @@ export class SendEmail extends ServiceTaskElement { }, }, }; - outputs: string; + + outputs = 'outputVariable'; static identifier = 'SendEmail'; getIdentifier(): string { diff --git a/projects/workflows-creator/src/lib/services/bpmn/node.service.ts b/projects/workflows-creator/src/lib/services/bpmn/node.service.ts index 386f973..07f0ab7 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/node.service.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/node.service.ts @@ -34,18 +34,36 @@ export class BpmnNodesService extends NodeService { * > Get all the nodes that are of type `ACTION` * * The function is a bit more complicated than that, but that's the gist of it + * @param selectedEvent - Optional event identifier to filter actions by eventBinded property * @returns An array of action nodes. */ - getActions() { - return this.nodes - .map( - Node => - new Node( - this.localizationSvc.getLocalizedStringMap(), - this.utils.uuid(), - ), - ) - .filter(n => n.type === NodeTypes.ACTION); + getActions(selectedEvent?: string) { + const localizedStrings = this.localizationSvc.getLocalizedStringMap(); + + const actions = this.nodes + .map(Node => new Node(localizedStrings, this.utils.uuid())) + .filter(n => n.type === NodeTypes.ACTION) + .sort((a, b) => + a.name.toString().localeCompare(b.name.toString(), undefined, { + sensitivity: 'base', + }), + ); + + return actions.filter(action => { + const a = action as WorkflowAction & {eventBinded?: string[]}; + const events = a.eventBinded; + + if (!Array.isArray(events)) { + // unbound actions always allowed + return true; + } + + if (!selectedEvent) { + // no selectedEvent → return only unbound actions + return false; + } + return events.includes(selectedEvent); + }); } /** diff --git a/projects/workflows-creator/src/lib/services/bpmn/strategies/create/basic-interval-create.strategy.ts b/projects/workflows-creator/src/lib/services/bpmn/strategies/create/basic-interval-create.strategy.ts index 8813cf2..61da4f7 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/strategies/create/basic-interval-create.strategy.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/strategies/create/basic-interval-create.strategy.ts @@ -7,7 +7,17 @@ import { ModdleElement, RecordOfAnyType, } from '../../../../types'; -import {WorkflowElement} from '../../../../classes'; +import {State, WorkflowElement} from '../../../../classes'; + +enum WeekDaysEnum { + sunday = 1, + monday = 2, + tuesday = 3, + wednesday = 4, + thursday = 5, + friday = 6, + saturday = 7, +} @Injectable() export class CreateBasicIntervalStrategy @@ -38,9 +48,7 @@ export class CreateBasicIntervalStrategy const state = workflowNode.state; const timeCycle = this.moddle.create('bpmn:FormalExpression', { 'xsi:type': 'bpmn:tFormalExpression', - body: `R/P${state.get('timescale')}${state.get('value')}${state.get( - 'interval', - )}`, + body: this.intervalBodyPrepare(state), }); timerEventDefinition['timeCycle'] = timeCycle; @@ -53,6 +61,53 @@ export class CreateBasicIntervalStrategy }); } + private intervalBodyPrepare(state: State) { + if ( + state.get('interval') === 'M' && + state.get('toInterval') && + state.get('TimeInterval') + ) { + const val = + state.get('value') == 1 + ? '*' + : `${state.get('toInterval').month}/${state.get('value')}`; + const timeZoneDate = new Date(); + timeZoneDate.setHours(state.get('TimeInterval').hour); + timeZoneDate.setMinutes(state.get('TimeInterval').min); + return `0 timeZoneDate(${timeZoneDate})timeZoneDateEnd ${ + state.get('toInterval').date + } ${val} ?`; + } else if ( + state.get('interval') === 'W' && + state.get('toInterval') && + state.get('TimeInterval') + ) { + const val = state.get('value') == 1 ? '' : `/${state.get('value')}`; + let weekDays = state + .get('toInterval') + ?.ids?.map( + (day: string) => WeekDaysEnum[day as keyof typeof WeekDaysEnum], + ) + .join(','); + const timeZoneDate = new Date(); + timeZoneDate.setHours(state.get('TimeInterval').hour); + timeZoneDate.setMinutes(state.get('TimeInterval').min); + return `0 timeZoneDate(${timeZoneDate})timeZoneDateEnd ? * ${weekDays}${val}`; + } else if (state.get('interval') === 'D' && state.get('TimeInterval')) { + const today = new Date(); + today.setHours(state.get('TimeInterval').hour); + today.setMinutes(state.get('TimeInterval').min); + let isoString = ''; + if (today.getTime() < new Date().getTime()) { + today.setDate(today.getDate() + 1); + } + isoString = today.toISOString(); + return `R/${isoString}/P${state.get('value')}${state.get('interval')}`; + } else { + return '0 0 0 ? * *'; + } + } + /** * It takes an object of attributes and a node, and returns the same object of attributes, but with * any attribute that is a state reference replaced with the value of that state diff --git a/projects/workflows-creator/src/lib/services/bpmn/strategies/create/task-create.strategy.ts b/projects/workflows-creator/src/lib/services/bpmn/strategies/create/task-create.strategy.ts index 72562df..b18d0f4 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/strategies/create/task-create.strategy.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/strategies/create/task-create.strategy.ts @@ -142,38 +142,53 @@ export class CreateTaskStrategy implements CreateStrategy { let read = ''; if (froms.length > 0) { if (prevIds.length) { - read = `var readObj = ${prevIds - .map(id => `JSON.parse(execution.getVariable('${id}'))`) - .join(' || ')} || {};`; + read = `${prevIds + .map( + (id, index) => + `var readObj${index} = JSON.parse(execution.getVariable('${id}')) || {};`, + ) + .join('\n')} + `; } } - const getVariables = froms - .map( - p => - `var ${(p as FromParam).from}Local = readObj.${ - (p as FromParam).from - };`, - ) - .join('\n'); + const getVariables = froms.map( + p => + ` + var ${(p as FromParam).from}Local; + ${prevIds + .map( + (_: any, index: number) => ` + if(readObj${index}.${ + (p as FromParam).from + } && readObj${index}.${(froms[0] as FromParam).from}.length){ + ${(froms[0] as FromParam).from}Local = readObj${index}.${ + (froms[0] as FromParam).from + }; + } + `, + ) + .join('\n')} + `, + ); const setVariabels = Object.keys(params).reduce( (p: string, key: string) => { const tmp = params[key]; if (isFormattedParam(tmp)) { - return `${p}\njson.prop("${key}", ${tmp.formatter(state)});`; + return `${p}\njson["${key}"] = ${tmp.formatter(state)};`; } else if (isFromParam(tmp)) { - return `${p}\njson.prop("${key}", ${tmp.from}Local);`; + return `${p}\njson["${key}"] = ${tmp.from}Local;`; } else if (isStateParam(tmp)) { if ( tmp.state === 'recipients' && Array.isArray(state.get(tmp.state)) ) { const metaValue = this.transposeArrayToString(state.get(tmp.state)); - return `${p}\njson.prop("${key}", [${metaValue ?? ''}]);`; + return `${p}\njson["${key}"] = [${metaValue ?? ''}];`; } - return `${p}\njson.prop("${key}", "${state.get(tmp.state) ?? ''}");`; + return `${p}\njson["${key}"] = "${state.get(tmp.state) ?? ''}";`; } else { - return `${p}\njson.prop("${key}", "${tmp.value}");`; + return `${p}\njson["${key}"] = "${tmp.value}";`; } }, '', @@ -181,9 +196,9 @@ export class CreateTaskStrategy implements CreateStrategy { return [ read, getVariables, - `var json = S("{}");`, + `var json = {};`, setVariabels, - 'json', + 'JSON.stringify(json)', ].join('\n'); } diff --git a/projects/workflows-creator/src/lib/services/bpmn/strategies/link/gateway-link.strategy.ts b/projects/workflows-creator/src/lib/services/bpmn/strategies/link/gateway-link.strategy.ts index 5021d09..d936798 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/strategies/link/gateway-link.strategy.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/strategies/link/gateway-link.strategy.ts @@ -193,12 +193,13 @@ export class GatewayLinkStrategy implements LinkStrategy { ) { const lastNodeWithOutput = this.getLastNodeWithOutput(node); const read = `var readObj = JSON.parse(execution.getVariable('${lastNodeWithOutput.element.id}'));`; - const declarations = `var ids = [];var json = S("{}");`; + const declarations = `var ids = [];var json = {};`; const column = node.workflowNode.state.get('columnName'); const condition = this.getCondition(node); const loop = this.createLoopScript(node, condition, isElse); const setters = ` - json.prop("taskIds", ids); + json["taskIds"] = ids; + json = JSON.stringify(json); execution.setVariable('${flowId}',json); if(ids.length > 0){true;}else {false;} `; @@ -224,10 +225,30 @@ export class GatewayLinkStrategy implements LinkStrategy { ) { const column: string = node.workflowNode.state.get('columnName'); const conditionType = node.workflowNode.state.get('condition'); - const value = node.workflowNode.state.get('value'); + const valueType = node.workflowNode.state.get('valueType'); + const valueInputType = node.workflowNode.state.get('valueInputType'); - if (!conditionType && value) { - switch (value) { + if ( + valueInputType === InputTypes.Date && + (valueType === ValueTypes.Custom || + conditionType === ConditionTypes.Equal) + ) { + return ` + for (var key in readObj) { + var taskValuePair = readObj[key]; + if (taskValuePair && (taskValuePair.value || taskValuePair.value==='')) { + var readDateValue = taskValuePair.value.split('T')[0]; + var customDate = "${condition}"; + + if (${isElse ? '!' : ''}(readDateValue === customDate)) { + ids.push(taskValuePair.id); + } + } + } + `; + } + if (!conditionType && valueType && valueInputType === InputTypes.Date) { + switch (valueType) { case ValueTypes.PastToday: return ` for(var key in readObj){ @@ -252,6 +273,20 @@ export class GatewayLinkStrategy implements LinkStrategy { } } `; + case ValueTypes.Custom: + return ` + for (var key in readObj) { + var taskValuePair = readObj[key]; + if (taskValuePair && taskValuePair.value) { + var readDateValue = taskValuePair.value.split('T')[0]; + var customDate = "${condition}"; + + if (${isElse ? '!' : ''}(readDateValue === customDate)) { + ids.push(taskValuePair.id); + } + } + } + `; } } @@ -302,6 +337,28 @@ export class GatewayLinkStrategy implements LinkStrategy { } }`; } + if (column === InputTypes.Item) { + return `var selectedVals = ${condition}; + var selCol = selectedVals.split(','); + for(var key in readObj){ + var taskValuePair = readObj[key]; + if(taskValuePair && taskValuePair.value && taskValuePair.value.length){ + var hasItem = false; + var usCol = taskValuePair.value; + + for(var selKey in selCol){ + for(var myKey in usCol){ + if(usCol[myKey].value == selCol[selKey] && !hasItem){ + hasItem = true; + } + } + } + if(${conditionExpression}(hasItem)){ + ids.push(taskValuePair.id); + } + } + }`; + } switch (conditionType) { case ConditionTypes.PastToday: return ` @@ -322,9 +379,13 @@ export class GatewayLinkStrategy implements LinkStrategy { var taskValuePair = readObj[key]; if(taskValuePair && taskValuePair.value){ var readDateValue = new Date(taskValuePair.value); + var today = new Date(); + readDateValue.setHours(0,0,0,0); + today.setHours(0,0,0,0); + readDateValue.setDate(readDateValue.getDate()${condition}); if(${ isElse ? '!' : '' - }(readDateValue > new Date() && readDateValue.setDate(readDateValue.getDate()${condition}) < new Date())){ + }(readDateValue.valueOf() === today.valueOf())){ ids.push(taskValuePair.id); } } @@ -369,12 +430,21 @@ export class GatewayLinkStrategy implements LinkStrategy { private getCondition(node: BpmnStatementNode) { let value = node.workflowNode.state.get('value'); const valueType = node.workflowNode.state.get('valueInputType'); - if (valueType === InputTypes.Text || valueType === InputTypes.List) { - value = `'${value}'`; - } - if (value && valueType === InputTypes.People) { - return `'${value.ids}'`; - } + if (value) + switch (valueType) { + case InputTypes.Stepper: + case InputTypes.Text: + value = `'${value}'`; + break; + case InputTypes.OptionList: + case InputTypes.List: + value = `'${value.value}'`; + break; + case InputTypes.People: + return `'${value.ids}'`; + case InputTypes.Date: + return `${value.split('T')[0]}`; + } const condition = node.workflowNode.state.get('condition'); const pair = this.conditions.find(item => item.condition === condition); if (!pair) { diff --git a/projects/workflows-creator/src/lib/services/bpmn/strategies/link/or-gateway-link.strategy.ts b/projects/workflows-creator/src/lib/services/bpmn/strategies/link/or-gateway-link.strategy.ts index 9b4c948..2ed9de6 100644 --- a/projects/workflows-creator/src/lib/services/bpmn/strategies/link/or-gateway-link.strategy.ts +++ b/projects/workflows-creator/src/lib/services/bpmn/strategies/link/or-gateway-link.strategy.ts @@ -112,12 +112,13 @@ export class OrGatewayLinkStrategy implements LinkStrategy { ) { const lastNodeWithOutput = this.getLastNodeWithOutput(node); const read = `var readObj = JSON.parse(execution.getVariable('${lastNodeWithOutput.element.id}'));`; - const declarations = `var ids = [];var json = S("{}");`; + const declarations = `var ids = [];var json = {};`; const column = node.workflowNode.state.get('columnName'); const condition = this.getCondition(node); const loop = this.createLoopScript(node, condition, isElse); const setters = ` - json.prop("taskIds", ids); + json["taskIds"] = ids; + json.stringify(json); execution.setVariable('${flowId}',json); if(ids.length > 0){true;}else {false;} `; @@ -161,9 +162,13 @@ export class OrGatewayLinkStrategy implements LinkStrategy { var taskValuePair = readObj[key]; if(taskValuePair && taskValuePair.value){ var readDateValue = new Date(taskValuePair.value); + var today = new Date(); + readDateValue.setHours(0,0,0,0); + today.setHours(0,0,0,0); + readDateValue.setDate(readDateValue.getDate()${condition}); if(${ isElse ? '!' : '' - }(readDateValue > new Date() && readDateValue.setDate(readDateValue.getDate()${condition}) < new Date())){ + }(readDateValue.valueOf() === today.valueOf())){ ids.push(taskValuePair.id); } } diff --git a/projects/workflows-creator/src/lib/services/statement/events/oninterval.event.ts b/projects/workflows-creator/src/lib/services/statement/events/oninterval.event.ts index 60b3434..26fa0f1 100644 --- a/projects/workflows-creator/src/lib/services/statement/events/oninterval.event.ts +++ b/projects/workflows-creator/src/lib/services/statement/events/oninterval.event.ts @@ -2,8 +2,9 @@ import {LocalizedStringKeys, StartElementTypes} from '../../../enum'; import {RecordOfAnyType} from '../../../types'; import {BpmnEvent} from '../../../types/bpmn.types'; import {TriggerOnInterval} from '../../bpmn/elements/tasks/trigger-on-interval.task'; +import {TimeIntervalInput, ToIntervalInput} from '../inputs'; import {IntervalInput} from '../inputs/interval.input'; -import {ValueInput} from '../inputs/value.input'; +import {StepperInput} from '../inputs/stepper.input'; export class OnIntervalEvent extends BpmnEvent { groupType: string; @@ -14,7 +15,12 @@ export class OnIntervalEvent extends BpmnEvent { name = 'On Interval'; statement = 'Every '; properties = {}; - prompts = [ValueInput.identifier, IntervalInput.identifier]; + prompts = [ + StepperInput.identifier, + IntervalInput.identifier, + ToIntervalInput.identifier, + TimeIntervalInput.identifier, + ]; static identifier = 'OnIntervalEvent'; constructor( localizedStringMap: RecordOfAnyType, diff --git a/projects/workflows-creator/src/lib/services/statement/events/onvalue.event.ts b/projects/workflows-creator/src/lib/services/statement/events/onvalue.event.ts index a8c4c41..f792117 100644 --- a/projects/workflows-creator/src/lib/services/statement/events/onvalue.event.ts +++ b/projects/workflows-creator/src/lib/services/statement/events/onvalue.event.ts @@ -3,8 +3,8 @@ import {RecordOfAnyType} from '../../../types'; import {BpmnEvent} from '../../../types/bpmn.types'; import {GatewayElement} from '../../bpmn/elements/gateways/gateway.element'; import {ReadColumnValue} from '../../bpmn/elements/tasks/read-column.task'; -import {ColumnInput} from '../inputs/column.input'; import {ConditionInput} from '../inputs/condition.input'; +import {CriteriaInput} from '../inputs/criteria.input'; import {ValueInput} from '../inputs/value.input'; export class OnValueEvent extends BpmnEvent { @@ -16,7 +16,7 @@ export class OnValueEvent extends BpmnEvent { statement = 'check if '; properties = {}; prompts = [ - ColumnInput.identifier, + CriteriaInput.identifier, ConditionInput.identifier, ValueInput.identifier, ]; diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/column.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/column.input.ts index 0d942ef..7495fef 100644 --- a/projects/workflows-creator/src/lib/services/statement/inputs/column.input.ts +++ b/projects/workflows-creator/src/lib/services/statement/inputs/column.input.ts @@ -9,7 +9,7 @@ export class ColumnInput extends WorkflowPrompt { inputKey = 'column'; listNameField = 'text'; listValueField = 'value'; - placeholder = 'Column'; + placeholder = 'column'; options = (state: State) => state.get('columns'); static identifier = 'ColumnInput'; diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/criteria.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/criteria.input.ts new file mode 100644 index 0000000..754a0f1 --- /dev/null +++ b/projects/workflows-creator/src/lib/services/statement/inputs/criteria.input.ts @@ -0,0 +1,20 @@ +import {State, WorkflowPrompt} from '../../../classes'; +import {InputTypes} from '../../../enum'; +import {RecordOfAnyType} from '../../../types'; + +export class CriteriaInput extends WorkflowPrompt { + prefix = ''; + suffix = ''; + typeFunction = () => InputTypes.OptionList; + inputKey = 'column'; + listNameField = 'text'; + listValueField = 'value'; + placeholder = 'criteria'; + options = (state: State) => + state.get('columns'); + static identifier = 'CriteriaInput'; + + getIdentifier(): string { + return CriteriaInput.identifier; + } +} diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/email.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/email.input.ts index 141ec59..019a7e6 100644 --- a/projects/workflows-creator/src/lib/services/statement/inputs/email.input.ts +++ b/projects/workflows-creator/src/lib/services/statement/inputs/email.input.ts @@ -9,7 +9,7 @@ export class EmailDataInput extends WorkflowPrompt { suffix = ''; typeFunction = () => InputTypes.Email; inputKey = 'email'; - placeholder = 'Email'; + placeholder = 'email'; static identifier = 'EmailDataInput'; getIdentifier(): string { diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/index.ts b/projects/workflows-creator/src/lib/services/statement/inputs/index.ts index ea94d81..9207aaa 100644 --- a/projects/workflows-creator/src/lib/services/statement/inputs/index.ts +++ b/projects/workflows-creator/src/lib/services/statement/inputs/index.ts @@ -7,3 +7,7 @@ export * from './value.input'; export * from './interval.input'; export * from './triggercolumn.input'; export * from './valuetype.input'; +export * from './criteria.input'; +export * from './stepper.input'; +export * from './tointerval.input'; +export * from './timeinterval.input'; diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/interval.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/interval.input.ts index dbae337..2bcf237 100644 --- a/projects/workflows-creator/src/lib/services/statement/inputs/interval.input.ts +++ b/projects/workflows-creator/src/lib/services/statement/inputs/interval.input.ts @@ -3,13 +3,13 @@ import {InputTypes} from '../../../enum'; import {RecordOfAnyType} from '../../../types'; export class IntervalInput extends WorkflowPrompt { - prefix = ''; - suffix = ''; + prefix: string | {state: string} = {state: 'valuePrefix'}; + suffix: string | {state: string} = {state: 'intervalValueSuffix'}; typeFunction = () => InputTypes.List; inputKey = 'interval'; listNameField = 'text'; listValueField = 'value'; - placeholder = 'Interval'; + placeholder = 'interval'; options = (state: State) => state.get('intervalList'); static identifier = 'IntervalInput'; diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/stepper.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/stepper.input.ts new file mode 100644 index 0000000..06433e9 --- /dev/null +++ b/projects/workflows-creator/src/lib/services/statement/inputs/stepper.input.ts @@ -0,0 +1,21 @@ +import {State, WorkflowPrompt} from '../../../classes'; +import {InputTypes} from '../../../enum'; +import {RecordOfAnyType} from '../../../types'; + +export class StepperInput extends WorkflowPrompt { + prefix = ''; + suffix = ''; + typeFunction = () => InputTypes.Stepper; + inputKey = 'value'; + listNameField = 'text'; + listValueField = 'value'; + placeholder = 'n'; + customPlaceholder: string | {state: string} = {state: 'stepperPlaceholder'}; + options = (state: State) => + state.get('stepperCount') as []; + static identifier = 'StepperInput'; + + getIdentifier(): string { + return StepperInput.identifier; + } +} diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/timeinterval.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/timeinterval.input.ts new file mode 100644 index 0000000..b25aa25 --- /dev/null +++ b/projects/workflows-creator/src/lib/services/statement/inputs/timeinterval.input.ts @@ -0,0 +1,30 @@ +import {State, WorkflowPrompt} from '../../../classes'; +import {InputTypes, IntervalType} from '../../../enum'; +import {BpmnNode, RecordOfAnyType} from '../../../types'; +export class TimeIntervalInput extends WorkflowPrompt { + prefix: string | {state: string} = {state: 'timeIntervalSuffix'}; + suffix = ''; + typeFunction = () => InputTypes.IntervalTime; + inputKey = 'TimeInterval'; + listNameField = 'text'; + listValueField = 'value'; + placeholder = 'hh:mm'; + customPlaceholder: string | {state: string} = {state: 'timeStatePlaceholder'}; + isHidden = (node: BpmnNode) => { + return ![ + IntervalType.Weeks, + IntervalType.Months, + IntervalType.Week, + IntervalType.Month, + IntervalType.Days, + IntervalType.Day, + ].includes(node.state.get('intervalType')); + }; + options = (state: State) => + state.get('timevalues'); + static identifier = 'TimeIntervalInput'; + + getIdentifier(): string { + return TimeIntervalInput.identifier; + } +} diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/tointerval.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/tointerval.input.ts new file mode 100644 index 0000000..fcf6c88 --- /dev/null +++ b/projects/workflows-creator/src/lib/services/statement/inputs/tointerval.input.ts @@ -0,0 +1,30 @@ +import {State, WorkflowPrompt} from '../../../classes'; +import {InputTypes, IntervalType} from '../../../enum'; +import {BpmnNode, RecordOfAnyType} from '../../../types'; + +export class ToIntervalInput extends WorkflowPrompt { + prefix = ''; + suffix = ''; + typeFunction = (state: State) => + state.get('valueInputTypes') as InputTypes; + inputKey = 'toInterval'; + listNameField = 'text'; + listValueField = 'value'; + placeholder = 'weekday'; + customPlaceholder: string | {state: string} = {state: 'dateStatePlaceholder'}; + isHidden = (node: BpmnNode) => { + return ![ + IntervalType.Weeks, + IntervalType.Months, + IntervalType.Week, + IntervalType.Month, + ].includes(node.state.get('intervalType')); + }; + options = (state: State) => + state.get('intervalOption'); + static identifier = 'ToIntervalInput'; + + getIdentifier(): string { + return ToIntervalInput.identifier; + } +} diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/value.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/value.input.ts index 293db18..481b0bd 100644 --- a/projects/workflows-creator/src/lib/services/statement/inputs/value.input.ts +++ b/projects/workflows-creator/src/lib/services/statement/inputs/value.input.ts @@ -9,12 +9,12 @@ import { import {BpmnNode, RecordOfAnyType} from '../../../types'; export class ValueInput extends WorkflowListPrompt { - prefix: string | {state: string} = ''; + prefix: string | {state: string} = {state: 'valuePrefix'}; suffix: string | {state: string} = {state: 'valueSuffix'}; inputKey = 'value'; listNameField = 'text'; listValueField = 'value'; - placeholder = 'Something'; + placeholder = 'something'; customPlaceholder: string | {state: string} = {state: 'valuePlaceholder'}; isHidden = (node: BpmnNode) => { @@ -32,6 +32,7 @@ export class ValueInput extends WorkflowListPrompt { InputTypes.Number, InputTypes.People, InputTypes.Percentage, + InputTypes.Date, ].includes(node.state.get('valueInputType')) && node.state.get('valueType') !== ValueTypes.Custom) ); diff --git a/projects/workflows-creator/src/lib/services/statement/inputs/valuetype.input.ts b/projects/workflows-creator/src/lib/services/statement/inputs/valuetype.input.ts index 58ad5fc..43c785b 100644 --- a/projects/workflows-creator/src/lib/services/statement/inputs/valuetype.input.ts +++ b/projects/workflows-creator/src/lib/services/statement/inputs/valuetype.input.ts @@ -9,7 +9,7 @@ export class ValueTypeInput extends WorkflowListPrompt { inputKey = 'valueType'; listNameField = 'text'; listValueField = 'value'; - placeholder = 'Something'; + placeholder = 'something'; options = (state: State) => state.get('valueTypes') as []; @@ -20,6 +20,7 @@ export class ValueTypeInput extends WorkflowListPrompt { InputTypes.Number, InputTypes.People, InputTypes.Percentage, + InputTypes.Date, ].includes(node.state.get('valueInputType')); }; diff --git a/projects/workflows-creator/src/lib/types/base.types.ts b/projects/workflows-creator/src/lib/types/base.types.ts index 91916cf..d38576f 100644 --- a/projects/workflows-creator/src/lib/types/base.types.ts +++ b/projects/workflows-creator/src/lib/types/base.types.ts @@ -172,6 +172,7 @@ export type EmailInput = { subject: string; body: string; focusKey: string; + caretPos: number; }; /** diff --git a/projects/workflows-creator/src/lib/workflow-builder.module.ts b/projects/workflows-creator/src/lib/workflow-builder.module.ts index 2501d7d..f730147 100644 --- a/projects/workflows-creator/src/lib/workflow-builder.module.ts +++ b/projects/workflows-creator/src/lib/workflow-builder.module.ts @@ -89,6 +89,12 @@ import {TriggerColumnInput} from './services/statement/inputs/triggercolumn.inpu import {ValueTypeInput} from './services/statement/inputs/valuetype.input'; import {TooltipRenderComponent} from './builder/tooltip-render/tooltip-render.component'; import {LocalizationPipe} from './pipes/localization.pipe'; +import { + CriteriaInput, + StepperInput, + TimeIntervalInput, + ToIntervalInput, +} from './services'; @NgModule({ declarations: [ BuilderComponent, @@ -143,7 +149,11 @@ import {LocalizationPipe} from './pipes/localization.pipe'; {provide: BPMN_ELEMENTS, useClass: ChangeColumnValue, multi: true}, {provide: BPMN_ELEMENTS, useClass: ProcessPropertiesElement, multi: true}, {provide: BPMN_INPUTS, useClass: ColumnInput, multi: true}, + {provide: BPMN_INPUTS, useClass: CriteriaInput, multi: true}, + {provide: BPMN_INPUTS, useClass: StepperInput, multi: true}, + {provide: BPMN_INPUTS, useClass: TimeIntervalInput, multi: true}, {provide: BPMN_INPUTS, useClass: TriggerColumnInput, multi: true}, + {provide: BPMN_INPUTS, useClass: ToIntervalInput, multi: true}, {provide: BPMN_INPUTS, useClass: IntervalInput, multi: true}, {provide: BPMN_INPUTS, useClass: ConditionInput, multi: true}, {provide: BPMN_INPUTS, useClass: EmailDataInput, multi: true},