Skip to content

Commit c112f1e

Browse files
authored
Merge pull request #474 from OpenBeta/feat/#417
Feat: Allow for query of cumulative image weight resolves #417
2 parents 795d3a1 + 5561c10 commit c112f1e

File tree

5 files changed

+182
-3
lines changed

5 files changed

+182
-3
lines changed

src/__tests__/areas.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,27 @@ describe('areas API', () => {
9898
})
9999
})
100100

101+
it('retrieves an area and its cumulative media weight', async () => {
102+
const response = await queryAPI({
103+
query: `
104+
query area($input: ID) {
105+
area(uuid: $input) {
106+
uuid
107+
imageByteSum
108+
}
109+
}
110+
`,
111+
operationName: 'area',
112+
variables: { input: ca.metadata.area_id },
113+
userUuid,
114+
app
115+
})
116+
expect(response.statusCode).toBe(200)
117+
const areaResult = response.body.data.area
118+
expect(areaResult.uuid).toBe(muuidToString(ca.metadata.area_id))
119+
expect(areaResult.imageByteSum).toBe(0)
120+
})
121+
101122
it('retrieves an area omitting organizations that exclude it', async () => {
102123
const response = await queryAPI({
103124
query: areaQuery,

src/graphql/resolvers.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,10 @@ const resolvers = {
221221
return []
222222
},
223223

224-
aggregate: async (node: AreaType) => {
225-
return node.aggregate
226-
},
224+
aggregate: async (node: AreaType) => node.aggregate,
225+
226+
imageByteSum: async (node: AreaType, _, { dataSources: { areas } }: GQLContext) =>
227+
await areas.computeImageByteSum(node.metadata.area_id),
227228

228229
ancestors: async (parent) => parent.ancestors?.split(',') ?? [],
229230

src/graphql/schema/Area.gql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ type Area {
7979
authorMetadata: AuthorMetadata!
8080
"Organizations associated with this area or its parent areas"
8181
organizations: [Organization]
82+
83+
"""
84+
If you were to sum all approximate image sizes for this area, you get
85+
a kind of picture of what the cost to cache all of the media in a given
86+
area might be. You could use this to indicate a recommended compression
87+
ratio or maybe know ahead of time if a given cache exercise would exceed
88+
current storage capacity.
89+
"""
90+
imageByteSum: Int!
8291
}
8392

8493
"""

src/model/AreaDataSource.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,30 @@ export default class AreaDataSource extends MongoDataSource<AreaType> {
133133
return rs
134134
}
135135

136+
async computeImageByteSum (uuid: muuid.MUUID): Promise<number> {
137+
const descendantUuids = await this.areaModel.find({
138+
ancestors: { $regex: new RegExp(`(?:^|,)${uuid.toString()}(?:,|$)`) }
139+
}, { 'metadata.area_id': 1, climbs: 1 })
140+
141+
return await this.mediaObjectModal.aggregate([
142+
{
143+
$match: {
144+
'entityTags.targetId': {
145+
$in: [...descendantUuids.map(i => i.metadata.area_id),
146+
...descendantUuids.reduce((prev, curr) => [...prev, ...curr.climbs], [])
147+
]
148+
}
149+
}
150+
},
151+
{
152+
$group: {
153+
_id: null,
154+
totalSize: { $sum: '$size' }
155+
}
156+
}
157+
]).then(d => d[0]?.totalSize) ?? 0
158+
}
159+
136160
/**
137161
* Find a climb by uuid. Also return the parent area object (crag or boulder).
138162
*
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { getAreaModel, createIndexes } from "../../db"
2+
import inMemoryDB from "../../utils/inMemoryDB"
3+
import MutableAreaDataSource from "../MutableAreaDataSource"
4+
import muid, { MUUID } from 'uuid-mongodb'
5+
import { AreaType } from "../../db/AreaTypes"
6+
import AreaDataSource from "../AreaDataSource"
7+
import MutableMediaDataSource from "../MutableMediaDataSource"
8+
import { MediaObjectGQLInput } from "../../db/MediaObjectTypes"
9+
import MutableClimbDataSource from "../MutableClimbDataSource"
10+
import muuid from 'uuid-mongodb'
11+
12+
13+
function mediaInput(val: number) {
14+
return {
15+
userUuid: 'a2eb6353-65d1-445f-912c-53c6301404bd',
16+
mediaUrl: `/u/a2eb6353-65d1-445f-912c-53c6301404bd/photo${val}.jpg`,
17+
width: 800,
18+
height: 600,
19+
format: 'jpeg',
20+
size: 45000 + Math.floor(Math.random() * 100)
21+
} satisfies MediaObjectGQLInput}
22+
23+
describe("Test area data source", () => {
24+
let areas: AreaDataSource
25+
let rootCountry: AreaType
26+
let areaCounter = 0
27+
const testUser = muid.v4()
28+
29+
async function addArea(name?: string, extra?: Partial<{ leaf: boolean, boulder: boolean, parent: MUUID | AreaType}>) {
30+
function isArea(x: any): x is AreaType {
31+
return typeof x.metadata?.area_id !== 'undefined'
32+
}
33+
34+
areaCounter += 1
35+
if (name === undefined || name === 'test') {
36+
name = process.uptime().toString() + '-' + areaCounter.toString()
37+
}
38+
39+
let parent: MUUID | undefined = undefined
40+
if (extra?.parent) {
41+
if (isArea(extra.parent)) {
42+
parent = extra.parent.metadata?.area_id
43+
} else {
44+
parent = extra.parent
45+
}
46+
}
47+
48+
return MutableAreaDataSource.getInstance().addArea(
49+
testUser,
50+
name,
51+
parent ?? rootCountry.metadata.area_id,
52+
undefined,
53+
undefined,
54+
extra?.leaf,
55+
extra?.boulder
56+
)
57+
}
58+
59+
beforeAll(async () => {
60+
await inMemoryDB.connect()
61+
await getAreaModel().collection.drop()
62+
await createIndexes()
63+
areas = MutableAreaDataSource.getInstance()
64+
// We need a root country, and it is beyond the scope of these tests
65+
rootCountry = await MutableAreaDataSource.getInstance().addCountry("USA")
66+
})
67+
68+
afterAll(inMemoryDB.close)
69+
70+
describe("Image size summing", () => {
71+
test("Area image size summing should not produce false counts", async () => {
72+
const area = await addArea()
73+
const val = await areas.computeImageByteSum(area.metadata.area_id)
74+
expect(val).toBe(0)
75+
})
76+
77+
test("Area image size summing should work for direct tags", async () => {
78+
const area = await addArea()
79+
const media = MutableMediaDataSource.getInstance()
80+
const [object] = await media.addMediaObjects([mediaInput(0)])
81+
media.upsertEntityTag({ entityType: 1, entityUuid: area.metadata.area_id, mediaId: object._id })
82+
const val = await areas.computeImageByteSum(area.metadata.area_id)
83+
expect(val).toBe(object.size)
84+
})
85+
86+
test("Area image size summing should work for direct tags to children", async () => {
87+
const media = MutableMediaDataSource.getInstance()
88+
const area = await addArea()
89+
let child = area
90+
let sizeAccumulator = 0
91+
for (const idx of Array.from({ length: 10}).map((_, idx) => idx)) {
92+
child = await addArea(undefined, { parent: child.metadata.area_id})
93+
const [object] = await media.addMediaObjects([mediaInput((idx + 1) * 10)])
94+
media.upsertEntityTag({ entityType: 1, entityUuid: child.metadata.area_id, mediaId: object._id })
95+
96+
// We always query the top level
97+
expect(await areas.computeImageByteSum(area.metadata.area_id).then(d => {
98+
sizeAccumulator += d
99+
return sizeAccumulator
100+
})).toBe(sizeAccumulator)
101+
// equally, we expect the child to not get reverse-polluted
102+
expect(await areas.computeImageByteSum(child.metadata.area_id)).toBe(object.size)
103+
}
104+
})
105+
106+
test("Area image size summing should work for direct tags to climbs", async () => {
107+
const area = await addArea()
108+
const media = MutableMediaDataSource.getInstance()
109+
const climbs = MutableClimbDataSource.getInstance()
110+
const child = await addArea(undefined, { parent: area.metadata.area_id})
111+
expect(await areas.computeImageByteSum(area.metadata.area_id)).toBe(0)
112+
expect(await areas.computeImageByteSum(child.metadata.area_id)).toBe(0)
113+
114+
const [object] = await media.addMediaObjects([mediaInput(2 * 100)])
115+
const [climb] = await climbs.addOrUpdateClimbs(object.userUuid, child.metadata.area_id, [{
116+
name: "climb",
117+
grade: "6c+"
118+
}])
119+
120+
media.upsertEntityTag({ entityType: 0, entityUuid: muuid.from(climb), mediaId: object._id })
121+
expect(await areas.computeImageByteSum(area.metadata.area_id)).toBe(object.size)
122+
})
123+
})
124+
})

0 commit comments

Comments
 (0)