Replacement with Automation Rules
Migration instruction from Webhooks to Automation Rules
It’s time to completely deprecate the Webhook plugin. Therefore we’ve created this article in order to help you with the migration process from Webhooks to Automation Rules.
Webhooks will be removed the 24 of November, 2021
All the Webhook profiles have to be migrated before this date to Automation Rules
Let’s go through the migration process together with you and start with comparison of blocks in Automation rules and Webhook.
Trigger block
When
in Automation rules correspond to the Trigger on events
and Trigger on entities
blocks in Webhooks plugin profile. You can choose the entity to be affected from the dropdown in ‘When’ same as how you can check the box of Trigger on Entities
.
Filter block
Filters in Automation rules go directly after the triggers and they correspond to the Filter
block in webhooks.
You can do far more in filtering with Automation rules than what you used to be able to do in Webhooks plugin:
Action block
Then
in Automation Rules corresponds to the combination of template
block and Webhook URL
in Webhooks plugin:
Webhook plugin URL | Webhook plugin Template | Automation Rule action | Corresponding example |
---|---|---|---|
External link | JSON payload | Send HTTP request action, Webhook URL as URL JSON payload as Request body | External integration |
Targetprocess API link /api/v1/ pointing to the same entity as in the trigger | fields of the entity, including {{ID}} of the entity | Update entity action, with all the corresponding fields set | Modify an entity on its creation |
Targetprocess API link /api/v1/ pointing to a different entity from what we have in the trigger | fields of the entity | Create entity action, with all the corresponding fields set | Add another entity |
External link | empty template | Javasciprt action, where we enumerate all the fields that used to be send by default in Webhook plugin | External integration with empty template |
Example: External integration (sending Slack notifications)
Slack notification using Webhooks plugin shows the procedure to create a webhook for slack notification.
Slack notifications using Automation Rules shows the procedure to create an Automation rule for sending slack notification.
The below picture depicts the complete comparison of how it looks when all trigger, filter and action parts are put all together in both Webhooks and Automation rules.
You can modify any block as you wish with UI if feasible or some complex features with Javascript.
Feel free to have a look at our Dev guide that contains more real time examples of Automation Rules: Automation Rules overview
Example: modify an entity on its creation
This example sets the default description on creation of Userstory. This is how it looks in webhooks and Automation rules.
And the result for both would look like this.
Example: add another entity (follower) when a work item is created or updated
Now when users create an entity (e.g. User Story, Feature, Bug, etc.), they are automatically subscribed as followers to this entity and nested ones.
The result in both the cases would look like this.
Example: External integration with empty template
For User Story
Webhook setup:
Automation rule setup:
{
"pipeline": [
{
"type": "source:targetprocess:EntityChanged",
"entityTypes": [
"UserStory"
],
"modifications": {
"created": true,
"deleted": true,
"updated": [
"Feature",
"CustomFields",
"Description",
"EntityState",
"EntityType",
"Iteration",
"Name",
"Priority",
"Project",
"Release",
"AssignedTeams",
"TeamIteration",
"Owner",
"InitialEstimate",
"EffortToDo",
"Effort",
"EffortCompleted",
"LastCommentedUser",
"LastEditor",
"NumericPriority",
"Progress",
"TimeRemain",
"TimeSpent",
"CreateDate",
"EndDate",
"LastCommentDate",
"ModifyDate",
"PlannedEndDate",
"PlannedStartDate",
"StartDate",
"Id"
]
}
},
{
"type": "action:JavaScript",
"script": "const webhookUrl = \"https://webhook.site/b146b650-9dbb-4131-83f3-774084af8353\";\n\nconst api = context.getService(\"targetprocess/api/v2\");\nconst [author, customFields, newOwner] = await Promise.all([\n api.getByIdAsync(\"GeneralUser\", args.Author.Id, {\n select: \"{id,email,modifydate,firstname,lastname,isadministrator}\"\n }),\n api.queryAsync(\"CustomField\", {\n select: \"{name,entityFieldName,value,isSystem,required,fieldType,config,isdynamicColumn,entityFieldNameV2}\",\n orderBy: \"entityFieldName\",\n where: `entitytype.id=${args.Current.EntityType.Id} and Process.Projects.Count(Id=${args.Current.Project.Id})>0`\n }),\n api.getByIdAsync(\"GeneralUser\", args.Current.Owner.Id, {\n select: \"fullName\"\n })\n]);\nconst oldOwner = args.Previous && args.Previous.Owner.Id != args.Current.Owner.Id\n ? await api.getByIdAsync(\"GeneralUser\", args.Previous.Owner.Id, {\n select: \"fullName\"\n })\n : newOwner;\n\nfunction getProcessedCfValue(field, cfValue, valueArray) {\n let value = cfValue || undefined;\n if (value || field.fieldType == \"Number\") {\n switch (field.fieldType) {\n case \"Date\":\n if (value) {\n let date = new Date(value);\n let offset = value.substring(19, 22);\n let ticks = ((date.getTime() * 10000) + 621355968000000000) + (parseInt(offset) * 60 * 600000000);\n value = value.substring(0, 10);\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: ticks });\n }\n break;\n case \"Number\":\n if (!value) { value = 0; }\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n case \"Entity\":\n value = value.Id;\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n case \"URL\":\n value = `${value.label}\\r\\n${value.url}`;\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n default:\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n }\n }\n return value;\n}\n\nfunction buildMeta(field, value) {\n return {\n \"Name\": field.name,\n \"Items\": field.value ? field.value.split(\"\\r\\n\") : [],\n \"IsSystem\": field.isSystem,\n \"EntityFieldName\": field.entityFieldName,\n \"FieldType\": fieldTypes.indexOf(field.fieldType),\n \"Required\": field.required,\n \"Value\": value,\n \"DefaultValue\": field.config.defaultValue || undefined,\n \"IsDynamicColumn\": field.isdynamicColumn,\n \"EntityFieldNameV2\": field.entityFieldNameV2,\n \"CalculationModel\": field.config.calculationModel || undefined\n }\n}\n\nlet newFieldValues = [];\nlet newFieldMeta = [];\nlet oldFieldValues = [];\nlet oldFieldMeta = [];\nconst fieldTypes = [\"Text\", \"DropDown\", \"CheckBox\", \"URL\", \"Date\", \"RichText\", \"Number\", \"Money\", \"Entity\",\n \"MultipleSelectionList\", \"TemplatedURL\", \"MultipleEntities\", \"Money\"];\nfor (let field of customFields) {\n let newValue = getProcessedCfValue(field, args.Current[field.name], newFieldValues);\n newFieldMeta.push(buildMeta(field, newValue));\n if (args.Previous) {\n let oldValue = getProcessedCfValue(field, args.Previous[field.name], oldFieldValues);\n oldFieldMeta.push(buildMeta(field, oldValue));\n }\n}\n\nfunction buildEntity(enity, fieldValues, fieldMeta, owner) {\n if (!enity) { return {} }\n let formattedEntity = {\n \n \"ID\": args.ResourceId,\n \"UserStoryID\": args.ResourceId,\n \"Name\": enity.Name,\n \"Description\": enity.Description || undefined,\n \"StartDate\": enity.StartDate ? enity.StartDate.substr(0, 19) : undefined,\n \"CreateDate\": enity.CreateDate.substring(0, 19),\n \"ModifyDate\": enity.ModifyDate.substring(0, 19),\n \"PlannedStartDate\": enity.PlannedStartDate ? enity.PlannedStartDate.substr(0, 19) : undefined,\n \"PlannedEndDate\": enity.PlannedEndDate ? enity.PlannedEndDate.substr(0, 19) : undefined,\n \"LastCommentDate\": enity.LastCommentDate ? enity.LastCommentDate.substr(0, 19) : undefined,\n \"NumericPriority\": enity.NumericPriority,\n \"Effort\": enity.Effort,\n \"EffortCompleted\": enity.EffortCompleted,\n \"EffortToDo\": enity.EffortToDo,\n \"TimeSpent\": enity.TimeSpent,\n \"TimeRemain\": enity.TimeRemain,\n \"InitialEstimate\": enity.InitialEstimate,\n \"LastCommentUserID\": enity.LastCommentedUser ? enity.LastCommentedUser.Id : undefined,\n \"OwnerID\": enity.Creator.Id,\n \"LastEditorID\": enity.LastEditor.Id,\n \"EntityStateID\": enity.EntityState.Id,\n \"PriorityID\": enity.Priority.Id,\n \"ProjectID\": enity.Project.Id,\n \"ParentID\": enity.Feature ? enity.Feature.Id : undefined,\n \"SquadIterationID\": enity.SquadIterationID? enity.SquadIterationID.Id :undefined,\n \"ReleaseID\": enity.Release ? enity.Release.Id : undefined,\n\n \"FeatureID\": enity.Feature ? enity.Feature.Id : undefined,\n \"EntityTypeName\": \"Tp.BusinessObjects.\" + args.ResourceType,\n \"EntityStateName\": enity.EntityState.Name,\n \"PriorityName\": enity.Priority.Name,\n \"ProjectName\": enity.Project.Name,\n \"ParentName\": enity.Feature ? enity.Feature.Name : undefined,\n \"FeatureName\": enity.Feature ? enity.Feature.Name : undefined,\n \"ReleaseName\": enity.Release ? enity.Release.Name : \"Backlog\",\n \"EntityTypeID\": enity.EntityType.Id,\n \"CustomFieldsMetaInfo\": fieldMeta,\n \"EndDate\": enity.EndDate ? enity.EndDate.substring(0, 19) : undefined,\n \"Progress\": enity.Progress,\n \"LastStateChangeDate\": enity.LastStateChangeDate ? enity.LastStateChangeDate.substring(0, 19) : undefined,\n \"OwnerName\": owner,\n \"SquadID\": enity.Team ? enity.Team.Id : undefined,\n \"SquadName\": enity.Team ? enity.Team.Name : undefined,\n \"ResponsibleSquadID\": enity.ResponsibleTeam ? enity.ResponsibleTeam.Id : undefined\n };\n for (let cf of fieldValues) {\n formattedEntity[cf.cfColumn] = cf.cfValue;\n }\n return formattedEntity;\n}\n\nconst http = require('http');\nawait http.postAsync(webhookUrl, {\n body: {\n \"Entity\": buildEntity(args.Current, newFieldValues, newFieldMeta, newOwner),\n \"OldEntity\": buildEntity(args.Previous, oldFieldValues, oldFieldMeta, oldOwner),\n \"ChangedFields\": args.Modification == \"Updated\" ? args.ChangedFields : [],\n \"Author\": {\n \"ID\": author.id,\n \"UserID\": author.id,\n \"FirstName\": author.firstname,\n \"LastName\": author.lastname,\n \"Email\": author.email,\n \"ModifyDate\": author.modifydate? author.modifydate.substring(0, 19):undefined,\n \"IsAdministrator\": author.isadministrator\n },\n \"Modification\": args.Modification\n },\n headers: { 'content-type': 'application/json' }\n})"
}
]
}
For Role Efforts
Webhook Setup :
Automation Rule setup:
{
"pipeline": [
{
"type": "source:targetprocess:EntityChanged",
"entityTypes": [
"roleeffort"
],
"modifications": {
"created": true,
"deleted": true,
"updated": [
"Effort"
]
}
},
{
"type": "action:JavaScript",
"script": "const webhookUrl = \"https://webhook.site/b146b650-9dbb-4131-83f3-774084af8353\";\nconst api = context.getService(\"targetprocess/api/v2\");\n\nconst author = await api.getByIdAsync(\"GeneralUser\", args.Author.Id, {\n select: \"{id,email,modifydate,firstname,lastname,isadministrator}\"\n});\nconst http = require('http');\n\nawait http.postAsync(webhookUrl, {\n body: {\n \"Entity\": {\n \"ID\": args.ResourceId,\n \"RoleEffortID\": args.ResourceId,\n \"InitialEstimate\": args.Current.InitialEstimate,\n \"Effort\": args.Current.Effort,\n \"EffortCompleted\": args.Current.EffortCompleted,\n \"EffortToDo\": args.Current.EffortToDo,\n \"TimeSpent\": args.Current.TimeSpent,\n \"TimeRemain\": args.Current.TimeRemain,\n \"AssignableID\": args.Current.Assignable.Id,\n \"RoleID\": args.Current.Role.Id,\n \"AssignableName\": args.Current.Assignable.Name,\n \"RoleName\": args.Current.Role.Name\n \n \n },\n \"OldEntity\": args.Previous ? {\n \"ID\": args.ResourceId,\n \"RoleEffortID\": args.ResourceId,\n \"InitialEstimate\": args.Previous.InitialEstimate,\n \"Effort\": args.Previous.Effort,\n \"EffortCompleted\": args.Previous.EffortCompleted,\n \"EffortToDo\": args.Previous.EffortToDo,\n \"TimeSpent\": args.Previous.TimeSpent,\n \"TimeRemain\": args.Previous.TimeRemain,\n \"AssignableID\": args.Previous.Assignable.Id,\n \"RoleID\": args.Previous.Role.Id,\n \"AssignableName\": args.Previous.Assignable.Name,\n \"RoleName\": args.Previous.Role.Name\n\n } : {},\n \"ChangedFields\": args.Modification==\"Updated\"?args.ChangedFields:[],\n \"Author\": {\n \"ID\": author.id,\n \"UserID\": author.id,\n \"FirstName\": author.firstname,\n \"LastName\": author.lastname,\n \"Email\": author.email,\n \"ModifyDate\": author.modifydate? author.modifydate.substring(0, 19):undefined,\n \"IsAdministrator\": author.isadministrator\n },\n \"Modification\": args.Modification\n },\n headers: { 'content-type': 'application/json' }\n})"
}
]
}
For Bug
Webhook setup:
Automation Rule setup:
{
"pipeline": [
{
"type": "source:targetprocess:EntityChanged",
"entityTypes": [
"bug"
],
"modifications": {
"created": true,
"deleted": false,
"updated": [
"EntityState"
]
}
},
{
"or": [
{
"and": [
{
"value": {
"type": "constant",
"value": "Testing Rejected"
},
"target": {
"name": "Name",
"type": "field",
"target": {
"name": "EntityState",
"type": "field",
"target": {
"type": "pipelineBlockOutput"
}
}
},
"operator": {
"type": "is"
}
}
]
}
],
"type": "filter:Relational"
},
{
"type": "action:JavaScript",
"script": "const webhookUrl = \"https://webhook.site/b64de715-1b5c-4c77-bd0a-6d3b4beffa40\";\n\nconst api = context.getService(\"targetprocess/api/v2\");\nconst [author, customFields, newOwner] = await Promise.all([\n api.getByIdAsync(\"GeneralUser\", args.Author.Id, {\n select: \"{id,email,modifydate,firstname,lastname,isadministrator}\"\n }),\n api.queryAsync(\"CustomField\", {\n select: \"{name,entityFieldName,value,isSystem,required,fieldType,config,isdynamicColumn,entityFieldNameV2}\",\n orderBy: \"entityFieldName\",\n where: `entitytype.id=${args.Current.EntityType.Id} and Process.Projects.Count(Id=${args.Current.Project.Id})>0`\n }),\n api.getByIdAsync(\"GeneralUser\", args.Current.Owner.Id, {\n select: \"fullName\"\n })\n]);\nconst oldOwner = args.Previous && args.Previous.Owner.Id != args.Current.Owner.Id\n ? await api.getByIdAsync(\"GeneralUser\", args.Previous.Owner.Id, {\n select: \"fullName\"\n })\n : newOwner;\n\nfunction getProcessedCfValue(field, cfValue, valueArray) {\n let value = cfValue || undefined;\n if (value || field.fieldType == \"Number\") {\n switch (field.fieldType) {\n case \"Date\":\n if (value) {\n let date = new Date(value);\n let offset = value.substring(19, 22);\n let ticks = ((date.getTime() * 10000) + 621355968000000000) + (parseInt(offset) * 60 * 600000000);\n value = value.substring(0, 10);\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: ticks });\n }\n break;\n case \"Number\":\n if (!value) { value = 0; }\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n case \"Entity\":\n value = value.Id;\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n case \"URL\":\n value = `${value.label}\\r\\n${value.url}`;\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n default:\n valueArray.push({ cfColumn: field.entityFieldName, cfValue: value });\n break;\n }\n }\n return value;\n}\n\nfunction buildMeta(field, value) {\n return {\n \"Name\": field.name,\n \"Items\": field.value ? field.value.split(\"\\r\\n\") : [],\n \"IsSystem\": field.isSystem,\n \"EntityFieldName\": field.entityFieldName,\n \"FieldType\": fieldTypes.indexOf(field.fieldType),\n \"Required\": field.required,\n \"Value\": value,\n \"DefaultValue\": field.config.defaultValue || undefined,\n \"IsDynamicColumn\": field.isdynamicColumn,\n \"EntityFieldNameV2\": field.entityFieldNameV2,\n \"CalculationModel\": field.config.calculationModel || undefined\n }\n}\n\nlet newFieldValues = [];\nlet newFieldMeta = [];\nlet oldFieldValues = [];\nlet oldFieldMeta = [];\nconst fieldTypes = [\"Text\", \"DropDown\", \"CheckBox\", \"URL\", \"Date\", \"RichText\", \"Number\", \"Money\", \"Entity\",\n \"MultipleSelectionList\", \"TemplatedURL\", \"MultipleEntities\", \"Money\"];\nfor (let field of customFields) {\n let newValue = getProcessedCfValue(field, args.Current[field.name], newFieldValues);\n newFieldMeta.push(buildMeta(field, newValue));\n if (args.Previous) {\n let oldValue = getProcessedCfValue(field, args.Previous[field.name], oldFieldValues);\n oldFieldMeta.push(buildMeta(field, oldValue));\n }\n}\n\nfunction buildEntity(enity, fieldValues, fieldMeta, owner) {\n if (!enity) { return {} }\n let formattedEntity = {\n\n \"ID\": args.ResourceId,\n \"BugID\": args.ResourceId,\n \"Name\": enity.Name,\n \"CreateDate\": enity.CreateDate.substring(0, 19),\n \"ModifyDate\": enity.ModifyDate.substring(0, 19),\n \"NumericPriority\": enity.NumericPriority,\n \"Effort\": enity.Effort,\n \"EffortCompleted\": enity.EffortCompleted,\n \"EffortToDo\": enity.EffortToDo,\n \"TimeSpent\": enity.TimeSpent,\n \"TimeRemain\": enity.TimeRemain,\n \"OwnerID\": enity.Creator.Id,\n \"LastEditorID\": enity.LastEditor.Id,\n \"EntityStateID\": enity.EntityState.Id,\n \"PriorityID\": enity.Priority.Id,\n \"ProjectID\": enity.Project.Id,\n\"SeverityID\": enity.Severity.Id,\n \"EntityTypeName\": \"Tp.BusinessObjects.\" + args.ResourceType,\n \"EntityStateName\": enity.EntityState.Name,\n \"PriorityName\": enity.Priority.Name,\n \"ProjectName\": enity.Project.Name,\n \"ReleaseName\": enity.Release ? enity.Release.Name : \"Backlog\",\n \"SeverityName\": enity.Severity.Name,\n \"EntityTypeID\": enity.EntityType.Id,\n \"CustomFieldsMetaInfo\": fieldMeta,\n\n \"Description\": enity.Description || undefined,\n \"StartDate\": enity.StartDate ? enity.StartDate.substr(0, 19) : undefined,\n \n \"PlannedStartDate\": enity.PlannedStartDate ? enity.PlannedStartDate.substr(0, 19) : undefined,\n \"PlannedEndDate\": enity.PlannedEndDate ? enity.PlannedEndDate.substr(0, 19) : undefined,\n \"LastCommentDate\": enity.LastCommentDate ? enity.LastCommentDate.substr(0, 19) : undefined,\n \n \n \"InitialEstimate\": enity.InitialEstimate,\n \"LastCommentUserID\": enity.LastCommentedUser ? enity.LastCommentedUser.Id : undefined,\n \n \"ParentID\": enity.Feature ? enity.Feature.Id : undefined,\n \"SquadIterationID\": enity.SquadIterationID ? enity.SquadIterationID.Id : undefined,\n \"SquadIterationName\": enity.SquadIterationID ? enity.SquadIterationID.Name : undefined,\n \"ReleaseID\": enity.Release ? enity.Release.Id : undefined,\n\n \"FeatureID\": enity.Feature ? enity.Feature.Id : undefined,\n \"UserStoryName\": enity.UserStory ? enity.UserStory.Name : undefined,\n \"UserStoryID\": enity.UserStory ? enity.UserStory.Id : undefined,\n \"ParentName\": enity.Feature ? enity.Feature.Name : undefined,\n \"FeatureName\": enity.Feature ? enity.Feature.Name : undefined,\n \n \n \"EndDate\": enity.EndDate ? enity.EndDate.substring(0, 19) : undefined,\n \"Progress\": enity.Progress,\n \"LastStateChangeDate\": enity.LastStateChangeDate ? enity.LastStateChangeDate.substring(0, 19) : undefined,\n \"OwnerName\": owner,\n \"SquadID\": enity.Team ? enity.Team.Id : undefined,\n \"SquadName\": enity.Team ? enity.Team.Name : undefined,\n \"ResponsibleSquadID\": enity.ResponsibleTeam ? enity.ResponsibleTeam.Id : undefined\n };\n for (let cf of fieldValues) {\n formattedEntity[cf.cfColumn] = cf.cfValue;\n }\n return formattedEntity;\n}\n\nconst http = require('http');\nawait http.postAsync(webhookUrl, {\n body: {\n \n \"Entity\": buildEntity(args.Current, newFieldValues, newFieldMeta, newOwner),\n \"OldEntity\": buildEntity(args.Previous, oldFieldValues, oldFieldMeta, oldOwner),\n \"ChangedFields\": args.Modification == \"Updated\" ? args.ChangedFields : [],\n \"Author\": {\n \"ID\": author.id,\n \"UserID\": author.id,\n \"FirstName\": author.firstname,\n \"LastName\": author.lastname,\n \"Email\": author.email,\n \"ModifyDate\": author.modifydate ? author.modifydate.substring(0, 19) : undefined,\n \"IsAdministrator\": author.isadministrator\n },\n \"Modification\": args.Modification\n },\n headers: { 'content-type': 'application/json' }\n})"
}
]
}
Updated 4 months ago