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

Small changes for better SPARQL debugging #499

Merged
merged 15 commits into from
Jul 19, 2024
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@

**Bug Fixes and Minor Changes**

- Use `application/sparql-results+json` accept header for SPARQL requests
(<https://github.com/aws/graph-explorer/pull/499>)
- Fix text wrapping for labels in edge styling sidebar
(<https://github.com/aws/graph-explorer/pull/499>)
- Increase request body size limit for proxy server
(<https://github.com/aws/graph-explorer/pull/488>)
- Development scripts updated to be more consistent with the industry
Expand Down
6 changes: 3 additions & 3 deletions packages/graph-explorer-proxy-server/node-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,10 @@ const staticFilesVirtualPath =
const staticFilesPath = path.join(clientRoot, "dist");

proxyLogger.debug(
"Setting up static file virtual path:",
"Setting up static file virtual path: %s",
staticFilesVirtualPath
);
proxyLogger.debug("Setting up static file path:", staticFilesPath);
proxyLogger.debug("Setting up static file path: %s", staticFilesPath);

app.use(staticFilesVirtualPath, express.static(staticFilesPath));

Expand Down Expand Up @@ -272,7 +272,7 @@ app.post("/sparql", async (req, res, next) => {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
Accept: "application/sparql-results+json",
},
body,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { logger } from "../../utils";

function _gremlinFetch(connection: ConnectionConfig, options: any) {
return async (queryTemplate: string) => {
logger.debug(queryTemplate);
const body = JSON.stringify({ query: queryTemplate });
const headers: HeadersInit = {
"Content-Type": "application/json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { fetchDatabaseRequest } from "../fetchDatabaseRequest";
import { ConnectionConfig } from "../../core";
import { DEFAULT_SERVICE_TYPE } from "../../utils/constants";
import { Explorer } from "../useGEFetchTypes";
import { env } from "../../utils";
import { env, logger } from "../../utils";

function _openCypherFetch(connection: ConnectionConfig, options: any) {
return async (queryTemplate: string) => {
logger.debug(queryTemplate);
return fetchDatabaseRequest(connection, `${connection.url}/openCypher`, {
method: "POST",
headers: {
Expand All @@ -30,26 +31,7 @@ export function createOpenCypherExplorer(
return {
connection: connection,
async fetchSchema(options) {
let summary;
try {
const endpoint =
serviceType === DEFAULT_SERVICE_TYPE
? `${connection.url}/pg/statistics/summary?mode=detailed`
: `${connection.url}/summary?mode=detailed`;
const response = await fetchDatabaseRequest(connection, endpoint, {
method: "GET",
...options,
});

summary =
(response.payload
? (response.payload.graphSummary as GraphSummary)
: (response.graphSummary as GraphSummary)) || undefined;
} catch (e) {
if (env.DEV) {
console.error("[Summary API]", e);
}
}
const summary = await fetchSummary(serviceType, connection, options);
return fetchSchema(_openCypherFetch(connection, options), summary);
},
async fetchVertexCountsByType(req, options) {
Expand All @@ -66,3 +48,30 @@ export function createOpenCypherExplorer(
},
};
}

async function fetchSummary(
serviceType: string,
connection: ConnectionConfig,
options: RequestInit
) {
try {
const endpoint =
serviceType === DEFAULT_SERVICE_TYPE
? `${connection.url}/pg/statistics/summary?mode=detailed`
: `${connection.url}/summary?mode=detailed`;
const response = await fetchDatabaseRequest(connection, endpoint, {
method: "GET",
...options,
});

return (
(response.payload
? (response.payload.graphSummary as GraphSummary)
: (response.graphSummary as GraphSummary)) || undefined
);
} catch (e) {
if (env.DEV) {
logger.error("[Summary API]", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
SPARQLBlankNodeNeighborsResponse,
SparqlFetch,
} from "../types";
import { logger } from "../../../utils";

type RawBlankNodeNeighborsResponse = {
results: {
Expand Down Expand Up @@ -51,6 +52,12 @@ async function fetchBlankNodeNeighborsPredicates(
subjectURIs,
});

logger.log("[SPARQL Explorer] Fetching blank node neighbor predicates...", {
subQuery,
resourceURI,
resourceClass,
subjectURIs,
});
const response = await sparqlFetch<RawNeighborsPredicatesResponse>(template);
return response.results.bindings.map(result => {
if (isIncomingPredicate(result)) {
Expand Down Expand Up @@ -91,6 +98,7 @@ export default async function fetchBlankNodeNeighbors(
sparqlFetch: SparqlFetch,
req: SPARQLBlankNodeNeighborsRequest
): Promise<SPARQLBlankNodeNeighborsResponse> {
logger.log("[SPARQL Explorer] Fetching blank node one hop neighbors", req);
const neighborsTemplate = blankNodeOneHopNeighborsTemplate(req.subQuery);
const neighbors = await sparqlFetch<
RawBlankNodeNeighborsResponse | ErrorResponse
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from "../../../utils";
import type {
CountsByTypeRequest,
CountsByTypeResponse,
Expand All @@ -20,6 +21,7 @@ const fetchClassCounts = async (
req: CountsByTypeRequest
): Promise<CountsByTypeResponse> => {
const template = classWithCountsTemplates(req.label);
logger.log("[SPARQL Explorer] Fetching class counts...", req);
const response = await sparqlFetch<RawCountsByTypeResponse>(template);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
SparqlFetch,
SPARQLNeighborsRequest,
} from "../types";
import { logger } from "../../../utils";

type RawOneHopNeighborsResponse = {
results: {
Expand Down Expand Up @@ -46,6 +47,7 @@ const fetchOneHopNeighbors = async (
req: SPARQLNeighborsRequest
) => {
const oneHopTemplate = oneHopNeighborsTemplate(req);
logger.log("[SPARQL Explorer] Fetching oneHopNeighbors...", req);
const data = await sparqlFetch<RawOneHopNeighborsResponse>(oneHopTemplate);

const groupBySubject = groupBy(
Expand Down Expand Up @@ -110,6 +112,11 @@ export const fetchNeighborsPredicates = async (
subjectURIs,
});

logger.log("[SPARQL Explorer] Fetching neighbor predicates...", {
resourceURI,
resourceClass,
subjectURIs,
});
const response = await sparqlFetch<RawNeighborsPredicatesResponse>(template);
return response.results.bindings.map(result => {
if (isIncomingPredicate(result)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from "../../../utils";
import type { NeighborsCountResponse } from "../../useGEFetchTypes";
import neighborsCountTemplate from "../templates/neighborsCount/neighborsCountTemplate";
import { SparqlFetch, SPARQLNeighborsCountRequest } from "../types";
Expand Down Expand Up @@ -29,6 +30,7 @@ const fetchNeighborsCount = async (
const counts: Record<string, number> = {};

const template = neighborsCountTemplate(req);
logger.log("[SPARQL Explorer] Fetching neighbor counts...", req);
const response = await sparqlFetch<RawNeighborCount>(template);
response.results.bindings.forEach(result => {
const count = Number(result.count.value);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { batchPromisesSerially } from "../../../utils";
import { batchPromisesSerially, logger } from "../../../utils";
import { DEFAULT_CONCURRENT_REQUESTS_LIMIT } from "../../../utils/constants";
import type { SchemaResponse } from "../../useGEFetchTypes";
import classesWithCountsTemplates from "../templates/classesWithCountsTemplates";
Expand Down Expand Up @@ -71,6 +71,10 @@ const fetchPredicatesByClass = async (
const classPredicatesTemplate = predicatesByClassTemplate({
class: resourceClass,
});
logger.log("[SPARQL Explorer] Fetching predicates by class...", {
resourceClass,
countsByClass,
});
const predicatesResponse =
await sparqlFetch<RawPredicatesSamplesResponse>(
classPredicatesTemplate
Expand Down Expand Up @@ -107,6 +111,7 @@ const fetchPredicatesByClass = async (

const fetchClassesSchema = async (sparqlFetch: SparqlFetch) => {
const classesTemplate = classesWithCountsTemplates();
logger.log("[SPARQL Explorer] Fetching classes schema...");
const classesCounts =
await sparqlFetch<RawClassesWCountsResponse>(classesTemplate);

Expand All @@ -126,6 +131,7 @@ const fetchPredicatesWithCounts = async (
sparqlFetch: SparqlFetch
): Promise<Record<string, number>> => {
const template = predicatesWithCountsTemplate();
logger.log("[SPARQL Explorer] Fetching predicates with counts...");
const data = await sparqlFetch<RawPredicatesWCountsResponse>(template);

const values = data.results.bindings;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from "../../../utils";
import type {
ErrorResponse,
KeywordSearchResponse,
Expand Down Expand Up @@ -31,6 +32,8 @@ const keywordSearch = async (
req: SPARQLKeywordSearchRequest
): Promise<KeywordSearchResponse> => {
const template = keywordSearchTemplate(req);

logger.log("[SPARQL Explorer] Fetching search results...", req);
const data = await sparqlFetch<RawKeywordResponse | ErrorResponse>(template);

if (isErrorResponse(data)) {
Expand Down
52 changes: 33 additions & 19 deletions packages/graph-explorer/src/connector/sparql/sparqlExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ import {
} from "./types";
import { ConnectionConfig } from "../../core";
import { v4 } from "uuid";
import { env } from "../../utils";
import { env, logger } from "../../utils";

const replaceBlankNodeFromSearch = (
blankNodes: BlankNodesMap,
request: KeywordSearchRequest,
response: KeywordSearchResponse
) => {
logger.log(
"[SPARQL Explorer] Replacing blank node from search with keywordSearchBlankNodesIdsTemplate"
);
return response.vertices.map(vertex => {
if (!vertex.data.__isBlank) {
return vertex;
Expand All @@ -53,6 +56,9 @@ const replaceBlankNodeFromNeighbors = (
request: SPARQLNeighborsRequest,
response: KeywordSearchResponse
) => {
logger.log(
"[SPARQL Explorer] Replacing blank node from search with oneHopNeighborsBlankNodesIdsTemplate"
);
return response.vertices.map(vertex => {
if (!vertex.data.__isBlank) {
return vertex;
Expand Down Expand Up @@ -128,15 +134,16 @@ const storedBlankNodeNeighborsRequest = (

function _sparqlFetch(connection: ConnectionConfig, options?: any) {
return async (queryTemplate: string) => {
logger.debug(queryTemplate);
const body = `query=${encodeURIComponent(queryTemplate)}`;
const headers = options?.queryId
? {
accept: "application/json",
accept: "application/sparql-results+json",
"Content-Type": "application/x-www-form-urlencoded",
queryId: options.queryId,
}
: {
accept: "application/json",
accept: "application/sparql-results+json",
"Content-Type": "application/x-www-form-urlencoded",
};
return fetchDatabaseRequest(connection, `${connection.url}/sparql`, {
Expand All @@ -148,29 +155,36 @@ function _sparqlFetch(connection: ConnectionConfig, options?: any) {
};
}

async function fetchSummary(
connection: ConnectionConfig,
options: RequestInit
) {
try {
const response = await fetchDatabaseRequest(
connection,
`${connection.url}/rdf/statistics/summary?mode=detailed`,
{
method: "GET",
...options,
}
);

return (response?.payload?.graphSummary as GraphSummary) || undefined;
} catch (e) {
if (env.DEV) {
logger.error("[Summary API]", e);
}
}
}

export function createSparqlExplorer(
connection: ConnectionConfig,
blankNodes: BlankNodesMap
): Explorer {
return {
connection: connection,
async fetchSchema(options) {
let summary;
try {
const response = await fetchDatabaseRequest(
connection,
`${connection.url}/rdf/statistics/summary?mode=detailed`,
{
method: "GET",
...options,
}
);
summary = (response.payload.graphSummary as GraphSummary) || undefined;
} catch (e) {
if (env.DEV) {
console.error("[Summary API]", e);
}
}
const summary = await fetchSummary(connection, options);
return fetchSchema(_sparqlFetch(connection, options), summary);
},
async fetchVertexCountsByType(req, options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import dedent from "dedent";
// It returns the number of instances of the given class
export default function classWithCountsTemplates(className: string) {
return dedent`
# Fetch the number of instances of the given class
SELECT (COUNT(?start) AS ?instancesCount) {
?start a <${className}>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import dedent from "dedent";
// It returns a list of classes with the number of instances for each class
export default function classesWithCountsTemplates() {
return dedent`
# Fetch a list of classes with the number of instances for each class
SELECT ?class (COUNT(?start) AS ?instancesCount) {
?start a ?class
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dedent from "dedent";
import { SPARQLKeywordSearchRequest } from "../../types";
import {
getFilterObject,
Expand All @@ -19,7 +20,8 @@ export default function keywordSearchBlankNodesIdsTemplate({
offset = 0,
exactMatch = true,
}: SPARQLKeywordSearchRequest): string {
return `
return dedent`
# Fetch all blank nodes ids from a generic keyword search request
SELECT DISTINCT ?bNode {
?bNode ?pred ?value {
SELECT DISTINCT ?bNode {
Expand Down
Loading
Loading