From b14dae68291b6e05c855149b4a1ada19ccb16653 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 22 Jul 2020 10:58:08 +0100 Subject: [PATCH] feat(gql): implemented resolvers & tests for stream commits & branch commits --- modules/core/graph/resolvers/branches.js | 7 +- modules/core/graph/resolvers/commits.js | 19 ++- .../graph/schemas/branchesAndCommits.graphql | 5 +- modules/core/migrations/000-core.js | 30 ++--- modules/core/services/commits.js | 6 +- modules/core/tests/graph.spec.js | 126 ++++++++++++++++-- 6 files changed, 152 insertions(+), 41 deletions(-) diff --git a/modules/core/graph/resolvers/branches.js b/modules/core/graph/resolvers/branches.js index 4455b56c3..a1dcdd1d3 100644 --- a/modules/core/graph/resolvers/branches.js +++ b/modules/core/graph/resolvers/branches.js @@ -10,7 +10,8 @@ const { updateCommit, deleteCommit, getCommitsByBranchId, - getCommitsByBranchName + getCommitsByBranchName, + getCommitsTotalCountByBranchId } = require( '../../services/commits' ) const { @@ -42,10 +43,6 @@ module.exports = { async author( parent, args, context, info ) { return await getUserById( { userId: parent.authorId } ) - }, - - async commits( parent, args, context, info ) { - throw new ApolloError( 'not implemented' ) } }, diff --git a/modules/core/graph/resolvers/commits.js b/modules/core/graph/resolvers/commits.js index b8441697e..3e17f15a6 100644 --- a/modules/core/graph/resolvers/commits.js +++ b/modules/core/graph/resolvers/commits.js @@ -13,7 +13,10 @@ const { getCommitsByBranchId, getCommitsByBranchName, getCommitsByUserId, - getCommitsTotalCountByUserId + getCommitsByStreamId, + getCommitsTotalCountByStreamId, + getCommitsTotalCountByUserId, + getCommitsTotalCountByBranchId } = require( '../../services/commits' ) const { @@ -28,6 +31,13 @@ module.exports = { Query: {}, Stream: { + async commits( parent, args, context, info ) { + let { commits: items, cursor } = await getCommitsByStreamId( { streamId: parent.id, limit: args.limit, cursor: args.cursor } ) + let totalCount = await getCommitsTotalCountByStreamId( { streamId: parent.id } ) + + return { items, cursor, totalCount } + }, + async commit( parent, args, context, info ) { throw new ApolloError( 'not implemented' ) } @@ -46,13 +56,12 @@ module.exports = { }, Branch: { - async commits( parent, args, context, info ) { + let { commits, cursor } = await getCommitsByBranchId( { branchId: parent.id, limit: args.limit, cursor: args.cursor } ) + let totalCount = await getCommitsTotalCountByBranchId( { branchId: parent.id } ) - throw new ApolloError( 'not implemented' ) - + return { items: commits, totalCount, cursor } } - }, Mutation: { diff --git a/modules/core/graph/schemas/branchesAndCommits.graphql b/modules/core/graph/schemas/branchesAndCommits.graphql index e8cd88b08..d549aa1fa 100644 --- a/modules/core/graph/schemas/branchesAndCommits.graphql +++ b/modules/core/graph/schemas/branchesAndCommits.graphql @@ -22,12 +22,13 @@ type Commit { referencedObject: String! realObject: Object message: String - author: String + authorName: String authorId: String + createdAt: String } type CommitCollectionUserNode { - commitId: String! + id: String! referencedObject: String! realObject: Object message: String diff --git a/modules/core/migrations/000-core.js b/modules/core/migrations/000-core.js index 13ba190c0..02e8fa29d 100644 --- a/modules/core/migrations/000-core.js +++ b/modules/core/migrations/000-core.js @@ -17,7 +17,7 @@ exports.up = async knex => { table.boolean( 'completed' ).defaultTo( false ) } ) - // Users. + // Users. await knex.schema.createTable( 'users', table => { table.string( 'id', 10 ).primary( ) table.string( 'username', 20 ).unique( ).notNullable( ) @@ -34,8 +34,8 @@ exports.up = async knex => { // Roles. // Roles keep track of their name and the target resource they are applied to. - // The target resource must be a table name. - // The heigher the weight, the bigger the permissions. + // The target resource must be a table name. + // The heigher the weight, the bigger the permissions. await knex.schema.createTable( 'user_roles', table => { table.string( 'name' ).primary( ) table.text( 'description' ).notNullable( ) @@ -44,7 +44,7 @@ exports.up = async knex => { table.integer( 'weight' ).defaultTo( 100 ).notNullable( ) } ) - // Server-wide access control list. + // Server-wide access control list. await knex.schema.createTable( 'server_acl', table => { table.string( 'userId', 10 ).references( 'id' ).inTable( 'users' ).primary( ).onDelete( 'cascade' ) table.string( 'role' ).references( 'name' ).inTable( 'user_roles' ).notNullable( ).onDelete( 'cascade' ) @@ -70,13 +70,13 @@ exports.up = async knex => { } ) // Registered application scopes table. - // Scopes limit what a token can actually do. + // Scopes limit what a token can actually do. await knex.schema.createTable( 'scopes', table => { table.string( 'name' ).primary( ) table.text( 'description' ).notNullable( ) } ) - // Token >- -< Scopes junction table. + // Token >- -< Scopes junction table. await knex.schema.createTable( 'token_scopes', table => { table.string( 'tokenId' ).references( 'id' ).inTable( 'api_tokens' ).notNullable( ).onDelete( 'cascade' ).index( ) table.string( 'scopeName' ).references( 'name' ).inTable( 'scopes' ).notNullable( ).onDelete( 'cascade' ).index( ) @@ -104,7 +104,7 @@ exports.up = async knex => { table.unique( [ 'userId', 'resourceId' ] ) } ) - // Objects Table. + // Objects Table. // id - the object's *hash* // totalChildrenCount - how many subchildren, regardless of depth, this object has // totalChildrenCountByDepth - how many subchildren does this object have at a specific nesting depth. @@ -119,11 +119,11 @@ exports.up = async knex => { table.jsonb( 'data' ) } ) - // Closure table for tracking the nesting relationships of objects. + // Closure table for tracking the nesting relationships of objects. // Note: the usecase optimised for is that when we request an object, we either: // a) interactively request/query for its subchildren (sequentially) // or - // b) we want all of it! + // b) we want all of it! await knex.schema.createTable( 'object_children_closure', table => { table.string( 'parent' ).notNullable( ).index( ) table.string( 'child' ).notNullable( ).index( ) @@ -132,8 +132,8 @@ exports.up = async knex => { table.index( [ 'parent', 'minDepth' ], 'full_pcd_index' ) } ) - // Commit table. - // Any object can be "blessed" as a commit. + // Commit table. + // Any object can be "blessed" as a commit. await knex.schema.createTable( 'commits', table => { table.string( 'id', 10 ).primary( ) table.string( 'referencedObject' ).references( 'id' ).inTable( 'objects' ).notNullable( ) @@ -142,7 +142,7 @@ exports.up = async knex => { table.timestamp( 'createdAt' ).defaultTo( knex.fn.now( ) ) } ) - // Commit inheritance table. + // Commit inheritance table. // Tracks the inheritance of commits. A commit may have: // - one ancestor (simple sequential push) // - more ancestors (result of a merge) @@ -152,7 +152,7 @@ exports.up = async knex => { table.unique( [ 'parent', 'child' ], 'commit_parent_child_index' ) } ) - // Branches table. + // Branches table. // A branch is a end-user scope-bound collection of commits. await knex.schema.createTable( 'branches', table => { table.string( 'id', 10 ).primary( ) @@ -165,7 +165,7 @@ exports.up = async knex => { table.unique( [ 'streamId', 'name' ] ) } ) - // Junction Table Branches >- -< Commits + // Junction Table Branches >- -< Commits await knex.schema.createTable( 'branch_commits', table => { table.string( 'branchId', 10 ).references( 'id' ).inTable( 'branches' ).notNullable( ).onDelete( 'cascade' ) table.string( 'commitId' ).references( 'id' ).inTable( 'commits' ).notNullable( ).onDelete( 'cascade' ) @@ -206,4 +206,4 @@ exports.down = async knex => { await knex.raw( `DROP TYPE IF EXISTS speckle_reference_type ` ) await knex.raw( `DROP TYPE IF EXISTS speckle_acl_role_type ` ) -} \ No newline at end of file +} diff --git a/modules/core/services/commits.js b/modules/core/services/commits.js index 58fb42bc1..1c4acb0a5 100644 --- a/modules/core/services/commits.js +++ b/modules/core/services/commits.js @@ -85,7 +85,7 @@ module.exports = { async getCommitsByBranchId( { branchId, limit, cursor } ) { limit = limit || 20 - let query = BranchCommits( ).columns( [ 'commitId', 'message', 'referencedObject', { author: 'name' }, { authorId: 'users.id' }, 'commits.createdAt' ] ).select( ) + let query = BranchCommits( ).columns( [ { id: 'commitId' }, 'message', 'referencedObject', { authorName: 'name' }, { authorId: 'users.id' }, 'commits.createdAt' ] ).select( ) .join( 'commits', 'commits.id', 'branch_commits.commitId' ) .join( 'users', 'commits.author', 'users.id' ) .where( 'branchId', branchId ) @@ -126,7 +126,7 @@ module.exports = { async getCommitsByStreamId( { streamId, limit, cursor } ) { limit = limit || 20 let query = StreamCommits( ) - .columns( [ 'commitId', 'message', 'referencedObject', { author: 'name' }, { authorId: 'users.id' }, 'commits.createdAt' ] ).select( ) + .columns( [ { id: 'commitId' }, 'message', 'referencedObject', { authorName: 'name' }, { authorId: 'users.id' }, 'commits.createdAt' ] ).select( ) .join( 'commits', 'commits.id', 'stream_commits.commitId' ) .join( 'users', 'commits.author', 'users.id' ) .where( 'streamId', streamId ) @@ -147,7 +147,7 @@ module.exports = { let query = Commits( ) - .columns( [ 'commitId', 'message', 'referencedObject', 'commits.createdAt', { streamId: 'stream_commits.streamId' }, { streamName: 'streams.name' } ] ).select( ) + .columns( [ { id: 'commitId' }, 'message', 'referencedObject', 'commits.createdAt', { streamId: 'stream_commits.streamId' }, { streamName: 'streams.name' } ] ).select( ) .join( 'stream_commits', 'commits.id', 'stream_commits.commitId' ) .join( 'streams', 'stream_commits.streamId', 'streams.id' ) .where( 'author', userId ) diff --git a/modules/core/tests/graph.spec.js b/modules/core/tests/graph.spec.js index e0e6e4410..f8150758c 100644 --- a/modules/core/tests/graph.spec.js +++ b/modules/core/tests/graph.spec.js @@ -453,7 +453,7 @@ describe( 'GraphQL API Core', ( ) => { for ( let i = 10; i < 20; i++ ) { let c1 = { - message: 'what a message for a first commit', + message: `what a message for commit number ${i}`, streamId: ts1, objectId: objIds[ i ], branchName: 'master', @@ -461,14 +461,15 @@ describe( 'GraphQL API Core', ( ) => { let res = await sendRequest( userA.token, { query: `mutation( $myCommit: CommitCreateInput! ) { commitCreate( commit: $myCommit ) }`, variables: { myCommit: c1 } } ) } - const res = await sendRequest( userA.token, { query: `{ user { commits( limit: 3 ) { totalCount cursor items { commitId message referencedObject } } } }` } ) + const res = await sendRequest( userA.token, { query: `{ user { commits( limit: 3 ) { totalCount cursor items { id message referencedObject } } } }` } ) + expect( res ).to.be.json expect( res.body.errors ).to.not.exist expect( res.body.data.user.commits.totalCount ).to.equal( 11 ) expect( res.body.data.user.commits.cursor ).to.exist expect( res.body.data.user.commits.items.length ).to.equal( 3 ) - const res2 = await sendRequest( userA.token, { query: `{ user { commits( limit: 3, cursor: "${res.body.data.user.commits.cursor}") { totalCount cursor items { commitId message referencedObject } } } }` } ) + const res2 = await sendRequest( userA.token, { query: `{ user { commits( limit: 3, cursor: "${res.body.data.user.commits.cursor}") { totalCount cursor items { id message referencedObject } } } }` } ) expect( res2 ).to.be.json expect( res2.body.errors ).to.not.exist expect( res2.body.data.user.commits.totalCount ).to.equal( 11 ) @@ -545,6 +546,7 @@ describe( 'GraphQL API Core', ( ) => { expect( stream.collaborators[ 1 ].role ).to.equal( 'stream:owner' ) } ) + let bees = [ ] it( 'should retrieve all stream branches', async ( ) => { let query = ` query{ @@ -573,6 +575,8 @@ describe( 'GraphQL API Core', ( ) => { expect( res.body.data.stream.branches.totalCount ).to.equal( 3 ) expect( res.body.data.stream.branches.cursor ).to.exist + bees = res.body.data.stream.branches.items + let query2 = ` query{ stream(id: "${ts1}"){ @@ -600,23 +604,123 @@ describe( 'GraphQL API Core', ( ) => { } ) it( 'should retrieve a stream branch', async ( ) => { - // note: adding another commit for the sake of it - const res1 = await sendRequest( userA.token, { query: `mutation($branch:BranchUpdateInput!) { branchUpdate(streamId:"${ts1}", branch:$branch) }`, variables: { branch: { id: b1.id, commits: [ c2.id ] } } } ) - const res = await sendRequest( userA.token, { query: `query { stream(id:"${ts1}") { branch(id:"${retrievedStream.branches.branches[0].id}") { name description commits { totalCount } } } } ` } ) + + const res = await sendRequest( userA.token, { query: `query { stream(id:"${ts1}") { branch( name: "${bees[1].name}" ) { name description } } } ` } ) expect( res ).to.be.json expect( res.body.errors ).to.not.exist - expect( res.body.data.stream.branch.name ).to.equal( 'branch 1' ) - expect( res.body.data.stream.branch.commits.totalCount ).to.equal( 2 ) - + expect( res.body.data.stream.branch.name ).to.equal( 'dim/dev' ) } ) it( 'should retrieve a branch`s commits', async ( ) => { - assert.fail( 'todo' ) + let query = ` + query { + stream( id: "${ts1}" ) { + branch( name: "master" ) { + id + name + commits( limit: 5 ) { + totalCount + cursor + items { + id + message + createdAt + referencedObject + authorId + } + } + } + } + } + ` + const res = await sendRequest( userA.token, { query: query } ) + expect( res.body.data.stream.branch.commits.items.length ).to.equal( 5 ) + expect( res.body.data.stream.branch.commits.items[ 0 ] ).to.have.property( 'id' ) + expect( res.body.data.stream.branch.commits.items[ 0 ] ).to.have.property( 'message' ) + expect( res.body.data.stream.branch.commits.items[ 0 ] ).to.have.property( 'createdAt' ) + + let query2 = ` + query { + stream( id: "${ts1}" ) { + branch( name: "master" ) { + id + name + commits( limit: 3, cursor: "${res.body.data.stream.branch.commits.cursor}" ) { + totalCount + cursor + items { + id + message + createdAt + referencedObject + authorId + authorName + } + } + } + } + }` + + const res2 = await sendRequest( userA.token, { query: query2 } ) + // console.log( res2.body.errors ) + // console.log( res2.body.data.stream.branch.commits ) + + expect( res2.body.data.stream.branch.commits.items.length ).to.equal( 3 ) + expect( res2.body.data.stream.branch.commits.items[ 0 ] ).to.have.property( 'id' ) + expect( res2.body.data.stream.branch.commits.items[ 0 ] ).to.have.property( 'message' ) + expect( res2.body.data.stream.branch.commits.items[ 0 ] ).to.have.property( 'createdAt' ) } ) it( 'should retrieve all stream commits', async ( ) => { - assert.fail( 'todo' ) + let query = ` + query { + stream( id: "${ts1}" ) { + commits( limit: 10 ) { + totalCount + cursor + items { + id + message + authorId + authorName + } + } + } + } + ` + const res = await sendRequest( userA.token, { query: query } ) + + expect( res ).to.be.json + expect( res.body.errors ).to.not.exist + expect( res.body.data.stream.commits.items.length ).to.equal( 10 ) + expect( res.body.data.stream.commits.totalCount ).to.equal( 12 ) + + let query2 = ` + query { + stream( id: "${ts1}" ) { + commits( limit: 10, cursor: "${res.body.data.stream.commits.cursor}" ) { + totalCount + cursor + items { + id + message + authorId + authorName + } + } + } + } + ` + + const res2 = await sendRequest( userA.token, { query: query2 } ) + console.log( res2.body.errors ) + console.log( res2.body.data.stream.commits ) + + expect( res2 ).to.be.json + expect( res2.body.errors ).to.not.exist + expect( res2.body.data.stream.commits.items.length ).to.equal( 2 ) + } ) it( 'should retrieve a stream commit', async ( ) => {