mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
stats page
This commit is contained in:
parent
6e6d08f098
commit
d6ba37c882
8 changed files with 334 additions and 6 deletions
89
config/aggregation.json
Normal file
89
config/aggregation.json
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
{
|
||||||
|
"comments": {
|
||||||
|
"aggs": {
|
||||||
|
"deleted": {
|
||||||
|
"filter": {
|
||||||
|
"term": {
|
||||||
|
"hidden_from_users": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_24h": {
|
||||||
|
"filter": {
|
||||||
|
"range": {
|
||||||
|
"posted_at": {
|
||||||
|
"gt": "now-24h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"aggs": {
|
||||||
|
"deleted": {
|
||||||
|
"filter": {
|
||||||
|
"term": {
|
||||||
|
"hidden_from_users": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"non_deleted": {
|
||||||
|
"aggs": {
|
||||||
|
"all_time": {
|
||||||
|
"date_histogram": {
|
||||||
|
"field": "created_at",
|
||||||
|
"interval": "day"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avg_comments": {
|
||||||
|
"avg": {
|
||||||
|
"field": "comment_count"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faves_gt_0": {
|
||||||
|
"filter": {
|
||||||
|
"range": {
|
||||||
|
"faves": {
|
||||||
|
"gt": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_24h": {
|
||||||
|
"filter": {
|
||||||
|
"range": {
|
||||||
|
"created_at": {
|
||||||
|
"gt": "now-24h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"score_gt_0": {
|
||||||
|
"filter": {
|
||||||
|
"range": {
|
||||||
|
"score": {
|
||||||
|
"gt": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"score_lt_0": {
|
||||||
|
"filter": {
|
||||||
|
"range": {
|
||||||
|
"score": {
|
||||||
|
"lt": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"term": {
|
||||||
|
"hidden_from_users": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ config :philomena,
|
||||||
cdn_host: "",
|
cdn_host: "",
|
||||||
proxy_host: nil,
|
proxy_host: nil,
|
||||||
quick_tags_json: File.read!("config/quick_tag_table.json"),
|
quick_tags_json: File.read!("config/quick_tag_table.json"),
|
||||||
|
aggregation_json: File.read!("config/aggregation.json"),
|
||||||
footer_json: File.read!("config/footer.json")
|
footer_json: File.read!("config/footer.json")
|
||||||
|
|
||||||
config :philomena, :pow,
|
config :philomena, :pow,
|
||||||
|
|
|
@ -82,19 +82,24 @@ defmodule Philomena.Elasticsearch do
|
||||||
reindex(ecto_query, batch_size, ids)
|
reindex(ecto_query, batch_size, ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_results(elastic_query, pagination_params \\ %{}) do
|
def search(query_body) do
|
||||||
page_number = pagination_params[:page_number] || 1
|
|
||||||
page_size = pagination_params[:page_size] || 25
|
|
||||||
elastic_query = Map.merge(elastic_query, %{from: (page_number - 1) * page_size, size: page_size, _source: false})
|
|
||||||
|
|
||||||
{:ok, %{body: results, status_code: 200}} =
|
{:ok, %{body: results, status_code: 200}} =
|
||||||
Elastix.Search.search(
|
Elastix.Search.search(
|
||||||
unquote(elastic_url),
|
unquote(elastic_url),
|
||||||
unquote(index_name),
|
unquote(index_name),
|
||||||
[unquote(doc_type)],
|
[unquote(doc_type)],
|
||||||
elastic_query
|
query_body
|
||||||
)
|
)
|
||||||
|
|
||||||
|
results
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_results(elastic_query, pagination_params \\ %{}) do
|
||||||
|
page_number = pagination_params[:page_number] || 1
|
||||||
|
page_size = pagination_params[:page_size] || 25
|
||||||
|
elastic_query = Map.merge(elastic_query, %{from: (page_number - 1) * page_size, size: page_size, _source: false})
|
||||||
|
|
||||||
|
results = search(elastic_query)
|
||||||
time = results["took"]
|
time = results["took"]
|
||||||
count = results["hits"]["total"]
|
count = results["hits"]["total"]
|
||||||
entries = results["hits"]["hits"] |> Enum.map(&String.to_integer(&1["_id"]))
|
entries = results["hits"]["hits"] |> Enum.map(&String.to_integer(&1["_id"]))
|
||||||
|
|
65
lib/philomena_web/controllers/stat_controller.ex
Normal file
65
lib/philomena_web/controllers/stat_controller.ex
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
defmodule PhilomenaWeb.StatController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Images.Image
|
||||||
|
alias Philomena.Comments.Comment
|
||||||
|
alias Philomena.Topics.Topic
|
||||||
|
alias Philomena.Forums.Forum
|
||||||
|
alias Philomena.Posts.Post
|
||||||
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Repo
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
{image_aggs, comment_aggs } = aggregations()
|
||||||
|
{forums, topics, posts} = forums()
|
||||||
|
{users, users_24h} = users()
|
||||||
|
|
||||||
|
render(
|
||||||
|
conn,
|
||||||
|
"index.html",
|
||||||
|
image_aggs: image_aggs,
|
||||||
|
comment_aggs: comment_aggs,
|
||||||
|
forums_count: forums,
|
||||||
|
topics_count: topics,
|
||||||
|
posts_count: posts,
|
||||||
|
users_count: users,
|
||||||
|
users_24h: users_24h
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp aggregations do
|
||||||
|
data =
|
||||||
|
Application.get_env(:philomena, :aggregation_json)
|
||||||
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
{Image.search(data["images"]), Comment.search(data["comments"])}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp forums do
|
||||||
|
forums =
|
||||||
|
Forum
|
||||||
|
|> where(access_level: "normal")
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
|
||||||
|
first_topic = Repo.one(first(Topic))
|
||||||
|
last_topic = Repo.one(last(Topic))
|
||||||
|
first_post = Repo.one(first(Post))
|
||||||
|
last_post = Repo.one(last(Post))
|
||||||
|
|
||||||
|
{forums, last_topic.id - first_topic.id, last_post.id - first_post.id}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp users do
|
||||||
|
first_user = Repo.one(first(User))
|
||||||
|
last_user = Repo.one(last(User))
|
||||||
|
time = DateTime.utc_now() |> DateTime.add(-86400, :second)
|
||||||
|
|
||||||
|
last_24h =
|
||||||
|
User
|
||||||
|
|> where([u], u.created_at > ^time)
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
|
||||||
|
{last_user.id - first_user.id, last_24h}
|
||||||
|
end
|
||||||
|
end
|
|
@ -131,6 +131,7 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/pages", PageController, only: [:show]
|
resources "/pages", PageController, only: [:show]
|
||||||
resources "/dnp", DnpEntryController, only: [:index, :show]
|
resources "/dnp", DnpEntryController, only: [:index, :show]
|
||||||
resources "/staff", StaffController, only: [:index]
|
resources "/staff", StaffController, only: [:index]
|
||||||
|
resources "/stats", StatController, only: [:index]
|
||||||
|
|
||||||
get "/:id", ImageController, :show
|
get "/:id", ImageController, :show
|
||||||
# get "/:forum_id", ForumController, :show # impossible to do without constraints
|
# get "/:forum_id", ForumController, :show # impossible to do without constraints
|
||||||
|
|
76
lib/philomena_web/templates/stat/index.html.slime
Normal file
76
lib/philomena_web/templates/stat/index.html.slime
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
h1 Site Statistics
|
||||||
|
|
||||||
|
elixir:
|
||||||
|
img_bucket = @image_aggs["aggregations"]
|
||||||
|
cmt_bucket = @comment_aggs["aggregations"]
|
||||||
|
|
||||||
|
.walloftext
|
||||||
|
h3 Images
|
||||||
|
p
|
||||||
|
' There are
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(img_bucket["non_deleted"]["doc_count"])
|
||||||
|
' non-deleted images total in our database. Of these,
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(img_bucket["non_deleted"]["last_24h"]["doc_count"])
|
||||||
|
' images were uploaded in the last 24 hours.
|
||||||
|
p
|
||||||
|
' This net total excludes the
|
||||||
|
=> number_with_delimiter(img_bucket["deleted"]["doc_count"])
|
||||||
|
' images that have been deleted or marked as duplicates.
|
||||||
|
|
||||||
|
h3 Comments
|
||||||
|
p
|
||||||
|
' There are
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(@comment_aggs["hits"]["total"])
|
||||||
|
' comments on the site. Of these,
|
||||||
|
=> number_with_delimiter(cmt_bucket["deleted"]["doc_count"])
|
||||||
|
' have been deleted.
|
||||||
|
p
|
||||||
|
' In the last 24 hours,
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(cmt_bucket["last_24h"]["doc_count"])
|
||||||
|
' comments have been posted.
|
||||||
|
p
|
||||||
|
' There are, on average,
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(trunc(img_bucket["non_deleted"]["avg_comments"]["value"]))
|
||||||
|
' comments on each image on the site.
|
||||||
|
|
||||||
|
h3 Votes
|
||||||
|
p
|
||||||
|
' Out of
|
||||||
|
=> number_with_delimiter(img_bucket["doc_count"])
|
||||||
|
' images,
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(img_bucket["score_gt_0"]["doc_count"])
|
||||||
|
' images have a score above 0, and
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(img_bucket["score_lt_0"]["doc_count"])
|
||||||
|
' images have a score below 0.
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(img_bucket["faves_gt_0"]["doc_count"])
|
||||||
|
' images have been faved by at least one user.
|
||||||
|
|
||||||
|
h3 Forums
|
||||||
|
p
|
||||||
|
' In our
|
||||||
|
=> @forums_count
|
||||||
|
' forums there have been
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(@topics_count)
|
||||||
|
' topics started. There have been
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(@posts_count)
|
||||||
|
' replies to topics in total.
|
||||||
|
|
||||||
|
h3 Users
|
||||||
|
p
|
||||||
|
' There are
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(@users_count)
|
||||||
|
' users on the site. Of these,
|
||||||
|
span.stat>
|
||||||
|
= number_with_delimiter(@users_24h)
|
||||||
|
' have joined in the last 24 hours.
|
3
lib/philomena_web/views/stat_view.ex
Normal file
3
lib/philomena_web/views/stat_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule PhilomenaWeb.StatView do
|
||||||
|
use PhilomenaWeb, :view
|
||||||
|
end
|
88
test/philomena_web/controllers/stat_controller_test.exs
Normal file
88
test/philomena_web/controllers/stat_controller_test.exs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
defmodule PhilomenaWeb.StatControllerTest do
|
||||||
|
use PhilomenaWeb.ConnCase
|
||||||
|
|
||||||
|
alias Philomena.Stats
|
||||||
|
|
||||||
|
@create_attrs %{}
|
||||||
|
@update_attrs %{}
|
||||||
|
@invalid_attrs %{}
|
||||||
|
|
||||||
|
def fixture(:stat) do
|
||||||
|
{:ok, stat} = Stats.create_stat(@create_attrs)
|
||||||
|
stat
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "index" do
|
||||||
|
test "lists all stats", %{conn: conn} do
|
||||||
|
conn = get(conn, Routes.stat_path(conn, :index))
|
||||||
|
assert html_response(conn, 200) =~ "Listing Stats"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "new stat" do
|
||||||
|
test "renders form", %{conn: conn} do
|
||||||
|
conn = get(conn, Routes.stat_path(conn, :new))
|
||||||
|
assert html_response(conn, 200) =~ "New Stat"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create stat" do
|
||||||
|
test "redirects to show when data is valid", %{conn: conn} do
|
||||||
|
conn = post(conn, Routes.stat_path(conn, :create), stat: @create_attrs)
|
||||||
|
|
||||||
|
assert %{id: id} = redirected_params(conn)
|
||||||
|
assert redirected_to(conn) == Routes.stat_path(conn, :show, id)
|
||||||
|
|
||||||
|
conn = get(conn, Routes.stat_path(conn, :show, id))
|
||||||
|
assert html_response(conn, 200) =~ "Show Stat"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn} do
|
||||||
|
conn = post(conn, Routes.stat_path(conn, :create), stat: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "New Stat"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "edit stat" do
|
||||||
|
setup [:create_stat]
|
||||||
|
|
||||||
|
test "renders form for editing chosen stat", %{conn: conn, stat: stat} do
|
||||||
|
conn = get(conn, Routes.stat_path(conn, :edit, stat))
|
||||||
|
assert html_response(conn, 200) =~ "Edit Stat"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update stat" do
|
||||||
|
setup [:create_stat]
|
||||||
|
|
||||||
|
test "redirects when data is valid", %{conn: conn, stat: stat} do
|
||||||
|
conn = put(conn, Routes.stat_path(conn, :update, stat), stat: @update_attrs)
|
||||||
|
assert redirected_to(conn) == Routes.stat_path(conn, :show, stat)
|
||||||
|
|
||||||
|
conn = get(conn, Routes.stat_path(conn, :show, stat))
|
||||||
|
assert html_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn, stat: stat} do
|
||||||
|
conn = put(conn, Routes.stat_path(conn, :update, stat), stat: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "Edit Stat"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete stat" do
|
||||||
|
setup [:create_stat]
|
||||||
|
|
||||||
|
test "deletes chosen stat", %{conn: conn, stat: stat} do
|
||||||
|
conn = delete(conn, Routes.stat_path(conn, :delete, stat))
|
||||||
|
assert redirected_to(conn) == Routes.stat_path(conn, :index)
|
||||||
|
assert_error_sent 404, fn ->
|
||||||
|
get(conn, Routes.stat_path(conn, :show, stat))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_stat(_) do
|
||||||
|
stat = fixture(:stat)
|
||||||
|
{:ok, stat: stat}
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue