Skip to content
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
8 changes: 8 additions & 0 deletions packages/plugins/google-discovery/src/api/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
import { Schema } from "effect";
import { ScopeId } from "@executor/sdk";

import { GoogleDiscoveryStoredSourceSchema } from "../sdk/stored-source";

export { HttpApiSchema };

const scopeIdParam = HttpApiSchema.param("scopeId", ScopeId);
const namespaceParam = HttpApiSchema.param("namespace", Schema.String);

const AuthPayload = Schema.Union(
Schema.Struct({
Expand Down Expand Up @@ -125,4 +128,9 @@ export class GoogleDiscoveryGroup extends HttpApiGroup.make("googleDiscovery")
.addSuccess(HtmlResponse)
.addError(ApiError),
)
.add(
HttpApiEndpoint.get("getSource")`/scopes/${scopeIdParam}/google-discovery/sources/${namespaceParam}`
.addSuccess(Schema.NullOr(GoogleDiscoveryStoredSourceSchema))
.addError(ApiError),
)
{}
6 changes: 6 additions & 0 deletions packages/plugins/google-discovery/src/api/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ export const GoogleDiscoveryHandlers = HttpApiBuilder.group(
});
}).pipe(Effect.orDie),
)
.handle("getSource", ({ path }) =>
Effect.gen(function* () {
const ext = yield* GoogleDiscoveryExtensionService;
return yield* ext.getSource(path.namespace);
}).pipe(Effect.orDie),
)
.handle("oauthCallback", ({ urlParams }) =>
Effect.gen(function* () {
const ext = yield* GoogleDiscoveryExtensionService;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,78 @@
import { useAtomValue, Result } from "@effect-atom/atom-react";
import { useScope } from "@executor/react/api/scope-context";
import { Badge } from "@executor/react/components/badge";
import { Button } from "@executor/react/components/button";

import { googleDiscoverySourceAtom } from "./atoms";

export default function EditGoogleDiscoverySource({
sourceId,
onSave,
}: {
readonly sourceId: string;
readonly onSave: () => void;
}) {
const scopeId = useScope();
const sourceResult = useAtomValue(googleDiscoverySourceAtom(scopeId, sourceId));

const source = Result.isSuccess(sourceResult) ? sourceResult.value : null;
const config = source?.config;
const authKind = config?.auth.kind ?? "none";

return (
<div className="space-y-6">
<div>
<h1 className="text-xl font-semibold text-foreground">
Edit Google Discovery Source
</h1>
<p className="mt-1 text-[13px] text-muted-foreground">
This source is managed from its Discovery document and can be refreshed from the source header.
View configuration for this Google API source. To change authentication,
remove and re-add the source with updated OAuth credentials.
</p>
</div>

<div className="flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-3">
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-card-foreground">
{sourceId}
{source?.name ?? sourceId}
</p>
{config?.discoveryUrl && (
<p className="mt-0.5 text-xs text-muted-foreground font-mono truncate">
{config.discoveryUrl}
</p>
)}
</div>
<Badge variant="secondary" className="text-[10px]">
Google Discovery
</Badge>
</div>

{config && (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="rounded-lg border border-border bg-card/50 p-3">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground mb-1">Service</p>
<p className="text-sm font-medium text-foreground">
{config.service}
</p>
</div>
<div className="rounded-lg border border-border bg-card/50 p-3">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground mb-1">Version</p>
<p className="text-sm font-medium text-foreground">
{config.version}
</p>
</div>
</div>

<div className="rounded-lg border border-border bg-card/50 p-3">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground mb-1">Authentication</p>
<p className="text-sm font-medium text-foreground capitalize">
{authKind === "oauth2" ? "OAuth 2.0" : authKind}
</p>
</div>
</div>
)}

<div className="flex items-center justify-end border-t border-border pt-4">
<Button onClick={onSave}>Done</Button>
</div>
Expand Down
10 changes: 10 additions & 0 deletions packages/plugins/google-discovery/src/react/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import type { ScopeId } from "@executor/sdk";
import { GoogleDiscoveryClient } from "./client";

export const googleDiscoverySourceAtom = (
scopeId: ScopeId,
namespace: string,
) =>
GoogleDiscoveryClient.query("googleDiscovery", "getSource", {
path: { scopeId, namespace },
timeToLive: "15 seconds",
});

export const probeGoogleDiscovery = GoogleDiscoveryClient.mutation(
"googleDiscovery",
"probeDiscovery",
Expand Down
11 changes: 11 additions & 0 deletions packages/plugins/google-discovery/src/sdk/binding-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export interface GoogleDiscoveryBindingStore {
readonly putSource: (source: GoogleDiscoveryStoredSource) => Effect.Effect<void>;
readonly removeSource: (namespace: string) => Effect.Effect<void>;
readonly listSources: () => Effect.Effect<readonly GoogleDiscoveryStoredSource[]>;
readonly getSource: (
namespace: string,
) => Effect.Effect<GoogleDiscoveryStoredSource | null>;
readonly getSourceConfig: (
namespace: string,
) => Effect.Effect<GoogleDiscoveryStoredSourceData | null>;
Expand Down Expand Up @@ -120,6 +123,14 @@ const makeStore = (
return entries.map((e) => JSON.parse(e.value) as GoogleDiscoveryStoredSource);
}),

getSource: (namespace) =>
Effect.gen(function* () {
const raw = yield* sources.get(namespace);
if (!raw) return null;
// @effect-diagnostics-next-line preferSchemaOverJson:off
return JSON.parse(raw) as GoogleDiscoveryStoredSource;
}),

getSourceConfig: (namespace) =>
Effect.gen(function* () {
const raw = yield* sources.get(namespace);
Expand Down
8 changes: 8 additions & 0 deletions packages/plugins/google-discovery/src/sdk/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {

import type {
GoogleDiscoveryBindingStore,
GoogleDiscoveryStoredSource,
} from "./binding-store";
import { makeInMemoryBindingStore } from "./binding-store";
import { extractGoogleDiscoveryManifest } from "./document";
Expand Down Expand Up @@ -110,6 +111,9 @@ export interface GoogleDiscoveryPluginExtension {
readonly completeOAuth: (
input: GoogleDiscoveryOAuthCompleteInput,
) => Effect.Effect<GoogleDiscoveryOAuthAuthResult, GoogleDiscoveryOAuthError>;
readonly getSource: (
namespace: string,
) => Effect.Effect<GoogleDiscoveryStoredSource | null>;
}

const DISCOVERY_SERVICE_HOST = "https://www.googleapis.com/discovery/v1/apis";
Expand Down Expand Up @@ -293,6 +297,7 @@ export const googleDiscoveryPlugin = (options?: {
runtime: false,
canRemove: true,
canRefresh: true,
canEdit: true,
}),
),
),
Expand Down Expand Up @@ -558,6 +563,9 @@ export const googleDiscoveryPlugin = (options?: {
scopes: [...session.scopes],
};
}),

getSource: (namespace: string) =>
bindingStore.getSource(namespace),
};

return {
Expand Down
19 changes: 19 additions & 0 deletions packages/plugins/google-discovery/src/sdk/stored-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Schema } from "effect";

import { GoogleDiscoveryStoredSourceData } from "./types";

// ---------------------------------------------------------------------------
// Stored source — the shape persisted by the binding store and exposed
// via the getSource HTTP endpoint.
// ---------------------------------------------------------------------------

export class GoogleDiscoveryStoredSourceSchema extends Schema.Class<GoogleDiscoveryStoredSourceSchema>(
"GoogleDiscoveryStoredSource",
)({
namespace: Schema.String,
name: Schema.String,
config: GoogleDiscoveryStoredSourceData,
}) {}

export type GoogleDiscoveryStoredSourceSchemaType =
typeof GoogleDiscoveryStoredSourceSchema.Type;
Loading