Merge branch 'paginate-moderation-log' into 'master'
Add pagination to moderation logs See merge request framasoft/mobilizon!914
This commit is contained in:
commit
65ea6325cd
|
@ -5,17 +5,50 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "ActionLogObject",
|
||||
"possibleTypes": [
|
||||
{
|
||||
"name": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "Event"
|
||||
},
|
||||
{
|
||||
"name": "Comment"
|
||||
"name": "Person"
|
||||
},
|
||||
{
|
||||
"name": "Report"
|
||||
},
|
||||
{
|
||||
"name": "ReportNote"
|
||||
},
|
||||
{
|
||||
"name": "User"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "ActivityObject",
|
||||
"possibleTypes": [
|
||||
{
|
||||
"name": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "Discussion"
|
||||
},
|
||||
{
|
||||
"name": "Event"
|
||||
},
|
||||
{
|
||||
"name": "Group"
|
||||
},
|
||||
{
|
||||
"name": "Member"
|
||||
},
|
||||
{
|
||||
"name": "Post"
|
||||
},
|
||||
{
|
||||
"name": "Resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -33,6 +66,18 @@
|
|||
"name": "Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "Interactable",
|
||||
"possibleTypes": [
|
||||
{
|
||||
"name": "Event"
|
||||
},
|
||||
{
|
||||
"name": "Group"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,28 +6,10 @@ import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from "@/constants";
|
|||
import { REFRESH_TOKEN } from "@/graphql/auth";
|
||||
import { saveTokenData } from "@/utils/auth";
|
||||
import { ApolloClient } from "apollo-client";
|
||||
import introspectionQueryResultData from "../../fragmentTypes.json";
|
||||
|
||||
export const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData: {
|
||||
__schema: {
|
||||
types: [
|
||||
{
|
||||
kind: "UNION",
|
||||
name: "SearchResult",
|
||||
possibleTypes: [
|
||||
{ name: "Event" },
|
||||
{ name: "Person" },
|
||||
{ name: "Group" },
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "INTERFACE",
|
||||
name: "Actor",
|
||||
possibleTypes: [{ name: "Person" }, { name: "Group" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
introspectionQueryResultData,
|
||||
});
|
||||
|
||||
export async function refreshAccessToken(
|
||||
|
|
|
@ -158,45 +158,54 @@ export const CREATE_REPORT_NOTE = gql`
|
|||
`;
|
||||
|
||||
export const LOGS = gql`
|
||||
query {
|
||||
actionLogs {
|
||||
id
|
||||
action
|
||||
actor {
|
||||
query ActionLogs($page: Int, $limit: Int) {
|
||||
actionLogs(page: $page, limit: $limit) {
|
||||
elements {
|
||||
id
|
||||
preferredUsername
|
||||
domain
|
||||
avatar {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
object {
|
||||
... on Report {
|
||||
id
|
||||
}
|
||||
... on ReportNote {
|
||||
report {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on Event {
|
||||
id
|
||||
title
|
||||
}
|
||||
... on Person {
|
||||
action
|
||||
actor {
|
||||
id
|
||||
preferredUsername
|
||||
domain
|
||||
name
|
||||
avatar {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
... on User {
|
||||
id
|
||||
email
|
||||
confirmedAt
|
||||
object {
|
||||
... on Report {
|
||||
id
|
||||
}
|
||||
... on ReportNote {
|
||||
report {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on Event {
|
||||
id
|
||||
title
|
||||
}
|
||||
... on Person {
|
||||
id
|
||||
preferredUsername
|
||||
domain
|
||||
name
|
||||
}
|
||||
... on Group {
|
||||
id
|
||||
preferredUsername
|
||||
domain
|
||||
name
|
||||
}
|
||||
... on User {
|
||||
id
|
||||
email
|
||||
confirmedAt
|
||||
}
|
||||
}
|
||||
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 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",
|
||||
"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 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",
|
||||
"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>
|
||||
</ul>
|
||||
</nav>
|
||||
<section>
|
||||
<ul v-if="actionLogs.length > 0">
|
||||
<li v-for="log in actionLogs" :key="log.id">
|
||||
<section v-if="actionLogs.total > 0 && actionLogs.elements.length > 0">
|
||||
<ul>
|
||||
<li v-for="log in actionLogs.elements" :key="log.id">
|
||||
<div class="box">
|
||||
<img
|
||||
class="image"
|
||||
|
@ -147,7 +147,10 @@
|
|||
<b slot="title">{{ log.object.title }}</b>
|
||||
</i18n>
|
||||
<i18n
|
||||
v-else-if="log.action === ActionLogAction.ACTOR_SUSPENSION"
|
||||
v-else-if="
|
||||
log.action === ActionLogAction.ACTOR_SUSPENSION &&
|
||||
log.object.__typename == 'Person'
|
||||
"
|
||||
tag="span"
|
||||
path="{moderator} suspended profile {profile}"
|
||||
>
|
||||
|
@ -169,7 +172,10 @@
|
|||
</router-link>
|
||||
</i18n>
|
||||
<i18n
|
||||
v-else-if="log.action === ActionLogAction.ACTOR_UNSUSPENSION"
|
||||
v-else-if="
|
||||
log.action === ActionLogAction.ACTOR_UNSUSPENSION &&
|
||||
log.object.__typename == 'Person'
|
||||
"
|
||||
tag="span"
|
||||
path="{moderator} has unsuspended profile {profile}"
|
||||
>
|
||||
|
@ -190,6 +196,56 @@
|
|||
>{{ displayNameAndUsername(log.object) }}
|
||||
</router-link>
|
||||
</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
|
||||
v-else-if="log.action === ActionLogAction.USER_DELETION"
|
||||
tag="span"
|
||||
|
@ -219,20 +275,31 @@
|
|||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else>
|
||||
<b-message type="is-info">{{ $t("No moderation logs yet") }}</b-message>
|
||||
</div>
|
||||
<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>
|
||||
<b-message type="is-info">{{ $t("No moderation logs yet") }}</b-message>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<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 { LOGS } from "@/graphql/report";
|
||||
import ReportCard from "@/components/Report/ReportCard.vue";
|
||||
import { ActionLogAction } from "@/types/enums";
|
||||
import RouteName from "../../router/name";
|
||||
import { displayNameAndUsername } from "../../types/actor";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -242,17 +309,39 @@ import { displayNameAndUsername } from "../../types/actor";
|
|||
actionLogs: {
|
||||
fetchPolicy: "cache-and-network",
|
||||
query: LOGS,
|
||||
variables() {
|
||||
return {
|
||||
page: this.page,
|
||||
limit: this.LOGS_PER_PAGE,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
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;
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
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>
|
||||
<style lang="scss" scoped>
|
||||
|
@ -265,4 +354,8 @@ img.image {
|
|||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
section ul li {
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,7 +27,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
|||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
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
|
||||
|> Enum.map(fn %ActionLog{
|
||||
|
@ -44,7 +45,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
|||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
{:ok, action_logs}
|
||||
{:ok, %Page{elements: action_logs, total: total}}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
|||
Represents a group of actors
|
||||
"""
|
||||
object :group do
|
||||
interfaces([:actor, :interactable, :activity_object])
|
||||
interfaces([:actor, :interactable, :activity_object, :action_log_object])
|
||||
|
||||
field(:id, :id, description: "Internal ID for this group")
|
||||
field(:url, :string, description: "The ActivityPub actor's URL")
|
||||
|
|
|
@ -22,6 +22,14 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
|||
field(:inserted_at, :datetime, description: "The time when the action was performed")
|
||||
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 """
|
||||
The different types of action log actions
|
||||
"""
|
||||
|
@ -62,6 +70,9 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
|||
%User{}, _ ->
|
||||
:user
|
||||
|
||||
%Actor{type: "Group"}, _ ->
|
||||
:group
|
||||
|
||||
_, _ ->
|
||||
nil
|
||||
end)
|
||||
|
@ -144,7 +155,7 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
|||
|
||||
object :admin_queries do
|
||||
@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(:limit, :integer, default_value: 10)
|
||||
resolve(&Admin.list_action_logs/3)
|
||||
|
|
|
@ -36,11 +36,10 @@ defmodule Mobilizon.Admin do
|
|||
@doc """
|
||||
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
|
||||
list_action_logs_query()
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -31,17 +31,20 @@ defmodule Mobilizon.GraphQL.Resolvers.AdminTest do
|
|||
query = """
|
||||
{
|
||||
actionLogs {
|
||||
action,
|
||||
actor {
|
||||
preferredUsername
|
||||
},
|
||||
object {
|
||||
... on Report {
|
||||
id,
|
||||
status
|
||||
total
|
||||
elements {
|
||||
action,
|
||||
actor {
|
||||
preferredUsername
|
||||
},
|
||||
... on ReportNote {
|
||||
content
|
||||
object {
|
||||
... on Report {
|
||||
id,
|
||||
status
|
||||
},
|
||||
... on ReportNote {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,9 +65,10 @@ defmodule Mobilizon.GraphQL.Resolvers.AdminTest do
|
|||
|
||||
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",
|
||||
"actor" => %{"preferredUsername" => moderator_2.preferred_username},
|
||||
|
|
|
@ -228,25 +228,28 @@ defmodule Mobilizon.GraphQL.Resolvers.CommentTest do
|
|||
query = """
|
||||
{
|
||||
actionLogs {
|
||||
action,
|
||||
actor {
|
||||
preferredUsername
|
||||
},
|
||||
object {
|
||||
... on Report {
|
||||
id,
|
||||
status
|
||||
total
|
||||
elements {
|
||||
action,
|
||||
actor {
|
||||
preferredUsername
|
||||
},
|
||||
... on ReportNote {
|
||||
content
|
||||
}
|
||||
... on Event {
|
||||
id,
|
||||
title
|
||||
},
|
||||
... on Comment {
|
||||
id,
|
||||
text
|
||||
object {
|
||||
... on Report {
|
||||
id,
|
||||
status
|
||||
},
|
||||
... on ReportNote {
|
||||
content
|
||||
}
|
||||
... on Event {
|
||||
id,
|
||||
title
|
||||
},
|
||||
... on Comment {
|
||||
id,
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,7 +263,7 @@ defmodule Mobilizon.GraphQL.Resolvers.CommentTest do
|
|||
|
||||
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",
|
||||
"actor" => %{"preferredUsername" => actor_moderator.preferred_username},
|
||||
"object" => %{"text" => comment.text, "id" => to_string(comment.id)}
|
||||
|
|
|
@ -1368,21 +1368,24 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
query = """
|
||||
{
|
||||
actionLogs {
|
||||
action,
|
||||
actor {
|
||||
preferredUsername
|
||||
},
|
||||
object {
|
||||
... on Report {
|
||||
id,
|
||||
status
|
||||
total
|
||||
elements {
|
||||
action,
|
||||
actor {
|
||||
preferredUsername
|
||||
},
|
||||
... on ReportNote {
|
||||
content
|
||||
}
|
||||
... on Event {
|
||||
id,
|
||||
title
|
||||
object {
|
||||
... on Report {
|
||||
id,
|
||||
status
|
||||
},
|
||||
... on ReportNote {
|
||||
content
|
||||
}
|
||||
... on Event {
|
||||
id,
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1394,7 +1397,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
|> auth_conn(user_moderator)
|
||||
|> 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",
|
||||
"actor" => %{"preferredUsername" => actor_moderator.preferred_username},
|
||||
"object" => %{"title" => event.title, "id" => to_string(event.id)}
|
||||
|
|
|
@ -685,15 +685,18 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do
|
|||
@moderation_logs_query """
|
||||
{
|
||||
actionLogs {
|
||||
action,
|
||||
actor {
|
||||
id,
|
||||
preferredUsername
|
||||
},
|
||||
object {
|
||||
...on Person {
|
||||
total
|
||||
elements {
|
||||
action,
|
||||
actor {
|
||||
id,
|
||||
preferredUsername
|
||||
},
|
||||
object {
|
||||
...on Person {
|
||||
id,
|
||||
preferredUsername
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -733,7 +736,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do
|
|||
|> auth_conn(modo)
|
||||
|> AbsintheHelpers.graphql_query(query: @moderation_logs_query)
|
||||
|
||||
actionlog = hd(res["data"]["actionLogs"])
|
||||
actionlog = hd(res["data"]["actionLogs"]["elements"])
|
||||
refute is_nil(actionlog)
|
||||
assert actionlog["action"] == "ACTOR_SUSPENSION"
|
||||
assert actionlog["actor"]["id"] == to_string(modo_actor_id)
|
||||
|
|
Loading…
Reference in a new issue