Skip to content

Commit f35741f

Browse files
Adam Erbsvc-squareup-copybara
authored andcommitted
Support callable GET & PUT misk actions
GitOrigin-RevId: 7a11efdc1d8d0e1974f12d527b26a918c604e9ff
1 parent d9731aa commit f35741f

File tree

7 files changed

+193
-88
lines changed

7 files changed

+193
-88
lines changed

misk-admin/web-actions/src/index.tsx

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState, useRef } from 'react';
1+
import React, { useEffect, useState, useRef, useCallback } from 'react';
22

33
import { createRoot } from 'react-dom/client';
44
import RequestEditor from '@web-actions/ui/RequestEditor';
@@ -9,6 +9,8 @@ import {
99
Spinner,
1010
VStack,
1111
Heading,
12+
Input,
13+
IconButton,
1214
} from '@chakra-ui/react';
1315
import ReadOnlyEditor from '@web-actions/ui/ReadOnlyViewer';
1416
import 'ace-builds';
@@ -19,21 +21,39 @@ import EndpointSelector, {
1921
import { ViewState } from 'src/viewState';
2022
import { fetchCached } from '@web-actions/network/http';
2123
import { MiskMetadataResponse } from '@web-actions/api/responseTypes';
24+
import { Select } from '@chakra-ui/react';
25+
import { createIcon } from '@chakra-ui/icons';
2226

2327
const endpointSelectionCallbacks: EndpointSelectionCallbacks = [];
2428

2529
function App() {
2630
const [viewState, setViewState] = useState<ViewState>({
31+
path: '',
2732
selectedAction: null,
2833
response: null,
34+
callables: [],
2935
});
3036
const [loading, setLoading] = useState<boolean>(true);
3137
const endPointSelectorRef = useRef<EndpointSelector>();
3238
const requestEditorRef = useRef<RequestEditor>();
3339

3440
useEffect(() => {
3541
endpointSelectionCallbacks.push((selectedAction) => {
36-
setViewState((curr) => ({ ...curr, selectedAction }));
42+
const callables = selectedAction.getCallablesByMethod();
43+
const defaultCallable = callables[0];
44+
45+
requestEditorRef.current?.setEndpointSelection(defaultCallable);
46+
47+
setViewState((curr) => ({
48+
...curr,
49+
selectedAction: selectedAction,
50+
path:
51+
defaultCallable?.pathPattern ||
52+
selectedAction.all[0]?.pathPattern ||
53+
'',
54+
selectedCallable: defaultCallable,
55+
callables: callables,
56+
}));
3757
});
3858
}, []);
3959

@@ -43,6 +63,8 @@ function App() {
4363
if (isShortcutKey && event.key === 'k') {
4464
event.preventDefault();
4565
endPointSelectorRef.current?.focusSelect();
66+
} else if (isShortcutKey && event.key === 'Enter') {
67+
requestEditorRef.current?.submitRequest();
4668
}
4769
};
4870
document.addEventListener('keydown', handleKeyPress);
@@ -54,7 +76,11 @@ function App() {
5476
fetchCached<MiskMetadataResponse>(`/api/web-actions/metadata`).finally(() => {
5577
setLoading(false);
5678
});
57-
79+
const ActionIcon = createIcon({
80+
displayName: 'ActionIcon',
81+
viewBox: '0 0 24 24',
82+
path: <polygon points="5 3 19 12 5 21 5 3" fill="currentColor" />,
83+
});
5884
return (
5985
<Box>
6086
{loading && (
@@ -92,6 +118,60 @@ function App() {
92118
<Heading color="white" size="sm" fontWeight="semibold">
93119
Request
94120
</Heading>
121+
<HStack flexGrow={1} w="100%">
122+
{viewState.callables.length > 0 && (
123+
<Select
124+
value={viewState.selectedCallable?.httpMethod}
125+
bg="white"
126+
width="fit-content"
127+
minWidth="fit-content"
128+
onChange={(e) => {
129+
const selected = viewState.callables.find(
130+
(it) => it.httpMethod === e.target.value,
131+
);
132+
requestEditorRef.current?.setEndpointSelection(selected);
133+
setViewState({
134+
...viewState,
135+
path: selected?.pathPattern || '',
136+
selectedCallable: selected,
137+
});
138+
}}
139+
>
140+
{viewState.callables.map((callable) => (
141+
<option
142+
key={callable.httpMethod}
143+
value={callable.httpMethod}
144+
>
145+
{callable.httpMethod}
146+
</option>
147+
))}
148+
</Select>
149+
)}
150+
<Input
151+
value={viewState.path}
152+
placeholder="Path"
153+
bg="white"
154+
onChange={(e) => {
155+
requestEditorRef.current?.setPath(e.target.value);
156+
setViewState({
157+
...viewState,
158+
path: e.target.value,
159+
});
160+
}}
161+
/>
162+
{viewState.selectedCallable && (
163+
<IconButton
164+
aria-label="Run"
165+
colorScheme={'green'}
166+
onClick={() => {}}
167+
>
168+
<ActionIcon />
169+
</IconButton>
170+
)}
171+
</HStack>
172+
<Heading color="white" size="xs" fontWeight="semibold">
173+
Body
174+
</Heading>
95175
<RequestEditor
96176
ref={requestEditorRef as any}
97177
endpointSelectionCallbacks={endpointSelectionCallbacks}
@@ -106,7 +186,7 @@ function App() {
106186
</VStack>
107187
<VStack height="100%" flexGrow={1} alignItems="start">
108188
<Heading color="white" size="sm" fontWeight="semibold">
109-
Endpoint Details
189+
Endpoint Metadata
110190
</Heading>
111191
<ReadOnlyEditor
112192
content={() => {
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
import { ActionGroup } from '@web-actions/api/responseTypes';
1+
import {
2+
ActionGroup,
3+
MiskWebActionDefinition,
4+
} from '@web-actions/api/responseTypes';
25

36
export interface ViewState {
7+
callables: MiskWebActionDefinition[];
8+
selectedCallable?: MiskWebActionDefinition;
9+
path: string;
410
selectedAction: ActionGroup | null;
511
response: string | null;
612
}

misk-admin/web-actions/src/web-actions/api/RealMetadataClient.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,58 @@ import {
22
ActionGroup,
33
MiskActions,
44
MiskMetadataResponse,
5+
MiskWebActionDefinition,
56
} from '@web-actions/api/responseTypes';
67
import { fetchCached } from '@web-actions/network/http';
78
import MetadataClient from '@web-actions/api/MetadataClient';
89

10+
function containsJson(mediaType: string[]) {
11+
return mediaType.some((it) => it.startsWith('application/json'));
12+
}
13+
914
export default class RealMetadataClient implements MetadataClient {
1015
async fetchMetadata(): Promise<MiskActions> {
1116
const response = await fetchCached<MiskMetadataResponse>(
1217
`/api/web-actions/metadata`,
1318
);
1419
const actionMap: Record<string, ActionGroup> = {};
1520
response.all['web-actions'].metadata.forEach((it) => {
16-
let group = actionMap[it.name];
21+
const qualifiedName = it.packageName + '.' + it.name;
22+
23+
let group = actionMap[qualifiedName];
1724
if (group === undefined) {
18-
group = { name: it.name, all: [] };
19-
actionMap[it.name] = group;
25+
group = {
26+
name: qualifiedName,
27+
callables: {},
28+
getCallablesByMethod(): MiskWebActionDefinition[] {
29+
return [
30+
this.callables['POST'],
31+
this.callables['PUT'],
32+
this.callables['GET'],
33+
].filter((it) => it !== undefined);
34+
},
35+
all: [],
36+
};
37+
actionMap[qualifiedName] = group;
2038
}
2139

22-
if (
23-
it.requestMediaTypes.some((mediaType) =>
24-
mediaType.startsWith('application/json'),
25-
)
40+
function maybeAddCallable(
41+
method: string,
42+
predicate: (it: MiskWebActionDefinition) => boolean = () => true,
2643
) {
27-
group.defaultCallable = it;
44+
if (
45+
it.httpMethod === method &&
46+
group.callables[method] === undefined &&
47+
predicate(it)
48+
) {
49+
group.callables[method] = it;
50+
}
2851
}
2952

53+
maybeAddCallable('POST', (it) => containsJson(it.requestMediaTypes));
54+
maybeAddCallable('PUT', (it) => containsJson(it.requestMediaTypes));
55+
maybeAddCallable('GET');
56+
3057
group.all.push(it);
3158
});
3259

misk-admin/web-actions/src/web-actions/api/responseTypes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export interface MiskMetadataResponse {
88

99
export interface MiskWebActionDefinition {
1010
name: string;
11+
httpMethod?: string;
12+
packageName: string;
1113
requestType: string;
1214
pathPattern: string;
1315
types: MiskObjectTypes;
@@ -27,7 +29,8 @@ export interface MiskFieldDefinition {
2729

2830
export interface ActionGroup {
2931
name: string;
30-
defaultCallable?: MiskWebActionDefinition;
32+
callables: Record<string, MiskWebActionDefinition>;
33+
getCallablesByMethod(): MiskWebActionDefinition[];
3134
all: MiskWebActionDefinition[];
3235
}
3336

misk-admin/web-actions/src/web-actions/completion/__test__/FakeMetadataClient.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import MetadataClient from '@web-actions/api/MetadataClient';
2-
import { MiskActions } from '@web-actions/api/responseTypes';
1+
import { MiskWebActionDefinition } from '@web-actions/api/responseTypes';
32

4-
export const MyAction = {
3+
export const MyAction: MiskWebActionDefinition = {
54
name: 'MyAction',
5+
packageName: 'xyz.block',
66
requestType: 'MyActionRequest',
77
pathPattern: '/api/v1/my-action',
88
requestMediaTypes: ['application/x-protobuf'],
@@ -34,21 +34,6 @@ export const MyAction = {
3434
annotations: [],
3535
},
3636
],
37-
'my-object-type': {
38-
fields: [],
39-
},
4037
},
4138
},
4239
};
43-
44-
export default class FakeMetadataClient implements MetadataClient {
45-
async fetchMetadata(): Promise<MiskActions> {
46-
return Promise.resolve({
47-
MyAction: {
48-
name: 'MyAction',
49-
defaultCallable: MyAction,
50-
all: [MyAction],
51-
},
52-
});
53-
}
54-
}

misk-admin/web-actions/src/web-actions/ui/EndpointSelection.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@ export default class EndpointSelection extends React.Component<Props, State> {
4141

4242
componentDidMount() {
4343
this.metadataClient.fetchMetadata().then((actions: MiskActions) => {
44-
const options = Object.entries(actions).map(([key, value]) => ({
45-
value,
46-
label: key,
47-
}));
44+
const options = Object.entries(actions)
45+
.map(([key, value]) => ({
46+
value: value,
47+
label: key,
48+
}))
49+
.sort((a, b) => a.value.name.localeCompare(b.value.name));
4850
this.fuse = new Fuse(options, {
4951
keys: ['label'],
50-
threshold: 0.3,
5152
useExtendedSearch: true,
5253
});
5354
this.setState({

0 commit comments

Comments
 (0)