Skip to content

Commit

Permalink
Merge pull request #247 from bento-platform/chore/auth-service-info
Browse files Browse the repository at this point in the history
[12] Replacement for service info link - request modal
  • Loading branch information
davidlougheed authored Jun 1, 2023
2 parents ce72263 + 12ccae9 commit 7c8baad
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 43 deletions.
40 changes: 15 additions & 25 deletions src/components/JsonDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,6 @@ JsonArrayDisplay.defaultProps = {
standalone: false,
};

const JsonPropertyDisplay = ({ value }) => {
console.debug("JsonPropertyDisplay", value);

if (Array.isArray(value) && value.length > 100) {
// Display property as an array with custom nav
return <JsonArrayDisplay doc={value} />;
}

if (typeof value === "object") {
// Display property as an object
return <ReactJson src={value} collapsed={true} {...DEFAULT_REACT_JSON_OPTIONS} />;
}

// Display primitive
return <span style={{fontFamily: "monospace"}}>{JSON.stringify(value)}</span>;
};

JsonPropertyDisplay.propTypes = {
value: PropTypes.any,
};

const JsonObjectDisplay = ({ doc }) => {
const entries = Object.entries(doc);
return (
Expand All @@ -114,7 +93,7 @@ const JsonObjectDisplay = ({ doc }) => {
<Collapse accordion>
{entries.map(([key, value]) =>
<Panel header={<span style={{fontFamily: "monospace"}}>{key}</span>} key={key}>
<JsonPropertyDisplay value={value} />
<JsonDisplay jsonSrc={value} showObjectWithReactJson={true} />
</Panel>,
)}
</Collapse>
Expand All @@ -126,21 +105,32 @@ JsonObjectDisplay.propTypes = {
doc: PropTypes.object,
};

const JsonDisplay = ({ jsonSrc }) => {
const JsonDisplay = ({ jsonSrc, showObjectWithReactJson }) => {
if (Array.isArray(jsonSrc)) {
// Special display for array nav
return <JsonArrayDisplay doc={jsonSrc || []} standalone />;
}

// Display for objects and primitives
return <JsonObjectDisplay doc={jsonSrc || {}} />;
if (typeof jsonSrc === "object") {
// Display for objects
return showObjectWithReactJson
? <ReactJson src={jsonSrc || {}} collapsed={true} {...DEFAULT_REACT_JSON_OPTIONS} />
: <JsonObjectDisplay doc={jsonSrc || {}} />;
}

// Display primitive
return <span style={{fontFamily: "monospace"}}>{JSON.stringify(jsonSrc)}</span>;
};

JsonDisplay.propTypes = {
jsonSrc: PropTypes.oneOfType([
PropTypes.object,
PropTypes.array,
PropTypes.string,
PropTypes.bool,
PropTypes.number,
]),
showObjectWithReactJson: PropTypes.bool,
};

export default JsonDisplay;
149 changes: 131 additions & 18 deletions src/components/ServiceList.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from "react";
import React, {useCallback, useEffect, useMemo, useState} from "react";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";

import { Link } from "react-router-dom";

import { Table, Typography, Tag, Icon} from "antd";
import {Table, Typography, Tag, Icon, Button, Modal, Form, Input, Divider, Skeleton} from "antd";

import { getIsAuthenticated } from "../lib/auth/utils";
import {getIsAuthenticated, makeAuthorizationHeader} from "../lib/auth/utils";
import { withBasePath } from "../utils/url";
import JsonDisplay from "./JsonDisplay";

const SERVICE_KIND_STYLING = { fontFamily: "monospace" };
const MAX_TABLE_PAGE_SIZE = 15;

// noinspection JSUnresolvedFunction
const getServiceTags = serviceInfo => [
Expand Down Expand Up @@ -50,7 +51,7 @@ const renderGitInfo = (tag, record, key) => <Tag key={key} color={tag.color}>{ta


/* eslint-disable react/prop-types */
const serviceColumns = (isAuthenticated) => [
const serviceColumns = (isAuthenticated, setRequestModalService) => [
{
title: "Kind",
dataIndex: "service_kind",
Expand Down Expand Up @@ -80,13 +81,6 @@ const serviceColumns = (isAuthenticated) => [
</> : null;
},
},
{
title: "URL",
dataIndex: "url",
// url is undefined when service-registry does not receive replies from
// the container.
render: (url) => url ? <a href={`${url}/service-info`}>{`${url}/service-info`}</a> : "N/A",
},
{
title: "Status",
dataIndex: "status",
Expand All @@ -106,10 +100,121 @@ const serviceColumns = (isAuthenticated) => [
]
),
},
{
title: "Actions",
render: (service) => {
const onClick = () =>
setRequestModalService(service.serviceInfo?.bento?.serviceKind ?? service.key ?? null);
return <Button size="small" onClick={onClick}>Make Request</Button>;
},
},
];
/* eslint-enable react/prop-types */

const ServiceRequestModal = ({service, onCancel}) => {
const bentoServicesByKind = useSelector(state => state.chordServices.itemsByKind);
const serviceUrl = useMemo(() => bentoServicesByKind[service]?.url, [bentoServicesByKind, service]);

const [requestPath, setRequestPath] = useState("service-info");
const [requestLoading, setRequestLoading] = useState(false);
const [requestData, setRequestData] = useState(null);
const [requestIsJSON, setRequestIsJSON] = useState(false);

const [hasAttempted, setHasAttempted] = useState(false);

const accessToken = useSelector((state) => state.auth.accessToken);

const performRequestModalGet = useCallback(() => {
if (!serviceUrl) {
setRequestData(null);
return;
}
(async () => {
setRequestLoading(true);

try {
const res = await fetch(`${serviceUrl}/${requestPath}`, {
headers: makeAuthorizationHeader(accessToken),
});

if ((res.headers.get("content-type") ?? "").includes("application/json")) {
const data = await res.json();
setRequestIsJSON(true);
setRequestData(data);
} else {
const data = await res.text();
setRequestIsJSON(false);
setRequestData(data);
}
} finally {
setRequestLoading(false);
}
})();
}, [serviceUrl, requestPath, accessToken]);

useEffect(() => {
setRequestData(null);
setRequestIsJSON(false);
setRequestPath("service-info");
setHasAttempted(false);
}, [service]);

useEffect(() => {
if (!hasAttempted) {
performRequestModalGet();
setHasAttempted(true);
}
}, [hasAttempted, performRequestModalGet]);

return (
<Modal
visible={service !== null}
title={`${service}: make a request`}
footer={null}
width={960}
onCancel={onCancel}
>
<Form layout="inline" style={{display: "flex"}}>
<Form.Item style={{flex: 1}} wrapperCol={{span: 24}}>
<Input
addonBefore={(serviceUrl ?? "ERROR") + "/"}
value={requestPath}
disabled={!hasAttempted || requestLoading}
onChange={e => setRequestPath(e.target.value)}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlFor="submit" onClick={e => {
performRequestModalGet();
e.preventDefault();
}}>GET</Button>
</Form.Item>
</Form>
<Divider />
{requestLoading ? <Skeleton loading={true} /> : (
requestIsJSON
? <JsonDisplay jsonSrc={requestData} />
: (
<div style={{maxWidth: "100%", overflowX: "auto"}}>
<pre>
{((typeof requestData) === "string" || requestData === null)
? requestData
: JSON.stringify(requestData)}
</pre>
</div>
)
)}
</Modal>
);
};
ServiceRequestModal.propTypes = {
service: PropTypes.string,
onCancel: PropTypes.func,
};

const ServiceList = () => {
const [requestModalService, setRequestModalService] = useState(null);

const dataSource = useSelector((state) =>
Object.entries(state.chordServices.itemsByKind).map(([kind, service]) => ({
...service,
Expand All @@ -123,24 +228,32 @@ const ServiceList = () => {
})),
);

const columns = serviceColumns(
useSelector((state) => state.auth.hasAttempted && getIsAuthenticated(state.auth.idTokenContents)),
);
const isAuthenticated = useSelector(
(state) => state.auth.hasAttempted && getIsAuthenticated(state.auth.idTokenContents));

const columns = useMemo(
() => serviceColumns(isAuthenticated, setRequestModalService),
[isAuthenticated]);

/** @type boolean */
const isLoading = useSelector((state) => state.chordServices.isFetching || state.services.isFetching);

return (
return <>
<ServiceRequestModal
visible={requestModalService !== null}
service={requestModalService}
onCancel={() => setRequestModalService(null)} />
<Table
bordered
style={{marginBottom: 24}}
size="middle"
columns={columns}
dataSource={dataSource}
rowKey="key"
pagination={{ defaultPageSize: MAX_TABLE_PAGE_SIZE }}
pagination={false}
loading={isLoading}
/>
);
</>;
};

export default ServiceList;

0 comments on commit 7c8baad

Please sign in to comment.