Add pagination to moderation logs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
6646391558
commit
495fbda330
@ -158,8 +158,9 @@ export const CREATE_REPORT_NOTE = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const LOGS = gql`
|
export const LOGS = gql`
|
||||||
query {
|
query ActionLogs($page: Int, $limit: Int) {
|
||||||
actionLogs {
|
actionLogs(page: $page, limit: $limit) {
|
||||||
|
elements {
|
||||||
id
|
id
|
||||||
action
|
action
|
||||||
actor {
|
actor {
|
||||||
@ -204,5 +205,7 @@ export const LOGS = gql`
|
|||||||
}
|
}
|
||||||
insertedAt
|
insertedAt
|
||||||
}
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -985,5 +985,7 @@
|
|||||||
"Unable to update the profile. The avatar picture may be too heavy.": "Unable to update the profile. The avatar picture may be too heavy.",
|
"Unable to update the profile. The avatar picture may be too heavy.": "Unable to update the profile. The avatar picture may be too heavy.",
|
||||||
"Unable to create the profile. The avatar picture may be too heavy.": "Unable to create the profile. The avatar picture may be too heavy.",
|
"Unable to create the profile. The avatar picture may be too heavy.": "Unable to create the profile. The avatar picture may be too heavy.",
|
||||||
"Error while loading the preview": "Error while loading the preview",
|
"Error while loading the preview": "Error while loading the preview",
|
||||||
"Instance feeds": "Instance feeds"
|
"Instance feeds": "Instance feeds",
|
||||||
|
"{moderator} suspended group {profile}": "{moderator} suspended group {profile}",
|
||||||
|
"{moderator} has unsuspended group {profile}": "{moderator} has unsuspended group {profile}"
|
||||||
}
|
}
|
||||||
|
@ -1079,5 +1079,7 @@
|
|||||||
"Unable to update the profile. The avatar picture may be too heavy.": "Impossible de mettre à jour le profil. L'image d'avatar est probablement trop lourde.",
|
"Unable to update the profile. The avatar picture may be too heavy.": "Impossible de mettre à jour le profil. L'image d'avatar est probablement trop lourde.",
|
||||||
"Unable to create the profile. The avatar picture may be too heavy.": "Impossible de créer le profil. L'image d'avatar est probablement trop lourde.",
|
"Unable to create the profile. The avatar picture may be too heavy.": "Impossible de créer le profil. L'image d'avatar est probablement trop lourde.",
|
||||||
"Error while loading the preview": "Erreur lors du chargement de l'aperçu",
|
"Error while loading the preview": "Erreur lors du chargement de l'aperçu",
|
||||||
"Instance feeds": "Flux de l'instance"
|
"Instance feeds": "Flux de l'instance",
|
||||||
|
"{moderator} suspended group {profile}": "{moderator} a suspendu le groupe {profile}",
|
||||||
|
"{moderator} has unsuspended group {profile}": "{moderator} a annulé la suspension du groupe {profile}"
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<section>
|
<section v-if="actionLogs.total > 0 && actionLogs.elements.length > 0">
|
||||||
<ul v-if="actionLogs.length > 0">
|
<ul>
|
||||||
<li v-for="log in actionLogs" :key="log.id">
|
<li v-for="log in actionLogs.elements" :key="log.id">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<img
|
<img
|
||||||
class="image"
|
class="image"
|
||||||
@ -147,7 +147,10 @@
|
|||||||
<b slot="title">{{ log.object.title }}</b>
|
<b slot="title">{{ log.object.title }}</b>
|
||||||
</i18n>
|
</i18n>
|
||||||
<i18n
|
<i18n
|
||||||
v-else-if="log.action === ActionLogAction.ACTOR_SUSPENSION"
|
v-else-if="
|
||||||
|
log.action === ActionLogAction.ACTOR_SUSPENSION &&
|
||||||
|
log.object.__typename == 'Person'
|
||||||
|
"
|
||||||
tag="span"
|
tag="span"
|
||||||
path="{moderator} suspended profile {profile}"
|
path="{moderator} suspended profile {profile}"
|
||||||
>
|
>
|
||||||
@ -169,7 +172,10 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</i18n>
|
</i18n>
|
||||||
<i18n
|
<i18n
|
||||||
v-else-if="log.action === ActionLogAction.ACTOR_UNSUSPENSION"
|
v-else-if="
|
||||||
|
log.action === ActionLogAction.ACTOR_UNSUSPENSION &&
|
||||||
|
log.object.__typename == 'Person'
|
||||||
|
"
|
||||||
tag="span"
|
tag="span"
|
||||||
path="{moderator} has unsuspended profile {profile}"
|
path="{moderator} has unsuspended profile {profile}"
|
||||||
>
|
>
|
||||||
@ -190,6 +196,56 @@
|
|||||||
>{{ displayNameAndUsername(log.object) }}
|
>{{ displayNameAndUsername(log.object) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</i18n>
|
</i18n>
|
||||||
|
<i18n
|
||||||
|
v-else-if="
|
||||||
|
log.action === ActionLogAction.ACTOR_SUSPENSION &&
|
||||||
|
log.object.__typename == 'Group'
|
||||||
|
"
|
||||||
|
tag="span"
|
||||||
|
path="{moderator} suspended group {profile}"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
slot="moderator"
|
||||||
|
:to="{
|
||||||
|
name: RouteName.ADMIN_PROFILE,
|
||||||
|
params: { id: log.actor.id },
|
||||||
|
}"
|
||||||
|
>@{{ log.actor.preferredUsername }}</router-link
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
slot="profile"
|
||||||
|
:to="{
|
||||||
|
name: RouteName.ADMIN_GROUP_PROFILE,
|
||||||
|
params: { id: log.object.id },
|
||||||
|
}"
|
||||||
|
>{{ displayNameAndUsername(log.object) }}
|
||||||
|
</router-link>
|
||||||
|
</i18n>
|
||||||
|
<i18n
|
||||||
|
v-else-if="
|
||||||
|
log.action === ActionLogAction.ACTOR_UNSUSPENSION &&
|
||||||
|
log.object.__typename == 'Group'
|
||||||
|
"
|
||||||
|
tag="span"
|
||||||
|
path="{moderator} has unsuspended group {profile}"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
slot="moderator"
|
||||||
|
:to="{
|
||||||
|
name: RouteName.ADMIN_PROFILE,
|
||||||
|
params: { id: log.actor.id },
|
||||||
|
}"
|
||||||
|
>@{{ log.actor.preferredUsername }}</router-link
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
slot="profile"
|
||||||
|
:to="{
|
||||||
|
name: RouteName.ADMIN_GROUP_PROFILE,
|
||||||
|
params: { id: log.object.id },
|
||||||
|
}"
|
||||||
|
>{{ displayNameAndUsername(log.object) }}
|
||||||
|
</router-link>
|
||||||
|
</i18n>
|
||||||
<i18n
|
<i18n
|
||||||
v-else-if="log.action === ActionLogAction.USER_DELETION"
|
v-else-if="log.action === ActionLogAction.USER_DELETION"
|
||||||
tag="span"
|
tag="span"
|
||||||
@ -219,20 +275,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<b-pagination
|
||||||
|
:total="actionLogs.total"
|
||||||
|
v-model="page"
|
||||||
|
:per-page="LOGS_PER_PAGE"
|
||||||
|
:aria-next-label="$t('Next page')"
|
||||||
|
:aria-previous-label="$t('Previous page')"
|
||||||
|
:aria-page-label="$t('Page')"
|
||||||
|
:aria-current-label="$t('Current page')"
|
||||||
|
>
|
||||||
|
</b-pagination>
|
||||||
|
</section>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<b-message type="is-info">{{ $t("No moderation logs yet") }}</b-message>
|
<b-message type="is-info">{{ $t("No moderation logs yet") }}</b-message>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue, Watch } from "vue-property-decorator";
|
||||||
import { IActionLog } from "@/types/report.model";
|
import { IActionLog } from "@/types/report.model";
|
||||||
import { LOGS } from "@/graphql/report";
|
import { LOGS } from "@/graphql/report";
|
||||||
import ReportCard from "@/components/Report/ReportCard.vue";
|
import ReportCard from "@/components/Report/ReportCard.vue";
|
||||||
import { ActionLogAction } from "@/types/enums";
|
import { ActionLogAction } from "@/types/enums";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import { displayNameAndUsername } from "../../types/actor";
|
import { displayNameAndUsername } from "../../types/actor";
|
||||||
|
import { Paginate } from "@/types/paginate";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@ -242,17 +309,39 @@ import { displayNameAndUsername } from "../../types/actor";
|
|||||||
actionLogs: {
|
actionLogs: {
|
||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network",
|
||||||
query: LOGS,
|
query: LOGS,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
page: this.page,
|
||||||
|
limit: this.LOGS_PER_PAGE,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class ReportList extends Vue {
|
export default class ReportList extends Vue {
|
||||||
actionLogs?: IActionLog[] = [];
|
actionLogs?: Paginate<IActionLog> = { total: 0, elements: [] };
|
||||||
|
|
||||||
|
page = parseInt((this.$route.query.page as string) || "1", 10);
|
||||||
|
|
||||||
|
LOGS_PER_PAGE = 10;
|
||||||
|
|
||||||
ActionLogAction = ActionLogAction;
|
ActionLogAction = ActionLogAction;
|
||||||
|
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
displayNameAndUsername = displayNameAndUsername;
|
displayNameAndUsername = displayNameAndUsername;
|
||||||
|
|
||||||
|
mounted(): void {
|
||||||
|
this.page = parseInt((this.$route.query.page as string) || "1", 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("page")
|
||||||
|
triggerLoadMoreMemberPageChange(page: string): void {
|
||||||
|
this.$router.replace({
|
||||||
|
name: RouteName.REPORT_LOGS,
|
||||||
|
query: { ...this.$route.query, page },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -265,4 +354,8 @@ img.image {
|
|||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section ul li {
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -27,7 +27,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
|||||||
%{context: %{current_user: %User{role: role}}}
|
%{context: %{current_user: %User{role: role}}}
|
||||||
)
|
)
|
||||||
when is_moderator(role) do
|
when is_moderator(role) do
|
||||||
with action_logs <- Mobilizon.Admin.list_action_logs(page, limit) do
|
with %Page{elements: action_logs, total: total} <-
|
||||||
|
Mobilizon.Admin.list_action_logs(page, limit) do
|
||||||
action_logs =
|
action_logs =
|
||||||
action_logs
|
action_logs
|
||||||
|> Enum.map(fn %ActionLog{
|
|> Enum.map(fn %ActionLog{
|
||||||
@ -44,7 +45,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
|||||||
end)
|
end)
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
{:ok, action_logs}
|
{:ok, %Page{elements: action_logs, total: total}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,6 +22,14 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
|||||||
field(:inserted_at, :datetime, description: "The time when the action was performed")
|
field(:inserted_at, :datetime, description: "The time when the action was performed")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@desc """
|
||||||
|
A paginated list of action logs
|
||||||
|
"""
|
||||||
|
object :paginated_action_log_list do
|
||||||
|
field(:elements, list_of(:action_log), description: "A list of action logs")
|
||||||
|
field(:total, :integer, description: "The total number of action logs in the list")
|
||||||
|
end
|
||||||
|
|
||||||
@desc """
|
@desc """
|
||||||
The different types of action log actions
|
The different types of action log actions
|
||||||
"""
|
"""
|
||||||
@ -147,7 +155,7 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
|||||||
|
|
||||||
object :admin_queries do
|
object :admin_queries do
|
||||||
@desc "Get the list of action logs"
|
@desc "Get the list of action logs"
|
||||||
field :action_logs, type: list_of(:action_log) do
|
field :action_logs, type: :paginated_action_log_list do
|
||||||
arg(:page, :integer, default_value: 1)
|
arg(:page, :integer, default_value: 1)
|
||||||
arg(:limit, :integer, default_value: 10)
|
arg(:limit, :integer, default_value: 10)
|
||||||
resolve(&Admin.list_action_logs/3)
|
resolve(&Admin.list_action_logs/3)
|
||||||
|
@ -36,11 +36,10 @@ defmodule Mobilizon.Admin do
|
|||||||
@doc """
|
@doc """
|
||||||
Returns the list of action logs.
|
Returns the list of action logs.
|
||||||
"""
|
"""
|
||||||
@spec list_action_logs(integer | nil, integer | nil) :: [ActionLog.t()]
|
@spec list_action_logs(integer | nil, integer | nil) :: Page.t()
|
||||||
def list_action_logs(page \\ nil, limit \\ nil) do
|
def list_action_logs(page \\ nil, limit \\ nil) do
|
||||||
list_action_logs_query()
|
list_action_logs_query()
|
||||||
|> Page.paginate(page, limit)
|
|> Page.build_page(page, limit)
|
||||||
|> Repo.all()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -31,6 +31,8 @@ defmodule Mobilizon.GraphQL.Resolvers.AdminTest do
|
|||||||
query = """
|
query = """
|
||||||
{
|
{
|
||||||
actionLogs {
|
actionLogs {
|
||||||
|
total
|
||||||
|
elements {
|
||||||
action,
|
action,
|
||||||
actor {
|
actor {
|
||||||
preferredUsername
|
preferredUsername
|
||||||
@ -46,6 +48,7 @@ defmodule Mobilizon.GraphQL.Resolvers.AdminTest do
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res =
|
res =
|
||||||
@ -62,9 +65,10 @@ defmodule Mobilizon.GraphQL.Resolvers.AdminTest do
|
|||||||
|
|
||||||
assert json_response(res, 200)["errors"] == nil
|
assert json_response(res, 200)["errors"] == nil
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["actionLogs"] |> length == 3
|
assert json_response(res, 200)["data"]["actionLogs"]["total"] == 3
|
||||||
|
assert json_response(res, 200)["data"]["actionLogs"]["elements"] |> length == 3
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["actionLogs"] == [
|
assert json_response(res, 200)["data"]["actionLogs"]["elements"] == [
|
||||||
%{
|
%{
|
||||||
"action" => "NOTE_DELETION",
|
"action" => "NOTE_DELETION",
|
||||||
"actor" => %{"preferredUsername" => moderator_2.preferred_username},
|
"actor" => %{"preferredUsername" => moderator_2.preferred_username},
|
||||||
|
@ -228,6 +228,8 @@ defmodule Mobilizon.GraphQL.Resolvers.CommentTest do
|
|||||||
query = """
|
query = """
|
||||||
{
|
{
|
||||||
actionLogs {
|
actionLogs {
|
||||||
|
total
|
||||||
|
elements {
|
||||||
action,
|
action,
|
||||||
actor {
|
actor {
|
||||||
preferredUsername
|
preferredUsername
|
||||||
@ -251,6 +253,7 @@ defmodule Mobilizon.GraphQL.Resolvers.CommentTest do
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res =
|
res =
|
||||||
@ -260,7 +263,7 @@ defmodule Mobilizon.GraphQL.Resolvers.CommentTest do
|
|||||||
|
|
||||||
refute json_response(res, 200)["errors"]
|
refute json_response(res, 200)["errors"]
|
||||||
|
|
||||||
assert hd(json_response(res, 200)["data"]["actionLogs"]) == %{
|
assert hd(json_response(res, 200)["data"]["actionLogs"]["elements"]) == %{
|
||||||
"action" => "COMMENT_DELETION",
|
"action" => "COMMENT_DELETION",
|
||||||
"actor" => %{"preferredUsername" => actor_moderator.preferred_username},
|
"actor" => %{"preferredUsername" => actor_moderator.preferred_username},
|
||||||
"object" => %{"text" => comment.text, "id" => to_string(comment.id)}
|
"object" => %{"text" => comment.text, "id" => to_string(comment.id)}
|
||||||
|
@ -1368,6 +1368,8 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
query = """
|
query = """
|
||||||
{
|
{
|
||||||
actionLogs {
|
actionLogs {
|
||||||
|
total
|
||||||
|
elements {
|
||||||
action,
|
action,
|
||||||
actor {
|
actor {
|
||||||
preferredUsername
|
preferredUsername
|
||||||
@ -1387,6 +1389,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res =
|
res =
|
||||||
@ -1394,7 +1397,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
|> auth_conn(user_moderator)
|
|> auth_conn(user_moderator)
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "actionLogs"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "actionLogs"))
|
||||||
|
|
||||||
assert hd(json_response(res, 200)["data"]["actionLogs"]) == %{
|
assert hd(json_response(res, 200)["data"]["actionLogs"]["elements"]) == %{
|
||||||
"action" => "EVENT_DELETION",
|
"action" => "EVENT_DELETION",
|
||||||
"actor" => %{"preferredUsername" => actor_moderator.preferred_username},
|
"actor" => %{"preferredUsername" => actor_moderator.preferred_username},
|
||||||
"object" => %{"title" => event.title, "id" => to_string(event.id)}
|
"object" => %{"title" => event.title, "id" => to_string(event.id)}
|
||||||
|
@ -685,6 +685,8 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do
|
|||||||
@moderation_logs_query """
|
@moderation_logs_query """
|
||||||
{
|
{
|
||||||
actionLogs {
|
actionLogs {
|
||||||
|
total
|
||||||
|
elements {
|
||||||
action,
|
action,
|
||||||
actor {
|
actor {
|
||||||
id,
|
id,
|
||||||
@ -698,6 +700,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
test "suspends a remote profile", %{conn: conn} do
|
test "suspends a remote profile", %{conn: conn} do
|
||||||
@ -733,7 +736,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do
|
|||||||
|> auth_conn(modo)
|
|> auth_conn(modo)
|
||||||
|> AbsintheHelpers.graphql_query(query: @moderation_logs_query)
|
|> AbsintheHelpers.graphql_query(query: @moderation_logs_query)
|
||||||
|
|
||||||
actionlog = hd(res["data"]["actionLogs"])
|
actionlog = hd(res["data"]["actionLogs"]["elements"])
|
||||||
refute is_nil(actionlog)
|
refute is_nil(actionlog)
|
||||||
assert actionlog["action"] == "ACTOR_SUSPENSION"
|
assert actionlog["action"] == "ACTOR_SUSPENSION"
|
||||||
assert actionlog["actor"]["id"] == to_string(modo_actor_id)
|
assert actionlog["actor"]["id"] == to_string(modo_actor_id)
|
||||||
|
Loading…
Reference in New Issue
Block a user