Skip to content

Use mutation #855

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Upcoming

- **Added** query editor for Gremlin connections
([#878](https://github.com/aws/graph-explorer/pull/878),
[#855](https://github.com/aws/graph-explorer/pull/855))

## Release v1.15.0

Graph Explorer now offers session persistence, allowing you to seamlessly
Expand Down
1 change: 1 addition & 0 deletions packages/graph-explorer/src/connector/emptyExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ export const emptyExplorer: Explorer = {
edge: null,
};
},
rawQuery: async () => toMappedQueryResults({}),
};
11 changes: 11 additions & 0 deletions packages/graph-explorer/src/connector/gremlin/gremlinExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createLoggerFromConnection } from "@/core/connector";
import { FeatureFlags } from "@/core";
import { vertexDetails } from "./vertexDetails";
import { edgeDetails } from "./edgeDetails";
import { rawQuery } from "./rawQuery";

function _gremlinFetch(
connection: ConnectionConfig,
Expand Down Expand Up @@ -137,5 +138,15 @@ export function createGremlinExplorer(
);
return result;
},
async rawQuery(req, options) {
options ??= {};
options.queryId = v4();
remoteLogger.info("[Gremlin Explorer] Fetching raw query...");
const result = await rawQuery(
_gremlinFetch(connection, featureFlags, options),
req
);
return result;
},
} satisfies Explorer;
}
42 changes: 42 additions & 0 deletions packages/graph-explorer/src/connector/gremlin/rawQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { logger, query } from "@/utils";
import {
ErrorResponse,
RawQueryRequest,
RawQueryResponse,
toMappedQueryResults,
} from "../useGEFetchTypes";
import { GAnyValue, GremlinFetch } from "./types";
import { mapResults } from "./mappers/mapResults";
import isErrorResponse from "../utils/isErrorResponse";

type Response = {
requestId: string;
status: {
message: string;
code: number;
};
result: {
data: GAnyValue;
};
};

export async function rawQuery(
gremlinFetch: GremlinFetch,
request: RawQueryRequest
): Promise<RawQueryResponse> {
const template = query`${request.query}`;

if (template.length <= 0) {
return toMappedQueryResults({});
}

// Fetch the results
const data = await gremlinFetch<Response | ErrorResponse>(template);
if (isErrorResponse(data)) {
logger.error(data.detailedMessage);
throw new Error(data.detailedMessage);
}

// Map the results
return mapResults(data.result.data);
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ export function createOpenCypherExplorer(
req
);
},
// eslint-disable-next-line @typescript-eslint/require-await
async rawQuery(_req, _options) {
remoteLogger.info("[openCypher Explorer] Fetching raw query...");
throw new Error(
"Raw query functionality is not implemented for openCypher"
);
},
} satisfies Explorer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,10 @@ export function createSparqlExplorer(
req
);
},
// eslint-disable-next-line @typescript-eslint/require-await
async rawQuery(_req, _options) {
remoteLogger.info("[SPARQL Explorer] Fetching raw query...");
throw new Error("Raw query functionality is not implemented for SPARQL");
},
} satisfies Explorer;
}
10 changes: 10 additions & 0 deletions packages/graph-explorer/src/connector/useGEFetchTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ export type EdgeDetailsResponse = {
edge: Edge | null;
};

export type RawQueryRequest = {
query: string;
};

export type RawQueryResponse = MappedQueryResults;

/**
* Abstracted interface to the common database queries used by
* Graph Explorer.
Expand Down Expand Up @@ -262,4 +268,8 @@ export type Explorer = {
req: EdgeDetailsRequest,
options?: ExplorerRequestOptions
) => Promise<EdgeDetailsResponse>;
rawQuery: (
req: RawQueryRequest,
options?: ExplorerRequestOptions
) => Promise<RawQueryResponse>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
selectedVertexTypeAtom,
} from "@/modules/SearchSidebar/useKeywordSearch";
import { isRestorePreviousSessionAvailableAtom } from "./graphSession";
import { selectedTabAtom } from "@/modules/SearchSidebar";
import { queryTextAtom } from "@/modules/SearchSidebar/QuerySearchTabContent";

export default function useResetState() {
return useRecoilCallback(
Expand All @@ -44,6 +46,8 @@ export default function useResetState() {
reset(selectedVertexTypeAtom);
reset(selectedAttributeAtom);
reset(partialMatchAtom);
reset(selectedTabAtom);
reset(queryTextAtom);

// Previous session
reset(isRestorePreviousSessionAvailableAtom);
Expand Down
9 changes: 9 additions & 0 deletions packages/graph-explorer/src/core/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ export const allowLoggingDbQueryAtom = atom({
effects: [asyncLocalForageEffect("allowLoggingDbQuery")],
});

/** */
export const showQueryEditorAtom = atom({
key: "feature-flag-show-query-editor",
default: false,
effects: [asyncLocalForageEffect("showQueryEditor")],
});

export type FeatureFlags = {
showRecoilStateLogging: boolean;
showDebugActions: boolean;
allowLoggingDbQuery: boolean;
showQueryEditor: boolean;
};

export const featureFlagsSelector = selector<FeatureFlags>({
Expand All @@ -35,6 +43,7 @@ export const featureFlagsSelector = selector<FeatureFlags>({
showRecoilStateLogging: get(showRecoilStateLoggingAtom),
showDebugActions: get(showDebugActionsAtom),
allowLoggingDbQuery: get(allowLoggingDbQueryAtom),
showQueryEditor: get(showQueryEditorAtom),
} satisfies FeatureFlags;
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useKeywordSearch from "./useKeywordSearch";
import { SearchResultsList } from "./SearchResultsList";
import { LoadedResults } from "./LoadedResults";
import {
Select,
SelectTrigger,
Expand All @@ -11,9 +11,16 @@ import {
Label,
Checkbox,
Input,
LoadingSpinner,
PanelEmptyState,
PanelError,
SearchSadIcon,
} from "@/components";

import { useTranslations } from "@/hooks";
import { KeywordSearchResponse } from "@/connector";
import { UseQueryResult } from "@tanstack/react-query";
import { useCancelKeywordSearch } from "./useKeywordSearchQuery";

export function FilterSearchTabContent() {
const t = useTranslations();
Expand Down Expand Up @@ -112,3 +119,48 @@ export function FilterSearchTabContent() {
</div>
);
}

function SearchResultsList({
query,
}: {
query: UseQueryResult<KeywordSearchResponse | null, Error>;
}) {
const cancelAll = useCancelKeywordSearch();

if (query.isLoading) {
return (
<PanelEmptyState
title="Searching..."
subtitle="Looking for matching results"
actionLabel="Cancel"
onAction={() => cancelAll()}
icon={<LoadingSpinner />}
className="p-8"
/>
);
}

if (query.isError && !query.data) {
return (
<PanelError error={query.error} onRetry={query.refetch} className="p-8" />
);
}

if (
!query.data ||
(query.data.vertices.length === 0 &&
query.data.edges.length === 0 &&
query.data.scalars.length === 0)
) {
return (
<PanelEmptyState
title="No Results"
subtitle="Your criteria does not match with any record"
icon={<SearchSadIcon />}
className="p-8"
/>
);
}

return <LoadedResults {...query.data} />;
}
Original file line number Diff line number Diff line change
@@ -1,69 +1,17 @@
import {
Button,
ButtonProps,
PanelEmptyState,
LoadingSpinner,
PanelError,
SearchSadIcon,
PanelFooter,
Spinner,
} from "@/components";
import { KeywordSearchResponse, MappedQueryResults } from "@/connector";
import { UseQueryResult } from "@tanstack/react-query";
import { useCancelKeywordSearch } from "./useKeywordSearchQuery";
import { Button, ButtonProps, PanelFooter, Spinner } from "@/components";
import { MappedQueryResults } from "@/connector";
import { NodeSearchResult } from "./NodeSearchResult";
import { useAddToGraphMutation } from "@/hooks/useAddToGraph";
import { PlusCircleIcon } from "lucide-react";
import { EdgeSearchResult } from "./EdgeSearchResult";
import { ScalarSearchResult } from "./ScalarSearchResult";
import { Edge, Vertex } from "@/core";

export function SearchResultsList({
query,
}: {
query: UseQueryResult<KeywordSearchResponse | null, Error>;
}) {
const cancelAll = useCancelKeywordSearch();

if (query.isLoading) {
return (
<PanelEmptyState
title="Searching..."
subtitle="Looking for matching results"
actionLabel="Cancel"
onAction={() => cancelAll()}
icon={<LoadingSpinner />}
className="p-8"
/>
);
}

if (query.isError && !query.data) {
return (
<PanelError error={query.error} onRetry={query.refetch} className="p-8" />
);
}

if (
!query.data ||
(query.data.vertices.length === 0 &&
query.data.edges.length === 0 &&
query.data.scalars.length === 0)
) {
return (
<PanelEmptyState
title="No Results"
subtitle="Your criteria does not match with any record"
icon={<SearchSadIcon />}
className="p-8"
/>
);
}

return <LoadedResults {...query.data} />;
}

function LoadedResults({ vertices, edges, scalars }: MappedQueryResults) {
export function LoadedResults({
vertices,
edges,
scalars,
}: MappedQueryResults) {
const counts = [
vertices.length ? `${vertices.length} nodes` : null,
edges.length ? `${edges.length} edges` : null,
Expand Down
Loading