Packages
step_flow
1.8.0-rc0
1.9.0-rc2
1.9.0-rc1
1.9.0-rc0
1.8.2
1.8.1
1.8.1-rc8
1.8.1-rc7
1.8.1-rc6
1.8.1-rc5
1.8.1-rc4
1.8.1-rc3
1.8.1-rc2
1.8.1-rc1
1.8.1-rc0
1.8.0
1.8.0-rc3
1.8.0-rc2
1.8.0-rc1
1.8.0-rc0
1.7.3
1.7.3-rc4
1.7.3-rc3
1.7.3-rc2
1.7.3-rc1
1.7.3-rc0
1.7.2
1.7.2-rc4
1.7.2-rc3
1.7.2-rc2
1.7.2-rc1
1.7.2-rc0
1.7.1
1.7.0
1.7.0-rc1
1.7.0-rc0
1.6.1
1.6.1-rc1
1.6.1-rc0
1.6.0
1.6.0-rc9
1.6.0-rc8
1.6.0-rc7
1.6.0-rc6
1.6.0-rc5
1.6.0-rc4
1.6.0-rc3
1.6.0-rc20
1.6.0-rc2
1.6.0-rc19
1.6.0-rc18
1.6.0-rc17
1.6.0-rc16
1.6.0-rc15
1.6.0-rc14
1.6.0-rc13
1.6.0-rc12
1.6.0-rc11
1.6.0-rc10
1.6.0-rc1
1.5.0
1.5.0-rc1
1.4.2-rc2
1.4.2-rc1
1.4.1
1.4.1-rc1
1.4.0
1.4.0-rc4
1.4.0-rc3
1.4.0-rc2
1.4.0-rc1
1.3.1
1.3.0
1.3.0-rc
1.2.0
1.1.0
1.0.0
1.0.0-rc9
1.0.0-rc8
1.0.0-rc7
1.0.0-rc6
1.0.0-rc5
1.0.0-rc1
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.4
0.0.3
0.0.2
0.0.1
Step flow manager for Elixir applications
Current section
Files
Jump to
Current section
Files
lib/step_flow/notification_hooks/notification_hook_manager.ex
defmodule StepFlow.NotificationHooks.NotificationHookManager do
@moduledoc """
Notification manager context.
"""
alias StepFlow.Jobs
alias StepFlow.NotificationHooks.NotificationEndpoints
alias StepFlow.NotificationHooks.NotificationTemplates
alias StepFlow.Workflows
require Logger
def get_last_status(workflow_notification_data) do
if StepFlow.Map.get_by_key_or_atom(workflow_notification_data, "status") == nil do
[]
else
StepFlow.Map.get_by_key_or_atom(workflow_notification_data, "status")
end
end
def notification_slack_from_job(job, channel, exposed_domain_name, description) do
send(
:step_flow_slack_bot,
{:message,
"Error for job #{job.name} ##{job.id} <#{exposed_domain_name}/workflows/#{job.workflow_id} |Open Workflow>\n```#{description}```",
channel}
)
end
def notification_teams_from_job(job, url, workflow, exposed_domain_name, description) do
headers = [{"content-type", "application/json"}]
body =
Poison.encode!(%{
"@context": "https://schema.org/extensions",
"@type": "MessageCard",
potentialAction: [
%{
"@type": "OpenUri",
name: "View Workflow",
targets: [
%{
os: "default",
uri: "#{exposed_domain_name}/workflows/#{job.workflow_id}"
}
]
}
],
sections: [
%{
facts: [
%{
name: "Posted By:",
value: "#{exposed_domain_name}"
},
%{
name: "Workflow Reference:",
value: "#{workflow.reference}"
},
%{
name: "Workflow ID:",
value: "#{workflow.id}"
},
%{
name: "Workflow Identifier:",
value: "#{workflow.identifier}"
},
%{
name: "Job ID:",
value: "#{job.id}"
},
%{
name: "Job:",
value: "#{job.name}"
}
],
text: "```#{description}```"
}
],
summary: "MCAI Workflow Error Notification",
themeColor: "FF5733",
title: "MCAI Workflow Error Notification"
})
Logger.debug(
"#{__MODULE__}: POST #{url}, headers: #{inspect(headers)}, body: #{inspect(body)}"
)
case HTTPoison.post(url, body, headers) do
{:ok, %{body: body, headers: _, request: _, request_url: _, status_code: 200}} ->
{:ok, body}
{:ok, response} ->
Logger.error("Unable to notify: #{inspect(response)}")
{:error, error} ->
Logger.error("Unable to notify: #{inspect(error)}")
end
end
def notification_from_job(job_id, description \\ nil) do
job = Jobs.get_job!(job_id)
topic = "update_workflow_" <> Integer.to_string(job.workflow_id)
channel = StepFlow.Configuration.get_slack_channel()
workflow = Workflows.get_workflow!(job.workflow_id)
exposed_domain_name = StepFlow.Configuration.get_exposed_domain_name()
slack_token = StepFlow.Configuration.get_slack_token()
if !(blank?(slack_token) or blank?(description) or blank?(channel)) do
notification_slack_from_job(job, channel, exposed_domain_name, description)
end
teams_url = StepFlow.Configuration.get_teams_url()
if !(blank?(teams_url) or blank?(description)) do
notification_teams_from_job(job, teams_url, workflow, exposed_domain_name, description)
end
StepFlow.Notification.send(topic, %{workflow_id: job.workflow_id})
end
def notify_endpoint_with_status(
workflow_notification_url,
job,
workflow,
exposed_domain_name,
status,
notification_template_name,
workflow_endpoint_credentials
)
when is_binary(notification_template_name) do
case NotificationTemplates.get_notification_template_by_name(notification_template_name) do
nil ->
Logger.error("#{__MODULE__}: There is no notification template under that name")
{:error, "There is no notification template under that name."}
notification_template ->
notify_endpoint_with_status(
workflow_notification_url,
job,
workflow,
exposed_domain_name,
status,
notification_template,
workflow_endpoint_credentials
)
end
end
def notify_endpoint_with_status(
workflow_notification_url,
job,
workflow,
exposed_domain_name,
status,
notification_template,
workflow_endpoint_credentials
) do
case JSON.decode(notification_template.template_headers) do
{:error, err} ->
Logger.error("#{__MODULE__}: #{inspect(err)}")
{:error, "Couldn't parse the provided template headers."}
{:ok, json_headers} ->
fill_notification_template_body(
workflow_notification_url,
job,
workflow,
exposed_domain_name,
json_headers
|> Enum.map(fn elem -> Map.to_list(elem) end)
|> Enum.concat(),
status,
notification_template,
workflow_endpoint_credentials
)
end
end
defp fill_notification_template_body(
workflow_notification_url,
job,
workflow,
exposed_domain_name,
headers,
status,
notification_template,
workflow_endpoint_credentials
) do
accepted_parameters = %{
# Replacement for ternary operator
"exposed_domain_name" => (is_nil(exposed_domain_name) && "") || exposed_domain_name,
"workflow.id" => Integer.to_string(workflow.id),
"workflow.reference" => workflow.reference,
"workflow.identifier" => workflow.identifier,
"job.name" => (is_nil(job) && "") || job.name,
"job.id" => (is_nil(job) && "") || Integer.to_string(job.id),
"status" => status
}
case intern_template_process(notification_template.template_body, accepted_parameters) do
{:error, message} ->
{:error, message}
{:ok, template} ->
send_notification(
workflow_notification_url,
template,
headers,
job,
accepted_parameters,
workflow_endpoint_credentials
)
end
end
defp send_notification(
workflow_notification_url,
template_body_string,
headers,
job,
accepted_parameters,
workflow_endpoint_credentials
) do
body =
case JSON.decode(template_body_string) do
{:error, err} ->
Logger.error("#{__MODULE__}: #{inspect(err)}")
{:error, "Couldn't parse the provided template body."}
{:ok, json_body} ->
json_body
|> JSON.encode!()
end
options =
if workflow_endpoint_credentials != nil do
[username, password] = String.split(workflow_endpoint_credentials, ":", trim: true)
[hackney: [basic_auth: {username, password}]]
else
[]
end
Logger.debug(
"#{__MODULE__}: POST #{workflow_notification_url}, headers: #{inspect(headers)}, body: #{inspect(body)}"
)
case HTTPoison.post(workflow_notification_url, body, headers, options) do
{:error, err} ->
Logger.error("#{__MODULE__}: #{inspect(err)}")
{:error, "Couldn't post the notification with the provided notification template."}
{:ok, response} ->
if Map.has_key?(response, :status_code) do
condition =
if job do
"job_" <>
accepted_parameters["status"] <>
"_" <> Integer.to_string(job.id)
else
"workflow_" <> accepted_parameters["status"]
end
{
:ok,
%{
"condition" => condition,
"timestamp" =>
NaiveDateTime.utc_now()
|> NaiveDateTime.to_string()
|> NaiveDateTime.from_iso8601!()
|> NaiveDateTime.truncate(:second),
"response" => response.status_code
}
}
else
{:error, "The associated URL didn't reply when queried."}
end
end
end
def retrieve_status(
notification_endpoint,
_conditions,
_conditions_with_templates,
_status,
_workflow_notification_data,
_job,
_workflow,
_exposed_domain_name
)
when is_nil(notification_endpoint) do
Logger.error("#{__MODULE__}: no endpoints were defined.")
nil
end
def retrieve_status(
notification_endpoint,
conditions,
conditions_with_templates,
status,
workflow_notification_data,
job,
workflow,
exposed_domain_name
)
when status in ["error", "completed"] and job != nil do
cond do
Enum.member?(
conditions,
"job_completed"
) and status == "completed" ->
notify_endpoint(
notification_endpoint,
job,
workflow,
exposed_domain_name,
status,
conditions_with_templates["job_completed"],
retrieve_credentials(workflow_notification_data, notification_endpoint)
)
Enum.member?(
conditions,
"job_error"
) and status == "error" ->
notify_endpoint(
notification_endpoint,
job,
workflow,
exposed_domain_name,
status,
conditions_with_templates["job_error"],
retrieve_credentials(workflow_notification_data, notification_endpoint)
)
true ->
nil
end
end
def retrieve_status(
notification_endpoint,
conditions,
conditions_with_templates,
status,
workflow_notification_data,
nil,
workflow,
exposed_domain_name
)
when status in ["error", "completed"] do
cond do
Enum.member?(
conditions,
"workflow_completed"
) and status == "completed" ->
notify_endpoint(
notification_endpoint,
nil,
workflow,
exposed_domain_name,
status,
conditions_with_templates["workflow_completed"],
retrieve_credentials(workflow_notification_data, notification_endpoint)
)
Enum.member?(
conditions,
"workflow_error"
) and status == "error" ->
notify_endpoint(
notification_endpoint,
nil,
workflow,
exposed_domain_name,
status,
conditions_with_templates["workflow_error"],
retrieve_credentials(workflow_notification_data, notification_endpoint)
)
true ->
nil
end
end
def retrieve_status(
_notification_endpoint,
_conditions,
_conditions_with_templates,
status,
_workflow_notification_data,
_job,
_workflow,
_exposed_domain_name
)
when status not in ["error", "completed"],
do: nil
defp notify_endpoint(
notification_endpoint,
job,
workflow,
exposed_domain_name,
status,
conditions_with_templates,
credentials
) do
case notification_endpoint
|> StepFlow.Map.get_by_key_or_atom("endpoint_url", "")
|> notify_endpoint_with_status(
job,
workflow,
exposed_domain_name,
status,
conditions_with_templates,
credentials
) do
{:error, message} ->
Logger.error("#{__MODULE__}: #{message}")
nil
{:ok, payload} ->
payload
end
end
def manage_notification_status(workflow_id, job, status) do
workflow = Workflows.get_workflow!(workflow_id)
exposed_domain_name = StepFlow.Configuration.get_exposed_domain_name()
workflow_notifications_with_status =
for workflow_notification_data <- workflow.notification_hooks do
conditions_with_templates =
StepFlow.Map.get_by_key_or_atom(workflow_notification_data, "conditions")
last_status = get_last_status(workflow_notification_data)
endpoint_key = StepFlow.Map.get_by_key_or_atom(workflow_notification_data, "endpoint")
case {NotificationEndpoints.get_notification_endpoint_by_placeholder(endpoint_key),
is_map(conditions_with_templates)} do
{notification_endpoint, true} ->
conditions =
conditions_with_templates
|> Map.keys()
new_status =
retrieve_status(
notification_endpoint,
conditions,
conditions_with_templates,
status,
workflow_notification_data,
job,
workflow,
exposed_domain_name
)
if new_status != nil do
Map.put(workflow_notification_data, "status", last_status ++ [new_status])
else
workflow_notification_data
end
{nil, _} ->
Logger.error("Could not find an url for template endpoint key #{endpoint_key}")
{_, false} ->
Logger.error("Condition templates is not a map!")
end
end
{_, workflow} =
Workflows.update_workflow(workflow, %{
notification_hooks: workflow_notifications_with_status
})
workflow
end
defp retrieve_credentials(notification_data, notification_endpoint) do
notification_endpoint_credentials =
StepFlow.Map.get_by_key_or_atom(notification_endpoint, "endpoint_credentials")
cond do
!blank?(notification_endpoint_credentials) ->
notification_endpoint_credentials
!blank?(
notification_data_credentials =
StepFlow.Map.get_by_key_or_atom(notification_data, "credentials")
) ->
notification_data_credentials
true ->
nil
end
end
defp intern_template_process(template, accepted_parameters) do
accepted_parameters
|> Map.to_list()
|> replace(template)
|> check_remaining_to_template()
end
defp replace([], template), do: template
defp replace([{key, value} | keys], template) do
template =
String.replace(
template,
"#" <> key <> "#",
value
)
replace(keys, template)
end
defp check_remaining_to_template(template) do
case Regex.scan(~r/#(?:[[:word:]][._()-]?)*#/, template) do
[] ->
{:ok, template}
array ->
message = "Following parameters are not availables : #{inspect(array)}.
Please check the documentation for a list of available parameters."
Logger.error(message)
{:error, message}
end
end
defp blank?(path),
do: "" == path |> to_string() |> String.trim()
end