Skip to content

Commit

Permalink
✨ Load and display moderation status in event list (#249)
Browse files Browse the repository at this point in the history
  • Loading branch information
foysalit authored Dec 2, 2024
1 parent bd289bc commit 3c1fb44
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 27 deletions.
36 changes: 19 additions & 17 deletions components/mod-event/EventItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ReasonBadge } from '@/reports/ReasonBadge'
import { useConfigurationContext } from '@/shell/ConfigurationContext'
import { ItemTitle } from './ItemTitle'
import { PreviewCard } from '@/common/PreviewCard'
import { ModEventViewWithDetails } from './useModEventList'

const LinkToAuthor = ({
creatorHandle,
Expand All @@ -33,7 +34,7 @@ const LinkToAuthor = ({
const Comment = ({
modEvent,
}: {
modEvent: ToolsOzoneModerationDefs.ModEventView & {
modEvent: ModEventViewWithDetails & {
event:
| ToolsOzoneModerationDefs.ModEventEscalate
| ToolsOzoneModerationDefs.ModEventAcknowledge
Expand Down Expand Up @@ -102,12 +103,19 @@ function isMessageSubject(
return subject.messageId !== undefined
}

type ModEventType<T> = { event: T } & ToolsOzoneModerationDefs.ModEventView

function isModEventType<T>(
e: ToolsOzoneModerationDefs.ModEventView,
predicate: (event: unknown) => event is T,
): e is ModEventType<T> {
return predicate(e.event)
}

const Report = ({
modEvent,
}: {
modEvent: {
event: ToolsOzoneModerationDefs.ModEventReport
} & ToolsOzoneModerationDefs.ModEventView
modEvent: ModEventType<ToolsOzoneModerationDefs.ModEventReport>
}) => {
const isAppeal =
modEvent.event.reportType === ComAtprotoModerationDefs.REASONAPPEAL
Expand Down Expand Up @@ -208,7 +216,7 @@ const EventLabels = ({

if (!labels?.length) return null
return (
<LabelList className='flex-wrap'>
<LabelList className="flex-wrap">
<span className="text-gray-500 dark:text-gray-50">{header}</span>
{labels.map((label) => {
if (isTag) {
Expand All @@ -235,9 +243,7 @@ const EventLabels = ({
const Label = ({
modEvent,
}: {
modEvent: {
event: ToolsOzoneModerationDefs.ModEventLabel
} & ToolsOzoneModerationDefs.ModEventView
modEvent: ModEventType<ToolsOzoneModerationDefs.ModEventLabel>
}) => {
return (
<>
Expand Down Expand Up @@ -306,7 +312,7 @@ export const ModEventItem = ({
showContentAuthor,
showContentPreview,
}: {
modEvent: ToolsOzoneModerationDefs.ModEventView
modEvent: ModEventViewWithDetails
showContentDetails: boolean
showContentAuthor: boolean
showContentPreview: boolean
Expand All @@ -331,20 +337,16 @@ export const ModEventItem = ({
) {
eventItem = <TakedownOrMute modEvent={modEvent} />
}
if (ToolsOzoneModerationDefs.isModEventReport(modEvent.event)) {
// @ts-ignore
if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventReport)) {
eventItem = <Report modEvent={modEvent} />
}
if (ToolsOzoneModerationDefs.isModEventLabel(modEvent.event)) {
//@ts-ignore
if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventLabel)) {
eventItem = <Label modEvent={modEvent} />
}
if (ToolsOzoneModerationDefs.isModEventTag(modEvent.event)) {
//@ts-ignore
if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventTag)) {
eventItem = <Tag modEvent={modEvent} />
}
if (ToolsOzoneModerationDefs.isModEventEmail(modEvent.event)) {
//@ts-ignore
if (isModEventType(modEvent, ToolsOzoneModerationDefs.isModEventEmail)) {
eventItem = <Email modEvent={modEvent} />
}
const previewSubject = modEvent.subject.uri || modEvent.subject.did
Expand Down
12 changes: 10 additions & 2 deletions components/mod-event/ItemTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
ToolsOzoneModerationDefs,
ComAtprotoModerationDefs,
} from '@atproto/api'
import { ModEventViewWithDetails } from './useModEventList'
import { ReviewStateIcon } from '@/subject/ReviewStateMarker'

const dateFormatter = new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
Expand All @@ -15,7 +17,7 @@ export const ItemTitle = ({
showContentDetails,
showContentAuthor,
}: {
modEvent: ToolsOzoneModerationDefs.ModEventView
modEvent: ModEventViewWithDetails
showContentDetails: boolean
showContentAuthor: boolean
}) => {
Expand Down Expand Up @@ -110,13 +112,19 @@ export const ItemTitle = ({
</i>
</p>
{showContentDetails && (
<div>
<div className="flex flex-row items-center gap-1">
<SubjectOverview
withTruncation
subject={modEvent.subject}
hideActor={!showContentAuthor}
subjectRepoHandle={modEvent.subjectHandle}
/>
{modEvent.repo?.moderation.subjectStatus && (
<ReviewStateIcon
size="sm"
subjectStatus={modEvent.repo.moderation.subjectStatus}
/>
)}
</div>
)}
</div>
Expand Down
106 changes: 99 additions & 7 deletions components/mod-event/useModEventList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
Agent,
AtUri,
ChatBskyConvoDefs,
ComAtprotoAdminDefs,
ComAtprotoModerationDefs,
ComAtprotoRepoStrongRef,
ToolsOzoneModerationDefs,
ToolsOzoneModerationQueryEvents,
} from '@atproto/api'
import { useInfiniteQuery } from '@tanstack/react-query'
Expand All @@ -14,6 +16,7 @@ import { useLabelerAgent } from '@/shell/ConfigurationContext'
import { MOD_EVENT_TITLES, MOD_EVENTS } from './constants'
import { useWorkspaceAddItemsMutation } from '@/workspace/hooks'
import { DM_DISABLE_TAG, VIDEO_UPLOAD_DISABLE_TAG } from '@/lib/constants'
import { chunkArray } from '@/lib/util'

export type WorkspaceConfirmationOptions =
| 'subjects'
Expand All @@ -27,6 +30,11 @@ export type ModEventListQueryOptions = {
}
}

export type ModEventViewWithDetails = ToolsOzoneModerationDefs.ModEventView & {
repo?: ToolsOzoneModerationDefs.RepoViewDetail
record?: ToolsOzoneModerationDefs.RecordViewDetail
}

type CommentFilter = {
enabled: boolean
keyword: string
Expand Down Expand Up @@ -57,6 +65,70 @@ const initialListState = {
showContentPreview: false,
}

const getReposAndRecordsForEvents = async (
labelerAgent: Agent,
events: ToolsOzoneModerationDefs.ModEventView[],
) => {
const repos = new Map<
string,
ToolsOzoneModerationDefs.RepoViewDetail | undefined
>()
const records = new Map<
string,
ToolsOzoneModerationDefs.RecordViewDetail | undefined
>()

for (const event of events) {
if (
ComAtprotoAdminDefs.isRepoRef(event.subject) ||
ChatBskyConvoDefs.isMessageRef(event.subject)
) {
repos.set(event.subject.did, undefined)
} else if (ComAtprotoRepoStrongRef.isMain(event.subject)) {
records.set(event.subject.uri, undefined)
}
}

const fetchers: Array<Promise<void>> = []

// Right now, we're only loading 25 events at a time so this chunking never really takes effect
// But to future proof page size change, we're implementing the chunking anyways
if (repos.size) {
for (const chunk of chunkArray(Array.from(repos.keys()), 50)) {
fetchers.push(
labelerAgent.tools.ozone.moderation
.getRepos({ dids: chunk })
.then(({ data }) => {
for (const repo of data.repos) {
if (ToolsOzoneModerationDefs.isRepoViewDetail(repo)) {
repos.set(repo.did, repo)
}
}
}),
)
}
}
if (records.size) {
for (const chunk of chunkArray(Array.from(records.keys()), 50)) {
fetchers.push(
labelerAgent.tools.ozone.moderation
.getRecords({ uris: chunk })
.then(({ data }) => {
for (const record of data.records) {
if (ToolsOzoneModerationDefs.isRecordViewDetail(record)) {
records.set(record.uri, record)
}
}
}),
)
}
}

await Promise.all(fetchers)

return { repos, records }
}

// The 2 fields need overriding because in the initialState, they are set as undefined so the alternative string type is not accepted without override
export type EventListState = Omit<
typeof initialListState,
Expand Down Expand Up @@ -152,7 +224,10 @@ export const useModEventList = (
}
}, [props.createdBy])

const results = useInfiniteQuery({
const results = useInfiniteQuery<{
events: ModEventViewWithDetails[]
cursor?: string
}>({
queryKey: ['modEventList', { listState }],
queryFn: async ({ pageParam }) => {
const {
Expand Down Expand Up @@ -256,12 +331,29 @@ export const useModEventList = (
})
}

const { data } =
await labelerAgent.tools.ozone.moderation.queryEvents({
limit: 25,
...queryParams,
})
return data
const { data } = await labelerAgent.tools.ozone.moderation.queryEvents({
limit: 25,
...queryParams,
})
const { repos, records } = await getReposAndRecordsForEvents(
labelerAgent,
data.events,
)

return {
events: data.events.map((e) => {
if (
ComAtprotoAdminDefs.isRepoRef(e.subject) ||
ChatBskyConvoDefs.isMessageRef(e.subject)
) {
return { ...e, repo: repos.get(e.subject.did) }
} else if (ComAtprotoRepoStrongRef.isMain(e.subject)) {
return { ...e, record: records.get(e.subject.uri) }
}
return { ...e }
}),
cursor: data.cursor,
}
},
getNextPageParam: (lastPage) => lastPage.cursor,
...(props.queryOptions || {}),
Expand Down
12 changes: 11 additions & 1 deletion components/subject/ReviewStateMarker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DM_DISABLE_TAG } from '@/lib/constants'
import { classNames } from '@/lib/util'
import { ToolsOzoneModerationDefs } from '@atproto/api'
import {
CheckCircleIcon,
Expand Down Expand Up @@ -111,8 +112,10 @@ export const SubjectReviewStateBadge = ({
export const ReviewStateIcon = ({
subjectStatus,
className,
size = 'md',
}: {
subjectStatus: ToolsOzoneModerationDefs.SubjectStatusView
size?: 'md' | 'sm'
className?: string
}) => {
let text =
Expand All @@ -134,10 +137,17 @@ export const ReviewStateIcon = ({
Icon = ScaleIcon
}

const sizeClasses = size === 'sm' ? 'h-4 w-4' : 'h-6 w-6'

return (
<Icon
title={text}
className={`h-6 w-6 inline-block align-text-bottom ${color} ${className}`}
className={classNames(
sizeClasses,
color,
className,
`inline-block align-text-bottom`,
)}
/>
)
}
Expand Down

0 comments on commit 3c1fb44

Please sign in to comment.