From b2ccc1b61e52bf54f2e7d0c753d821743ea187f0 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Mon, 9 Dec 2019 22:45:47 +0100 Subject: [PATCH 1/7] Better debugging --- .../src/schema/resolvers/helpers/Resolver.js | 83 +++++++++++------- .../resolvers/helpers/databaseLogger.js | 15 ++++ backend/src/schema/resolvers/reports.js | 86 ++++++++++--------- 3 files changed, 113 insertions(+), 71 deletions(-) create mode 100644 backend/src/schema/resolvers/helpers/databaseLogger.js diff --git a/backend/src/schema/resolvers/helpers/Resolver.js b/backend/src/schema/resolvers/helpers/Resolver.js index b094231c0..64ba60f5e 100644 --- a/backend/src/schema/resolvers/helpers/Resolver.js +++ b/backend/src/schema/resolvers/helpers/Resolver.js @@ -1,9 +1,9 @@ -import { getNeode } from '../../../bootstrap/neo4j' +import log from './databaseLogger' export const undefinedToNullResolver = list => { const resolvers = {} list.forEach(key => { - resolvers[key] = async (parent, params, context, resolveInfo) => { + resolvers[key] = async parent => { return typeof parent[key] === 'undefined' ? null : parent[key] } }) @@ -11,7 +11,6 @@ export const undefinedToNullResolver = list => { } export default function Resolver(type, options = {}) { - const instance = getNeode() const { idAttribute = 'id', undefinedToNull = [], @@ -22,32 +21,49 @@ export default function Resolver(type, options = {}) { } = options const _hasResolver = (resolvers, { key, connection }, { returnType }) => { - return async (parent, params, context, resolveInfo) => { + return async (parent, params, { driver, cypherParams }, resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] const id = parent[idAttribute] - const statement = `MATCH(:${type} {${idAttribute}: {id}})${connection} RETURN related` - const result = await instance.cypher(statement, { id }) - let response = result.records.map(r => r.get('related').properties) - if (returnType === 'object') response = response[0] || null - return response + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const cypher = ` + MATCH(:${type} {${idAttribute}: $id})${connection} + RETURN related {.*} as related + ` + const result = await txc.run(cypher, { id, cypherParams }) + log(result) + return result.records.map(r => r.get('related')) + }) + try { + let response = await readTxResultPromise + if (returnType === 'object') response = response[0] || null + return response + } finally { + session.close() + } } } const booleanResolver = obj => { const resolvers = {} for (const [key, condition] of Object.entries(obj)) { - resolvers[key] = async (parent, params, { cypherParams }, resolveInfo) => { + resolvers[key] = async (parent, params, { cypherParams, driver }, resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] - const result = await instance.cypher( - ` - ${condition.replace('this', 'this {id: $parent.id}')} as ${key}`, - { - parent, - cypherParams, - }, - ) - const [record] = result.records - return record.get(key) + const id = parent[idAttribute] + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const nodeCondition = condition.replace('this', 'this {id: $id}') + const cypher = `${nodeCondition} as ${key}` + const result = await txc.run(cypher, { id, cypherParams }) + log(result) + const [response] = result.records.map(r => r.get(key)) + return response + }) + try { + return await readTxResultPromise + } finally { + session.close() + } } } return resolvers @@ -56,16 +72,25 @@ export default function Resolver(type, options = {}) { const countResolver = obj => { const resolvers = {} for (const [key, connection] of Object.entries(obj)) { - resolvers[key] = async (parent, params, context, resolveInfo) => { + resolvers[key] = async (parent, params, { driver, cypherParams }, resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] - const id = parent[idAttribute] - const statement = ` - MATCH(u:${type} {${idAttribute}: {id}})${connection} - RETURN COUNT(DISTINCT(related)) as count - ` - const result = await instance.cypher(statement, { id }) - const [response] = result.records.map(r => r.get('count').toNumber()) - return response + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const id = parent[idAttribute] + const cypher = ` + MATCH(u:${type} {${idAttribute}: $id})${connection} + RETURN COUNT(DISTINCT(related)) as count + ` + const result = await txc.run(cypher, { id, cypherParams }) + log(result) + const [response] = result.records.map(r => r.get('count').toNumber()) + return response + }) + try { + return await readTxResultPromise + } finally { + session.close() + } } } return resolvers diff --git a/backend/src/schema/resolvers/helpers/databaseLogger.js b/backend/src/schema/resolvers/helpers/databaseLogger.js new file mode 100644 index 000000000..1e97b4d72 --- /dev/null +++ b/backend/src/schema/resolvers/helpers/databaseLogger.js @@ -0,0 +1,15 @@ +import Debug from 'debug' +const debugCypher = Debug('human-connection:neo4j:cypher') +const debugStats = Debug('human-connection:neo4j:stats') + +export default function log(response) { + const { statement, counters, resultConsumedAfter, resultAvailableAfter } = response.summary + const { text, parameters } = statement + debugCypher('%s', text) + debugCypher('%o', parameters) + debugStats('%o', counters) + debugStats('%o', { + resultConsumedAfter: resultConsumedAfter.toNumber(), + resultAvailableAfter: resultAvailableAfter.toNumber(), + }) +} diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js index a1d98bb41..7e1218ba5 100644 --- a/backend/src/schema/resolvers/reports.js +++ b/backend/src/schema/resolvers/reports.js @@ -1,3 +1,5 @@ +import log from './helpers/databaseLogger' + const transformReturnType = record => { return { ...record.get('report').properties, @@ -16,8 +18,7 @@ export default { const { driver, user } = context const session = driver.session() const reportWriteTxResultPromise = session.writeTransaction(async txc => { - const reportTransactionResponse = await txc.run( - ` + const cypher = ` MATCH (submitter:User {id: $submitterId}) MATCH (resource {id: $resourceId}) WHERE resource:User OR resource:Post OR resource:Comment @@ -27,15 +28,16 @@ export default { CREATE (report)<-[filed:FILED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter) RETURN report, resource, labels(resource)[0] AS type - `, - { - resourceId, - submitterId: user.id, - createdAt: new Date().toISOString(), - reasonCategory, - reasonDescription, - }, - ) + ` + const params = { + resourceId, + submitterId: user.id, + createdAt: new Date().toISOString(), + reasonCategory, + reasonDescription, + } + const reportTransactionResponse = await txc.run(cypher, params) + log(reportTransactionResponse) return reportTransactionResponse.records.map(transformReturnType) }) try { @@ -82,24 +84,24 @@ export default { const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : '' const reportReadTxPromise = session.readTransaction(async tx => { - const allReportsTransactionResponse = await tx.run( - ` - MATCH (report:Report)-[:BELONGS_TO]->(resource) - WHERE (resource:User OR resource:Post OR resource:Comment) - ${filterClause} - WITH report, resource, - [(submitter:User)-[filed:FILED]->(report) | filed {.*, submitter: properties(submitter)} ] as filed, - [(moderator:User)-[reviewed:REVIEWED]->(report) | reviewed {.*, moderator: properties(moderator)} ] as reviewed, - [(resource)<-[:WROTE]-(author:User) | author {.*} ] as optionalAuthors, - [(resource)-[:COMMENTS]->(post:Post) | post {.*} ] as optionalCommentedPosts, - resource {.*, __typename: labels(resource)[0] } as resourceWithType - WITH report, optionalAuthors, optionalCommentedPosts, reviewed, filed, - resourceWithType {.*, post: optionalCommentedPosts[0], author: optionalAuthors[0] } as finalResource - RETURN report {.*, resource: finalResource, filed: filed, reviewed: reviewed } - ${orderByClause} - ${offset} ${limit} - `, - ) + const cypher = ` + MATCH (report:Report)-[:BELONGS_TO]->(resource) + WHERE (resource:User OR resource:Post OR resource:Comment) + ${filterClause} + WITH report, resource, + [(submitter:User)-[filed:FILED]->(report) | filed {.*, submitter: properties(submitter)} ] as filed, + [(moderator:User)-[reviewed:REVIEWED]->(report) | reviewed {.*, moderator: properties(moderator)} ] as reviewed, + [(resource)<-[:WROTE]-(author:User) | author {.*} ] as optionalAuthors, + [(resource)-[:COMMENTS]->(post:Post) | post {.*} ] as optionalCommentedPosts, + resource {.*, __typename: labels(resource)[0] } as resourceWithType + WITH report, optionalAuthors, optionalCommentedPosts, reviewed, filed, + resourceWithType {.*, post: optionalCommentedPosts[0], author: optionalAuthors[0] } as finalResource + RETURN report {.*, resource: finalResource, filed: filed, reviewed: reviewed } + ${orderByClause} + ${offset} ${limit} + ` + const allReportsTransactionResponse = await tx.run(cypher) + log(allReportsTransactionResponse) return allReportsTransactionResponse.records.map(record => record.get('report')) }) try { @@ -119,13 +121,13 @@ export default { const { id } = parent let filed const readTxPromise = session.readTransaction(async tx => { - const allReportsTransactionResponse = await tx.run( - ` + const cypher = ` MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id}) RETURN filed, submitter - `, - { id }, - ) + ` + const params = { id } + const allReportsTransactionResponse = await tx.run(cypher, params) + log(allReportsTransactionResponse) return allReportsTransactionResponse.records.map(record => ({ submitter: record.get('submitter').properties, filed: record.get('filed').properties, @@ -153,14 +155,14 @@ export default { const { id } = parent let reviewed const readTxPromise = session.readTransaction(async tx => { - const allReportsTransactionResponse = await tx.run( - ` - MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User) - RETURN moderator, review - ORDER BY report.updatedAt DESC, review.updatedAt DESC - `, - { id }, - ) + const cypher = ` + MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User) + RETURN moderator, review + ORDER BY report.updatedAt DESC, review.updatedAt DESC + ` + const params = { id } + const allReportsTransactionResponse = await tx.run(cypher, params) + log(allReportsTransactionResponse) return allReportsTransactionResponse.records.map(record => ({ review: record.get('review').properties, moderator: record.get('moderator').properties, From 3beef5e3fbd5bd99129dc5d36a74175bc735a4c9 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Tue, 10 Dec 2019 01:28:12 +0100 Subject: [PATCH 2/7] Reduce database statements in notifications --- backend/src/schema/resolvers/notifications.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 7f9c52e1e..185ff91e0 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -1,3 +1,5 @@ +import log from './helpers/databaseLogger' + const resourceTypes = ['Post', 'Comment'] const transformReturnType = record => { @@ -45,13 +47,19 @@ export default { const cypher = ` MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) ${whereClause} - RETURN resource, notification, user + WITH user, notification, resource, + [(resource)<-[:WROTE]-(author:User) | author {.*}] as authors, + [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts + WITH resource, user, notification, authors, posts, + resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} as finalResource + RETURN notification {.*, from: finalResource, to: properties(user)} ${orderByClause} ${offset} ${limit} ` try { const result = await session.run(cypher, { id: currentUser.id }) - return result.records.map(transformReturnType) + log(result) + return result.records.map(r => r.get('notification')) } finally { session.close() } @@ -68,6 +76,7 @@ export default { RETURN resource, notification, user ` const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id }) + log(result) const notifications = await result.records.map(transformReturnType) return notifications[0] } finally { From deefc4e12f0db4cbedd5f3c8186711ff2fc11600 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Tue, 10 Dec 2019 01:40:41 +0100 Subject: [PATCH 3/7] Improve notification query performance Just don't ask for stuff that you don't want to show anyways --- webapp/graphql/Fragments.js | 14 ++++++++++---- webapp/graphql/PostQuery.js | 6 +++++- webapp/graphql/User.js | 4 +++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/webapp/graphql/Fragments.js b/webapp/graphql/Fragments.js index 495f15094..6da4dec9a 100644 --- a/webapp/graphql/Fragments.js +++ b/webapp/graphql/Fragments.js @@ -1,5 +1,15 @@ import gql from 'graphql-tag' +export const linkableUserFragment = lang => gql` + fragment user on User { + id + slug + name + avatar + disabled + deleted + } +` export const userFragment = lang => gql` fragment user on User { id @@ -32,8 +42,6 @@ export const postCountsFragment = gql` } ` export const postFragment = lang => gql` - ${userFragment(lang)} - fragment post on Post { id title @@ -68,8 +76,6 @@ export const postFragment = lang => gql` } ` export const commentFragment = lang => gql` - ${userFragment(lang)} - fragment comment on Comment { id createdAt diff --git a/webapp/graphql/PostQuery.js b/webapp/graphql/PostQuery.js index 3de1178b0..b6b4c2b6f 100644 --- a/webapp/graphql/PostQuery.js +++ b/webapp/graphql/PostQuery.js @@ -1,9 +1,10 @@ import gql from 'graphql-tag' -import { postFragment, commentFragment, postCountsFragment } from './Fragments' +import { userFragment, postFragment, commentFragment, postCountsFragment } from './Fragments' export default i18n => { const lang = i18n.locale().toUpperCase() return gql` + ${userFragment(lang)} ${postFragment(lang)} ${postCountsFragment} ${commentFragment(lang)} @@ -23,6 +24,7 @@ export default i18n => { export const filterPosts = i18n => { const lang = i18n.locale().toUpperCase() return gql` + ${userFragment(lang)} ${postFragment(lang)} ${postCountsFragment} @@ -38,6 +40,7 @@ export const filterPosts = i18n => { export const profilePagePosts = i18n => { const lang = i18n.locale().toUpperCase() return gql` + ${userFragment(lang)} ${postFragment(lang)} ${postCountsFragment} @@ -66,6 +69,7 @@ export const PostsEmotionsByCurrentUser = () => { export const relatedContributions = i18n => { const lang = i18n.locale().toUpperCase() return gql` + ${userFragment(lang)} ${postFragment(lang)} ${postCountsFragment} diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index 4ed252261..b5281b641 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -1,5 +1,5 @@ import gql from 'graphql-tag' -import { userFragment, postFragment, commentFragment } from './Fragments' +import { linkableUserFragment, userFragment, postFragment, commentFragment } from './Fragments' export default i18n => { const lang = i18n.locale().toUpperCase() @@ -49,6 +49,7 @@ export const minimisedUserQuery = () => { export const notificationQuery = i18n => { const lang = i18n.locale().toUpperCase() return gql` + ${linkableUserFragment()} ${commentFragment(lang)} ${postFragment(lang)} @@ -78,6 +79,7 @@ export const notificationQuery = i18n => { export const markAsReadMutation = i18n => { const lang = i18n.locale().toUpperCase() return gql` + ${linkableUserFragment()} ${commentFragment(lang)} ${postFragment(lang)} From 12236c9324a75fdfffc814601ca740c1a4b9e2b6 Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Tue, 10 Dec 2019 16:35:27 +0100 Subject: [PATCH 4/7] Convert to transaction function/refactor - update incorrect variables --- backend/src/schema/resolvers/notifications.js | 37 ++--- .../schema/resolvers/notifications.spec.js | 9 +- backend/src/schema/resolvers/reports.js | 127 +++++++++--------- 3 files changed, 93 insertions(+), 80 deletions(-) diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 185ff91e0..eca12900d 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -44,22 +44,29 @@ export default { } const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : '' const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : '' - const cypher = ` - MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) - ${whereClause} - WITH user, notification, resource, - [(resource)<-[:WROTE]-(author:User) | author {.*}] as authors, - [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts - WITH resource, user, notification, authors, posts, - resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} as finalResource - RETURN notification {.*, from: finalResource, to: properties(user)} - ${orderByClause} - ${offset} ${limit} - ` + + const readTxResultPromise = session.readTransaction(async transaction => { + const notificationsTransactionResponse = await transaction.run( + ` + MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) + ${whereClause} + WITH user, notification, resource, + [(resource)<-[:WROTE]-(author:User) | author {.*}] as authors, + [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts + WITH resource, user, notification, authors, posts, + resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} as finalResource + RETURN notification {.*, from: finalResource, to: properties(user)} + ${orderByClause} + ${offset} ${limit} + `, + { id: currentUser.id }, + ) + log(notificationsTransactionResponse) + return notificationsTransactionResponse.records.map(record => record.get('notification')) + }) try { - const result = await session.run(cypher, { id: currentUser.id }) - log(result) - return result.records.map(r => r.get('notification')) + const notifications = await readTxResultPromise + return notifications } finally { session.close() } diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index 24b8280bd..89bbd2528 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -184,6 +184,7 @@ describe('given some notifications', () => { data: { notifications: expect.arrayContaining(expected), }, + errors: undefined, }) }) }) @@ -233,7 +234,10 @@ describe('given some notifications', () => { ` await expect( mutate({ mutation: deletePostMutation, variables: { id: 'p3' } }), - ).resolves.toMatchObject({ data: { DeletePost: { id: 'p3', deleted: true } } }) + ).resolves.toMatchObject({ + data: { DeletePost: { id: 'p3', deleted: true } }, + errors: undefined, + }) authenticatedUser = await user.toJson() } @@ -242,11 +246,12 @@ describe('given some notifications', () => { query({ query: notificationQuery, variables: { ...variables, read: false } }), ).resolves.toMatchObject({ data: { notifications: [expect.any(Object), expect.any(Object)] }, + errors: undefined, }) await deletePostAction() await expect( query({ query: notificationQuery, variables: { ...variables, read: false } }), - ).resolves.toMatchObject({ data: { notifications: [] } }) + ).resolves.toMatchObject({ data: { notifications: [] }, errors: undefined }) }) }) }) diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js index 7e1218ba5..f72697af0 100644 --- a/backend/src/schema/resolvers/reports.js +++ b/backend/src/schema/resolvers/reports.js @@ -13,12 +13,12 @@ const transformReturnType = record => { export default { Mutation: { fileReport: async (_parent, params, context, _resolveInfo) => { - let createdRelationshipWithNestedAttributes const { resourceId, reasonCategory, reasonDescription } = params const { driver, user } = context const session = driver.session() - const reportWriteTxResultPromise = session.writeTransaction(async txc => { - const cypher = ` + const reportWriteTxResultPromise = session.writeTransaction(async transaction => { + const reportTransactionResponse = await transaction.run( + ` MATCH (submitter:User {id: $submitterId}) MATCH (resource {id: $resourceId}) WHERE resource:User OR resource:Post OR resource:Comment @@ -28,33 +28,32 @@ export default { CREATE (report)<-[filed:FILED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter) RETURN report, resource, labels(resource)[0] AS type - ` - const params = { - resourceId, - submitterId: user.id, - createdAt: new Date().toISOString(), - reasonCategory, - reasonDescription, - } - const reportTransactionResponse = await txc.run(cypher, params) + `, + { + resourceId, + submitterId: user.id, + createdAt: new Date().toISOString(), + reasonCategory, + reasonDescription, + }, + ) log(reportTransactionResponse) return reportTransactionResponse.records.map(transformReturnType) }) try { - const txResult = await reportWriteTxResultPromise - if (!txResult[0]) return null - createdRelationshipWithNestedAttributes = txResult[0] + const [createdRelationshipWithNestedAttributes] = await reportWriteTxResultPromise + if (!createdRelationshipWithNestedAttributes) return null + return createdRelationshipWithNestedAttributes } finally { session.close() } - return createdRelationshipWithNestedAttributes }, }, Query: { reports: async (_parent, params, context, _resolveInfo) => { const { driver } = context const session = driver.session() - let reports, orderByClause, filterClause + let orderByClause, filterClause switch (params.orderBy) { case 'createdAt_asc': orderByClause = 'ORDER BY report.createdAt ASC' @@ -83,35 +82,35 @@ export default { params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : '' const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : '' - const reportReadTxPromise = session.readTransaction(async tx => { - const cypher = ` - MATCH (report:Report)-[:BELONGS_TO]->(resource) - WHERE (resource:User OR resource:Post OR resource:Comment) - ${filterClause} - WITH report, resource, - [(submitter:User)-[filed:FILED]->(report) | filed {.*, submitter: properties(submitter)} ] as filed, - [(moderator:User)-[reviewed:REVIEWED]->(report) | reviewed {.*, moderator: properties(moderator)} ] as reviewed, - [(resource)<-[:WROTE]-(author:User) | author {.*} ] as optionalAuthors, - [(resource)-[:COMMENTS]->(post:Post) | post {.*} ] as optionalCommentedPosts, - resource {.*, __typename: labels(resource)[0] } as resourceWithType - WITH report, optionalAuthors, optionalCommentedPosts, reviewed, filed, - resourceWithType {.*, post: optionalCommentedPosts[0], author: optionalAuthors[0] } as finalResource - RETURN report {.*, resource: finalResource, filed: filed, reviewed: reviewed } - ${orderByClause} - ${offset} ${limit} - ` - const allReportsTransactionResponse = await tx.run(cypher) + const reportReadTxPromise = session.readTransaction(async transaction => { + const allReportsTransactionResponse = await transaction.run( + ` + MATCH (report:Report)-[:BELONGS_TO]->(resource) + WHERE (resource:User OR resource:Post OR resource:Comment) + ${filterClause} + WITH report, resource, + [(submitter:User)-[filed:FILED]->(report) | filed {.*, submitter: properties(submitter)} ] as filed, + [(moderator:User)-[reviewed:REVIEWED]->(report) | reviewed {.*, moderator: properties(moderator)} ] as reviewed, + [(resource)<-[:WROTE]-(author:User) | author {.*} ] as optionalAuthors, + [(resource)-[:COMMENTS]->(post:Post) | post {.*} ] as optionalCommentedPosts, + resource {.*, __typename: labels(resource)[0] } as resourceWithType + WITH report, optionalAuthors, optionalCommentedPosts, reviewed, filed, + resourceWithType {.*, post: optionalCommentedPosts[0], author: optionalAuthors[0] } as finalResource + RETURN report {.*, resource: finalResource, filed: filed, reviewed: reviewed } + ${orderByClause} + ${offset} ${limit} + `, + ) log(allReportsTransactionResponse) return allReportsTransactionResponse.records.map(record => record.get('report')) }) try { - const txResult = await reportReadTxPromise - if (!txResult[0]) return null - reports = txResult + const reports = await reportReadTxPromise + if (!reports) return [] + return reports } finally { session.close() } - return reports }, }, Report: { @@ -120,23 +119,24 @@ export default { const session = context.driver.session() const { id } = parent let filed - const readTxPromise = session.readTransaction(async tx => { - const cypher = ` - MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id}) - RETURN filed, submitter - ` - const params = { id } - const allReportsTransactionResponse = await tx.run(cypher, params) - log(allReportsTransactionResponse) - return allReportsTransactionResponse.records.map(record => ({ + const readTxPromise = session.readTransaction(async transaction => { + const filedReportsTransactionResponse = await transaction.run( + ` + MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id}) + RETURN filed, submitter + `, + { id }, + ) + log(filedReportsTransactionResponse) + return filedReportsTransactionResponse.records.map(record => ({ submitter: record.get('submitter').properties, filed: record.get('filed').properties, })) }) try { - const txResult = await readTxPromise - if (!txResult[0]) return null - filed = txResult.map(reportedRecord => { + const filedReports = await readTxPromise + if (!filedReports) return [] + filed = filedReports.map(reportedRecord => { const { submitter, filed } = reportedRecord const relationshipWithNestedAttributes = { ...filed, @@ -154,23 +154,24 @@ export default { const session = context.driver.session() const { id } = parent let reviewed - const readTxPromise = session.readTransaction(async tx => { - const cypher = ` - MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User) - RETURN moderator, review - ORDER BY report.updatedAt DESC, review.updatedAt DESC - ` - const params = { id } - const allReportsTransactionResponse = await tx.run(cypher, params) - log(allReportsTransactionResponse) - return allReportsTransactionResponse.records.map(record => ({ + const readTxPromise = session.readTransaction(async transaction => { + const reviewedReportsTransactionResponse = await transaction.run( + ` + MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User) + RETURN moderator, review + ORDER BY report.updatedAt DESC, review.updatedAt DESC + `, + { id }, + ) + log(reviewedReportsTransactionResponse) + return reviewedReportsTransactionResponse.records.map(record => ({ review: record.get('review').properties, moderator: record.get('moderator').properties, })) }) try { - const txResult = await readTxPromise - reviewed = txResult.map(reportedRecord => { + const reviewedReports = await readTxPromise + reviewed = reviewedReports.map(reportedRecord => { const { review, moderator } = reportedRecord const relationshipWithNestedAttributes = { ...review, From 0a50a02f88095bffdd065e6ac1b4955dc4cd03bc Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Tue, 10 Dec 2019 17:00:29 +0100 Subject: [PATCH 5/7] Follow @roschaefer suggestion/remove guard clauses --- backend/src/schema/resolvers/reports.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js index f72697af0..0565c4d8a 100644 --- a/backend/src/schema/resolvers/reports.js +++ b/backend/src/schema/resolvers/reports.js @@ -106,7 +106,6 @@ export default { }) try { const reports = await reportReadTxPromise - if (!reports) return [] return reports } finally { session.close() @@ -135,7 +134,6 @@ export default { }) try { const filedReports = await readTxPromise - if (!filedReports) return [] filed = filedReports.map(reportedRecord => { const { submitter, filed } = reportedRecord const relationshipWithNestedAttributes = { From 4f0df2f28f184a8745daad18ece89cd23daa55c5 Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Tue, 10 Dec 2019 17:07:07 +0100 Subject: [PATCH 6/7] Remove disable from query request - we removed it, prevent null pointer error --- backend/src/schema/resolvers/reports.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/schema/resolvers/reports.spec.js b/backend/src/schema/resolvers/reports.spec.js index cd8b61985..5c7610164 100644 --- a/backend/src/schema/resolvers/reports.spec.js +++ b/backend/src/schema/resolvers/reports.spec.js @@ -489,7 +489,6 @@ describe('file a report on a resource', () => { id createdAt updatedAt - disable closed resource { __typename From cc7cd6b8b01e02b75f290ebe2ffcb2fd1322d1b8 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Tue, 10 Dec 2019 18:13:34 +0100 Subject: [PATCH 7/7] Fix backend tests --- backend/src/schema/resolvers/reports.spec.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/schema/resolvers/reports.spec.js b/backend/src/schema/resolvers/reports.spec.js index 5c7610164..8b1bb925d 100644 --- a/backend/src/schema/resolvers/reports.spec.js +++ b/backend/src/schema/resolvers/reports.spec.js @@ -21,7 +21,6 @@ describe('file a report on a resource', () => { id createdAt updatedAt - disable closed rule resource { @@ -623,7 +622,6 @@ describe('file a report on a resource', () => { id: expect.any(String), createdAt: expect.any(String), updatedAt: expect.any(String), - disable: false, closed: false, resource: { __typename: 'User', @@ -644,7 +642,6 @@ describe('file a report on a resource', () => { id: expect.any(String), createdAt: expect.any(String), updatedAt: expect.any(String), - disable: false, closed: false, resource: { __typename: 'Post', @@ -665,7 +662,6 @@ describe('file a report on a resource', () => { id: expect.any(String), createdAt: expect.any(String), updatedAt: expect.any(String), - disable: false, closed: false, resource: { __typename: 'Comment',