Current section

Files

Jump to
step_flow lib step_flow controllers workflows.ex
Raw

lib/step_flow/controllers/workflows.ex

defmodule StepFlow.Controllers.Workflows do
@moduledoc false
require Logger
alias StepFlow.Controllers.Jobs
alias StepFlow.Controllers.Progressions
@doc """
Returns an attribute map to have a workflow configuration.
"""
def get_attr(workflow) do
%{
schema_version: workflow.schema_version,
identifier: workflow.identifier,
version_major: workflow.version_major,
version_minor: workflow.version_minor,
version_micro: workflow.version_micro,
tags: workflow.tags,
is_live: workflow.is_live,
deleted: workflow.deleted,
notification_hooks:
Enum.map(workflow.notification_hooks, fn x -> Map.drop(x, ["status"]) end),
reference: workflow.reference,
user_uuid: workflow.user_uuid,
steps: Enum.map(workflow.steps, fn x -> Map.drop(x, [:jobs, :status, "started"]) end),
start_parameters: workflow.start_parameters,
parameters: workflow.parameters
}
end
def get_steps_with_status(steps, workflow_jobs, result \\ [])
def get_steps_with_status([], _workflow_jobs, result), do: result
def get_steps_with_status(nil, _workflow_jobs, result), do: result
def get_steps_with_status([step | steps], workflow_jobs, result) do
name = StepFlow.Map.get_by_key_or_atom(step, :name)
step_id = StepFlow.Map.get_by_key_or_atom(step, :id)
jobs =
workflow_jobs
|> Enum.filter(fn job -> job.name == name && job.step_id == step_id end)
job_status = count_step_jobs_status(jobs)
status = get_step_status_from_job_status(job_status)
step =
step
|> Map.put(:status, status)
|> Map.put(:jobs, job_status)
result = List.insert_at(result, -1, step)
get_steps_with_status(steps, workflow_jobs, result)
end
defp get_step_status_from_job_status(job_status) do
cond do
job_status.errors > 0 ->
:error
is_still_processing(job_status) ->
:processing
job_status.stopped > 0 ->
:stopped
job_status.paused > 0 ->
:paused
is_skipped(job_status) ->
:skipped
is_completed(job_status) ->
:completed
true ->
:pending
end
end
def update_step(workflow, step, key, value) do
workflow = StepFlow.Workflows.get_workflow!(workflow.id)
steps =
get_attr(workflow).steps
|> Enum.map(fn s ->
if StepFlow.Map.get_by_key_or_atom(s, :id) == StepFlow.Map.get_by_key_or_atom(step, :id) do
Map.put(s, key, value)
else
s
end
end)
StepFlow.Workflows.update_workflow(workflow, %{steps: steps})
end
defp count_step_jobs_status(jobs) do
completed = count_status(jobs, :completed)
errors = count_status(jobs, :error)
skipped = count_status(jobs, :skipped)
stopped = count_status(jobs, :stopped)
processing = count_status(jobs, :processing)
paused = count_status(jobs, :paused)
queued = count_status(jobs, :queued)
dropped = count_status(jobs, :dropped)
%{
total: length(jobs) - dropped,
completed: completed,
dropped: dropped,
errors: errors,
paused: paused,
processing: processing,
queued: queued,
skipped: skipped,
stopped: stopped
}
end
defp count_status(jobs, status, count \\ 0)
defp count_status([], _status, count), do: count
defp count_status([job | jobs], status, count) do
count_completed =
job.status
|> Enum.filter(fn s -> s.state == :completed end)
|> length
# A job with at least one status.state at :completed is considered :completed
count =
if count_completed >= 1 do
if status == :completed do
count + 1
else
count
end
else
count_uncompleted_status(status, job, count)
end
count_status(jobs, status, count)
end
defp count_uncompleted_status(status, job, count) do
if status in [:processing, :error, :paused, :skipped, :stopped, :queued, :dropped, :completed] do
count_for_status(job, count, status)
else
raise RuntimeError
Logger.error("unreachable")
count
end
end
defp count_for_status(_job, count, :completed), do: count
defp count_for_status(job, count, :processing) do
if job.progressions == [] do
count
else
last_progression =
job.progressions
|> Progressions.get_last_progression()
last_status =
job.status
|> Jobs.get_last_status()
cond do
last_status == nil ->
count + 1
NaiveDateTime.compare(last_progression.updated_at, last_status.updated_at) == :gt ->
count + 1
NaiveDateTime.compare(last_progression.updated_at, last_status.updated_at) == :eq and
last_status.state == :processing ->
count + 1
true ->
count
end
end
end
defp count_for_status(job, count, :queued) do
last_status = Jobs.get_last_status(job.status)
last_progression = Progressions.get_last_progression(job.progressions)
case {last_status, last_progression} do
{nil, nil} ->
count + 1
{nil, _} ->
count
{last_status, nil} when last_status.state == :queued ->
count + 1
{last_status, nil} when last_status.state == :retrying ->
count + 1
{last_status, _} when last_status.state == :retrying ->
if NaiveDateTime.compare(last_progression.updated_at, last_status.updated_at) == :gt do
count
else
count + 1
end
{_last_status, _} ->
count
end
end
defp count_for_status(job, count, status) do
case Jobs.get_last_status(job.status) do
nil -> count
last_status when last_status.state == status -> count + 1
_last_status -> count
end
end
# If a job status is not taken into account in the matrix, the step is considered processing
defp is_still_processing(job_status) do
job_status.processing > 0 || job_status.queued > 0 ||
((job_status.skipped > 0 || job_status.completed > 0) &&
job_status.completed + job_status.skipped != job_status.total &&
job_status.stopped + job_status.paused == 0)
end
defp is_skipped(job_status) do
job_status.skipped > 0 && job_status.skipped == job_status.total
end
defp is_completed(job_status) do
job_status.completed > 0 && job_status.completed + job_status.skipped == job_status.total
end
end