Skip to content

Commit

Permalink
feat: Add mapping status filters to items (#768)
Browse files Browse the repository at this point in the history
* feat: Add mapping completion in curations

* fix: Update mapping statement query

* fix: Query

* feat: Add query

* feat: Add the mapping status filter to items
  • Loading branch information
LautaroPetaccio authored Aug 29, 2024
1 parent f6bf391 commit dd30fdf
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 35 deletions.
30 changes: 25 additions & 5 deletions src/Item/Item.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ export const MAX_TAGS_LENGTH = 20

type ItemWithTotalCount = ItemAttributes & { total_count: number }

export enum ItemMappingStatus {
MISSING_MAPPING = 'missing_mapping',
UNPUBLISHED_MAPPING = 'unpublished_mapping',
}

type ItemQueryOptions = {
status?: CurationStatus
synced?: boolean
mappingStatus?: ItemMappingStatus
}

export class Item extends Model<ItemAttributes> {
Expand Down Expand Up @@ -186,13 +192,14 @@ export class Item extends Model<ItemAttributes> {
limit: number = DEFAULT_LIMIT,
offset: number = 0
) {
const { status, synced } = options
const { status, synced, mappingStatus } = options
return await this.query<ItemWithTotalCount>(
ItemQueries.selectByCollectionIds(
[collectionId],
{
status,
synced,
mappingStatus,
},
limit,
offset
Expand All @@ -208,19 +215,20 @@ export const ItemQueries = Object.freeze({
limit: number = DEFAULT_LIMIT,
offset: number = 0
) => {
const { status, synced } = options
const { status, synced, mappingStatus } = options
return SQL`
SELECT items.*, count(*) OVER() AS total_count FROM (
SELECT DISTINCT ON (items.id) items.id, items.*
FROM ${raw(Item.tableName)} items
${
status || synced !== undefined
status || mappingStatus || synced !== undefined
? SQL`
JOIN (
LEFT JOIN (
SELECT
DISTINCT ON (item_curations.item_id) item_curations.item_id,
item_curations.content_hash,
item_curations.status
item_curations.status,
item_curations.is_mapping_complete
FROM ${raw(ItemCuration.tableName)}
ORDER BY item_curations.item_id, item_curations.created_at desc
) item_curations
Expand All @@ -240,9 +248,21 @@ export const ItemQueries = Object.freeze({
: SQL``
}
WHERE items.collection_id = ANY(${collectionIds})
${
synced !== undefined
? SQL`AND item_curations.status IS NOT NULL`
: SQL``
}
AND ${
status ? SQL`item_curations.status = ${status}` : SQL`1 = 1`
}
AND ${
mappingStatus === ItemMappingStatus.MISSING_MAPPING
? SQL`items.mappings IS NULL`
: mappingStatus === ItemMappingStatus.UNPUBLISHED_MAPPING
? SQL`(item_curations.is_mapping_complete = false OR item_curations.is_mapping_complete IS NULL) AND items.mappings IS NOT NULL`
: SQL`1 = 1`
}
ORDER BY items.id
) items
ORDER BY items.created_at ASC
Expand Down
95 changes: 71 additions & 24 deletions src/Item/Item.router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import {
} from '../Collection/Collection.types'
import { VIDEO_PATH, buildTPItemURN } from './utils'
import { hasPublicAccess } from './access'
import { Item } from './Item.model'
import { Item, ItemMappingStatus } from './Item.model'
import {
FullItem,
ItemAttributes,
Expand Down Expand Up @@ -579,11 +579,6 @@ describe('Item router', () => {

describe('and the collection is a third party collection', () => {
beforeEach(() => {
;(Item.findByCollectionIds as jest.Mock).mockResolvedValueOnce([
dbTPItem,
dbTPItemPublished,
dbTPItemNotPublished,
])
;(Collection.findOne as jest.Mock).mockResolvedValueOnce(
dbTPCollectionMock
)
Expand Down Expand Up @@ -612,27 +607,79 @@ describe('Item router', () => {
;(peerAPI.fetchWearables as jest.Mock).mockResolvedValueOnce([
tpWearableWithMappings,
])
url = `/collections/${dbCollectionMock.id}/items`
})

it('should return all the items of a collection with their URNs and the isMappingComplete property as true for the item with a mapping', () => {
return server
.get(buildURL(url))
.set(createAuthHeaders('get', url))
.expect(200)
.then((response: any) => {
expect(response.body).toEqual({
data: [
{
...resultingTPItem,
isMappingComplete: true,
},
{ ...resultTPItemPublished, is_published: true },
resultTPItemNotPublished,
],
ok: true,
})
describe('and the mapping status filter is applied', () => {
beforeEach(() => {
;(Item.findByCollectionIdAndStatus as jest.Mock).mockResolvedValueOnce(
[dbTPItem, dbTPItemPublished, dbTPItemNotPublished]
)
url = `/collections/${dbTPCollectionMock.id}/items`
})

it('should return all the items of a collection that comply with the mapping status filter', async () => {
const response = await server
.get(buildURL(url))
.set(createAuthHeaders('get', url))
.query({ mappingStatus: ItemMappingStatus.MISSING_MAPPING })
.expect(200)

expect(response.body).toEqual({
data: [
{
...resultingTPItem,
isMappingComplete: true,
},
{ ...resultTPItemPublished, is_published: true },
resultTPItemNotPublished,
],
ok: true,
})

expect(
Item.findByCollectionIdAndStatus as jest.Mock
).toHaveBeenLastCalledWith(
dbTPCollectionMock.id,
{
synced: undefined,
status: undefined,
mappingStatus: ItemMappingStatus.MISSING_MAPPING,
},
undefined,
undefined
)
})
})

describe('and there are not filters applied', () => {
beforeEach(() => {
;(Item.findByCollectionIds as jest.Mock).mockResolvedValueOnce([
dbTPItem,
dbTPItemPublished,
dbTPItemNotPublished,
])
url = `/collections/${dbTPCollectionMock.id}/items`
})

it('should return all the items of a collection with their URNs and the isMappingComplete property as true for the item with a mapping', () => {
return server
.get(buildURL(url))
.set(createAuthHeaders('get', url))
.expect(200)
.then((response: any) => {
expect(response.body).toEqual({
data: [
{
...resultingTPItem,
isMappingComplete: true,
},
{ ...resultTPItemPublished, is_published: true },
resultTPItemNotPublished,
],
ok: true,
})
})
})
})
})
})
Expand Down
7 changes: 6 additions & 1 deletion src/Item/Item.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
} from '../Pagination/utils'
import { CurationStatus } from '../Curation'
import { MulterFile } from '../S3/types'
import { Item } from './Item.model'
import { Item, ItemMappingStatus } from './Item.model'
import { ItemAttributes, ItemContents } from './Item.types'
import { areItemRepresentationsValid, upsertItemSchema } from './Item.schema'
import { FullItem } from './Item.types'
Expand Down Expand Up @@ -365,13 +365,17 @@ export class ItemRouter extends Router {
): Promise<PaginatedResponse<FullItem> | FullItem[]> => {
const id = server.extractFromReq(req, 'id')
let status: CurationStatus | undefined
let mappingStatus: ItemMappingStatus | undefined
let synced: string | undefined
try {
status = server.extractFromReq(req, 'status')
} catch (error) {}
try {
synced = server.extractFromReq(req, 'synced')
} catch (error) {}
try {
mappingStatus = server.extractFromReq(req, 'mappingStatus')
} catch (error) {}

if (status && !Object.values(CurationStatus).includes(status)) {
throw new HTTPError(
Expand All @@ -392,6 +396,7 @@ export class ItemRouter extends Router {
limit,
offset: page && limit ? getOffset(page, limit) : undefined,
status,
mappingStatus,
synced: synced ? synced === 'true' : undefined,
})

Expand Down
12 changes: 7 additions & 5 deletions src/Item/Item.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
ThirdPartyItemInsertByURNError,
MaximunAmountOfTagsReachedError,
} from './Item.errors'
import { Item, MAX_TAGS_LENGTH } from './Item.model'
import { Item, ItemMappingStatus, MAX_TAGS_LENGTH } from './Item.model'
import {
FullItem,
ItemAttributes,
Expand Down Expand Up @@ -192,6 +192,7 @@ export class ItemService {
filters: {
status?: CurationStatus
synced?: boolean
mappingStatus?: ItemMappingStatus
limit?: number
offset?: number
}
Expand All @@ -200,18 +201,19 @@ export class ItemService {
items: FullItem[]
totalItems: number
}> {
const { synced, status, limit, offset } = filters
const { synced, status, mappingStatus, limit, offset } = filters
const dbCollection = await this.collectionService.getDBCollection(
collectionId
)
const isTP = isTPCollection(dbCollection)
const dbItemsWithCount =
status && isTP
(status || mappingStatus) && isTP
? await Item.findByCollectionIdAndStatus(
collectionId,
{
synced,
status: CurationStatus.PENDING,
status: status ? CurationStatus.PENDING : undefined,
mappingStatus,
},
limit,
offset
Expand Down Expand Up @@ -358,7 +360,7 @@ export class ItemService {
dbItem: ThirdPartyItemAttributes,
dbCollection?: CollectionAttributes
): Promise<{ item: FullItem; collection?: CollectionAttributes }> {
let item: FullItem = Bridge.toFullItem(dbItem)
let item: FullItem = Bridge.toFullItem(dbItem, dbCollection)
let collection =
dbCollection ?? (await Collection.findOne(dbItem.collection_id))

Expand Down

0 comments on commit dd30fdf

Please sign in to comment.