Expose more statistics

* differenciate local & all events/comments/groups
* add instance follows/followings

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2020-10-07 10:05:07 +02:00
parent f45ed84d24
commit a9d0c7d7bc
8 changed files with 118 additions and 10 deletions

View File

@ -5,8 +5,13 @@ export const STATISTICS = gql`
statistics { statistics {
numberOfUsers numberOfUsers
numberOfEvents numberOfEvents
numberOfLocalEvents
numberOfComments numberOfComments
numberOfLocalComments
numberOfGroups numberOfGroups
numberOfLocalGroups
numberOfInstanceFollowings
numberOfInstanceFollowers
} }
} }
`; `;

View File

@ -1,6 +1,11 @@
export interface IStatistics { export interface IStatistics {
numberOfUsers: number; numberOfUsers: number;
numberOfEvents: number; numberOfEvents: number;
numberOfLocalEvents: number;
numberOfComments: number; numberOfComments: number;
numberOfLocalComments: number;
numberOfGroups: number; numberOfGroups: number;
numberOfLocalGroups: number;
numberOfInstanceFollowings: number;
numberOfInstanceFollowers: number;
} }

View File

@ -14,13 +14,13 @@
<strong slot="number">{{ statistics.numberOfUsers }}</strong> <strong slot="number">{{ statistics.numberOfUsers }}</strong>
</i18n> </i18n>
<i18n tag="p" path="and {number} groups"> <i18n tag="p" path="and {number} groups">
<strong slot="number">{{ statistics.numberOfGroups }}</strong> <strong slot="number">{{ statistics.numberOfLocalGroups }}</strong>
</i18n> </i18n>
<i18n tag="p" path="Who published {number} events"> <i18n tag="p" path="Who published {number} events">
<strong slot="number">{{ statistics.numberOfEvents }}</strong> <strong slot="number">{{ statistics.numberOfLocalEvents }}</strong>
</i18n> </i18n>
<i18n tag="p" path="And {number} comments"> <i18n tag="p" path="And {number} comments">
<strong slot="number">{{ statistics.numberOfComments }}</strong> <strong slot="number">{{ statistics.numberOfLocalComments }}</strong>
</i18n> </i18n>
</div> </div>
<div class="column contact"> <div class="column contact">
@ -140,7 +140,7 @@ section {
.statistics { .statistics {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, 150px); grid-template-columns: repeat(auto-fit, 150px);
grid-template-rows: repeat(2, 1fr); gap: 2rem 0;
p { p {
text-align: right; text-align: right;
padding: 0 15px; padding: 0 15px;
@ -157,6 +157,9 @@ section {
} }
} }
.contact { .contact {
h4 {
font-weight: bold;
}
p { p {
width: 200px; width: 200px;
white-space: nowrap; white-space: nowrap;

View File

@ -12,9 +12,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Statistics do
{:ok, {:ok,
%{ %{
number_of_users: StatisticsModule.get_cached_value(:local_users), number_of_users: StatisticsModule.get_cached_value(:local_users),
number_of_events: StatisticsModule.get_cached_value(:local_events), number_of_events: StatisticsModule.get_cached_value(:federation_events),
number_of_comments: StatisticsModule.get_cached_value(:local_comments), number_of_local_events: StatisticsModule.get_cached_value(:local_events),
number_of_groups: StatisticsModule.get_cached_value(:local_groups) number_of_comments: StatisticsModule.get_cached_value(:federation_comments),
number_of_local_comments: StatisticsModule.get_cached_value(:local_comments),
number_of_groups: StatisticsModule.get_cached_value(:federation_groups),
number_of_local_groups: StatisticsModule.get_cached_value(:local_groups),
number_of_instance_followings: StatisticsModule.get_cached_value(:instance_followings),
number_of_instance_followers: StatisticsModule.get_cached_value(:instance_followers)
}} }}
end end
end end

View File

@ -10,9 +10,20 @@ defmodule Mobilizon.GraphQL.Schema.StatisticsType do
object :statistics do object :statistics do
# Instance name # Instance name
field(:number_of_users, :integer, description: "The number of local users") field(:number_of_users, :integer, description: "The number of local users")
field(:number_of_events, :integer, description: "The number of local events") field(:number_of_events, :integer, description: "The total number of events")
field(:number_of_comments, :integer, description: "The number of local comments") field(:number_of_local_events, :integer, description: "The number of local events")
field(:number_of_groups, :integer, description: "The number of local groups") field(:number_of_comments, :integer, description: "The total number of comments")
field(:number_of_local_comments, :integer, description: "The number of local events")
field(:number_of_groups, :integer, description: "The total number of groups")
field(:number_of_local_groups, :integer, description: "The number of local groups")
field(:number_of_instance_followers, :integer,
description: "The number of this instance's followers"
)
field(:number_of_instance_followings, :integer,
description: "The number of instances this instance follows"
)
end end
object :statistics_queries do object :statistics_queries do

View File

@ -1076,6 +1076,17 @@ defmodule Mobilizon.Actors do
|> Page.build_page(page, limit) |> Page.build_page(page, limit)
end end
@doc """
Returns the number of followers for an actor
"""
@spec count_followers_for_actor(Actor.t()) :: integer()
def count_followers_for_actor(%Actor{id: actor_id}) do
actor_id
|> follower_for_actor_query()
|> where(approved: true)
|> Repo.aggregate(:count)
end
@doc """ @doc """
Returns the list of followings for an actor. Returns the list of followings for an actor.
If actor A follows actor B and C, actor A's followings are B and C. If actor A follows actor B and C, actor A's followings are B and C.
@ -1087,6 +1098,17 @@ defmodule Mobilizon.Actors do
|> Repo.all() |> Repo.all()
end end
@doc """
Returns the number of followings for an actor
"""
@spec count_followings_for_actor(Actor.t()) :: integer()
def count_followings_for_actor(%Actor{id: actor_id}) do
actor_id
|> followings_for_actor_query()
|> where(approved: true)
|> Repo.aggregate(:count)
end
@doc """ @doc """
Returns the list of external followings for an actor. Returns the list of external followings for an actor.
""" """

View File

@ -4,6 +4,7 @@ defmodule Mobilizon.Service.Statistics do
""" """
alias Mobilizon.{Actors, Discussions, Events, Users} alias Mobilizon.{Actors, Discussions, Events, Users}
alias Mobilizon.Federation.ActivityPub.Relay
def get_cached_value(key) do def get_cached_value(key) do
case Cachex.fetch(:statistics, key, fn key -> case Cachex.fetch(:statistics, key, fn key ->
@ -44,4 +45,14 @@ defmodule Mobilizon.Service.Statistics do
defp create_cache(:federation_groups) do defp create_cache(:federation_groups) do
Actors.count_groups() Actors.count_groups()
end end
defp create_cache(:instance_followers) do
relay_actor = Relay.get_actor()
Actors.count_followers_for_actor(relay_actor)
end
defp create_cache(:instance_followings) do
relay_actor = Relay.get_actor()
Actors.count_followings_for_actor(relay_actor)
end
end end

View File

@ -0,0 +1,46 @@
defmodule Mobilizon.GraphQL.Resolvers.StatisticsTest do
use Mobilizon.Web.ConnCase
import Mobilizon.Factory
alias Mobilizon.GraphQL.AbsintheHelpers
describe "statistics resolver" do
@statistics_query """
query {
statistics {
numberOfUsers
numberOfEvents
numberOfLocalEvents
numberOfComments
numberOfLocalComments
numberOfGroups
numberOfLocalGroups
numberOfInstanceFollowings
numberOfInstanceFollowers
}
}
"""
test "get statistics", %{conn: conn} do
Cachex.clear(:statistics)
insert(:event)
insert(:comment)
insert(:group)
actor = insert(:actor, user: nil, domain: "toto.tld")
insert(:event, organizer_actor: actor, local: false)
res = AbsintheHelpers.graphql_query(conn, query: @statistics_query)
assert res["data"]["statistics"]["numberOfUsers"] == 6
assert res["data"]["statistics"]["numberOfLocalEvents"] == 2
assert res["data"]["statistics"]["numberOfEvents"] == 3
assert res["data"]["statistics"]["numberOfLocalComments"] == 1
assert res["data"]["statistics"]["numberOfLocalGroups"] == 1
insert(:event)
# We keep the value in cache
assert res["data"]["statistics"]["numberOfLocalEvents"] == 2
end
end
end