finish CloudLogItem

This commit is contained in:
Sidney Alcantara
2021-11-05 11:38:18 +11:00
parent 96a1d7142f
commit d0f8eeea42
8 changed files with 172 additions and 75 deletions

View File

@@ -40,6 +40,7 @@
"lodash": "^4.17.21",
"moment": "^2.29.1",
"notistack": "^2.0.2",
"pb-util": "^1.0.1",
"query-string": "^6.8.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.0.0",

View File

@@ -1,6 +1,8 @@
import { format } from "date-fns";
import _get from "lodash/get";
import ReactJson from "react-json-view";
import { struct } from "pb-util";
import jsonFormat from "json-format";
import {
styled,
@@ -12,13 +14,15 @@ import {
Chip as MuiChip,
ChipProps,
Typography,
Divider,
} from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import LogSeverityIcon from "./LogSeverityIcon";
import CloudLogSeverityIcon from "./CloudLogSeverityIcon";
import { DATE_FORMAT, TIME_FORMAT } from "@src/constants/dates";
const Accordion = styled(MuiAccordion)(({ theme }) => ({
background: "none",
marginTop: 0,
"&::before": { display: "none" },
@@ -30,6 +34,11 @@ const AccordionSummary = styled(MuiAccordionSummary)(({ theme }) => ({
minHeight: 32,
alignItems: "flex-start",
"&.Mui-expanded": {
backgroundColor: theme.palette.action.hover,
"&:hover": { backgroundColor: theme.palette.action.selected },
},
"& svg": {
fontSize: 18,
height: 20,
@@ -58,13 +67,12 @@ const AccordionSummary = styled(MuiAccordionSummary)(({ theme }) => ({
"& .log-preview": {
flexShrink: 1,
".Mui-expanded&": {
overflow: "visible",
whiteSpace: "pre-wrap",
},
// ".Mui-expanded&": {
// overflow: "visible",
// whiteSpace: "pre-wrap",
// },
},
margin: theme.spacing(0, -1.5),
padding: theme.spacing(0, 1.375, 0, 1.5),
borderRadius: theme.shape.borderRadius,
"&:hover": { backgroundColor: theme.palette.action.hover },
@@ -80,19 +88,26 @@ const Chip = styled((props: ChipProps) => <MuiChip size="small" {...props} />)({
});
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
paddingLeft: theme.spacing(18 / 8 + 2),
paddingRight: 0,
paddingLeft: theme.spacing(18 / 8 + 2 + 1.5),
paddingRight: theme.spacing(18 / 8 + 2 + 1.5),
}));
export interface ILogItemProps {
export interface ICloudLogItemProps {
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#FIELDS.insert_id
data: Record<string, any>;
chips?: string[];
}
export default function LogItem({ data, chips }: ILogItemProps) {
export default function CloudLogItem({
data: dataProp,
chips,
}: ICloudLogItemProps) {
const theme = useTheme();
const data = { ...dataProp };
if (dataProp.payload === "jsonPayload" && dataProp.jsonPayload)
data.jsonPayload = struct.decode(dataProp.jsonPayload ?? {});
const timestamp = new Date(
data.timestamp.seconds * 1000 + data.timestamp.nanos / 1_000_000
);
@@ -130,7 +145,7 @@ export default function LogItem({ data, chips }: ILogItemProps) {
aria-controls={`${data.insertId}-content`}
id={`${data.insertId}-header`}
>
<LogSeverityIcon severity={data.severity} />
<CloudLogSeverityIcon severity={data.severity} />
<time dateTime={timestamp.toISOString()}>
<Typography variant="inherit" color="text.secondary" component="span">
@@ -153,16 +168,40 @@ export default function LogItem({ data, chips }: ILogItemProps) {
<Typography variant="inherit" noWrap className="log-preview">
{data.payload === "textPayload" && data.textPayload}
{_get(data, "httpRequest.requestUrl")?.split(".run.app").pop()}
{data.payload === "jsonPayload" && JSON.stringify(data.jsonPayload)}
{data.payload === "jsonPayload" &&
jsonFormat(data.jsonPayload, { type: "space", char: " ", size: 2 })}
</Typography>
</AccordionSummary>
<AccordionDetails>
{data.payload === "textPayload" && (
<Typography variant="inherit" style={{ whiteSpace: "pre-wrap" }}>
{data.textPayload}
</Typography>
)}
{data.payload === "jsonPayload" && (
<ReactJson
src={data.jsonPayload}
name="jsonPayload"
theme={theme.palette.mode === "dark" ? "monokai" : "rjv-default"}
iconStyle="triangle"
style={{ font: "inherit", backgroundColor: "transparent" }}
displayDataTypes={false}
quotesOnKeys={false}
/>
)}
{data.payload && <Divider sx={{ my: 1 }} />}
<ReactJson
src={data}
collapsed={!!data.payload}
name="Full log entry"
theme={theme.palette.mode === "dark" ? "monokai" : "rjv-default"}
iconStyle="triangle"
style={{ font: "inherit", backgroundColor: "transparent" }}
displayDataTypes={false}
quotesOnKeys={false}
/>
</AccordionDetails>
</Accordion>

View File

@@ -0,0 +1,56 @@
import _get from "lodash/get";
import { List, ListProps } from "@mui/material";
import CloudLogSubheader from "./CloudLogSubheader";
import CloudLogItem from "./CloudLogItem";
export interface ICloudLogListProps extends Partial<ListProps> {
items: Record<string, any>[];
}
export default function CloudLogList({ items, ...props }: ICloudLogListProps) {
const renderedLogItems: React.ReactNodeArray = [];
if (Array.isArray(items)) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
const prevItem = items[i - 1];
if (
_get(item, "labels.execution_id") !==
_get(prevItem, "labels.execution_id")
) {
renderedLogItems.push(
<CloudLogSubheader key={_get(item, "labels.execution_id")}>
Function <code>{_get(item, "resource.labels.function_name")}</code>{" "}
execution <code>{_get(item, "labels.execution_id")}</code>
</CloudLogSubheader>
);
}
renderedLogItems.push(
<li key={item.insertId}>
<CloudLogItem
data={item}
chips={[
// Rowy Run HTTP request
"httpRequest.requestMethod",
"httpRequest.status",
// Rowy audit logs
"jsonPayload.type",
"jsonPayload.ref.tableId",
"jsonPayload.ref.rowId",
"jsonPayload.data.updatedField",
"jsonPayload.rowyUser.displayName",
]}
/>
</li>
);
}
}
return (
<List disablePadding {...({ component: "ol" } as any)} {...props}>
{renderedLogItems}
</List>
);
}

View File

@@ -22,14 +22,14 @@ export const SEVERITY_LEVELS = {
EMERGENCY: "One or more systems are unusable.",
};
export interface ILogSeverityIconProps extends SvgIconProps {
export interface ICloudLogSeverityIconProps extends SvgIconProps {
severity: keyof typeof SEVERITY_LEVELS;
}
export default function LogSeverityIcon({
export default function CloudLogSeverityIcon({
severity,
...props
}: ILogSeverityIconProps) {
}: ICloudLogSeverityIconProps) {
const commonIconProps: SvgIconProps = {
...props,
"aria-hidden": "false",

View File

@@ -0,0 +1,18 @@
import { styled, ListSubheader } from "@mui/material";
export const CloudLogSubheader = styled((props) => (
<ListSubheader disableGutters disableSticky={false} {...props} />
))(({ theme }) => ({
marginTop: theme.spacing(2),
...theme.typography.subtitle2,
padding: theme.spacing((32 - 20) / 2 / 8, 1.5),
"& code": { fontSize: "90%" },
".MuiPaper-elevation24 &": {
backgroundImage:
"linear-gradient(rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.16))",
},
}));
export default CloudLogSubheader;

View File

@@ -1,16 +1,17 @@
import useSWR from "swr";
import _get from "lodash/get";
import { Button, LinearProgress } from "@mui/material";
// import LoadingButton from "@mui/lab/LoadingButton";
import Modal, { IModalProps } from "@src/components/Modal";
import { List, ListSubheader } from "@mui/material";
import LogItem from "./LogItem";
import CloudLogList from "./CloudLogList";
import { useProjectContext } from "@src/contexts/ProjectContext";
export default function CloudLogsModal(props: IModalProps) {
const { rowyRun } = useProjectContext();
const { data: logItems } = useSWR(
const { data, mutate, isValidating } = useSWR(
"logItems",
() =>
rowyRun
@@ -19,64 +20,20 @@ export default function CloudLogsModal(props: IModalProps) {
// path: "/logs",
// path: '/logs?filter=resource.labels.function_name="R-githubStars"',
path: `/logs?filter=logName="${encodeURIComponent(
"projects/rowyio/logs/rowy-audit-logs"
"projects/rowyio/logs/rowy-audit"
)}"`,
method: "GET",
},
})
: [],
{ fallbackData: [], revalidateOnMount: true }
);
const renderedLogItems: React.ReactNodeArray = [];
if (Array.isArray(logItems)) {
for (let i = 0; i < logItems.length; i++) {
const logItem = logItems[i];
const prevItem = logItems[i - 1];
if (
_get(logItem, "labels.execution_id") !==
_get(prevItem, "labels.execution_id")
) {
renderedLogItems.push(
<ListSubheader
key={_get(logItem, "labels.execution_id")}
disableGutters
disableSticky
sx={{
mt: 2,
typography: "subtitle2",
py: (32 - 20) / 2 / 8,
"& code": { fontSize: "90%" },
}}
>
Function{" "}
<code>{_get(logItem, "resource.labels.function_name")}</code>{" "}
execution <code>{_get(logItem, "labels.execution_id")}</code>
</ListSubheader>
);
}
renderedLogItems.push(
<li key={logItem.insertId}>
<LogItem
data={logItem}
chips={[
// Rowy Run HTTP request
"httpRequest.requestMethod",
"httpRequest.status",
// Rowy audit logs
"jsonPayload.eventType",
"jsonPayload.eventData.rowPath",
"jsonPayload.eventData.updatedField",
"jsonPayload.fields.rowyUser.structValue.fields.displayName.stringValue",
]}
/>
</li>
);
{
fallbackData: [],
revalidateOnMount: true,
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
}
);
return (
<Modal
@@ -84,11 +41,25 @@ export default function CloudLogsModal(props: IModalProps) {
maxWidth="xl"
fullWidth
fullHeight
title={`Cloud logs (${logItems?.length})`}
ScrollableDialogContentProps={{ disableBottomDivider: true }}
>
<List component="ol" subheader={<li />}>
{renderedLogItems}
</List>
<Button onClick={() => mutate()}>Refresh</Button>
{isValidating && (
<LinearProgress
style={{
position: "absolute",
top: "calc(var(--dialog-title-height) + 1px)",
transform: "translateY(-100%)",
left: 0,
right: 0,
borderRadius: 0,
marginTop: 0,
}}
/>
)}
{Array.isArray(data) && <CloudLogList items={data} sx={{ mx: -1.5 }} />}
</Modal>
);
}

View File

@@ -143,6 +143,13 @@ export const components = (theme: Theme): ThemeOptions => {
MuiDialog: {
styleOverrides: {
root: {
"--dialog-title-height": "64px",
[theme.breakpoints.down("sm")]: {
"--dialog-title-height": "56px",
},
},
paper: {
borderRadius: (theme.shape.borderRadius as number) * 2,

View File

@@ -12141,6 +12141,11 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
pb-util@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pb-util/-/pb-util-1.0.1.tgz#1df63f3a7f7c6ee74bfb87f39df3420dbe0707eb"
integrity sha512-cMm1ERTOTYb4LxAsLRN0oxhYTL3GivVItrBOiU4WSFcvaeynU/crNtHplNEGQIimSbl3/i7hxWflFvsx42tUVw==
pbkdf2@^3.0.3:
version "3.0.17"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6"