From d812022f4dd3891bdebee57287fa3b806cf97080 Mon Sep 17 00:00:00 2001 From: Dino Lozina Date: Thu, 23 Mar 2023 17:17:18 +0100 Subject: [PATCH 1/3] Added new graph methods --- src/client.ts | 58 ++++++++++++++++++++++++++++++++++++++++----------- src/graph.ts | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/client.ts b/src/client.ts index 3f118b5..b4dfd0f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -378,6 +378,7 @@ export class C8Client extends Fabric { const stream = this.stream(streamName, local, isCollectionStream); return stream; } + //getStreams() { } // already present getStreamStats( streamName: string, @@ -511,6 +512,12 @@ export class C8Client extends Fabric { return streamApp.activateStreamApplication(active); } + //************ + // Graphs Methods + getGraphs(): Promise { + return this.graphs(); + } + createGraph(graphName: string, properties: any = {}) { const graph = this.graph(graphName); return graph.create(properties); @@ -521,18 +528,20 @@ export class C8Client extends Fabric { return graph.drop(dropCollections); } - hasGraph(graphName: string) { + getGraph(graphName: string) { const graph = this.graph(graphName); - return graph.exists(); + return graph.get(); } - getGraph(graphName: string) { + hasGraph(graphName: string) { const graph = this.graph(graphName); - return graph.get(); + return graph.exists(); } - getGraphs(): Promise { - return this.graphs(); + async getEdges(graphName: string) { + const graph = this.graph(graphName); + const graphDetails = await graph.get(); + return graphDetails.edgeDefinitions; } insertEdge(graphName: string, definition: any) { @@ -575,12 +584,6 @@ export class C8Client extends Fabric { return graphEdgeCollection.remove(documentHandle, opts); } - async getEdges(graphName: string) { - const graph = this.graph(graphName); - const graphDetails = await graph.get(); - return graphDetails.edgeDefinitions; - } - linkEdge( graphName: string, collectionName: string, @@ -599,6 +602,33 @@ export class C8Client extends Fabric { }); } + addEdgeToEdgeCollection( + graphName: string, + collectionName: string, + properties: any = {}, + returnNew: boolean = false + ) { + const graph = this.graph(graphName); + return graph.addEdgeToEdgeCollection(collectionName, properties, returnNew); + } + + async listVertexCollections(graphName: string) { + const graph = this.graph(graphName); + return graph.listVertexCollections(); + } + + addVertexToCollection( + graphName: string, + collectionName: string, + properties: any = {}, + returnNew: boolean = false + ) { + const graph = this.graph(graphName); + return graph.addVertexToCollection(collectionName, properties, returnNew); + } + + //************ + // Users Methods hasUser(userName: string) { const user = this.user(userName); return user.hasUser(); @@ -803,6 +833,7 @@ export class C8Client extends Fabric { const apiKeys = this.apiKeys(keyid); return apiKeys.removeApiKey(); } + // ---------------------------------- listAccessibleDatabases(keyid: string, full?: boolean) { const apiKeys = this.apiKeys(keyid); @@ -827,6 +858,7 @@ export class C8Client extends Fabric { const apiKeys = this.apiKeys(keyid, dbName); return apiKeys.setDatabaseAccessLevel(permission); } + // ---------------------------------- listAccessibleCollections(keyid: string, dbName: string, full?: boolean) { const apiKeys = this.apiKeys(keyid, dbName); @@ -860,6 +892,7 @@ export class C8Client extends Fabric { const apiKeys = this.apiKeys(keyid, dbName); return apiKeys.setCollectionAccessLevel(collectionName, permission); } + // ---------------------------------- listAccessibleStreams(keyid: string, dbName: string, full?: boolean) { const apiKeys = this.apiKeys(keyid, dbName); @@ -885,6 +918,7 @@ export class C8Client extends Fabric { const apiKeys = this.apiKeys(keyid, dbName); return apiKeys.setStreamAccessLevel(streamName, permission); } + // ---------------------------------- getBillingAccessLevel(keyid: string) { const apiKeys = this.apiKeys(keyid); diff --git a/src/graph.ts b/src/graph.ts index 6d1e3c8..e96eddc 100755 --- a/src/graph.ts +++ b/src/graph.ts @@ -294,6 +294,7 @@ export class GraphEdgeCollection extends EdgeCollection { } const GRAPH_NOT_FOUND = 1924; + export class Graph { name: string; @@ -454,4 +455,48 @@ export class Graph { (res) => res.body.graph ); } + + //todo New Edge Endpoints + + // todo Add Vertex to given collection + addVertexToCollection( + collectionName: string, + properties: any = {}, + returnNew: boolean = false + ) { + return this._connection.request( + { + method: "POST", + path: `/_api/graph/${this.name}/vertex/${collectionName}`, + body: { + ...properties, + }, + qs: { + returnNew, + }, + }, + (res) => res.body + ); + } + + // todo Add new Edge to given collection + addEdgeToEdgeCollection( + edgeCollectionName: string, + properties: any = {}, + returnNew: boolean = false + ) { + return this._connection.request( + { + method: "POST", + path: `/_api/graph/${this.name}/edge/${edgeCollectionName}`, + body: { + ...properties, + }, + qs: { + returnNew, + }, + }, + (res) => res.body + ); + } } From bae6a6aea1f634f7f72501a8a05bd227e1e5e02e Mon Sep 17 00:00:00 2001 From: Dino Lozina Date: Thu, 23 Mar 2023 22:28:43 +0100 Subject: [PATCH 2/3] Added new graph methods for vertex --- src/client.ts | 92 +++++++++++++++++++++++++++++++++++++++++++++++---- src/graph.ts | 6 +--- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/client.ts b/src/client.ts index b4dfd0f..53a5427 100644 --- a/src/client.ts +++ b/src/client.ts @@ -512,8 +512,7 @@ export class C8Client extends Fabric { return streamApp.activateStreamApplication(active); } - //************ - // Graphs Methods + //------------ Graph ---------- getGraphs(): Promise { return this.graphs(); } @@ -549,6 +548,22 @@ export class C8Client extends Fabric { return graph.addEdgeDefinition(definition); } + removeEdgeDefinition(graphName: string, edgeCollectionName: string) { + const graph = this.graph(graphName); + return graph.removeEdgeDefinition(edgeCollectionName); + } + + getEdge( + graphName: string, + collectionName: string, + documentHandle: DocumentHandle, + opts: any = {} + ) { + const graph = this.graph(graphName); + const graphEdgeCollection = graph.edgeCollection(collectionName); + return graphEdgeCollection.document(documentHandle, opts); + } + updateEdge( graphName: string, collectionName: string, @@ -617,18 +632,83 @@ export class C8Client extends Fabric { return graph.listVertexCollections(); } - addVertexToCollection( + async addVertexCollection(graphName: string, collectionName: string) { + const graph = this.graph(graphName); + return graph.addVertexCollection(collectionName); + } + + async removeVertexCollection( + graphName: string, + collectionName: string, + dropCollection: boolean = false + ) { + const graph = this.graph(graphName); + return graph.removeVertexCollection(collectionName, dropCollection); + } + + addVertexToVertexCollection( graphName: string, collectionName: string, properties: any = {}, returnNew: boolean = false ) { const graph = this.graph(graphName); - return graph.addVertexToCollection(collectionName, properties, returnNew); + return graph.addVertexToVertexCollection( + collectionName, + properties, + returnNew + ); + } + + removeVertexFromVertexCollection( + graphName: string, + collectionName: string, + documentHandle: DocumentHandle, + opts: any = {} + ) { + const graph = this.graph(graphName); + return graph.vertexCollection(collectionName).remove(documentHandle, opts); + } + + getVertexFromVertexCollection( + graphName: string, + collectionName: string, + documentHandle: DocumentHandle, + opts: any = {} + ) { + const graph = this.graph(graphName); + return graph + .vertexCollection(collectionName) + .document(documentHandle, opts); + } + + updateVertexFromVertexCollection( + graphName: string, + collectionName: string, + documentHandle: DocumentHandle, + newValue: any, + opts: any = {} + ) { + const graph = this.graph(graphName); + return graph + .vertexCollection(collectionName) + .update(documentHandle, newValue, opts); + } + + replaceVertexFromVertexCollection( + graphName: string, + collectionName: string, + documentHandle: DocumentHandle, + newValue: any, + opts: any = {} + ) { + const graph = this.graph(graphName); + return graph + .vertexCollection(collectionName) + .replace(documentHandle, newValue, opts); } - //************ - // Users Methods + //------------ User ---------- hasUser(userName: string) { const user = this.user(userName); return user.hasUser(); diff --git a/src/graph.ts b/src/graph.ts index e96eddc..45cbf11 100755 --- a/src/graph.ts +++ b/src/graph.ts @@ -456,10 +456,7 @@ export class Graph { ); } - //todo New Edge Endpoints - - // todo Add Vertex to given collection - addVertexToCollection( + addVertexToVertexCollection( collectionName: string, properties: any = {}, returnNew: boolean = false @@ -479,7 +476,6 @@ export class Graph { ); } - // todo Add new Edge to given collection addEdgeToEdgeCollection( edgeCollectionName: string, properties: any = {}, From d8efebbc8e683d09c7f5d7521e5e20fdaf94e7f4 Mon Sep 17 00:00:00 2001 From: Dino Lozina Date: Fri, 24 Mar 2023 23:32:58 +0100 Subject: [PATCH 3/3] Updated graph tests --- src/test/16-graphs.ts | 518 ++++++++++++++++++++++++++++-------------- 1 file changed, 344 insertions(+), 174 deletions(-) diff --git a/src/test/16-graphs.ts b/src/test/16-graphs.ts index fbb29f0..07c467e 100755 --- a/src/test/16-graphs.ts +++ b/src/test/16-graphs.ts @@ -1,43 +1,20 @@ import { expect } from "chai"; -import { C8Client, Fabric } from "../jsC8"; -import { Graph } from "../graph"; -import { getDCListString } from "../util/helper"; +import { C8Client } from "../jsC8"; import * as dotenv from "dotenv"; -const range = (n: number): number[] => Array.from(Array(n).keys()); const C8_VERSION = Number(process.env.C8_VERSION || 30400); -function createCollections(fabric: Fabric) { - let vertexCollectionNames = range(2).map((i) => `vc${Date.now()}${i}`); - let edgeCollectionNames = range(2).map((i) => `ec${Date.now()}${i}`); - return Promise.all([ - ...vertexCollectionNames.map((name) => fabric.collection(name).create()), - ...edgeCollectionNames.map((name) => fabric.edgeCollection(name).create()), - ]).then(() => [vertexCollectionNames, edgeCollectionNames]); -} - -function createGraph( - graph: Graph, - vertexCollectionNames: string[], - edgeCollectionNames: string[] -) { - return graph.create({ - edgeDefinitions: edgeCollectionNames.map((name) => ({ - collection: name, - from: vertexCollectionNames, - to: vertexCollectionNames, - })), - }); -} - describe("Graph API", function () { dotenv.config(); - // create fabric takes 11s in a standard cluster - this.timeout(60000); let c8Client: C8Client; - let dcList: string; - let name = `testfabric${Date.now()}`; + const graphName = "testGraph"; + const vertexCollection1 = "VertexCollection1"; + const vertexCollection2 = "VertexCollection2"; + const vertexCollection3 = "VertexCollection3"; + const edgeCollection1 = "EdgeCollection1"; + const edgeCollection2 = "EdgeCollection2"; + before(async () => { c8Client = new C8Client({ url: process.env.URL, @@ -45,156 +22,349 @@ describe("Graph API", function () { fabricName: process.env.FABRIC, c8Version: C8_VERSION, }); - - const response = await c8Client.getAllEdgeLocations(); - dcList = getDCListString(response); - - await c8Client.createFabric(name, ["root"], { dcList: dcList }); - c8Client.useFabric(name); + //Vertex + await c8Client.createCollection(vertexCollection1); + await c8Client.createCollection(vertexCollection2); + await c8Client.createCollection(vertexCollection3); + //Edge + await c8Client.createCollection(edgeCollection1, {}, true); + await c8Client.createCollection(edgeCollection2, {}, true); }); after(async () => { - try { - c8Client.useFabric("_system"); - await c8Client.dropFabric(name); - } finally { - c8Client.close(); - } + // Delete the collections + await c8Client.deleteCollection(vertexCollection1); + await c8Client.deleteCollection(vertexCollection2); + await c8Client.deleteCollection(vertexCollection3); + await c8Client.deleteCollection(edgeCollection1); + await c8Client.deleteCollection(edgeCollection2); }); - describe("graph.get", () => { - let graph: Graph; - let collectionNames: string[]; - before((done) => { - graph = c8Client.graph(`g${Date.now()}`); - createCollections(c8Client) - .then((names) => { - collectionNames = names.reduce((a, b) => a.concat(b)); - return createGraph(graph, names[0], names[1]); - }) - .then(() => void done()) - .catch(done); - }); - after((done) => { - graph - .drop() - .then(() => - Promise.all( - collectionNames.map((name) => c8Client.collection(name).drop()) - ) - ) - .then(() => void done()) - .catch(done); - }); - it("fetches information about the graph", (done) => { - graph - .get() - .then((data) => { - expect(data).to.have.property("name", graph.name); - done(); - }) - .catch(done); + + describe("graph", () => { + describe("graph.create", () => { + it("should create a graph", async () => { + const response = await c8Client.createGraph(graphName, { + edgeDefinitions: [ + { + collection: edgeCollection1, + from: [vertexCollection1], + to: [vertexCollection2], + }, + ], + }); + expect(response).to.have.all.keys( + "_key", + "numberOfShards", + "replicationFactor", + "minReplicationFactor", + "isSmart", + "edgeDefinitions", + "orphanCollections", + "_rev", + "_id", + "name" + ); + expect(response.name).to.equal(graphName); + }); + }); + + describe("graph.get", () => { + it("should get a graph", async () => { + const response = await c8Client.getGraph(graphName); + expect(response).to.have.all.keys( + "_key", + "numberOfShards", + "replicationFactor", + "minReplicationFactor", + "isSmart", + "edgeDefinitions", + "orphanCollections", + "_rev", + "_id", + "name" + ); + expect(response.name).to.equal(graphName); + }); + }); + + describe("graphs.get", () => { + it("should get all graphs", async () => { + const response = await c8Client.getGraphs(); + expect(response).to.be.an("array"); + }); + }); + + describe("graph.exists", () => { + it("should check if a graph exists", async () => { + const response = await c8Client.hasGraph(graphName); + expect(response).to.equal(true); + }); }); }); - describe("graph.create", () => { - let edgeCollectionNames: string[]; - let vertexCollectionNames: string[]; - before((done) => { - createCollections(c8Client) - .then((names) => { - [vertexCollectionNames, edgeCollectionNames] = names; - done(); - }) - .catch(done); - }); - after((done) => { - Promise.all( - [...edgeCollectionNames, ...vertexCollectionNames].map((name) => - c8Client.collection(name).drop() - ) - ) - .then(() => void done()) - .catch(done); - }); - it("creates the graph", (done) => { - let graph = c8Client.graph(`g${Date.now()}`); - graph - .create({ - edgeDefinitions: edgeCollectionNames.map((name) => ({ - collection: name, - from: vertexCollectionNames, - to: vertexCollectionNames, - })), - }) - .then(() => graph.get()) - .then((data) => { - expect(data).to.have.property("name", graph.name); - done(); - }) - .catch(done); + + describe("vertex", () => { + describe("vertex.listVertexCollections", () => { + it("should list all vertex collections", async () => { + const response = await c8Client.listVertexCollections(graphName); + expect(response).to.be.an("array"); + }); + }); + + describe("vertex.add", () => { + it("should add vertex collection to graph", async () => { + const response = await c8Client.addVertexCollection( + graphName, + vertexCollection3 + ); + expect(response).to.have.all.keys( + "_key", + "numberOfShards", + "replicationFactor", + "minReplicationFactor", + "isSmart", + "edgeDefinitions", + "orphanCollections", + "_rev", + "_id", + "name" + ); + }); + }); + + describe("vertex.remove", () => { + it("should remove vertex collection to graph", async () => { + const response = await c8Client.removeVertexCollection( + graphName, + vertexCollection3 + ); + expect(response).to.have.all.keys( + "_key", + "numberOfShards", + "replicationFactor", + "minReplicationFactor", + "isSmart", + "edgeDefinitions", + "orphanCollections", + "_rev", + "_id", + "name" + ); + }); + }); + + describe("vertex.create", () => { + it("add vertex to vertex collection", async () => { + const response = await c8Client.addVertexToVertexCollection( + graphName, + vertexCollection1, + { _key: "T1", name: "Test1" } + ); + expect(response).to.have.all.keys("error", "code", "vertex"); + expect(response.error).to.equal(false); + expect(response.code).to.equal(202); + }); + + it("add vertex to vertex collection", async () => { + const response = await c8Client.addVertexToVertexCollection( + graphName, + vertexCollection1, + { _key: "T3", name: "Test3" }, + true + ); + expect(response).to.have.all.keys("error", "code", "vertex", "new"); + expect(response.error).to.equal(false); + expect(response.code).to.equal(202); + }); + }); + describe("vertex.update", () => { + it("update vertex in vertex collection", async () => { + const response = await c8Client.addVertexToVertexCollection( + graphName, + vertexCollection1, + { _key: "T2", name: "Test2" } + ); + expect(response).to.have.all.keys("error", "code", "vertex"); + expect(response.error).to.equal(false); + expect(response.code).to.equal(202); + }); + }); + describe("vertex.get", () => { + it("get vertex from vertex collection", async () => { + const response = await c8Client.getVertexFromVertexCollection( + graphName, + vertexCollection1, + { _key: "T2" } + ); + expect(response).to.have.all.keys("_id", "_key", "_rev", "name"); + expect(response._id).to.equal("VertexCollection1/T2"); + expect(response._key).to.equal("T2"); + expect(response.name).to.equal("Test2"); + }); + }); + describe("vertex.replace", () => { + it("replace vertex from vertex collection", async () => { + const response = await c8Client.replaceVertexFromVertexCollection( + graphName, + vertexCollection1, + { _key: "T2" }, + { name: "Test3" } + ); + expect(response).to.have.all.keys("_id", "_key", "_rev", "_oldRev"); + }); }); }); - describe("graph.drop", () => { - let graph: Graph; - let edgeCollectionNames: string[]; - let vertexCollectionNames: string[]; - beforeEach((done) => { - graph = c8Client.graph(`g${Date.now()}`); - createCollections(c8Client) - .then((names) => { - [vertexCollectionNames, edgeCollectionNames] = names; - return createGraph(graph, names[0], names[1]); - }) - .then(() => void done()) - .catch(done); - }); - afterEach((done) => { - Promise.all( - [...edgeCollectionNames, ...vertexCollectionNames].map((name) => - c8Client - .collection(name) - .drop() - .catch(() => null) - ) - ) - .then(() => void done()) - .catch(done); - }); - it("destroys the graph if not passed true", (done) => { - graph - .drop() - .then(() => - graph.get().then( - () => Promise.reject(new Error("Should not succeed")), - () => undefined - ) - ) - .then(() => c8Client.listCollections()) - .then((collections) => { - expect(collections.map((c: any) => c.name)).to.include.members([ - ...edgeCollectionNames, - ...vertexCollectionNames, - ]); - done(); - }) - .catch(done); - }); - it("additionally drops all of its collections if passed true", (done) => { - graph - .drop(true) - .then(() => - graph.get().then( - () => Promise.reject(new Error("Should not succeed")), - () => undefined - ) - ) - .then(() => c8Client.listCollections()) - .then((collections) => { - expect(collections.map((c: any) => c.name)).not.to.include.members([ - ...edgeCollectionNames, - ...vertexCollectionNames, - ]); - done(); - }) - .catch(done); + + describe("edge", () => { + describe("edge.getEdges", () => { + it("should get all edges", async () => { + const response = await c8Client.getEdges(graphName); + expect(response).to.be.an("array"); + }); + }); + + describe("edge.addEdgeDefinition", () => { + it("should add edge definition", async () => { + const response = await c8Client.insertEdge(graphName, { + collection: edgeCollection2, + from: [vertexCollection1], + to: [vertexCollection2], + }); + expect(response).to.have.all.keys( + "_key", + "numberOfShards", + "replicationFactor", + "minReplicationFactor", + "isSmart", + "edgeDefinitions", + "orphanCollections", + "_rev", + "_id", + "name" + ); + }); + }); + + describe("edge.removeEdgeDefinition", () => { + it("should remove edge definition", async () => { + const response = await c8Client.removeEdgeDefinition( + graphName, + edgeCollection2 + ); + expect(response).to.have.all.keys( + "_key", + "numberOfShards", + "replicationFactor", + "minReplicationFactor", + "isSmart", + "edgeDefinitions", + "orphanCollections", + "_rev", + "_id", + "name" + ); + }); + }); + + describe("edge.addEdgeToEdgeCollection", () => { + it("should add edge to edge collection", async () => { + await c8Client.addVertexToVertexCollection( + graphName, + vertexCollection2, + { _key: "C2", name: "Test2" } + ); + const response = await c8Client.addEdgeToEdgeCollection( + graphName, + edgeCollection1, + { + _key: "E1", + _from: "VertexCollection1/T1", + _to: "VertexCollection2/C2", + } + ); + expect(response).to.have.all.keys("error", "code", "edge"); + expect(response.error).to.equal(false); + expect(response.code).to.equal(202); + }); + }); + + describe("edge.update", () => { + it("should update edge in edge collection", async () => { + const response = await c8Client.updateEdge( + graphName, + edgeCollection1, + { _key: "E1" }, + { name: "Test3" } + ); + expect(response).to.have.all.keys("_id", "_key", "_rev", "_oldRev"); + }); + }); + + describe("edge.replace", () => { + it("should replace edge in edge collection", async () => { + await c8Client.addVertexToVertexCollection( + graphName, + vertexCollection1, + { _key: "T4", name: "Test4" } + ); + await c8Client.addVertexToVertexCollection( + graphName, + vertexCollection2, + { _key: "C8", name: "Test8" } + ); + const response = await c8Client.replaceEdge( + graphName, + edgeCollection1, + { _key: "E1" }, + { _from: "VertexCollection1/T4", _to: "VertexCollection2/C8" } + ); + expect(response).to.have.all.keys("_id", "_key", "_rev", "_oldRev"); + }); + }); + + describe("edges.get", () => { + it("should get edge from edge collection", async () => { + const response = await c8Client.getEdge(graphName, edgeCollection1, { + _key: "E1", + }); + expect(response).to.have.all.keys( + "_id", + "_key", + "_rev", + "_from", + "_to" + ); + }); + }); + }); + + describe("delete", () => { + describe("edge.delete", () => { + it("should delete edge from edge collection", async () => { + const response = await c8Client.deleteEdge(graphName, edgeCollection1, { + _key: "E1", + }); + expect(response).to.equal(true); + }); + }); + + describe("vertex.delete", () => { + it("should delete vertex from vertex collection", async () => { + const response = await c8Client.removeVertexFromVertexCollection( + graphName, + vertexCollection1, + { _key: "T1" } + ); + expect(response).to.have.all.keys("error", "code", "removed"); + expect(response.code).to.equal(202); + expect(response.error).to.equal(false); + expect(response.removed).to.equal(true); + }); + }); + + describe("graph.delete", () => { + it("should delete a graph", async () => { + const response = await c8Client.deleteGraph(graphName, false); + expect(response).to.equal(true); + }); }); }); });