Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/__tests__/areas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { ApolloServer } from 'apollo-server'
import muuid from 'uuid-mongodb'
import { jest } from '@jest/globals'
import MutableAreaDataSource, { createInstance as createAreaInstance } from '../model/MutableAreaDataSource.js'
import { createInstance as createClimbInstance } from '../model/MutableClimbDataSource.js'
import MutableOrganizationDataSource, { createInstance as createOrgInstance } from '../model/MutableOrganizationDataSource.js'
import { AreaType } from '../db/AreaTypes.js'
import { ClimbChangeInputType } from '../db/ClimbTypes.js'
import { OrgType, OrganizationType, OrganizationEditableFieldsType } from '../db/OrganizationTypes.js'
import { queryAPI, setUpServer } from '../utils/testUtils.js'
import { muuidToString } from '../utils/helpers.js'
Expand All @@ -22,6 +24,7 @@ describe('areas API', () => {
let usa: AreaType
let ca: AreaType
let wa: AreaType
let ak: AreaType

beforeAll(async () => {
({ server, inMemoryDB } = await setUpServer())
Expand All @@ -38,13 +41,65 @@ describe('areas API', () => {
usa = await areas.addCountry('usa')
ca = await areas.addArea(user, 'CA', usa.metadata.area_id)
wa = await areas.addArea(user, 'WA', usa.metadata.area_id)
ak = await areas.addArea(user, 'AK', usa.metadata.area_id)
})

afterAll(async () => {
await server.stop()
await inMemoryDB.close()
})

describe('mutations', () => {
it('updates sorting order of subareas and queries returns them in order', async () => {
const updateSortingOrderQuery = `
mutation ($input: [AreaSortingInput]) {
updateAreasSortingOrder(input: $input)
}
`
const updateResponse = await queryAPI({
query: updateSortingOrderQuery,
variables: {
input: [
{ areaId: wa.metadata.area_id, leftRightIndex: 3 },
{ areaId: ca.metadata.area_id, leftRightIndex: 0 },
{ areaId: ak.metadata.area_id, leftRightIndex: 10 }
]
},
userUuid
})

expect(updateResponse.statusCode).toBe(200)
const sortingOrderResult = updateResponse.body.data.updateAreasSortingOrder
expect(sortingOrderResult).toHaveLength(3)

const areaChildrenQuery = `
query area($input: ID) {
area(uuid: $input) {
children {
uuid
metadata {
leftRightIndex
}
}
}
}
`

const areaChildrenResponse = await queryAPI({
query: areaChildrenQuery,
variables: { input: usa.metadata.area_id },
userUuid
})

expect(areaChildrenResponse.statusCode).toBe(200)
const areaResult = areaChildrenResponse.body.data.area
// In leftRightIndex order
expect(areaResult.children[0]).toMatchObject({ uuid: muuidToString(ca.metadata.area_id), metadata: { leftRightIndex: 0 } })
expect(areaResult.children[1]).toMatchObject({ uuid: muuidToString(wa.metadata.area_id), metadata: { leftRightIndex: 3 } })
expect(areaResult.children[2]).toMatchObject({ uuid: muuidToString(ak.metadata.area_id), metadata: { leftRightIndex: 10 } })
})
})

describe('queries', () => {
const areaQuery = `
query area($input: ID) {
Expand Down Expand Up @@ -101,5 +156,56 @@ describe('areas API', () => {
// ca and so should not be listed.
expect(areaResult.organizations).toHaveLength(0)
})

it('returns climbs in leftRightIndex order', async () => {
const climbs = createClimbInstance()
const leftRoute: ClimbChangeInputType = {
name: 'left',
disciplines: { sport: true },
description: 'Leftmost route on the wall',
leftRightIndex: 0
}
const middleRoute: ClimbChangeInputType = {
name: 'middle',
disciplines: { sport: true },
description: 'Middle route on the wall',
leftRightIndex: 1
}
const rightRoute: ClimbChangeInputType = {
name: 'right',
disciplines: { sport: true },
description: 'Rightmost route on the wall',
leftRightIndex: 2
}
await climbs.addOrUpdateClimbs(
user,
ca.metadata.area_id,
[middleRoute, leftRoute, rightRoute]
)

const areaClimbsQuery = `
query area($input: ID) {
area(uuid: $input) {
climbs {
name
metadata {
leftRightIndex
}
}
}
}
`
const areaClimbsResponse = await queryAPI({
query: areaClimbsQuery,
variables: { input: ca.metadata.area_id },
userUuid
})
expect(areaClimbsResponse.statusCode).toBe(200)
const areaResult = areaClimbsResponse.body.data.area
// In leftRightIndex order
expect(areaResult.climbs[0]).toMatchObject({ name: 'left', metadata: { leftRightIndex: 0 } })
expect(areaResult.climbs[1]).toMatchObject({ name: 'middle', metadata: { leftRightIndex: 1 } })
expect(areaResult.climbs[2]).toMatchObject({ name: 'right', metadata: { leftRightIndex: 2 } })
})
})
})
8 changes: 4 additions & 4 deletions src/graphql/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { OrganizationMutations, OrganizationQueries } from './organization/index
import { TickMutations, TickQueries } from './tick/index.js'
import { UserQueries, UserMutations, UserResolvers } from './user/index.js'
import { getAuthorMetadataFromBaseNode } from '../db/utils/index.js'
import { geojsonPointToLatitude, geojsonPointToLongitude } from '../utils/helpers.js'
import { compareAreaLeftRightIndex, compareClimbLeftRightIndex, geojsonPointToLatitude, geojsonPointToLongitude } from '../utils/helpers.js'

/**
* It takes a file name as an argument, reads the file, and returns a GraphQL DocumentNode.
Expand Down Expand Up @@ -202,7 +202,7 @@ const resolvers = {

children: async (parent: AreaType, _, { dataSources: { areas } }: Context) => {
if (parent.children.length > 0) {
return await areas.findManyByIds(parent.children)
return (await areas.findManyByIds(parent.children)).sort(compareAreaLeftRightIndex)
Copy link
Contributor

@vnugent vnugent May 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think if we let Mongo handle the sorting here and in findManyClimbsByUuids(). This way we don't have to write comparators.
Something like:

// inside AreaDataSource
 ...
 return await this.areaModel.find().
   where('metadata.leftRightIndex').
   in(parent.children).
   sort({ 'metadata.leftRightIndex': 1 }).lean()

edit: add lean()

}
return []
},
Expand All @@ -221,11 +221,11 @@ const resolvers = {
// Test to see if we have actual climb object returned from findOneAreaByUUID()
const isClimbTypeArray = (x: any[]): x is ClimbType[] => x[0].name != null
if (isClimbTypeArray(result)) {
return result
return result.sort(compareClimbLeftRightIndex)
}

// List of IDs, we need to convert them into actual climbs
return await areas.findManyClimbsByUuids(result as MUUID[])
return (await areas.findManyClimbsByUuids(result as MUUID[])).sort(compareClimbLeftRightIndex)
},

metadata: (node: AreaType) => {
Expand Down
4 changes: 2 additions & 2 deletions src/model/AreaDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getAreaModel, getMediaModel, getMediaObjectModel } from '../db/index.js
import { AreaType } from '../db/AreaTypes'
import { GQLFilter, AreaFilterParams, PathTokenParams, LeafStatusParams, ComparisonFilterParams, StatisticsType, CragsNear, BBoxType } from '../types'
import { getClimbModel } from '../db/ClimbSchema.js'
import { ClimbGQLQueryType } from '../db/ClimbTypes.js'
import { ClimbGQLQueryType, ClimbType } from '../db/ClimbTypes.js'
import { logger } from '../logger.js'

export default class AreaDataSource extends MongoDataSource<AreaType> {
Expand Down Expand Up @@ -117,7 +117,7 @@ export default class AreaDataSource extends MongoDataSource<AreaType> {
throw new Error(`Area ${uuid.toUUID().toString()} not found.`)
}

async findManyClimbsByUuids (uuidList: muuid.MUUID[]): Promise<any> {
async findManyClimbsByUuids (uuidList: muuid.MUUID[]): Promise<ClimbType[]> {
const rs = await this.climbModel.find().where('_id').in(uuidList)
return rs
}
Expand Down
16 changes: 16 additions & 0 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { MUUID } from 'uuid-mongodb'
import { Point } from '@turf/helpers'
import { AreaType } from '../db/AreaTypes'
import { ClimbType } from '../db/ClimbTypes'

export const muuidToString = (m: MUUID): string => m.toUUID().toString()

Expand All @@ -21,3 +23,17 @@ export function exhaustiveCheck (_value: never): never {

export const geojsonPointToLongitude = (point: Point): number => point.coordinates[0]
export const geojsonPointToLatitude = (point: Point): number => point.coordinates[1]

export function compareAreaLeftRightIndex (a: AreaType, b: AreaType): number {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered merging these two, but then I'd need to be able to narrow the type of a and b. And that's not very easy since these are custom types, not just strings or numbers. And so it gets a bit messy. I figure just splitting them out is a bit repetitive but a lot more readable.

if (a.metadata.leftRightIndex == null || b.metadata.leftRightIndex == null) {
return 0 // Preserve order if any element is missing leftRightIndex
}
return (a.metadata.leftRightIndex - b.metadata.leftRightIndex)
}

export function compareClimbLeftRightIndex (a: ClimbType, b: ClimbType): number {
if (a.metadata.left_right_index == null || b.metadata.left_right_index == null) {
return 0 // Preserve order if any element is missing left_right_index
}
return (a.metadata.left_right_index - b.metadata.left_right_index)
}