Skip to content

Commit

Permalink
✨ Use settings api to store queue config (#222)
Browse files Browse the repository at this point in the history
* ✨ Use settings api to store queue config

* ✨ Add role management per setting

* ⬆️ Update atproto/api version

* 🐛 Bring queue seed to the front
  • Loading branch information
foysalit authored Nov 8, 2024
1 parent 990c4c9 commit 6e4f373
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 52 deletions.
34 changes: 27 additions & 7 deletions app/reports/page-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import { ModActionPanelQuick } from '../actions/ModActionPanel/QuickAction'
import { ButtonGroup } from '@/common/buttons'
import { SubjectTable } from 'components/subject/table'
import { useTitle } from 'react-use'
import { QueueSelector, QUEUE_NAMES } from '@/reports/QueueSelector'
import { QueueSelector } from '@/reports/QueueSelector'
import { simpleHash, unique } from '@/lib/util'
import { useEmitEvent } from '@/mod-event/helpers/emitEvent'
import { useFluentReportSearchParams } from '@/reports/useFluentReportSearch'
import { useLabelerAgent } from '@/shell/ConfigurationContext'
import { WorkspacePanel } from 'components/workspace/Panel'
import { useWorkspaceOpener } from '@/common/useWorkspaceOpener'
import { QUEUE_SEED } from '@/lib/constants'
import { useQueueSetting } from 'components/setting/useQueueSetting'
import QueueFilterPanel from '@/reports/QueueFilter/Panel'

const TABS = [
Expand Down Expand Up @@ -294,6 +294,7 @@ function getTabFromParams({ reviewState }: { reviewState?: string | null }) {
function useModerationQueueQuery() {
const labelerAgent = useLabelerAgent()
const params = useSearchParams()
const { setting: queueSetting } = useQueueSetting()

const takendown = !!params.get('takendown')
const includeMuted = !!params.get('includeMuted')
Expand Down Expand Up @@ -393,7 +394,18 @@ function useModerationQueueQuery() {
}
})

return getQueueItems(labelerAgent, queryParams, queueName)
return getQueueItems(
labelerAgent,
queryParams,
queueName,
0,
queueSetting.data
? {
queueNames: queueSetting.data.queueNames,
queueSeed: queueSetting.data.queueSeed.setting,
}
: undefined,
)
},
getNextPageParam: (lastPage) => lastPage.cursor,
})
Expand All @@ -404,6 +416,7 @@ const getQueueItems = async (
queryParams: ToolsOzoneModerationQueryStatuses.QueryParams,
queueName: string | null,
attempt = 0,
queueSetting?: { queueNames: string[]; queueSeed: string },
) => {
const pageSize = 100
const { data } = await labelerAgent.tools.ozone.moderation.queryStatuses({
Expand All @@ -412,13 +425,19 @@ const getQueueItems = async (
...queryParams,
})

const queueIndex = QUEUE_NAMES.indexOf(queueName ?? '')
const queueIndex = queueSetting?.queueNames.indexOf(queueName ?? '')
const statusesInQueue = queueName
? data.subjectStatuses.filter((status) => {
const subjectDid = ComAtprotoAdminDefs.isRepoRef(status.subject)
? status.subject.did
: new AtUri(`${status.subject.uri}`).host
return getQueueIndex(subjectDid) === queueIndex
return (
getQueueIndex(
subjectDid,
queueSetting?.queueNames || [],
queueSetting?.queueSeed || '',
) === queueIndex
)
})
: data.subjectStatuses

Expand All @@ -434,12 +453,13 @@ const getQueueItems = async (
},
queueName,
++attempt,
queueSetting,
)
}

return { cursor: data.cursor, subjectStatuses: statusesInQueue }
}

function getQueueIndex(did: string) {
return simpleHash(did + QUEUE_SEED) % QUEUE_NAMES.length
function getQueueIndex(did: string, queueNames: string[], queueSeed: string) {
return simpleHash(`${queueSeed}:${did}`) % queueNames.length
}
2 changes: 2 additions & 0 deletions components/config/Labeler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ServerConfig } from './server-config'
import { useConfigurationContext } from '@/shell/ConfigurationContext'
import { usePdsAgent } from '@/shell/AuthContext'
import { LocalPreferences } from './LocalPreferences'
import { QueueSetting } from 'components/setting/Queue'

const BrowserReactJsonView = dynamic(() => import('react-json-view'), {
ssr: false,
Expand Down Expand Up @@ -41,6 +42,7 @@ export function LabelerConfig() {

<ServerConfig />
<LocalPreferences />
<QueueSetting />
<ExternalLabelerConfig />
</div>
)
Expand Down
31 changes: 12 additions & 19 deletions components/reports/QueueSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import { Dropdown } from '@/common/Dropdown'
import { QUEUE_CONFIG } from '@/lib/constants'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { useQueueSetting } from 'components/setting/useQueueSetting'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'

type QueueConfig = Record<string, { name: string }>

const getQueueConfig = () => {
const config = QUEUE_CONFIG
try {
return JSON.parse(config) as QueueConfig
} catch (err) {
return {}
}
}

export const QUEUES = getQueueConfig()
export const QUEUE_NAMES = Object.keys(QUEUES)

export const QueueSelector = () => {
const searchParams = useSearchParams()
const router = useRouter()
const pathname = usePathname()
const queueName = searchParams.get('queueName')
const { setting: queueSetting } = useQueueSetting()

const selectQueue = (queue: string) => () => {
const nextParams = new URLSearchParams(searchParams)
Expand All @@ -34,14 +21,20 @@ export const QueueSelector = () => {
}

// If no queues are configured, just use a static title
if (!QUEUE_NAMES.length) {
if (
queueSetting.isLoading ||
!queueSetting.data ||
!queueSetting.data?.queueNames.length
) {
return (
<h3 className="flex items-center text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
Queue
</h3>
)
}

const { queueNames, queueList } = queueSetting.data

return (
<Dropdown
containerClassName="inline-block"
Expand All @@ -51,14 +44,14 @@ export const QueueSelector = () => {
text: 'All',
onClick: selectQueue(''),
},
...QUEUE_NAMES.map((q) => ({
text: QUEUES[q].name,
...queueNames.map((q) => ({
text: queueList[q].name,
onClick: selectQueue(q),
})),
]}
>
<h3 className="flex items-center text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
{queueName ? `${QUEUES[queueName].name} Queue` : 'Queue'}
{queueName ? `${queueList[queueName].name} Queue` : 'Queue'}
<ChevronDownIcon
className="text-gray-900 dark:text-gray-200 h-4 w-4"
aria-hidden="true"
Expand Down
2 changes: 1 addition & 1 deletion components/repositories/RepositoriesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from 'next/link'
import { UserGroupIcon } from '@heroicons/react/20/solid'
import { formatDistanceToNow } from 'date-fns'
import { AppBskyActorProfile, ComAtprotoAdminDefs } from '@atproto/api'
import { AppBskyActorProfile } from '@atproto/api'
import { Repo } from '@/lib/types'
import { LoadMoreButton } from '../common/LoadMoreButton'
import { ReviewStateIcon } from '@/subject/ReviewStateMarker'
Expand Down
181 changes: 181 additions & 0 deletions components/setting/Queue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { ActionButton } from '@/common/buttons'
import { Card } from '@/common/Card'
import { FormLabel, Input, Select } from '@/common/forms'
import { useQueueSetting } from './useQueueSetting'
import { FormEvent, useRef } from 'react'
import dynamic from 'next/dynamic'
import { isDarkModeEnabled } from '@/common/useColorScheme'
import { useServerConfig } from '@/shell/ConfigurationContext'
import { isRoleSuperiorOrSame, MemberRoleNames } from 'components/team/helpers'

const BrowserReactJsonView = dynamic(() => import('react-json-view'), {
ssr: false,
})

export const QueueSetting = () => {
const { setting: queueSetting, upsert: upsertQueueSetting } =
useQueueSetting()
const darkMode = isDarkModeEnabled()
const queueListRef = useRef<HTMLInputElement>(null)
const { role } = useServerConfig()

const canManageQueueSeed =
(queueSetting.data?.queueSeed &&
!queueSetting.data.queueSeed.managerRole) ||
(!!role &&
!!queueSetting.data?.queueSeed.managerRole &&
isRoleSuperiorOrSame(role, queueSetting.data?.queueSeed.managerRole))

const canManageQueueList =
(queueSetting.data?.queueList &&
!queueSetting.data.queueList.managerRole) ||
(!!role &&
!!queueSetting.data?.queueList.managerRole &&
isRoleSuperiorOrSame(role, queueSetting.data?.queueList.managerRole))

return (
<>
<div className="flex flex-row justify-between my-4">
<h4 className="font-medium text-gray-700 dark:text-gray-100">
Queue Setting
</h4>
</div>
<Card className="mb-4 pb-4">
<div className="p-2">
<form
onSubmit={async (
e: FormEvent<HTMLFormElement> & { target: HTMLFormElement },
) => {
e.preventDefault()
const queueSeedManagerRole = e.target.queueSeedManagerRole.value
const queueSeedSetting = e.target.queueSeed.value
const queueListSetting = JSON.parse(
e.target.queueList.value || {},
)
const queueListManagerRole = e.target.queueListManagerRole.value

await upsertQueueSetting.mutateAsync({
queueSeed: {
setting: queueSeedSetting,
managerRole: queueSeedManagerRole,
},
queueList: {
setting: queueListSetting,
managerRole: queueListManagerRole,
},
})
e.target.reset()
}}
>
<FormLabel label="Queue Seed" className="mb-2" />
<div className="flex flex-row gap-2">
<FormLabel
label="Manager Role"
htmlFor="queueSeedManagerRole"
className="mb-2"
>
<Select
required
id="queueSeedManagerRole"
name="queueSeedManagerRole"
disabled={!canManageQueueSeed || upsertQueueSetting.isLoading}
>
{Object.entries(MemberRoleNames).map(([role, name]) => (
<option
key={role}
value={role}
selected={
queueSetting.data?.queueSeed.managerRole === role
}
>
{name}
</option>
))}
</Select>
</FormLabel>
<FormLabel
label="Value"
htmlFor="queueSeed"
className="flex-1 mb-3"
>
<Input
required
type="text"
id="queueSeed"
name="queueSeed"
defaultValue={queueSetting.data?.queueSeed.setting}
placeholder="Queue seeder for balancing"
className="block w-full"
disabled={!canManageQueueSeed}
/>
</FormLabel>
</div>

<FormLabel label="Queue List" className="my-2" />

<FormLabel
label="Manager Role"
htmlFor="queueListManagerRole"
className="flex-1 mb-2"
>
<Select
required
id="queueListManagerRole"
name="queueListManagerRole"
disabled={!canManageQueueSeed || upsertQueueSetting.isLoading}
>
{Object.entries(MemberRoleNames).map(([role, name]) => (
<option
key={role}
value={role}
selected={queueSetting.data?.queueList.managerRole === role}
>
{name}
</option>
))}
</Select>
</FormLabel>

<FormLabel
label="Configure Queues"
htmlFor="queueList"
className="flex-1 mb-3"
/>
<input
type="hidden"
name="queueList"
ref={queueListRef}
value={JSON.stringify(queueSetting.data?.queueList.setting || {})}
/>
<BrowserReactJsonView
src={queueSetting.data?.queueList.setting || {}}
theme={darkMode ? 'harmonic' : 'rjv-default'}
name={null}
quotesOnKeys={false}
displayObjectSize={false}
displayDataTypes={false}
enableClipboard={false}
onEdit={(edit) => {
if (queueListRef.current)
queueListRef.current.value = JSON.stringify(edit.updated_src)
}}
onAdd={(add) => {
if (queueListRef.current)
queueListRef.current.value = JSON.stringify(add.updated_src)
}}
onDelete={(del) => {
if (queueListRef.current)
queueListRef.current.value = JSON.stringify(del.updated_src)
}}
/>
<div className="mt-3 mb-2 flex flex-row justify-end">
<ActionButton appearance="primary" size="sm" type="submit">
Save Setting
</ActionButton>
</div>
</form>
</div>
</Card>
</>
)
}
Loading

0 comments on commit 6e4f373

Please sign in to comment.