Skip to content

Commit 781bce4

Browse files
authored
feat(First Listed At): Support sorting by firstListedAt (#211)
* feat: Sort by first listed at * chore: Update commons * chore: Master package files * chore: Update commons * chore: Master package files * chore: Update commons * fix: Tests * chore: Master package files * chore: Update commons * chore: Test getCollectionsQuery * chore: Test fromCollectionFragment * chore: Test getItemQuery * chore: Test fromItemFragment * chore: Use SortDirection instead of strings
1 parent d89dcdc commit 781bce4

File tree

13 files changed

+242
-27
lines changed

13 files changed

+242
-27
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"printWidth": 80
2727
},
2828
"dependencies": {
29-
"@dcl/schemas": "^6.5.0",
29+
"@dcl/schemas": "^6.6.0",
3030
"@types/sqlite3": "^3.1.7",
3131
"@well-known-components/env-config-provider": "^1.2.0",
3232
"@well-known-components/http-server": "^1.1.6",

src/adapters/sources/collections.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function createCollectionsSource(
1616
[CollectionSortBy.RECENTLY_REVIEWED]: result.reviewedAt,
1717
[CollectionSortBy.NAME]: result.name,
1818
[CollectionSortBy.SIZE]: result.size,
19+
[CollectionSortBy.RECENTLY_LISTED]: result.firstListedAt,
1920
},
2021
}))
2122
}

src/adapters/sources/items.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function createItemsSource(
1616
[ItemSortBy.RECENTLY_SOLD]: result.soldAt,
1717
[ItemSortBy.NAME]: result.name,
1818
[ItemSortBy.CHEAPEST]: result.available > 0 ? +result.price : null,
19+
[ItemSortBy.RECENTLY_LISTED]: result.firstListedAt,
1920
},
2021
}))
2122
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ async function initComponents(): Promise<AppComponents> {
386386
[ItemSortBy.RECENTLY_SOLD]: SortDirection.DESC,
387387
[ItemSortBy.NAME]: SortDirection.ASC,
388388
[ItemSortBy.CHEAPEST]: SortDirection.ASC,
389+
[ItemSortBy.RECENTLY_LISTED]: SortDirection.DESC,
389390
},
390391
maxCount: 1000,
391392
})
@@ -589,6 +590,7 @@ async function initComponents(): Promise<AppComponents> {
589590
[CollectionSortBy.NEWEST]: SortDirection.DESC,
590591
[CollectionSortBy.RECENTLY_REVIEWED]: SortDirection.DESC,
591592
[CollectionSortBy.SIZE]: SortDirection.DESC,
593+
[CollectionSortBy.RECENTLY_LISTED]: SortDirection.DESC,
592594
},
593595
maxCount: 1000,
594596
})

src/ports/collections/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ export type CollectionFragment = {
1515
createdAt: string
1616
updatedAt: string
1717
reviewedAt: string
18+
firstListedAt: string | null
1819
}

src/ports/collections/utils.spec.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { ChainId, CollectionSortBy, Network } from '@dcl/schemas'
2+
import { CollectionFragment } from './types'
3+
import { fromCollectionFragment, getCollectionsQuery } from './utils'
4+
5+
describe('#getCollectionsQuery', () => {
6+
describe('when sortBy is CollectionSortBy.RECENTLY_LISTED', () => {
7+
let query: string
8+
9+
beforeEach(() => {
10+
query = getCollectionsQuery({
11+
sortBy: CollectionSortBy.RECENTLY_LISTED,
12+
})
13+
})
14+
15+
it('should add firstListedAt_not: null where filter', () => {
16+
expect(query.includes('firstListedAt_not: null')).toBeTruthy()
17+
})
18+
19+
it('should add firstListedAt as orderBy', () => {
20+
expect(query.includes('orderBy: firstListedAt')).toBeTruthy()
21+
})
22+
23+
it('should add desc as orderDirection', () => {
24+
expect(query.includes('orderDirection: desc')).toBeTruthy()
25+
})
26+
})
27+
})
28+
29+
describe('#fromCollectionFragment', () => {
30+
let collectionFragment: CollectionFragment
31+
32+
beforeEach(() => {
33+
collectionFragment = {
34+
id: 'id',
35+
urn: 'urn',
36+
name: 'name',
37+
creator: 'creator',
38+
searchIsStoreMinter: false,
39+
itemsCount: 100,
40+
createdAt: '100',
41+
updatedAt: '200',
42+
reviewedAt: '300',
43+
firstListedAt: null,
44+
}
45+
})
46+
47+
describe('when fragment firstListedAt is null', () => {
48+
beforeEach(() => {
49+
collectionFragment.firstListedAt = null
50+
})
51+
52+
it('should return a Collection with firstListedAt as null', () => {
53+
const collection = fromCollectionFragment(
54+
collectionFragment,
55+
Network.MATIC,
56+
ChainId.MATIC_MUMBAI
57+
)
58+
59+
expect(collection.firstListedAt).toBeNull()
60+
})
61+
})
62+
63+
describe('when fragment firstListedAt is "100"', () => {
64+
beforeEach(() => {
65+
collectionFragment.firstListedAt = '100'
66+
})
67+
68+
it('should return a Collection with firstListedAt as 100000', () => {
69+
const collection = fromCollectionFragment(
70+
collectionFragment,
71+
Network.MATIC,
72+
ChainId.MATIC_MUMBAI
73+
)
74+
75+
expect(collection.firstListedAt).toBe(100000)
76+
})
77+
})
78+
})

src/ports/collections/utils.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
CollectionSortBy,
66
Network,
77
} from '@dcl/schemas'
8+
import { SortDirection } from '../merger/types'
89
import { CollectionFragment } from './types'
910

1011
export const COLLECTION_DEFAULT_SORT_BY = CollectionSortBy.NAME
@@ -26,6 +27,9 @@ export function fromCollectionFragment(
2627
size: fragment.itemsCount,
2728
network,
2829
chainId,
30+
firstListedAt: fragment.firstListedAt
31+
? +fragment.firstListedAt * 1000
32+
: null,
2933
}
3034

3135
return collection
@@ -42,6 +46,7 @@ export const getCollectionFragment = () => `
4246
createdAt
4347
updatedAt
4448
reviewedAt
49+
firstListedAt
4550
}
4651
`
4752

@@ -87,6 +92,12 @@ export function getCollectionsQuery(
8792
where.push(`searchText_contains: "${search.trim().toLowerCase()}"`)
8893
}
8994

95+
// Sorting by a nullable field will return null values first.
96+
// We do not want them so we filter them out.
97+
if (sortBy === CollectionSortBy.RECENTLY_LISTED) {
98+
where.push('firstListedAt_not: null')
99+
}
100+
90101
const max = 1000
91102
const total = isCount
92103
? max
@@ -101,22 +112,27 @@ export function getCollectionsQuery(
101112
switch (sortBy) {
102113
case CollectionSortBy.NEWEST:
103114
orderBy = 'createdAt'
104-
orderDirection = 'desc'
115+
orderDirection = SortDirection.DESC
105116
break
106117
case CollectionSortBy.RECENTLY_REVIEWED:
107118
orderBy = 'reviewedAt'
108-
orderDirection = 'desc'
119+
orderDirection = SortDirection.DESC
109120
break
110121
case CollectionSortBy.NAME:
111122
orderBy = 'name'
112-
orderDirection = 'asc'
123+
orderDirection = SortDirection.ASC
113124
break
114125
case CollectionSortBy.SIZE:
115126
orderBy = 'itemsCount'
116-
orderDirection = 'desc'
127+
orderDirection = SortDirection.DESC
128+
break
129+
case CollectionSortBy.RECENTLY_LISTED:
130+
orderBy = 'firstListedAt'
131+
orderDirection = SortDirection.DESC
132+
break
117133
default:
118134
orderBy = 'name'
119-
orderDirection = 'asc'
135+
orderDirection = SortDirection.ASC
120136
}
121137

122138
return `

src/ports/items/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type ItemFragment = {
4747
reviewedAt: string
4848
soldAt: string
4949
beneficiary: string
50+
firstListedAt: string | null
5051
}
5152

5253
export interface IItemsComponent {

src/ports/items/utils.spec.ts

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1-
import { EmotePlayMode, GenderFilterOption } from '@dcl/schemas';
2-
import { getItemsQuery } from './utils';
1+
import {
2+
ChainId,
3+
EmoteCategory,
4+
EmotePlayMode,
5+
GenderFilterOption,
6+
ItemSortBy,
7+
Network,
8+
Rarity,
9+
} from '@dcl/schemas'
10+
import { ItemFragment, FragmentItemType } from './types'
11+
import { fromItemFragment, getItemsQuery } from './utils'
312

413
describe("#getItemsQuery", () => {
514
describe("when minPrice is defined", () => {
@@ -92,4 +101,96 @@ describe("#getItemsQuery", () => {
92101
)
93102
})
94103
})
95-
});
104+
105+
describe('when sortBy is ItemSortBy.RECENTLY_LISTED', () => {
106+
let query: string
107+
108+
beforeEach(() => {
109+
query = getItemsQuery({
110+
sortBy: ItemSortBy.RECENTLY_LISTED,
111+
})
112+
})
113+
114+
it('should add firstListedAt_not: null where filter', () => {
115+
expect(query.includes('firstListedAt_not: null')).toBeTruthy()
116+
})
117+
118+
it('should add firstListedAt as orderBy', () => {
119+
expect(query.includes('orderBy: firstListedAt')).toBeTruthy()
120+
})
121+
122+
it('should add desc as orderDirection', () => {
123+
expect(query.includes('orderDirection: desc')).toBeTruthy()
124+
})
125+
})
126+
})
127+
128+
describe('#fromItemFragment', () => {
129+
let itemFragment: ItemFragment
130+
131+
beforeEach(() => {
132+
itemFragment = {
133+
id: 'id',
134+
price: 'price',
135+
blockchainId: 'blockchainId',
136+
image: 'image',
137+
rarity: Rarity.COMMON,
138+
available: 'available',
139+
itemType: FragmentItemType.EMOTE_V1,
140+
collection: {
141+
id: 'collection',
142+
creator: 'creator',
143+
},
144+
metadata: {
145+
wearable: null,
146+
emote: {
147+
category: EmoteCategory.DANCE,
148+
description: 'description',
149+
loop: false,
150+
name: 'name',
151+
},
152+
},
153+
searchWearableBodyShapes: null,
154+
searchEmoteBodyShapes: null,
155+
searchIsStoreMinter: false,
156+
createdAt: '100',
157+
updatedAt: '100',
158+
reviewedAt: '100',
159+
soldAt: '100',
160+
beneficiary: 'beneficiary',
161+
firstListedAt: null,
162+
}
163+
})
164+
165+
describe('when fragment firstListedAt is null', () => {
166+
beforeEach(() => {
167+
itemFragment.firstListedAt = null
168+
})
169+
170+
it('should return an Item with firstListedAt as null', () => {
171+
const collection = fromItemFragment(
172+
itemFragment,
173+
Network.MATIC,
174+
ChainId.MATIC_MUMBAI
175+
)
176+
177+
expect(collection.firstListedAt).toBeNull()
178+
})
179+
})
180+
181+
describe('when fragment firstListedAt is "100"', () => {
182+
beforeEach(() => {
183+
itemFragment.firstListedAt = '100'
184+
})
185+
186+
it('should return a Collection with firstListedAt as 100000', () => {
187+
const collection = fromItemFragment(
188+
itemFragment,
189+
Network.MATIC,
190+
ChainId.MATIC_MUMBAI
191+
)
192+
193+
expect(collection.firstListedAt).toBe(100000)
194+
})
195+
})
196+
})

0 commit comments

Comments
 (0)