History

feat(web): refresh source plugin screens and hot reload

Rhys Sullivan committed 7 months ago

Commit e7a8bd41 parent f1b09f317 files · +799 −943Browse files

Showing the changes to plugins/openapi (2 of 17 files). Show all

Unified · Split

2 files
  1. plugins/openapi/react/components.tsx +140 −125
  2. plugins/openapi/react/index.ts +7 −4
plugins/openapi/react/components.tsx+140−125
@@ -15,7 +15,6 @@ import { · 14 unmodified lines
15import {15import {
16 Alert,16 Alert,
17 Button,17 Button,
18 Card,
19 DocumentPanel,18 DocumentPanel,
20 IconPencil,19 IconPencil,
21 Input,20 Input,
@@ -149,7 +148,7 @@ const Section = (props: { · 127 unmodified lines
149 children: ReactNode;148 children: ReactNode;
150}) => (149}) => (
151 <section>150 <section>
152 <h2 className="mb-3 text-sm font-semibold text-foreground">{props.title}</h2>151 <h2 className="mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">{props.title}</h2>
153 {props.children}152 {props.children}
154 </section>153 </section>
155);154);
@@ -387,7 +386,7 @@ function OpenApiSourceForm(props: { · 231 unmodified lines
387 };386 };
388 387
389 return (388 return (
390 <Card className="space-y-6 p-6">389 <div className="space-y-6 rounded-lg border border-border bg-card p-6 text-sm ring-1 ring-foreground/[0.04]">
391 <Section title="Connection">390 <Section title="Connection">
392 <div className="grid gap-4">391 <div className="grid gap-4">
393 <div className="grid gap-2">392 <div className="grid gap-2">
@@ -477,59 +476,47 @@ function OpenApiSourceForm(props: { · 83 unmodified lines
477 </div>476 </div>
478 </Section>477 </Section>
479 478
480 <Section title="Preview">479 {(preview || previewMutation.status === "pending") && (
481 <div className="flex items-center gap-3">480 <Section title="Preview">
482 <Button481 {previewMutation.status === "pending" ? (
483 variant="outline"482 <div className="text-xs text-muted-foreground">Loading preview...</div>
484 type="button"483 ) : preview && (
485 onClick={() => {484 <div className="rounded-lg border border-border bg-muted/30 px-4 py-3 text-sm">
486 void runPreview({ mode: "manual" });485 <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 text-xs">
487 }}486 <dt className="font-medium text-foreground">Title</dt>
488 disabled={previewMutation.status === "pending" || submitMutation.status === "pending"}487 <dd className="text-muted-foreground">{preview.title ?? "—"}</dd>
489 >488 <dt className="font-medium text-foreground">Version</dt>
490 {previewMutation.status === "pending" ? "Previewing..." : "Preview Spec"}489 <dd className="text-muted-foreground">{preview.version ?? "—"}</dd>
491 </Button>490 <dt className="font-medium text-foreground">Operations</dt>
492 {preview && (491 <dd className="text-muted-foreground">{preview.operationCount}</dd>
493 <div className="text-xs text-muted-foreground">492 <dt className="font-medium text-foreground">Base URL</dt>
494 {preview.operationCount} operations493 <dd className="font-mono text-muted-foreground">{preview.baseUrl ?? "—"}</dd>
495 {preview.version ? ` · v${preview.version}` : ""}494 {preview.namespace && (
495 <>
496 <dt className="font-medium text-foreground">Namespace</dt>
497 <dd className="text-muted-foreground">{preview.namespace}</dd>
498 </>
499 )}
500 {preview.securitySchemes.length > 0 && (
501 <>
502 <dt className="font-medium text-foreground">Auth</dt>
503 <dd className="text-muted-foreground">
504 {preview.securitySchemes
505 .map((scheme) => `${scheme.name} (${previewSecuritySchemeLabel(scheme)})`)
506 .join(", ")}
507 </dd>
508 </>
509 )}
510 </dl>
511 {preview.warnings.length > 0 && (
512 <Alert variant="warning" className="mt-3 text-xs">
513 {preview.warnings.join(" ")}
514 </Alert>
515 )}
496 </div>516 </div>
497 )}517 )}
498 </div>518 </Section>
499 {preview && (519 )}
500 <div className="mt-3 rounded-lg border border-border bg-muted/30 px-4 py-3 text-sm">
501 <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 text-xs">
502 <dt className="font-medium text-foreground">Title</dt>
503 <dd className="text-muted-foreground">{preview.title ?? "—"}</dd>
504 <dt className="font-medium text-foreground">Version</dt>
505 <dd className="text-muted-foreground">{preview.version ?? "—"}</dd>
506 <dt className="font-medium text-foreground">Base URL</dt>
507 <dd className="font-mono text-muted-foreground">{preview.baseUrl ?? "—"}</dd>
508 {preview.namespace && (
509 <>
510 <dt className="font-medium text-foreground">Namespace</dt>
511 <dd className="text-muted-foreground">{preview.namespace}</dd>
512 </>
513 )}
514 {preview.securitySchemes.length > 0 && (
515 <>
516 <dt className="font-medium text-foreground">Auth</dt>
517 <dd className="text-muted-foreground">
518 {preview.securitySchemes
519 .map((scheme) => `${scheme.name} (${previewSecuritySchemeLabel(scheme)})`)
520 .join(", ")}
521 </dd>
522 </>
523 )}
524 </dl>
525 {preview.warnings.length > 0 && (
526 <Alert variant="warning" className="mt-3 text-xs">
527 {preview.warnings.join(" ")}
528 </Alert>
529 )}
530 </div>
531 )}
532 </Section>
533 520
534 {error && (521 {error && (
535 <Alert variant="destructive">522 <Alert variant="destructive">
@@ -548,7 +535,7 @@ function OpenApiSourceForm(props: { · 12 unmodified lines
548 {submitMutation.status === "pending" ? props.busyLabel : props.submitLabel}535 {submitMutation.status === "pending" ? props.busyLabel : props.submitLabel}
549 </Button>536 </Button>
550 </div>537 </div>
551 </Card>538 </div>
552 );539 );
553}540}
554 541
@@ -564,35 +551,50 @@ export function OpenApiAddSourcePage() { · 9 unmodified lines
564 551
565 if (!workspace.enabled) {552 if (!workspace.enabled) {
566 return (553 return (
567 <Section title="Add Source">554 <div className="min-h-0 flex-1 overflow-y-auto">
568 <div className="text-sm text-muted-foreground">Loading workspace...</div>555 <div className="mx-auto w-full max-w-3xl px-6 py-10">
569 </Section>556 <div className="text-sm text-muted-foreground">Loading workspace...</div>
557 </div>
558 </div>
570 );559 );
571 }560 }
572 561
573 return (562 return (
574 <OpenApiSourceForm563 <div className="min-h-0 flex-1 overflow-y-auto">
575 mode="create"564 <div className="mx-auto w-full max-w-3xl px-6 py-10">
576 workspaceId={workspace.workspaceId}565 <div className="mb-8">
577 initialValue={initialValue}566 <h1 className="font-display text-2xl tracking-tight text-foreground">
578 submitLabel="Create Source"567 Add OpenAPI Source
579 busyLabel="Creating..."568 </h1>
580 onSubmit={async (payload) => {569 <p className="mt-1.5 text-sm leading-6 text-muted-foreground">
581 const source = await createSource({570 Import operations from an OpenAPI specification as callable tools.
582 path: { workspaceId: workspace.workspaceId },571 </p>
583 payload,572 </div>
584 reactivityKeys: {573
585 sources: [workspace.workspaceId],574 <OpenApiSourceForm
586 },575 mode="create"
587 });576 workspaceId={workspace.workspaceId}
588 577 initialValue={initialValue}
589 startTransition(() => {578 submitLabel="Create Source"
590 void navigation.detail(source.id, {579 busyLabel="Creating..."
591 tab: "model",580 onSubmit={async (payload) => {
592 });581 const source = await createSource({
593 });582 path: { workspaceId: workspace.workspaceId },
594 }}583 payload,
595 />584 reactivityKeys: {
585 sources: [workspace.workspaceId],
586 },
587 });
588
589 startTransition(() => {
590 void navigation.detail(source.id, {
591 tab: "model",
592 });
593 });
594 }}
595 />
596 </div>
597 </div>
596 );598 );
597}599}
598 600
@@ -603,9 +605,11 @@ export function OpenApiEditSourcePage(props: { · 4 unmodified lines
603 605
604 if (!workspace.enabled) {606 if (!workspace.enabled) {
605 return (607 return (
606 <Section title="Edit Source">608 <div className="min-h-0 flex-1 overflow-y-auto">
607 <div className="text-sm text-muted-foreground">Loading workspace...</div>609 <div className="mx-auto w-full max-w-3xl px-6 py-10">
608 </Section>610 <div className="text-sm text-muted-foreground">Loading workspace...</div>
611 </div>
612 </div>
609 );613 );
610 }614 }
611 615
@@ -641,53 +645,64 @@ function OpenApiEditSourcePageReady(props: { · 29 unmodified lines
641 );645 );
642 646
643 if (!Result.isSuccess(configResult)) {647 if (!Result.isSuccess(configResult)) {
644 if (Result.isFailure(configResult)) {
645 return (
646 <Section title="Edit Source">
647 <Alert variant="destructive">
648 Failed loading source configuration.
649 </Alert>
650 </Section>
651 );
652 }
653
654 return (648 return (
655 <Section title="Edit Source">649 <div className="min-h-0 flex-1 overflow-y-auto">
656 <div className="text-sm text-muted-foreground">Loading configuration...</div>650 <div className="mx-auto w-full max-w-3xl px-6 py-10">
657 </Section>651 {Result.isFailure(configResult) ? (
652 <Alert variant="destructive">
653 Failed loading source configuration.
654 </Alert>
655 ) : (
656 <div className="text-sm text-muted-foreground">Loading configuration...</div>
657 )}
658 </div>
659 </div>
658 );660 );
659 }661 }
660 662
661 return (663 return (
662 <OpenApiSourceForm664 <div className="min-h-0 flex-1 overflow-y-auto">
663 mode="edit"665 <div className="mx-auto w-full max-w-3xl px-6 py-10">
664 workspaceId={props.workspaceId}666 <div className="mb-8">
665 initialValue={inputFromConfig(configResult.value)}667 <h1 className="font-display text-2xl tracking-tight text-foreground">
666 submitLabel="Save Changes"668 Edit OpenAPI Source
667 busyLabel="Saving..."669 </h1>
668 onSubmit={async (config) => {670 <p className="mt-1.5 text-sm leading-6 text-muted-foreground">
669 const source = await updateSource({671 Update the connection settings for {props.source.name}.
670 path: {672 </p>
671 workspaceId: props.workspaceId,673 </div>
672 sourceId: props.source.id,674
673 },675 <OpenApiSourceForm
674 payload: config,676 mode="edit"
675 reactivityKeys: {677 workspaceId={props.workspaceId}
676 sources: [props.workspaceId],678 initialValue={inputFromConfig(configResult.value)}
677 source: [props.workspaceId, props.source.id],679 submitLabel="Save Changes"
678 sourceInspection: [props.workspaceId, props.source.id],680 busyLabel="Saving..."
679 sourceInspectionTool: [props.workspaceId, props.source.id],681 onSubmit={async (config) => {
680 sourceDiscovery: [props.workspaceId, props.source.id],682 const source = await updateSource({
681 },683 path: {
682 });684 workspaceId: props.workspaceId,
683 685 sourceId: props.source.id,
684 startTransition(() => {686 },
685 void navigation.detail(source.id, {687 payload: config,
686 tab: "model",688 reactivityKeys: {
687 });689 sources: [props.workspaceId],
688 });690 source: [props.workspaceId, props.source.id],
689 }}691 sourceInspection: [props.workspaceId, props.source.id],
690 />692 sourceInspectionTool: [props.workspaceId, props.source.id],
693 sourceDiscovery: [props.workspaceId, props.source.id],
694 },
695 });
696
697 startTransition(() => {
698 void navigation.detail(source.id, {
699 tab: "model",
700 });
701 });
702 }}
703 />
704 </div>
705 </div>
691 );706 );
692}707}
693 708
plugins/openapi/react/index.ts+7−4
@@ -1,4 +1,7 @@
1import { defineExecutorFrontendPlugin } from "@executor/react/plugins";1import {
2 defineExecutorFrontendPlugin,
3 liveFrontendPluginComponent,
4} from "@executor/react/plugins";
2 5
3import {6import {
4 OpenApiAddSourcePage,7 OpenApiAddSourcePage,
@@ -13,17 +16,17 @@ export const OpenApiReactPlugin = defineExecutorFrontendPlugin({ · 8 unmodified lines
13 {16 {
14 key: "add",17 key: "add",
15 path: "add",18 path: "add",
16 component: OpenApiAddSourcePage,19 component: liveFrontendPluginComponent(() => OpenApiAddSourcePage),
17 },20 },
18 {21 {
19 key: "detail",22 key: "detail",
20 path: "sources/$sourceId",23 path: "sources/$sourceId",
21 component: OpenApiDetailRoute,24 component: liveFrontendPluginComponent(() => OpenApiDetailRoute),
22 },25 },
23 {26 {
24 key: "edit",27 key: "edit",
25 path: "sources/$sourceId/edit",28 path: "sources/$sourceId/edit",
26 component: OpenApiEditRoute,29 component: liveFrontendPluginComponent(() => OpenApiEditRoute),
27 },30 },
28 ],31 ],
29});32});