Skip to content

Commit

Permalink
fix: populate verb right panel and verb schema (#1452)
Browse files Browse the repository at this point in the history
Updates:
- Populate right panel with ingress and calls
- Add verb schema tab to view the verb's schema
- Fix layout bugs on call list
- Refactor and reuse code

Fixes #734 
Fixes #1377 

![Screenshot 2024-05-09 at 1 02
11 PM](https://github.com/TBD54566975/ftl/assets/51647/5c7d2d52-af06-45c3-98cb-c4ecb0d3d7e3)
![Screenshot 2024-05-09 at 12 56
12 PM](https://github.com/TBD54566975/ftl/assets/51647/2040746d-c2f0-4860-9119-f47f67819e53)
  • Loading branch information
wesbillman authored May 10, 2024
1 parent 3066d64 commit ffa5eca
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 106 deletions.
43 changes: 41 additions & 2 deletions backend/controller/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,40 @@ func (*ConsoleService) Ping(context.Context, *connect.Request[ftlv1.PingRequest]
return connect.NewResponse(&ftlv1.PingResponse{}), nil
}

func visitNode(sch *schema.Schema, n schema.Node, verbString *string) error {
return schema.Visit(n, func(n schema.Node, next func() error) error {
switch n := n.(type) {
case *schema.Ref:
data, err := sch.ResolveRefMonomorphised(n)
if err != nil {
return err
}
*verbString += data.String() + "\n\n"

err = visitNode(sch, data, verbString)
if err != nil {
return err
}
default:
}
return next()
})
}

func verbSchemaString(sch *schema.Schema, verb *schema.Verb) (string, error) {
var verbString string
err := visitNode(sch, verb.Request, &verbString)
if err != nil {
return "", err
}
err = visitNode(sch, verb.Response, &verbString)
if err != nil {
return "", err
}
verbString += verb.String()
return verbString, nil
}

func (c *ConsoleService) GetModules(ctx context.Context, req *connect.Request[pbconsole.GetModulesRequest]) (*connect.Response[pbconsole.GetModulesResponse], error) {
deployments, err := c.dal.GetDeploymentsWithMinReplicas(ctx)
if err != nil {
Expand All @@ -65,7 +99,7 @@ func (c *ConsoleService) GetModules(ctx context.Context, req *connect.Request[pb
case *schema.Verb:
//nolint:forcetypeassert
v := decl.ToProto().(*schemapb.Verb)
verbSchema := schema.VerbFromProto(v) // TODO: include all of the types that the verb references
verbSchema := schema.VerbFromProto(v)
var jsonRequestSchema string
if requestData, ok := verbSchema.Request.(*schema.Ref); ok {
jsonSchema, err := schema.DataToJSONSchema(sch, *requestData)
Expand All @@ -78,9 +112,14 @@ func (c *ConsoleService) GetModules(ctx context.Context, req *connect.Request[pb
}
jsonRequestSchema = string(jsonData)
}

schemaString, err := verbSchemaString(sch, decl)
if err != nil {
return nil, err
}
verbs = append(verbs, &pbconsole.Verb{
Verb: v,
Schema: verbSchema.String(),
Schema: schemaString,
JsonRequestSchema: jsonRequestSchema,
})

Expand Down
76 changes: 76 additions & 0 deletions backend/controller/console_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package controller

import (
"testing"

"github.com/TBD54566975/ftl/backend/schema"
"github.com/alecthomas/assert/v2"
)

func TestVerbSchemaString(t *testing.T) {
verb := &schema.Verb{
Name: "Echo",
Request: &schema.Ref{Module: "foo", Name: "EchoRequest"},
Response: &schema.Ref{Module: "foo", Name: "EchoResponse"},
}
sch := &schema.Schema{
Modules: []*schema.Module{
{Name: "foo", Decls: []schema.Decl{
verb,
&schema.Data{
Name: "EchoRequest",
Fields: []*schema.Field{
{Name: "Name", Type: &schema.String{}},
{Name: "Nested", Type: &schema.Ref{Module: "foo", Name: "Nested"}},
{Name: "External", Type: &schema.Ref{Module: "bar", Name: "BarData"}},
},
},
&schema.Data{
Name: "EchoResponse",
Fields: []*schema.Field{
{Name: "Message", Type: &schema.String{}},
},
},
&schema.Data{
Name: "Nested",
Fields: []*schema.Field{
{Name: "Field", Type: &schema.String{}},
},
},
}},
{Name: "bar", Decls: []schema.Decl{
verb,
&schema.Data{
Name: "BarData",
Export: true,
Fields: []*schema.Field{
{Name: "Name", Type: &schema.String{}},
},
}},
}},
}

expected := `data EchoRequest {
Name String
Nested foo.Nested
External bar.BarData
}
data Nested {
Field String
}
export data BarData {
Name String
}
data EchoResponse {
Message String
}
verb Echo(foo.EchoRequest) foo.EchoResponse`

schemaString, err := verbSchemaString(sch, verb)
assert.NoError(t, err)
assert.Equal(t, expected, schemaString)
}
6 changes: 3 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"@bufbuild/protoc-gen-es": "1.9.0",
"@codemirror/commands": "^6.5.0",
"@codemirror/gutter": "^0.19.9",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.10.1",
"@codemirror/state": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2",
Expand All @@ -46,6 +45,7 @@
"@viz-js/viz": "3.4.0",
"codemirror": "^5.65.16",
"codemirror-json-schema": "^0.7.1",
"codemirror-json5": "^1.0.3",
"elkjs": "^0.9.2",
"fnv1a": "^1.1.1",
"highlight.js": "^11.8.0",
Expand Down
8 changes: 3 additions & 5 deletions frontend/src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { lintGutter } from '@codemirror/lint'
import { lintKeymap } from '@codemirror/lint'
import { linter } from '@codemirror/lint'
import {

indentOnInput,
bracketMatching,
foldGutter,
Expand All @@ -29,7 +28,7 @@ import {
} from '@codemirror/autocomplete'

import { useRef, useEffect, useCallback } from 'react'
import { json, jsonParseLinter } from '@codemirror/lang-json'
import { json5, json5ParseLinter } from 'codemirror-json5'
import { jsonSchemaLinter, jsonSchemaHover, stateExtensions, handleRefresh } from 'codemirror-json-schema'
import { useDarkMode } from '../providers/dark-mode-provider'
import { defaultKeymap } from '@codemirror/commands'
Expand Down Expand Up @@ -81,7 +80,7 @@ export const CodeEditor = (
indentOnInput(),
drawSelection(),
foldGutter(),
linter(jsonParseLinter(), {
linter(json5ParseLinter(), {
delay: 300
}),
linter(jsonSchemaLinter(), {
Expand All @@ -101,8 +100,7 @@ export const CodeEditor = (
extensions: [
commonExtensions,
isDarkMode ? atomone : githubLight,
json(),

json5(),
editingExtensions
],
})
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/RightPanelAttribute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const RightPanelAttribute = ({ name, value }: { name?: string, value?: string }) => {
return (
<div className='flex justify-between space-x-2 items-center text-sm'>
<span className='text-gray-500 dark:text-gray-400'>{name}</span>
<span className='flex-1 min-w-0 text-right' title={value}>
<pre className='whitespace-pre-wrap overflow-x-scroll'>{value}</pre>
</span>
</div>
)
}
9 changes: 4 additions & 5 deletions frontend/src/features/calls/CallList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ export const CallList = ({ calls }: { calls: CallEvent[] | undefined }) => {
{calls?.map((call, index) => (
<tr
key={`${index}-${call.timeStamp?.toDate().toUTCString()}`}
className={`border-b border-gray-100 dark:border-slate-700 font-roboto-mono ${
selectedCall?.equals(call) ? 'bg-indigo-50 dark:bg-slate-700' : ''
} relative flex cursor-pointer hover:bg-indigo-50 dark:hover:bg-slate-700`}
className={`border-b border-gray-100 dark:border-slate-700 font-roboto-mono
${selectedCall?.equals(call) ? 'bg-indigo-50 dark:bg-slate-700' : ''} relative flex cursor-pointer hover:bg-indigo-50 dark:hover:bg-slate-700`}
onClick={() => handleCallClicked(call)}
>
<td className='p-1 w-40 items-center flex-none text-gray-400 dark:text-gray-400'>
Expand All @@ -66,10 +65,10 @@ export const CallList = ({ calls }: { calls: CallEvent[] | undefined }) => {
<td className='p-1 w-14 items-center flex-none text-gray-400 dark:text-gray-400 truncate'>
{formatDuration(call.duration)}
</td>
<td className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300'>
<td className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300 truncate' title={call.sourceVerbRef && verbRefString(call.sourceVerbRef)}>
{call.sourceVerbRef && verbRefString(call.sourceVerbRef)}
</td>
<td className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300'>
<td className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300 truncate' title={call.destinationVerbRef && verbRefString(call.destinationVerbRef)}>
{call.destinationVerbRef && verbRefString(call.destinationVerbRef)}
</td>
<td className='p-1 flex-1 flex-grow truncate' title={call.request}>
Expand Down
10 changes: 2 additions & 8 deletions frontend/src/features/console/right-panel/ConfigPanels.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RightPanelAttribute } from '../../../components/RightPanelAttribute'
import { Config } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import { ExpandablePanelProps } from '../ExpandablePanel'

Expand All @@ -6,14 +7,7 @@ export const configPanels = (config: Config) => {
{
title: config.config?.name,
expanded: true,
children: (
<div className='flex justify-between items-center text-sm'>
<span>Type</span>
<span>
<pre>{config.config?.type?.value?.case}</pre>
</span>
</div>
),
children: <RightPanelAttribute name='Name' value={config.config?.name} />,
},
] as ExpandablePanelProps[]
}
51 changes: 4 additions & 47 deletions frontend/src/features/console/right-panel/ModulePanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ import {
import { Module } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import { ExpandablePanelProps } from '../ExpandablePanel'
import { CodeBlock } from '../../../components'

import { MetadataCalls } from '../../../protos/xyz/block/ftl/v1/schema/schema_pb'
import { NavigateFunction } from 'react-router-dom'

interface InCall {
module: string
verb?: string
}
import { RightPanelAttribute } from '../../../components/RightPanelAttribute'
import { callsIn, callsOut } from '../../modules/module.utils'

export const modulePanels = (
allModules: Module[],
Expand Down Expand Up @@ -52,12 +47,7 @@ export const modulePanels = (
title: 'Secrets',
expanded: false,
children: module.secrets.map((s, index) => (
<div key={`secret-${s.secret?.name}-${index}`} className='flex justify-between items-center text-sm'>
<span className='truncate pr-2'>{s.secret?.name}</span>
<span>
<pre>{s.secret?.type?.value?.case}</pre>
</span>
</div>
<RightPanelAttribute key={`secret-${s.secret?.name}-${index}`} name={s.secret?.name} value={s.secret?.type?.value?.case} />
)),
})
}
Expand All @@ -68,12 +58,7 @@ export const modulePanels = (
title: 'Configs',
expanded: false,
children: module.configs.map((c) => (
<div key={c.config?.name} className='flex justify-between items-center text-sm'>
<span className='truncate pr-2'>{c.config?.name}</span>
<span>
<pre>{c.config?.type?.value?.case}</pre>
</span>
</div>
<RightPanelAttribute key={c.config?.name} name={c.config?.name} value={c.config?.type?.value?.case} />
)),
})
}
Expand Down Expand Up @@ -119,31 +104,3 @@ export const modulePanels = (

return panels
}

const callsIn = (modules: Module[], module: Module) => {
const allCalls: InCall[] = []
modules.forEach((m) => {
m.verbs?.forEach((v) => {
v.verb?.metadata
.filter((meta) => meta.value.case === 'calls')
.map((meta) => meta.value.value as MetadataCalls)
.forEach((call) => {
call.calls.forEach((c) => {
if (c.module === module.name) {
allCalls.push({ module: m.name, verb: v.verb?.name })
}
})
})
})
})

return allCalls
}

const callsOut = (module: Module) => {
const calls = module.verbs?.flatMap((v) =>
v.verb?.metadata.filter((meta) => meta.value.case === 'calls').map((meta) => meta.value.value as MetadataCalls),
)

return calls
}
Empty file.
10 changes: 2 additions & 8 deletions frontend/src/features/console/right-panel/SecretPanels.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RightPanelAttribute } from '../../../components/RightPanelAttribute'
import { Secret } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import { ExpandablePanelProps } from '../ExpandablePanel'

Expand All @@ -6,14 +7,7 @@ export const secretPanels = (secret: Secret) => {
{
title: 'Details',
expanded: true,
children: (
<div className='flex justify-between items-center text-sm'>
<span>Type</span>
<span>
<pre>{secret.secret?.type?.value?.case}</pre>
</span>
</div>
),
children: <RightPanelAttribute name='Type' value={secret.secret?.type?.value?.case} />,
},
] as ExpandablePanelProps[]
}
Loading

0 comments on commit ffa5eca

Please sign in to comment.