Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d2e617a
feat : expose response headers in DocsService
hyunw9 Apr 5, 2025
ed925b2
fix : restore removed comments
hyunw9 Apr 5, 2025
0c5f150
fix: Define Header type for better clarity
hyunw9 Apr 9, 2025
d788723
feat : define responseData
hyunw9 Apr 13, 2025
23fac82
feat : modify response
hyunw9 Apr 13, 2025
9b9921c
feat : modify responseData
hyunw9 Apr 13, 2025
718f68e
feat : remove unused method : extractHeaders()
hyunw9 Apr 14, 2025
9705ab2
feat : edit response Form
hyunw9 Apr 14, 2025
296645f
Merge pull request #2 from hyunw9/feat-define-ResponseData
hyunw9 Apr 14, 2025
08f344b
feat : Update unnecessary useEffect
hyunw9 Apr 16, 2025
56e3d2c
feat : Deduplicate render logic
hyunw9 Apr 16, 2025
46af150
faet : Revert header variable name
hyunw9 Apr 16, 2025
8306d0c
feat : Delete fallback parameter
hyunw9 Apr 16, 2025
62839b8
feat : Deduplicate header build logic
hyunw9 Apr 16, 2025
add4c40
Merge pull request #4 from hyunw9/feat/update-review
hyunw9 Apr 16, 2025
5e1fa52
feat : Declare list available header types
hyunw9 Apr 19, 2025
690f573
feat : Change headers type from Map to Array
hyunw9 Apr 19, 2025
61f864c
feat : Delete parsing logic
hyunw9 Apr 19, 2025
abdad9b
feat : Redesign header extracting logic
hyunw9 Apr 19, 2025
a9b8f6e
feat : Change method name
hyunw9 Apr 19, 2025
5836119
feat : Delete json parse logic
hyunw9 Apr 19, 2025
a9bab84
feat : Update header type
hyunw9 Apr 19, 2025
e0cef46
Merge pull request #6 from hyunw9/feat/update-review
hyunw9 Apr 19, 2025
aa14f38
feat : Expand ResponseData
hyunw9 Apr 29, 2025
a7dbee2
feat : Add Status, ExecutionTime, ResponseSize, TimeStamp
hyunw9 Apr 29, 2025
8f2f600
feat : Apply additional Component to DebugPage
hyunw9 Apr 29, 2025
5260cee
feat : Update Cache, Error handling logic
hyunw9 Apr 29, 2025
295debf
feat : Deduplicate preparing `ResponseData`
hyunw9 Apr 29, 2025
b4309dc
feat : Deduplicate responseData status bar logic
hyunw9 May 12, 2025
67bf1fd
feat : Update docs-client/src/lib/json-util.ts
hyunw9 May 13, 2025
7f8a887
feat : Delete static header set
hyunw9 May 13, 2025
d1ac966
Merge branch 'add-Response-Header' of https://github.com/hyunw9/armer…
hyunw9 May 13, 2025
52f2271
Merge branch 'main' into add-Response-Header
hyunw9 May 29, 2025
355f172
Merge branch 'main' into add-Response-Header
hyunw9 Jun 15, 2025
d0d668e
Merge branch 'main' into add-Response-Header
hyunw9 Jun 17, 2025
0f1501c
chore : Remove unused import pharase
hyunw9 Jun 17, 2025
25614a5
Merge branch 'add-Response-Header' of https://github.com/hyunw9/armer…
hyunw9 Jun 17, 2025
8763d92
fix : Adjust header info layout
hyunw9 Jun 22, 2025
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
154 changes: 132 additions & 22 deletions docs-client/src/containers/MethodPage/DebugPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ const DebugPage: React.FunctionComponent<Props> = ({
const [requestBody, setRequestBody] = useState('');
const [debugResponse, setDebugResponse] = useState('');
const [additionalQueries, setAdditionalQueries] = useState('');
const [debugResponseHeaders, setDebugResponseHeaders] = useState<
[string, string[]][]
>([]);
const [additionalPath, setAdditionalPath] = useState('');
const [additionalHeaders, setAdditionalHeaders] = useState('');
const [stickyHeaders, toggleStickyHeaders] = useReducer(toggle, false);
Expand All @@ -159,13 +162,36 @@ const DebugPage: React.FunctionComponent<Props> = ({
false,
);

const [currentApiId, setCurrentApiId] = useState<string>(
method.id || method.name,
);
const [responseCache, setResponseCache] = useState<
Record<string, { body: string; headers: Map<string, string[]> }>
>({});

const classes = useStyles();

const transport = TRANSPORTS.getDebugTransport(method);
if (!transport) {
throw new Error("This method doesn't have a debug transport.");
}

useEffect(() => {
Comment thread
hyunw9 marked this conversation as resolved.
const apiId = method.id || method.name;
if (apiId !== currentApiId) {
setCurrentApiId(apiId);
if (responseCache[apiId]) {
setDebugResponse(responseCache[apiId].body);
setDebugResponseHeaders(
Array.from(responseCache[apiId].headers.entries()),
);
} else {
setDebugResponse('');
setDebugResponseHeaders([]);
}
}
}, [method, currentApiId, responseCache]);

useEffect(() => {
const urlParams = new URLSearchParams(location.search);

Expand Down Expand Up @@ -202,6 +228,7 @@ const DebugPage: React.FunctionComponent<Props> = ({

if (!keepDebugResponse) {
setDebugResponse('');
setDebugResponseHeaders([]);
toggleKeepDebugResponse(false);
}
setSnackbarOpen(false);
Expand Down Expand Up @@ -307,23 +334,23 @@ const DebugPage: React.FunctionComponent<Props> = ({
escapeSingleQuote(requestBody),
);

const headers = new Headers();
headers.set('content-type', transport.getDebugMimeType());
const headersObj = new Headers();
Comment thread
hyunw9 marked this conversation as resolved.
Outdated
headersObj.set('content-type', transport.getDebugMimeType());
if (process.env.WEBPACK_DEV === 'true') {
headers.set(docServiceDebug, 'true');
headersObj.set(docServiceDebug, 'true');
}
if (serviceType === ServiceType.GRAPHQL) {
headers.set('accept', 'application/json');
headersObj.set('accept', 'application/json');
}
if (additionalHeaders) {
const entries = Object.entries(JSON.parse(additionalHeaders));
entries.forEach(([key, value]) => {
headers.set(key, String(value));
headersObj.set(key, String(value));
});
}

const headerOptions: string[] = [];
headers.forEach((value, key) => {
headersObj.forEach((value, key) => {
headerOptions.push(`-H '${key}: ${value}'`);
});

Expand Down Expand Up @@ -364,6 +391,7 @@ const DebugPage: React.FunctionComponent<Props> = ({

const onClear = useCallback(() => {
setDebugResponse('');
setDebugResponseHeaders([]);
}, []);

const executeRequest = useCallback(
Expand All @@ -390,24 +418,29 @@ const DebugPage: React.FunctionComponent<Props> = ({
const headersText = params.get('headers');
const headers = headersText ? JSON.parse(headersText) : {};

let executedDebugResponse;
try {
executedDebugResponse = await transport.send(
const { body, headers: responseHeaders } = await transport.send(
method,
headers,
parseServerRootPath(docServiceRoute),
executedRequestBody,
executedEndpointPath,
queries,
);
setDebugResponse(body);
setDebugResponseHeaders(Array.from(responseHeaders.entries()));
setResponseCache((prev) => ({
...prev,
[currentApiId]: {
body,
headers: responseHeaders,

@ikhoon ikhoon Apr 22, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more useful if we could record the execution time and display it in the debug console. Some may want to know whether the response is outdated or up-to-date.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think showing the execution time on the debug page could improve the user experience!

If we decide to display it, where would be the best place to show the execution time?
I was thinking of showing it in a separate section, similar to other API testing tools.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds good to me. Could you prototype your idea? I’d like to see how it looks and feels.

@hyunw9 hyunw9 Apr 23, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In modern API Testing tools, they display Response status, execution time, and ResponseData size.

So i was thinking what if we displayed those three information on the right section of response page Util.

Examples :

Postman Bruno

Prototype :

ResponsePage DebugConsole

More specifically, I thought of two possible versions :

Text-like Colored

--- Updated ---
I put together a quick prototype implementation. Let me know what you think !

Fail Success

Executed time should be rounded tho :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prototype looks great! Since the results are cached, would you mind also adding the timestamp of when the request was executed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! It’s necessary when we’re caching ResponseData.
I’ve just added the update :)

  1. As previously discussed, the cache now stores the entire ResponseData object instead of selected values.
  2. Updated the DebugPage to display the newly added fields from ResponseData. The logic for determining colors is as follows: Diff

Here are some examples pictures:

2xx 4xx Invalid

},
}));
} catch (e) {
if (e instanceof Object) {
executedDebugResponse = e.toString();
} else {
executedDebugResponse = '<unknown>';
}
const message = e instanceof Object ? e.toString() : '<unknown>';
setDebugResponse(message);
setDebugResponseHeaders([]);
}
setDebugResponse(executedDebugResponse);
},
[
useRequestBody,
Expand All @@ -416,6 +449,7 @@ const DebugPage: React.FunctionComponent<Props> = ({
method,
transport,
docServiceRoute,
currentApiId,
],
);

Expand Down Expand Up @@ -505,6 +539,22 @@ const DebugPage: React.FunctionComponent<Props> = ({
transport,
]);

useEffect(() => {
const newApiId = method.id || method.name;
Comment thread
hyunw9 marked this conversation as resolved.
Outdated
if (newApiId !== currentApiId) {
setCurrentApiId(newApiId);
if (responseCache[newApiId]) {
setDebugResponse(responseCache[newApiId].body);
setDebugResponseHeaders(
Array.from(responseCache[newApiId].headers.entries()),
);
} else {
setDebugResponse('');
setDebugResponseHeaders([]);
}
}
}, [method, currentApiId, responseCache]);

const supportedExamplePaths = useMemo(() => {
if (
serviceType === ServiceType.HTTP ||
Expand Down Expand Up @@ -572,7 +622,7 @@ const DebugPage: React.FunctionComponent<Props> = ({
<Grid item xs={12} sm={6}>
<Grid container spacing={1}>
<Grid item xs="auto">
<Tooltip title="Copy response">
<Tooltip title="Copy response body">
<div>
<IconButton
onClick={onCopy}
Expand All @@ -596,13 +646,45 @@ const DebugPage: React.FunctionComponent<Props> = ({
</Tooltip>
</Grid>
</Grid>
<SyntaxHighlighter
language="json"
style={githubGist}
wrapLines={false}
>
{debugResponse}
</SyntaxHighlighter>
{debugResponse && (
<>
{Object.keys(debugResponseHeaders).length > 0 && (
<>
<Typography
variant="subtitle1"
style={{ marginTop: '1rem' }}
>
Response Headers:
</Typography>
<SyntaxHighlighter
language="json"
style={githubGist}
wrapLines={false}
>
{JSON.stringify(
Object.fromEntries(
Array.from(debugResponseHeaders).map(
([key, values]) => [key, values.join(', ')],
),
),
null,
2,
)}
</SyntaxHighlighter>
</>
)}
Comment thread
hyunw9 marked this conversation as resolved.
Outdated
<Typography variant="subtitle1" style={{ marginTop: '1rem' }}>
Response Body:
</Typography>
<SyntaxHighlighter
language="json"
style={githubGist}
wrapLines={false}
>
{debugResponse}
</SyntaxHighlighter>
</>
)}
</Grid>
</Grid>
<Snackbar
Expand Down Expand Up @@ -684,6 +766,34 @@ const DebugPage: React.FunctionComponent<Props> = ({
</Tooltip>
</Grid>
</Grid>
{Object.keys(debugResponseHeaders).length > 0 && (
<>
<Typography
variant="subtitle1"
style={{ marginTop: '1rem' }}
>
Response Headers:
</Typography>
<SyntaxHighlighter
language="json"
style={githubGist}
wrapLines={false}
>
{JSON.stringify(
Object.fromEntries(
Array.from(debugResponseHeaders).map(
([key, values]) => [key, values.join(', ')],
),
),
null,
2,
)}
</SyntaxHighlighter>
</>
)}
<Typography variant="subtitle1" style={{ marginTop: '1rem' }}>
Response Body:
</Typography>
<SyntaxHighlighter
language="json"
style={githubGist}
Expand Down
21 changes: 19 additions & 2 deletions docs-client/src/lib/transports/annotated-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Endpoint, Method } from '../specification';

import Transport from './transport';
import { isValidJsonMimeType, validateJsonObject } from '../json-util';
import { ResponseData } from '../types';

export const ANNOTATED_HTTP_MIME_TYPE = 'application/json; charset=utf-8';

Expand Down Expand Up @@ -88,7 +89,7 @@ export default class AnnotatedHttpTransport extends Transport {
bodyJson?: string,
endpointPath?: string,
queries?: string,
): Promise<Response> {
): Promise<ResponseData> {
const endpoint = this.getDebugMimeTypeEndpoint(method);

const hdrs = new Headers();
Expand Down Expand Up @@ -116,10 +117,26 @@ export default class AnnotatedHttpTransport extends Transport {
}
newPath = pathPrefix + newPath;

return fetch(encodeURI(newPath), {
const response = await fetch(encodeURI(newPath), {
headers: hdrs,
method: method.httpMethod,
body: bodyJson,
});

const responseHeaders = new Map<string, string[]>();
response.headers.forEach((value, key) => {
const lowerKey = key.toLowerCase();
if (!responseHeaders.has(lowerKey)) {
responseHeaders.set(lowerKey, []);
}
responseHeaders.get(lowerKey)!.push(value);
});

const responseText = await response.text();

return {
body: responseText,
headers: responseHeaders,
};
}
}
21 changes: 19 additions & 2 deletions docs-client/src/lib/transports/grahpql-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import Transport from './transport';
import { Method } from '../specification';
import { validateJsonObject } from '../json-util';
import { ResponseData } from '../types';

export const GRAPHQL_HTTP_MIME_TYPE = 'application/graphql+json';

Expand All @@ -36,7 +37,7 @@ export default class GraphqlHttpTransport extends Transport {
bodyJson?: string,
endpointPath?: string,
queries?: string,
): Promise<Response> {
): Promise<ResponseData> {
const endpoint = this.getDebugMimeTypeEndpoint(method);

const hdrs = new Headers();
Expand All @@ -59,10 +60,26 @@ export default class GraphqlHttpTransport extends Transport {
}
newPath = pathPrefix + newPath;

return fetch(encodeURI(newPath), {
const response = await fetch(encodeURI(newPath), {
headers: hdrs,
method: method.httpMethod,
body: bodyJson,
});

const responseHeaders = new Map<string, string[]>();
Comment thread
hyunw9 marked this conversation as resolved.
Outdated
response.headers.forEach((value, key) => {
const lowerKey = key.toLowerCase();
if (!responseHeaders.has(lowerKey)) {
responseHeaders.set(lowerKey, []);
}
responseHeaders.get(lowerKey)!.push(value);
});

const responseText = await response.text();

return {
body: responseText,
headers: responseHeaders,
};
}
}
22 changes: 19 additions & 3 deletions docs-client/src/lib/transports/grpc-unframed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Method } from '../specification';

import Transport from './transport';
import { validateJsonObject } from '../json-util';
import { ResponseData } from '../types';

export const GRPC_UNFRAMED_MIME_TYPE =
'application/json; charset=utf-8; protocol=gRPC';
Expand All @@ -36,7 +37,7 @@ export default class GrpcUnframedTransport extends Transport {
pathPrefix: string,
bodyJson?: string,
endpointPath?: string,
): Promise<Response> {
): Promise<ResponseData> {
if (!bodyJson) {
throw new Error('A gRPC request must have body.');
}
Expand All @@ -56,11 +57,26 @@ export default class GrpcUnframedTransport extends Transport {
}

const newPath = pathPrefix + (endpointPath ?? endpoint.pathMapping);

return fetch(newPath, {
const response = await fetch(newPath, {
headers: hdrs,
method: 'POST',
body: bodyJson,
});

const responseHeaders = new Map<string, string[]>();
response.headers.forEach((value, key) => {
const lowerKey = key.toLowerCase();
if (!responseHeaders.has(lowerKey)) {
responseHeaders.set(lowerKey, []);
}
responseHeaders.get(lowerKey)!.push(value);
});

const responseText = await response.text();

return {
body: responseText,
headers: responseHeaders,
};
}
}
Loading