defmodule Oban.Console.Interactive do alias Oban.Console.Jobs alias Oban.Console.Queues alias Oban.Console.Storage alias Oban.Console.View.Printer @spec start() :: :ok def start do initial_menu() end @spec initial_menu() :: :ok defp initial_menu do Printer.menu("Menu:", [ {1, "Jobs"}, {2, "Queues"}, {3, "Profile"}, {0, "Exit"} ]) ["Select an option: "] |> Printer.gets(:downcase) |> command(:initial_menu) end @spec goodbye() :: :ok defp goodbye, do: Printer.menu("Goodbye!", []) @spec profile() :: :ok defp profile do list = case Storage.get_profiles() do %{} = profiles -> options = profiles |> Enum.filter(fn {p, _} -> p != "selected" end) |> Enum.with_index() |> Enum.map(fn {{k, _}, i} -> {to_string(i + 1), k} end) Printer.menu("Profiles", options) options nil -> [] end name = Printer.gets(["Select/Create your profile", "Number/Name: "]) case Storage.find_or_create_profile(name, list) do :ok -> Printer.green("Profile set") |> IO.puts() {:error, error} -> Printer.red("Error | #{error}") |> IO.puts() end end @spec queues() :: :ok defp queues do Queues.show_list() Printer.menu( "Queues:", [ {1, "List/Refresh"}, {2, "Pause"}, {3, "Resume"}, {0, "Return"} ] ) ["Select an option: "] |> Printer.gets(:downcase) |> command(:queues) end @spec resume_queue() :: :ok defp resume_queue do ["Resume", "Queue name: "] |> get_customer_string_list_input() |> command(:resume_queues) end @spec pause_queue() :: :ok defp pause_queue do ["Pause", "Queue name: "] |> get_customer_string_list_input() |> command(:pause_queues) end @spec jobs(Keyword.t(), boolean()) :: :ok defp jobs(opts \\ [], list \\ true) do if list, do: Jobs.show_list(opts) Printer.menu("Jobs:", [ {1, "List/Refresh"}, {2, "Filter"}, {3, "Debug"}, {4, "Retry"}, {5, "Cancel"}, {6, "Clean"}, {7, "History"}, {0, "Return"} ]) ["Select an option: "] |> Printer.gets(:downcase) |> command(:jobs) end @spec debug_jobs() :: :ok defp debug_jobs do ["Debug", "Job IDs (comma separated): "] |> get_customer_integer_list_input() |> command(:debug_jobs) end @spec clean_jobs :: :ok defp clean_jobs, do: Jobs.clean_storage() @spec cancel_jobs() :: :ok defp cancel_jobs do ["Cancel", "Job IDs (comma separated): "] |> get_customer_integer_list_input() |> command(:cancel_jobs) end @spec retry_jobs() :: :ok defp retry_jobs do ["Retry", "Job IDs (comma separated): "] |> get_customer_integer_list_input() |> command(:retry_jobs) end defp parse_filter_history(filter) do filter |> Map.new(fn {k, v} -> {String.to_atom(k), v} end) |> Map.to_list() |> inspect() end defp job_filters_history do case Storage.get_profile() do {_, %{"filters" => filters}} -> filters |> Enum.with_index() |> Enum.map(fn {filter, i} -> {i, parse_filter_history(filter)} end) |> then(fn list -> Printer.menu("Filters History", list) end) :ok _ -> Printer.red("Error | Profile not found | Add a Profile at the initial menu") |> IO.puts() :ok end end defp command(value, :initial_menu) when value in ["1", "jobs"], do: jobs() defp command(value, :initial_menu) when value in ["2", "queues"], do: queues() defp command(value, :initial_menu) when value in ["3", "profile"], do: profile() && initial_menu() defp command(value, :initial_menu) when value in ["0", "exit"], do: goodbye() defp command(value, :queues) when value in ["1", "list", "refresh", "list/refresh", ""], do: queues() defp command(value, :queues) when value in ["2", "pause"], do: pause_queue() && queues() defp command(value, :queues) when value in ["3", "resume"], do: resume_queue() && queues() defp command(value, :resume_queues) when value in ["0", "return", "", []], do: queues() defp command(value, :resume_queues), do: Queues.resume_queues(value) defp command(value, :pause_queues) when value in ["0", "return", "", []], do: queues() defp command(value, :pause_queues), do: Queues.pause_queues(value) defp command(value, :jobs) when value in ["1", "list", "refresh", "list/refresh", ""], do: jobs() defp command(value, :jobs) when value in ["2", "filter"], do: filter_jobs() defp command(value, :jobs) when value in ["3", "debug"], do: debug_jobs() && jobs([], false) defp command(value, :jobs) when value in ["4", "retry"], do: retry_jobs() && jobs() defp command(value, :jobs) when value in ["5", "cancel"], do: cancel_jobs() && jobs() defp command(value, :jobs) when value in ["6", "clean"], do: clean_jobs() && jobs() defp command(value, :jobs) when value in ["7", "history"], do: job_filters_history() && jobs([], false) defp command(value, :debug_jobs) when value in ["0", "return", "", []], do: jobs() defp command(value, :debug_jobs), do: Jobs.debug_jobs(value) defp command(value, :retry_jobs) when value in ["0", "return", "", []], do: jobs() defp command(value, :retry_jobs), do: Jobs.retry_jobs(value) defp command(value, :cancel_jobs) when value in ["0", "return", "", []], do: jobs() defp command(value, :cancel_jobs), do: Jobs.cancel_jobs(value) defp command(value, :filter_jobs_opts) do with true <- String.contains?(value, "["), {:ok, opts} <- eval_opts(value) do jobs(opts) else {:error, _error} -> Printer.red("Error | Filter not valid") |> IO.puts() jobs() _ -> goodbye() end end defp command(value, _) when value in ["0", "return", ""], do: initial_menu() defp command(value, :jobs) do value |> String.to_integer() |> Jobs.debug_jobs() jobs([], false) rescue ArgumentError -> command(value, :filter_jobs_opts) end defp command(_, _), do: goodbye() defp eval_opts(opts) do {keyword_list, _binding} = Code.eval_string(opts) {:ok, keyword_list} rescue _ -> {:error, "Not valid list"} end defp print_list(list), do: Enum.map_join(list, ", ", &to_string/1) defp filter_jobs do previous_selected_opts = Storage.get_last_jobs_opts() previous_listed_ids = Storage.get_last_jobs_ids() previous_selected_ids = Keyword.get(previous_selected_opts, :ids, []) previous_selected_states = Keyword.get(previous_selected_opts, :states, []) previous_selected_queues = Keyword.get(previous_selected_opts, :queues, []) previous_selected_workers = Keyword.get(previous_selected_opts, :workers, []) previous_selected_sorts = Keyword.get(previous_selected_opts, :sorts, []) previous_selected_args_meta = Keyword.get(previous_selected_opts, :args_meta, nil) Printer.break() print_selected("Listed IDs", previous_listed_ids) print_selected("Available States", [ "1. available, 2. scheduled, 3. retryable, 4. executing, 5. completed, 6. discarded, 7. cancelled" ]) Printer.break() print_selected("Selected IDs", previous_selected_ids) print_selected("Selected States", previous_selected_states) print_selected("Selected Queues", previous_selected_queues) print_selected("Selected Workers", previous_selected_workers) print_selected("Contains Args/Meta", [previous_selected_args_meta]) print_selected("Selected Sort", previous_selected_sorts) Printer.break() print_selected("Hint", ["Use comma to separate values", "Use - to clean filters", "Use : to sort values"]) ids = ["Filter", "IDs: "] |> get_customer_integer_list_input() |> fallback(previous_selected_ids) states = ["Filter", "States: "] |> get_customer_string_list_input() |> fallback(previous_selected_states) queues = ["Filter", "Queues: "] |> get_customer_string_list_input() |> fallback(previous_selected_queues) workers = ["Filter", "Workers: "] |> get_customer_string_list_input() |> fallback(previous_selected_workers) args_meta = ["Filter", "Args/Meta: "] |> get_customer_string_list_input() |> List.first() |> fallback(previous_selected_args_meta) sorts = ["Filter", "Sorts: "] |> get_customer_string_list_input() |> Enum.map(fn s -> splited = if String.contains?(s, ":"), do: String.split(s, ":"), else: String.split(s, " ") trimmed = Enum.map(splited, &String.trim/1) case trimmed do [field] -> "asc:#{field}" [field, "asc"] -> "asc:#{field}" ["asc", field] -> "asc:#{field}" [field, "desc"] -> "desc:#{field}" ["desc", field] -> "desc:#{field}" _ -> nil end end) |> Enum.reject(&is_nil/1) |> fallback(previous_selected_sorts) limit = ["Filter", "Limit (default 20): "] |> get_customer_integer_list_input() |> List.first() |> fallback(20) jobs( ids: ids, states: states, limit: limit, workers: workers, queues: queues, sorts: sorts, args_meta: args_meta ) end defp print_selected(_, []), do: :ok defp print_selected(_, [nil]), do: :ok defp print_selected(text, list), do: Printer.title([text, print_list(list)]) |> IO.puts() defp fallback(nil, default), do: default defp fallback("-", _), do: [] defp fallback([], default), do: default defp fallback(["-"], _), do: [] defp fallback(value, _), do: value defp get_customer_integer_list_input(title) do Printer.gets(title) |> String.split(",") |> Enum.map(fn s -> s |> String.trim() |> parse_to_integer() end) |> presence() end defp get_customer_string_list_input(title) do Printer.gets(title) |> String.split(",") |> Enum.map(&String.trim/1) |> presence() end defp parse_to_integer(""), do: nil defp parse_to_integer(value) do String.to_integer(value) rescue ArgumentError -> nil end defp presence(nil), do: nil defp presence([]), do: nil defp presence(""), do: nil defp presence([_ | _] = list), do: Enum.filter(list, &presence/1) defp presence(value), do: value end