Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev branch #7

Merged
merged 13 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
## Upcoming


## Release 1.2.0

This release includes the following feature enhancements and bug fixes:

**Features**
- Significantly reduced size of Docker image (https://github.com/aws/graph-explorer/pull/104)
- Improved schema synchronization performance via Summary API integration (https://github.com/aws/graph-explorer/pull/80)
- Improved error messaging when no/insufficient IAM role is found (https://github.com/aws/graph-explorer/pull/81)
- Updated Connections UI documentation for single server changes (https://github.com/aws/graph-explorer/pull/59)
- Added manual trigger for ECR updates (https://github.com/aws/graph-explorer/pull/68)

**Bug fixes**
- Fixed incorrect display of non-string IDs for Gremlin (https://github.com/aws/graph-explorer/pull/60)
- Fixed a database synchronization error caused by white spaces in labels for Gremlin requests (https://github.com/aws/graph-explorer/pull/84)

## Release 1.1.0

This release includes the following feature enhancements and bug fixes:
Expand Down
9 changes: 3 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ FROM amazonlinux:2
WORKDIR /
COPY . /graph-explorer/
WORKDIR /graph-explorer
RUN yum install -y curl
RUN curl -sL https://rpm.nodesource.com/setup_16.x | bash -
RUN yum install -y nodejs
RUN yum install -y openssl
RUN npm install -g pnpm
RUN pnpm install
# Keeping all the RUN commands on a single line reduces the number of layers and,
# as a result, significantly reduces the final image size.
RUN curl -sL https://rpm.nodesource.com/setup_16.x | bash - && yum install -y nodejs openssl && npm install -g pnpm && pnpm install && rm -rf /var/cache/yum
WORKDIR /graph-explorer/
ENV HOME=/graph-explorer
RUN pnpm build
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graph-explorer",
"version": "1.1.0",
"version": "1.2.0",
"description": "Graph Explorer",
"packageManager": "[email protected]",
"engines": {
Expand Down
48 changes: 38 additions & 10 deletions packages/graph-explorer-proxy-server/node-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const getCredentials = async () => {
};

dotenv.config({ path: "../graph-explorer/.env" });

(async () => {
let creds = await getCredentials();
let requestSig;
Expand Down Expand Up @@ -64,23 +64,23 @@ dotenv.config({ path: "../graph-explorer/.env" });
await getAuthHeaders(language, req, reqObjects[0], reqObjects[2]);

return new Promise((resolve, reject) => {

const wrapper = (n) => {
fetch(url, { headers: req.headers, })
.then(async res => {

if (!res.ok) {
console.log("Response not ok");
console.log("Response not ok");
const error = res.status
return Promise.reject(error);
} else {
console.log("Response ok");
console.log("Response ok");
resolve(res);
}
})
.catch(async (err) => {
console.log("Attempt Credential Refresh");
creds = await getCredentials();
creds = await getCredentials();
if (creds === undefined) {
reject("Credentials undefined after credential refresh. Check that you have proper acccess")
}
Expand Down Expand Up @@ -119,7 +119,7 @@ dotenv.config({ path: "../graph-explorer/.env" });
}

async function getAuthHeaders(language, req, endpoint_url, requestSig) {
let authHeaders
let authHeaders
if (language == "sparql") {
authHeaders = await requestSig.requestAuthHeaders(
endpoint_url.port,
Expand Down Expand Up @@ -147,9 +147,9 @@ dotenv.config({ path: "../graph-explorer/.env" });
let data;
try {
response = await retryFetch(`${req.headers["graph-db-connection-url"]}/sparql?query=` +
encodeURIComponent(req.query.query) +
"&format=json", undefined, undefined, req, "sparql").then((res) => res)
encodeURIComponent(req.query.query) +
"&format=json", undefined, undefined, req, "sparql").then((res) => res)

data = await response.json();
res.send(data);
} catch (error) {
Expand All @@ -163,7 +163,35 @@ dotenv.config({ path: "../graph-explorer/.env" });
let data;
try {
response = await retryFetch(`${req.headers["graph-db-connection-url"]}/?gremlin=` +
encodeURIComponent(req.query.gremlin), undefined, undefined, req, "gremlin").then((res) => res)
encodeURIComponent(req.query.gremlin), undefined, undefined, req, "gremlin").then((res) => res)

data = await response.json();
res.send(data);
} catch (error) {
next(error);
console.log(error);
}
});

app.get("/pg/statistics/summary", async (req, res, next) => {
let response;
let data;
try {
response = await retryFetch(`${req.headers["graph-db-connection-url"]}/pg/statistics/summary`, undefined, undefined, req, "gremlin").then((res) => res)

data = await response.json();
res.send(data);
} catch (error) {
next(error);
console.log(error);
}
});

app.get("/rdf/statistics/summary", async (req, res, next) => {
let response;
let data;
try {
response = await retryFetch(`${req.headers["graph-db-connection-url"]}/rdf/statistics/summary`, undefined, undefined, req, "gremlin").then((res) => res)

data = await response.json();
res.send(data);
Expand Down
2 changes: 1 addition & 1 deletion packages/graph-explorer-proxy-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graph-explorer-proxy-server",
"version": "1.1.0",
"version": "1.2.0",
"description": "Server to facilitate communication between the browser and the supported graph database.",
"main": "node-server.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/graph-explorer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graph-explorer",
"version": "1.1.0",
"version": "1.2.0",
"description": "Graph Explorer",
"packageManager": "[email protected]",
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ function useUpdateLayout({
previousNodesRef.current = nodes;
previousLayoutRef.current = layout;
}
// nodes variable is not a dependency because
// it is kept in the reference and the hook will run using
// graphStructureVersion as trigger.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
cy,
layout,
additionalLayoutsConfig,
useAnimation,
nodes,
onLayoutUpdated,
graphStructureVersion,
mounted,
Expand Down
60 changes: 48 additions & 12 deletions packages/graph-explorer/src/connector/AbstractConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {

export type QueryOptions = {
abortSignal?: AbortSignal;
disableCache?: boolean;
};

export type VertexSchemaResponse = Pick<
Expand All @@ -18,21 +19,36 @@ export type VertexSchemaResponse = Pick<
| "displayNameAttribute"
| "longDisplayNameAttribute"
> & {
total?: number;
};

export type CountsByTypeRequest = {
label: string;
};
export type CountsByTypeResponse = {
total: number;
};

export type EdgeSchemaResponse = Pick<
EdgeTypeConfig,
"type" | "displayLabel" | "attributes"
> & {
total: number;
total?: number;
};

export type SchemaResponse = {
/**
* Total number of vertices.
*/
totalVertices?: number;
/**
* List of vertices definitions.
*/
vertices: VertexSchemaResponse[];
/**
* Total number of edges.
*/
totalEdges?: number;
/**
* List of edges definitions.
*/
Expand Down Expand Up @@ -189,8 +205,6 @@ export abstract class AbstractConnector {

protected abstract readonly basePath: string;

private readonly _requestCache: Map<string, CacheItem> = new Map();

public constructor(connection: ConfigurationWithConnection["connection"]) {
if (!connection?.url) {
throw new Error("Invalid configuration. Missing 'connection.url'");
Expand All @@ -204,6 +218,14 @@ export abstract class AbstractConnector {
*/
public abstract fetchSchema(options?: QueryOptions): Promise<SchemaResponse>;

/**
* Count the number of vertices of a given type
*/
public abstract fetchVertexCountsByType(
req: CountsByTypeRequest,
options?: QueryOptions
): Promise<CountsByTypeResponse>;

/**
* Fetch all directly connected neighbors of a given source
* filtered by vertex or edge types and/or vertex attributes.
Expand Down Expand Up @@ -233,15 +255,23 @@ export abstract class AbstractConnector {
/**
* This method performs requests and cache their responses.
*/
protected async request<TResult>(
protected async requestQueryTemplate<TResult = any>(
queryTemplate: string,
options?: { signal?: AbortSignal }
): Promise<any> {
const url = this._connection.url.replace(/\/$/, "");
options?: { signal?: AbortSignal; disableCache?: boolean }
): Promise<TResult> {
const encodedQuery = encodeURIComponent(queryTemplate);
const uri = `${url}${this.basePath}${encodedQuery}&format=json`;
const uri = `${this.basePath}${encodedQuery}&format=json`;
return this.request<TResult>(uri, options);
}

protected async request<TResult = any>(
uri: string,
options?: { signal?: AbortSignal; disableCache?: boolean }
): Promise<TResult> {
const url = this._connection.url.replace(/\/$/, "");
const currentUri = `${url}${uri}`;

const cachedResponse = await this._getFromCache(uri);
const cachedResponse = await this._getFromCache(currentUri);
if (
cachedResponse &&
cachedResponse.updatedAt +
Expand All @@ -251,7 +281,7 @@ export abstract class AbstractConnector {
return cachedResponse.data;
}

return this._requestAndCache<TResult>(uri, {
return this._requestAndCache<TResult>(currentUri, {
signal: options?.signal,
headers: this._getAuthHeaders(),
});
Expand All @@ -269,10 +299,16 @@ export abstract class AbstractConnector {
return headers;
}

private async _requestAndCache<TResult>(url: string, init?: RequestInit) {
private async _requestAndCache<TResult = any>(
url: string,
init?: RequestInit,
options?: Pick<QueryOptions, "disableCache">
) {
const response = await fetch(url, init);
const data = await response.json();
this._setToCache(url, { data, updatedAt: new Date().getTime() });
if (options?.disableCache !== true) {
this._setToCache(url, { data, updatedAt: new Date().getTime() });
}
return data as TResult;
}

Expand Down
45 changes: 39 additions & 6 deletions packages/graph-explorer/src/connector/gremlin/GremlinConnector.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type {
CountsByTypeRequest,
CountsByTypeResponse,
KeywordSearchRequest,
KeywordSearchResponse,
NeighborsCountRequest,
Expand All @@ -12,40 +14,71 @@ import { AbstractConnector } from "../AbstractConnector";
import fetchNeighbors from "./queries/fetchNeighbors";
import fetchNeighborsCount from "./queries/fetchNeighborsCount";
import fetchSchema from "./queries/fetchSchema";
import fetchVertexTypeCounts from "./queries/fetchVertexTypeCounts";
import keywordSearch from "./queries/keywordSearch";
import { GraphSummary } from "./types";

export default class GremlinConnector extends AbstractConnector {
protected readonly basePath = "/?gremlin=";
private readonly _summaryPath = "/pg/statistics/summary?mode=detailed";

fetchSchema(options?: QueryOptions): Promise<SchemaResponse> {
return fetchSchema(this._gremlinFetch(options));
// Stores the id type before casting it to string
private _rawIdTypeMap: Map<string, "string" | "number"> = new Map();

async fetchSchema(options?: QueryOptions): Promise<SchemaResponse> {
const ops = { ...options, disableCache: true };
let summary: GraphSummary | undefined;
try {
const response = await this.request<{
payload: { graphSummary: GraphSummary };
}>(this._summaryPath, ops);
summary = response.payload.graphSummary;
} catch (e) {
if (import.meta.env.DEV) {
console.error("[Summary API]", e);
}
}

return fetchSchema(this._gremlinFetch(ops), summary);
}

fetchVertexCountsByType(
req: CountsByTypeRequest,
options: QueryOptions | undefined
): Promise<CountsByTypeResponse> {
return fetchVertexTypeCounts(this._gremlinFetch(options), req);
}

fetchNeighbors(
req: NeighborsRequest,
options: QueryOptions | undefined
): Promise<NeighborsResponse> {
return fetchNeighbors(this._gremlinFetch(options), req);
return fetchNeighbors(this._gremlinFetch(options), req, this._rawIdTypeMap);
}

fetchNeighborsCount(
req: NeighborsCountRequest,
options?: QueryOptions
): Promise<NeighborsCountResponse> {
return fetchNeighborsCount(this._gremlinFetch(options), req);
return fetchNeighborsCount(
this._gremlinFetch(options),
req,
this._rawIdTypeMap
);
}

keywordSearch(
req: KeywordSearchRequest,
options?: QueryOptions
): Promise<KeywordSearchResponse> {
return keywordSearch(this._gremlinFetch(options), req);
return keywordSearch(this._gremlinFetch(options), req, this._rawIdTypeMap);
}

private _gremlinFetch<TResult>(options?: QueryOptions) {
return async (queryTemplate: string) => {
return super.request<TResult>(queryTemplate, {
return super.requestQueryTemplate<TResult>(queryTemplate, {
signal: options?.abortSignal,
disableCache: options?.disableCache,
});
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import type { Vertex } from "../../../@types/entities";
import type { NeighborsCountResponse } from "../../AbstractConnector";
import type { GVertex } from "../types";
import parsePropertiesValues from "./parsePropertiesValues";
<<<<<<< HEAD
import toStringId from './toStringId'
=======
import toStringId from "./toStringId";
>>>>>>> 0072316067b7c8f9e3d7d7464b956a3415649f61

const mapApiVertex = (
apiVertex: GVertex,
Expand Down
Loading