Skip to content

Commit

Permalink
✨ Add && separator for tags filter param on queryStatuses endpoint (#…
Browse files Browse the repository at this point in the history
…3261)

* ✨ Add && separator for tags filter param on queryStatuses endpoint

* 🐛 Handle potential empty tag search input

* 🐛 Handle empty condition
  • Loading branch information
foysalit authored Dec 18, 2024
1 parent 448139d commit 6a3e781
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 7 deletions.
4 changes: 3 additions & 1 deletion lexicons/tools/ozone/moderation/queryStatuses.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@
"tags": {
"type": "array",
"items": {
"type": "string"
"type": "string",
"maxLength": 25,
"description": "Items in this array are applied with OR filters. To apply AND filter, put all tags in the same string and separate using && characters"
}
},
"excludeTags": {
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/client/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12498,6 +12498,9 @@ export const schemaDict = {
type: 'array',
items: {
type: 'string',
maxLength: 25,
description:
'Items in this array are applied with OR filters. To apply AND filter, put all tags in the same string and separate using && characters',
},
},
excludeTags: {
Expand Down
3 changes: 3 additions & 0 deletions packages/ozone/src/lexicon/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12498,6 +12498,9 @@ export const schemaDict = {
type: 'array',
items: {
type: 'string',
maxLength: 25,
description:
'Items in this array are applied with OR filters. To apply AND filter, put all tags in the same string and separate using && characters',
},
},
excludeTags: {
Expand Down
47 changes: 41 additions & 6 deletions packages/ozone/src/mod-service/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import net from 'node:net'
import { Insertable, sql } from 'kysely'
import { Insertable, SelectQueryBuilder, sql } from 'kysely'
import { CID } from 'multiformats/cid'
import { AtUri, INVALID_HANDLE } from '@atproto/syntax'
import { InvalidRequestError } from '@atproto/xrpc-server'
Expand Down Expand Up @@ -791,6 +791,45 @@ export class ModerationService {
return result
}

applyTagFilter = (
builder: SelectQueryBuilder<any, any, any>,
tags: string[],
) => {
const { ref } = this.db.db.dynamic
// Build an array of conditions
const conditions = tags
.map((tag) => {
if (tag.includes('&&')) {
// Split by '&&' for AND logic
const subTags = tag
.split('&&')
// Make sure spaces on either sides of '&&' are trimmed
.map((subTag) => subTag.trim())
// Remove empty strings after trimming is applied
.filter(Boolean)

if (!subTags.length) return null

return sql`(${sql.join(
subTags.map(
(subTag) =>
sql`${ref('moderation_subject_status.tags')} ? ${subTag}`,
),
sql` AND `,
)})`
} else {
// Single tag condition
return sql`${ref('moderation_subject_status.tags')} ? ${tag}`
}
})
.filter(Boolean)

if (!conditions.length) return builder

// Combine all conditions with OR
return builder.where(sql`(${sql.join(conditions, sql` OR `)})`)
}

async getSubjectStatuses({
includeAllUserRecords,
cursor,
Expand Down Expand Up @@ -958,11 +997,7 @@ export class ModerationService {
}

if (tags.length) {
builder = builder.where(
sql`${ref('moderation_subject_status.tags')} ?| array[${sql.join(
tags,
)}]::TEXT[]`,
)
builder = this.applyTagFilter(builder, tags)
}

if (excludeTags.length) {
Expand Down
37 changes: 37 additions & 0 deletions packages/ozone/tests/moderation-status-tags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,42 @@ describe('moderation-status-tags', () => {
'follow-churn',
)
})

it('allows filtering by tags', async () => {
await modClient.emitEvent({
subject: {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.alice,
},
event: {
$type: 'tools.ozone.moderation.defs#modEventTag',
add: ['report:spam', 'lang:ja', 'lang:en'],
remove: [],
},
})
const [englishAndJapaneseQueue, englishOrJapaneseQueue] =
await Promise.all([
modClient.queryStatuses({
tags: ['lang:ja&&lang:en'],
}),
modClient.queryStatuses({
tags: ['report:ja', 'lang:en'],
}),
])

// Verify that the queue only contains 1 item with both en and ja tags which is alice's account
expect(englishAndJapaneseQueue.subjectStatuses.length).toEqual(1)
expect(englishAndJapaneseQueue.subjectStatuses[0].subject.did).toEqual(
sc.dids.alice,
)

// Verify that when querying for either en or ja tags, both alice and bob are returned
expect(englishOrJapaneseQueue.subjectStatuses.length).toEqual(2)
const englishOrJapaneseDids = englishOrJapaneseQueue.subjectStatuses.map(
({ subject }) => subject.did,
)
expect(englishOrJapaneseDids).toContain(sc.dids.alice)
expect(englishOrJapaneseDids).toContain(sc.dids.bob)
})
})
})
3 changes: 3 additions & 0 deletions packages/pds/src/lexicon/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12498,6 +12498,9 @@ export const schemaDict = {
type: 'array',
items: {
type: 'string',
maxLength: 25,
description:
'Items in this array are applied with OR filters. To apply AND filter, put all tags in the same string and separate using && characters',
},
},
excludeTags: {
Expand Down

0 comments on commit 6a3e781

Please sign in to comment.