Improve dashboard

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2020-10-15 11:04:05 +02:00
parent dacec2672b
commit f6480cb37e
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
10 changed files with 163 additions and 34 deletions

View file

@ -4,6 +4,7 @@ export const DASHBOARD = gql`
query { query {
dashboard { dashboard {
lastPublicEventPublished { lastPublicEventPublished {
id
uuid uuid
title title
picture { picture {
@ -12,10 +13,24 @@ export const DASHBOARD = gql`
url url
} }
} }
lastGroupCreated {
id
preferredUsername
domain
name
avatar {
id
url
}
}
numberOfUsers numberOfUsers
numberOfEvents numberOfEvents
numberOfComments numberOfComments
numberOfReports numberOfReports
numberOfGroups
numberOfFollowers
numberOfFollowings
numberOfConfirmedParticipationsToLocalEvents
} }
} }
`; `;

View file

@ -281,7 +281,7 @@
"Public feeds": "Public feeds", "Public feeds": "Public feeds",
"Public iCal Feed": "Public iCal Feed", "Public iCal Feed": "Public iCal Feed",
"Publish": "Publish", "Publish": "Publish",
"Published events": "Published events", "Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations",
"RSS/Atom Feed": "RSS/Atom Feed", "RSS/Atom Feed": "RSS/Atom Feed",
"Read Framasofts statement of intent on the Framablog": "Read Framasofts statement of intent on the Framablog", "Read Framasofts statement of intent on the Framablog": "Read Framasofts statement of intent on the Framablog",
"Redirecting to event…": "Redirecting to event…", "Redirecting to event…": "Redirecting to event…",
@ -800,5 +800,8 @@
"To register for an event by choosing one of your identities": "To register for an event by choosing one of your identities", "To register for an event by choosing one of your identities": "To register for an event by choosing one of your identities",
"To create or join an group and start organizing with other people": "To create or join an group and start organizing with other people", "To create or join an group and start organizing with other people": "To create or join an group and start organizing with other people",
"About {instance}": "About {instance}", "About {instance}": "About {instance}",
"Please read the {fullRules} published by {instance}'s administrators.": "Please read the {fullRules} published by {instance}'s administrators." "Please read the {fullRules} published by {instance}'s administrators.": "Please read the {fullRules} published by {instance}'s administrators.",
"Instances following you": "Instances following you",
"Instances you follow": "Instances you follow",
"Last group created": "Last group created"
} }

View file

@ -532,7 +532,7 @@
"Public page": "Page publique", "Public page": "Page publique",
"Publication date": "Date de publication", "Publication date": "Date de publication",
"Publish": "Publier", "Publish": "Publier",
"Published events": "Événements publiés", "Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Événements publiés avec <b>{comments}</b> commentaires et <b>{participations}</b> participations confirmées",
"RSS/Atom Feed": "Flux RSS/Atom", "RSS/Atom Feed": "Flux RSS/Atom",
"Radius": "Rayon", "Radius": "Rayon",
"Read Framasofts statement of intent on the Framablog": "Lire la note dintention de Framasoft sur le Framablog", "Read Framasofts statement of intent on the Framablog": "Lire la note dintention de Framasoft sur le Framablog",
@ -850,5 +850,8 @@
"To register for an event by choosing one of your identities": "Pour s'inscrire à un évènement en choisissant une de vos identités", "To register for an event by choosing one of your identities": "Pour s'inscrire à un évènement en choisissant une de vos identités",
"To create or join an group and start organizing with other people": "Pour créer ou rejoindre un groupe et commencer à vous organiser avec d'autres personnes", "To create or join an group and start organizing with other people": "Pour créer ou rejoindre un groupe et commencer à vous organiser avec d'autres personnes",
"About {instance}": "À propos de {instance}", "About {instance}": "À propos de {instance}",
"Please read the {fullRules} published by {instance}'s administrators.": "Merci de lire les {fullRules} publiées par les administrateur·ices de {instance}." "Please read the {fullRules} published by {instance}'s administrators.": "Merci de lire les {fullRules} publiées par les administrateur·ices de {instance}.",
"Instances following you": "Instances vous suivant",
"Instances you follow": "Instances que vous suivez",
"Last group created": "Dernier groupe créé"
} }

View file

@ -1,11 +1,17 @@
import { IEvent } from "@/types/event.model"; import { IEvent } from "@/types/event.model";
import { IGroup } from "./actor";
export interface IDashboard { export interface IDashboard {
lastPublicEventPublished: IEvent; lastPublicEventPublished: IEvent;
lastGroupCreated: IGroup;
numberOfUsers: number; numberOfUsers: number;
numberOfEvents: number; numberOfEvents: number;
numberOfComments: number; numberOfComments: number;
numberOfReports: number; numberOfReports: number;
numberOfGroups: number;
numberOfFollowers: number;
numberOfFollowings: number;
numberOfConfirmedParticipationsToLocalEvents: number;
} }
export enum InstanceTermsType { export enum InstanceTermsType {

View file

@ -18,11 +18,23 @@
<div class="tile is-parent is-vertical is-6"> <div class="tile is-parent is-vertical is-6">
<article class="tile is-child box"> <article class="tile is-child box">
<p class="dashboard-number">{{ dashboard.numberOfEvents }}</p> <p class="dashboard-number">{{ dashboard.numberOfEvents }}</p>
<p>{{ $t("Published events") }}</p> <p
v-html="
$t(
'Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations',
{
comments: dashboard.numberOfComments,
participations: dashboard.numberOfConfirmedParticipationsToLocalEvents,
}
)
"
/>
</article> </article>
<article class="tile is-child box"> <article class="tile is-child box">
<p class="dashboard-number">{{ dashboard.numberOfComments }}</p> <router-link :to="{ name: RouteName.ADMIN_GROUPS }">
<p>{{ $t("Comments") }}</p> <p class="dashboard-number">{{ dashboard.numberOfGroups }}</p>
<p>{{ $t("Groups") }}</p>
</router-link>
</article> </article>
</div> </div>
<div class="tile is-parent is-vertical"> <div class="tile is-parent is-vertical">
@ -32,29 +44,66 @@
<p>{{ $t("Users") }}</p> <p>{{ $t("Users") }}</p>
</router-link> </router-link>
</article> </article>
<article class="tile is-child box">
<router-link :to="{ name: RouteName.RELAY_FOLLOWERS }">
<p class="dashboard-number">{{ dashboard.numberOfFollowers }}</p>
<p>{{ $t("Instances following you") }}</p>
</router-link>
</article>
</div>
<div class="tile is-parent is-vertical">
<article class="tile is-child box"> <article class="tile is-child box">
<router-link :to="{ name: RouteName.REPORTS }"> <router-link :to="{ name: RouteName.REPORTS }">
<p class="dashboard-number">{{ dashboard.numberOfReports }}</p> <p class="dashboard-number">{{ dashboard.numberOfReports }}</p>
<p>{{ $t("Opened reports") }}</p> <p>{{ $t("Opened reports") }}</p>
</router-link> </router-link>
</article> </article>
<article class="tile is-child box">
<router-link :to="{ name: RouteName.RELAY_FOLLOWINGS }">
<p class="dashboard-number">{{ dashboard.numberOfFollowings }}</p>
<p>{{ $t("Instances you follow") }}</p>
</router-link>
</article>
</div> </div>
</div> </div>
<div class="tile is-parent" v-if="dashboard.lastPublicEventPublished"> <div class="tile">
<div class="tile is-parent is-vertical is-6" v-if="dashboard.lastPublicEventPublished">
<article class="tile is-child box">
<router-link <router-link
:to="{ :to="{
name: RouteName.EVENT, name: RouteName.EVENT,
params: { uuid: dashboard.lastPublicEventPublished.uuid }, params: { uuid: dashboard.lastPublicEventPublished.uuid },
}" }"
> >
<article class="tile is-child box"> <p>{{ $t("Last published event") }}</p>
<p class="dashboard-number">{{ $t("Last published event") }}</p>
<p class="subtitle">{{ dashboard.lastPublicEventPublished.title }}</p> <p class="subtitle">{{ dashboard.lastPublicEventPublished.title }}</p>
<figure class="image is-4by3" v-if="dashboard.lastPublicEventPublished.picture"> <figure class="image is-4by3" v-if="dashboard.lastPublicEventPublished.picture">
<img :src="dashboard.lastPublicEventPublished.picture.url" /> <img :src="dashboard.lastPublicEventPublished.picture.url" />
</figure> </figure>
</article>
</router-link> </router-link>
</article>
</div>
<div class="tile is-parent is-vertical" v-if="dashboard.lastGroupCreated">
<article class="tile is-child box">
<router-link
:to="{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(dashboard.lastGroupCreated) },
}"
>
<p>{{ $t("Last group created") }}</p>
<p class="subtitle">
{{
dashboard.lastGroupCreated.name ||
dashboard.lastGroupCreated.preferredUsername
}}
</p>
<figure class="image is-4by3" v-if="dashboard.lastGroupCreated.avatar">
<img :src="dashboard.lastGroupCreated.avatar.url" />
</figure>
</router-link>
</article>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -65,6 +114,7 @@
import { Component, Vue } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
import { DASHBOARD } from "@/graphql/admin"; import { DASHBOARD } from "@/graphql/admin";
import { IDashboard } from "@/types/admin.model"; import { IDashboard } from "@/types/admin.model";
import { usernameWithDomain } from "@/types/actor";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
@Component({ @Component({
@ -85,6 +135,8 @@ export default class Dashboard extends Vue {
dashboard!: IDashboard; dashboard!: IDashboard;
RouteName = RouteName; RouteName = RouteName;
usernameWithDomain = usernameWithDomain;
} }
</script> </script>

View file

@ -178,13 +178,25 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
_ -> nil _ -> nil
end end
last_group_created =
case Actors.list_actors(:Group) do
%Page{elements: [group | _]} -> group
_ -> nil
end
{:ok, {:ok,
%{ %{
number_of_users: Statistics.get_cached_value(:local_users), number_of_users: Statistics.get_cached_value(:local_users),
number_of_events: Statistics.get_cached_value(:local_events), number_of_events: Statistics.get_cached_value(:local_events),
number_of_groups: Statistics.get_cached_value(:local_groups),
number_of_comments: Statistics.get_cached_value(:local_comments), number_of_comments: Statistics.get_cached_value(:local_comments),
number_of_confirmed_participations_to_local_events:
Statistics.get_cached_value(:confirmed_participations_to_local_events),
number_of_reports: Mobilizon.Reports.count_opened_reports(), number_of_reports: Mobilizon.Reports.count_opened_reports(),
last_public_event_published: last_public_event_published number_of_followers: Statistics.get_cached_value(:instance_followers),
number_of_followings: Statistics.get_cached_value(:instance_followings),
last_public_event_published: last_public_event_published,
last_group_created: last_group_created
}} }}
end end

View file

@ -70,11 +70,19 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
end end
object :dashboard do object :dashboard do
field(:last_public_event_published, :event, description: "Last public event publish") field(:last_public_event_published, :event, description: "Last public event published")
field(:last_group_created, :group, description: "Last public group created")
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 number of local events")
field(:number_of_comments, :integer, description: "The number of local comments") field(:number_of_comments, :integer, description: "The number of local comments")
field(:number_of_groups, :integer, description: "The number of local groups")
field(:number_of_reports, :integer, description: "The number of current opened reports") field(:number_of_reports, :integer, description: "The number of current opened reports")
field(:number_of_followers, :integer, description: "The number of instance followers")
field(:number_of_followings, :integer, description: "The number of instance followings")
field(:number_of_confirmed_participations_to_local_events, :integer,
description: "The number of total confirmed participations to local events"
)
end end
object :admin_settings do object :admin_settings do

View file

@ -283,16 +283,24 @@ defmodule Mobilizon.Discussions do
end end
@doc """ @doc """
Counts local comments. Counts local comments under events
""" """
@spec count_local_comments :: integer @spec count_local_comments_under_events :: integer
def count_local_comments, do: Repo.one(count_local_comments_query()) def count_local_comments_under_events do
count_local_comments_query()
|> filter_comments_under_events()
|> Repo.one()
end
@doc """ @doc """
Counts all comments. Counts all comments.
""" """
@spec count_comments :: integer @spec count_comments_under_events :: integer
def count_comments, do: Repo.one(count_comments_query()) def count_comments_under_events do
count_comments_query()
|> filter_comments_under_events()
|> Repo.one()
end
def get_discussion(discussion_id) do def get_discussion(discussion_id) do
Discussion Discussion
@ -423,11 +431,8 @@ defmodule Mobilizon.Discussions do
@spec count_local_comments_query :: Ecto.Query.t() @spec count_local_comments_query :: Ecto.Query.t()
defp count_local_comments_query do defp count_local_comments_query do
from( count_comments_query()
c in Comment, |> where([c], local: true)
select: count(c.id),
where: c.local == ^true and c.visibility in ^@public_visibility
)
end end
@spec count_comments_query :: Ecto.Query.t() @spec count_comments_query :: Ecto.Query.t()
@ -439,6 +444,10 @@ defmodule Mobilizon.Discussions do
) )
end end
defp filter_comments_under_events(query) do
where(query, [c], is_nil(c.discussion_id) and not is_nil(c.event_id))
end
@spec preload_for_comment(Ecto.Query.t()) :: Ecto.Query.t() @spec preload_for_comment(Ecto.Query.t()) :: Ecto.Query.t()
defp preload_for_comment(query), do: preload(query, ^@comment_preloads) defp preload_for_comment(query), do: preload(query, ^@comment_preloads)
end end

View file

@ -798,6 +798,15 @@ defmodule Mobilizon.Events do
@moderator_roles [:moderator, :administrator, :creator] @moderator_roles [:moderator, :administrator, :creator]
@doc """
Returns the number of participations for all local events
"""
@spec count_confirmed_participants_for_local_events :: integer()
def count_confirmed_participants_for_local_events do
count_confirmed_participants_for_local_events_query()
|> Repo.one()
end
@doc """ @doc """
Returns the list of participants for an event. Returns the list of participants for an event.
Default behaviour is to not return :not_approved or :not_confirmed participants Default behaviour is to not return :not_approved or :not_confirmed participants
@ -1548,6 +1557,14 @@ defmodule Mobilizon.Events do
from(s in Session, where: s.track_id == ^track_id) from(s in Session, where: s.track_id == ^track_id)
end end
@spec count_confirmed_participants_for_local_events_query :: Ecto.Query.t()
defp count_confirmed_participants_for_local_events_query do
Participant
|> join(:inner, [p], e in Event, on: p.event_id == e.id)
|> where([p, e], e.local and p.role not in [^:not_approved, ^:not_confirmed, ^:rejected])
|> select([p], count(p.id))
end
@spec list_participants_for_event_query(String.t()) :: Ecto.Query.t() @spec list_participants_for_event_query(String.t()) :: Ecto.Query.t()
defp list_participants_for_event_query(event_id) do defp list_participants_for_event_query(event_id) do
from( from(

View file

@ -26,8 +26,12 @@ defmodule Mobilizon.Service.Statistics do
Events.count_local_events() Events.count_local_events()
end end
defp create_cache(:confirmed_participations_to_local_events) do
Events.count_confirmed_participants_for_local_events()
end
defp create_cache(:local_comments) do defp create_cache(:local_comments) do
Discussions.count_local_comments() Discussions.count_local_comments_under_events()
end end
defp create_cache(:local_groups) do defp create_cache(:local_groups) do
@ -39,7 +43,7 @@ defmodule Mobilizon.Service.Statistics do
end end
defp create_cache(:federation_comments) do defp create_cache(:federation_comments) do
Discussions.count_comments() Discussions.count_comments_under_events()
end end
defp create_cache(:federation_groups) do defp create_cache(:federation_groups) do