~ihabunek/triglav

897c77e93800878175ba864171b787a1094492f7 — Ivan Habunek 8 months ago 4863eff
Error history with atom feed
M assets/css/app.scss => assets/css/app.scss +12 -2
@@ 134,7 134,7 @@ span.osm-link {
  }
}

a.josm-remote {
a.box {
  border: 1px solid black;
  border-radius: 3px;
  color: black;


@@ 145,6 145,10 @@ a.josm-remote {
  &:hover {
    background-color: WhiteSmoke;
  }
}

a.josm-remote {
  @extend a.box;

  &.success {
    background-color: rgba($green, 0.3);


@@ 155,6 159,12 @@ a.josm-remote {
  }
}

.icon {
  width: 1em;
  height: 1em;
  display: inline-block;
}

// Utils

.tag { color: DimGray; white-space: nowrap; }


@@ 168,7 178,7 @@ a.josm-remote {
.bg-red { background-color: rgba($red, 0.3); }
.bg-green { background-color: rbga($green, 0.3); }

.bold { font-weight: bold; }
.text-bold { font-weight: bold; }
.text-smaller { font-size: 0.9rem; }

.text-gray { color: DimGray; }

A assets/static/images/feed.svg => assets/static/images/feed.svg +14 -0
@@ 0,0 1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 80 80"
  version="1.0"
  overflow="hidden"
  stroke="black"
>
  <g>
    <circle cx="10" cy="70" r="10" stroke-width="0" />
    <circle cx="0" cy="80" r="45" stroke-width="10" fill="none" />
    <circle cx="0" cy="80" r="70" stroke-width="10" fill="none" />
  </g>
</svg>

D assets/static/images/phoenix.png => assets/static/images/phoenix.png +0 -0
M lib/triglav/zet/errors.ex => lib/triglav/zet/errors.ex +9 -0
@@ 1,5 1,14 @@
defmodule Triglav.Zet.Errors do
  alias Triglav.Repo
  alias Triglav.Schemas.Error
  alias Triglav.Schemas.ErrorHistory

  import Ecto.Query

  def list_history() do
    from(h in ErrorHistory, order_by: [desc: :timestamp], limit: 50)
    |> Repo.all()
  end

  def render(%Error{key: "missing_route_master"}) do
    "Missing route_master relation"

M lib/triglav_web.ex => lib/triglav_web.ex +1 -0
@@ 68,6 68,7 @@ defmodule TriglavWeb do

      import TriglavWeb.ErrorHelpers
      import TriglavWeb.OsmHelpers
      import TriglavWeb.ViewHelpers
      import TriglavWeb.Gettext
      alias TriglavWeb.Router.Helpers, as: Routes
    end

A lib/triglav_web/controllers/zet/errors_controller.ex => lib/triglav_web/controllers/zet/errors_controller.ex +57 -0
@@ 0,0 1,57 @@
defmodule TriglavWeb.Zet.ErrorsController do
  use TriglavWeb, :controller

  alias Triglav.Zet.Errors
  alias Atomex.{Feed, Entry}

  def index(conn, _params) do
    history = Errors.list_history()
    render(conn, "index.html", history: history)
  end

  def atom(conn, _params) do
    TriglavWeb.Endpoint |> IO.inspect()
    history = Errors.list_history()
    root_url = TriglavWeb.Router.Helpers.url(conn)

    last_updated =
      if length(history) > 0,
        do: history |> List.first() |> Map.get(:timestamp),
        else: ~U"2000-01-01T00:00:00Z"

    feed =
      Feed.new(
        "#{root_url}/zet/errors/history",
        last_updated,
        "ZET error history"
      )
      |> Feed.link("#{root_url}/errors/history/atom", rel: "self")
      |> Feed.entries(Enum.map(history, &to_entry(&1, root_url)))
      |> Feed.build()
      |> Atomex.generate_document()

    conn
    |> put_resp_content_type("text/xml")
    |> send_resp(conn.status || 200, feed)
  end

  defp to_entry(history_item, root_url) do
    counts = %{
      current: history_item.count,
      previous: history_item.previous_count,
      created: history_item.created_count,
      resolved: history_item.resolved_count,
      diff: history_item.created_count - history_item.resolved_count
    }

    Entry.new(
      "#{root_url}/zet/errors/history/#{history_item.id}",
      history_item.timestamp,
      nil
    )
    |> Entry.add_field("counts", counts, nil)
    |> Entry.add_field("osm_sequence_number", nil, history_item.osm_sequence_number)
    |> Entry.add_field("zet_feed_version", nil, history_item.zet_feed_version)
    |> Entry.build()
  end
end

M lib/triglav_web/router.ex => lib/triglav_web/router.ex +2 -0
@@ 23,6 23,8 @@ defmodule TriglavWeb.Router do
      get "/routes", RoutesController, :index
      get "/routes/:id", RoutesController, :detail
      get "/stops", StopsController, :index
      get "/errors/history", ErrorsController, :index
      get "/errors/history/atom", ErrorsController, :atom
    end
  end


M lib/triglav_web/templates/layout/app.html.eex => lib/triglav_web/templates/layout/app.html.eex +5 -2
@@ 16,10 16,13 @@
            <a href="/">Triglav</a>
          </div>
          <div class="topnav-links">
            <a href="/zet/routes">ZET Routes</a>
            <a href="/zet/routes">Routes</a>
          </div>
          <div class="topnav-links">
            <a href="/zet/stops">ZET Stops</a>
            <a href="/zet/stops">Stops</a>
          </div>
          <div class="topnav-links">
            <a href="/zet/errors/history">Errors</a>
          </div>
        </div>
      </nav>

A lib/triglav_web/templates/zet/errors/index.html.eex => lib/triglav_web/templates/zet/errors/index.html.eex +51 -0
@@ 0,0 1,51 @@
<style>
  .bl { border-left: 1px dotted gray }
</style>

<main role="main" class="container">
  <h1>ZET error history</h1>

  <a href="/zet/errors/history/atom" class="box">
    <img class="icon" src="<%= static_path("/images/feed.svg") %>" />
    Atom feed
  </a>


  <table style="margin-top: 1rem; width: 100%;">
    <thead>
      <tr>
        <th>Timestamp</th>
        <th>Errors</th>
        <th>Diff</th>
        <th>Created</th>
        <th>Resolved</th>
        <th>ZET feed ID</th>
        <th>OSM sequence no.</th>
      </tr>
    </thead>
    <tbody>
      <%= for item <- @history do %>
        <tr>
          <td><%= item.timestamp %></td>
          <td><%= item.count %></td>

          <%= if item.created_count > item.resolved_count do %>
            <td class="text-red">
              + <%= item.created_count - item.resolved_count %>
            </td>
          <% else %>
            <td class="text-green">
              <%= if item.resolved_count > item.created_count do %>-<% end %>
              <%= item.resolved_count - item.created_count %>
            </td>
          <% end %>

          <td><%= item.created_count %></td>
          <td><%= item.resolved_count %></td>
          <td><%= item.zet_feed_version %></td>
          <td><%= item.osm_sequence_number %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
</main>

A lib/triglav_web/views/view_helpers.ex => lib/triglav_web/views/view_helpers.ex +7 -0
@@ 0,0 1,7 @@
defmodule TriglavWeb.ViewHelpers do
  alias TriglavWeb.Router.Helpers

  def static_path(path) do
    Helpers.static_path(TriglavWeb.Endpoint, path)
  end
end

A lib/triglav_web/views/zet/errors_view.ex => lib/triglav_web/views/zet/errors_view.ex +5 -0
@@ 0,0 1,5 @@
defmodule TriglavWeb.Zet.ErrorsView do
  use TriglavWeb, :view

  def title("index.html", _), do: "ZET Errors"
end

M mix.exs => mix.exs +10 -9
@@ 33,20 33,21 @@ defmodule Triglav.MixProject do
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:atomex, "~> 0.4.1"},
      {:ecto_sql, "~> 3.4"},
      {:geo, "~> 3.0"},
      {:geo_postgis, "~> 3.1"},
      {:gettext, "~> 0.11"},
      {:jason, "~> 1.0"},
      {:phoenix, "~> 1.5.4"},
      {:phoenix_ecto, "~> 4.1"},
      {:ecto_sql, "~> 3.4"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 2.11"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_dashboard, "~> 0.2"},
      {:telemetry_metrics, "~> 0.4"},
      {:telemetry_poller, "~> 0.4"},
      {:gettext, "~> 0.11"},
      {:jason, "~> 1.0"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:plug_cowboy, "~> 2.0"},
      {:geo, "~> 3.0"},
      {:geo_postgis, "~> 3.1"}
      {:postgrex, ">= 0.0.0"},
      {:telemetry_metrics, "~> 0.4"},
      {:telemetry_poller, "~> 0.4"}
    ]
  end


M mix.lock => mix.lock +2 -0
@@ 1,4 1,5 @@
%{
  "atomex": {:hex, :atomex, "0.4.1", "7d3910ff7795db91b9af9f8d3e65af7ac69f235adf03484995fc667a36f3edc5", [:mix], [{:xml_builder, "~> 2.1", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "f3ac737f7493d42cfddf917f3ac49d60e0a0cf1a35c0712851b07fe8c0a05c7a"},
  "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
  "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
  "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},


@@ 28,4 29,5 @@
  "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
  "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"},
  "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
  "xml_builder": {:hex, :xml_builder, "2.1.4", "e60e21c0a39b9dd8dec1db5a2525c713f7fe4e85ed247caedf22a9bcdd2d5069", [:mix], [], "hexpm", "48188a4df8b9168ceb8318d128299bce064d272e18967349b2592347c434e677"},
}