expand rowy logging into tabs: extension, webhook, column(derivative/action/default value/connector)

This commit is contained in:
Bobby Wang
2022-12-28 17:01:48 +09:30
parent 8747ffd144
commit 072686bb66
7 changed files with 314 additions and 416 deletions

View File

@@ -142,7 +142,7 @@ export const selectedCellAtom = atom<SelectedCell | null>(null);
export const contextMenuTargetAtom = atom<HTMLElement | null>(null);
export type CloudLogFilters = {
type: "rowy" | "audit" | "build";
type: "extension" | "webhook" | "column" | "audit" | "build";
timeRange:
| { type: "seconds" | "minutes" | "hours" | "days"; value: number }
| { type: "range"; start: Date; end: Date };

View File

@@ -25,7 +25,6 @@ import {
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
import EditIcon from "@mui/icons-material/EditOutlined";
// import ReorderIcon from "@mui/icons-material/Reorder";
import SettingsIcon from "@mui/icons-material/SettingsOutlined";
import EvalIcon from "@mui/icons-material/PlayCircleOutline";
@@ -319,24 +318,19 @@ export default function ColumnMenu({
},
disabled: !isConfigurable,
},
// {
// label: "Re-order",
// icon: <ReorderIcon />,
// onClick: () => alert("REORDER"),
// },
// {
// label: "Hide for everyone",
// activeLabel: "Show",
// icon: <VisibilityOffIcon />,
// activeIcon: <VisibilityIcon />,
// onClick: () => {
// actions.update(column.key, { hidden: !column.hidden });
// handleClose();
// },
// active: column.hidden,
// color: "error" as "error",
// },
{
key: "logs",
label: altPress ? "Logs" : "Logs…",
icon: <LogsIcon />,
onClick: () => {
setModal("cloudLogs");
setCloudLogFilters({
type: "column",
timeRange: { type: "days", value: 7 },
column: [column.key],
});
},
},
];
// TODO: Generalize
@@ -388,19 +382,6 @@ export default function ColumnMenu({
confirm: "Evaluate",
}),
},
{
key: "logs",
label: altPress ? "Logs" : "Logs…",
icon: <LogsIcon />,
onClick: () => {
setModal("cloudLogs");
setCloudLogFilters({
type: "rowy",
timeRange: { type: "days", value: 7 },
column: [column.key],
});
},
},
];
const columnActions: IMenuContentsProps["menuItems"] = [

View File

@@ -13,6 +13,7 @@ import {
InputAdornment,
Button,
Box,
CircularProgress,
} from "@mui/material";
import RefreshIcon from "@mui/icons-material/Refresh";
import { CloudLogs as LogsIcon } from "@src/assets/icons";
@@ -96,7 +97,7 @@ export default function CloudLogsModal({ onClose }: ITableModalProps) {
"&, & .MuiTab-root": {
minHeight: { md: "var(--dialog-title-height)" },
},
ml: { md: 18 },
ml: { md: 20 },
mr: { md: 40 / 8 + 3 },
minHeight: 32,
@@ -114,15 +115,17 @@ export default function CloudLogsModal({ onClose }: ITableModalProps) {
<ToggleButtonGroup
value={cloudLogFilters.type}
exclusive
onChange={(_, v) =>
onChange={(_, v) => {
setCloudLogFilters((c) => ({
type: v,
timeRange: c.timeRange,
}))
}
}));
}}
aria-label="Filter by log type"
>
<ToggleButton value="rowy">Rowy Logging</ToggleButton>
<ToggleButton value="extension">Extension</ToggleButton>
<ToggleButton value="webhook">Webhook</ToggleButton>
<ToggleButton value="column">Column</ToggleButton>
<ToggleButton value="audit">Audit</ToggleButton>
<ToggleButton value="build">Build</ToggleButton>
</ToggleButtonGroup>
@@ -142,154 +145,18 @@ export default function CloudLogsModal({ onClose }: ITableModalProps) {
</ToggleButtonGroup>
)}
{cloudLogFilters.type === "rowy" && <></>}
{cloudLogFilters.type === "audit" && (
<TextField
id="auditRowId"
label="Row ID:"
value={cloudLogFilters.auditRowId}
onChange={(e) =>
setCloudLogFilters((prev) => ({
...prev,
auditRowId: e.target.value,
}))
}
InputProps={{
startAdornment: (
<InputAdornment position="start">
{tableSettings.collection}/
</InputAdornment>
),
}}
className="labelHorizontal"
sx={{
"& .MuiInputBase-root, & .MuiInputBase-input": {
typography: "body2",
fontFamily: "mono",
},
"& .MuiInputAdornment-positionStart": {
m: "0 !important",
pointerEvents: "none",
},
"& .MuiInputBase-input": { pl: 0 },
}}
/>
)}
<div style={{ flexGrow: 1 }} />
{cloudLogFilters.type !== "build" && (
<>
{!isValidating && Array.isArray(data) && (
<Typography
variant="body2"
color="text.disabled"
display="block"
style={{ userSelect: "none" }}
>
{data.length} entries
</Typography>
)}
{cloudLogFilters.type !== "rowy" && (
<MultiSelect
aria-label="Severity"
labelPlural="severity levels"
options={Object.keys(SEVERITY_LEVELS)}
value={cloudLogFilters.severity ?? []}
onChange={(severity) =>
setCloudLogFilters((prev) => ({ ...prev, severity }))
}
TextFieldProps={{
style: { width: 130 },
placeholder: "Severity",
SelectProps: {
renderValue: () => {
if (
!Array.isArray(cloudLogFilters.severity) ||
cloudLogFilters.severity.length === 0
)
return `Severity`;
if (cloudLogFilters.severity.length === 1)
return (
<>
Severity{" "}
<CloudLogSeverityIcon
severity={cloudLogFilters.severity[0]}
style={{ marginTop: -2, marginBottom: -7 }}
/>
</>
);
return `Severity (${cloudLogFilters.severity.length})`;
},
},
}}
itemRenderer={(option) => (
<>
<CloudLogSeverityIcon
severity={option.value}
sx={{ mr: 1 }}
/>
{startCase(option.value.toLowerCase())}
</>
)}
/>
)}
<TimeRangeSelect
aria-label="Time range"
value={cloudLogFilters.timeRange}
onChange={(value) =>
setCloudLogFilters((c) => ({ ...c, timeRange: value }))
}
/>
<TableToolbarButton
onClick={() => mutate()}
title="Refresh"
icon={<RefreshIcon />}
disabled={isValidating}
/>
</>
)}
</Stack>
{isValidating && (
<LinearProgress
style={{
borderRadius: 0,
marginTop: -4,
marginBottom: -1,
minHeight: 4,
}}
/>
)}
</>
}
>
{cloudLogFilters.type === "build" ? (
<BuildLogs />
) : (
<Box
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
overflowY: "visible",
}}
>
<Box mt={1}>
{cloudLogFilters.type === "rowy" ? (
<Stack
direction="row"
spacing={2}
justifyContent="flex-start"
alignItems="center"
sx={{
overflowX: "auto",
overflowY: "hidden",
}}
>
<Typography
variant="body2"
color="text.disabled"
display="block"
style={{ userSelect: "none" }}
>
{isValidating ? "Loading" : `${data?.length ?? 0} entries`}
</Typography>
<Button
onClick={(v) => {
setCloudLogFilters((prev) => ({
@@ -304,77 +171,101 @@ export default function CloudLogsModal({ onClose }: ITableModalProps) {
>
Reset
</Button>
<MultiSelect
multiple
SearchBoxProps={{
sx: { display: "none" },
}}
aria-label="Type:"
options={[
"extension",
"hooks",
"action",
"derivative-script",
"derivative-function",
"defaultValue",
"connector",
]}
value={cloudLogFilters.functionType ?? []}
onChange={(v) => {
setCloudLogFilters((prev) => ({
...prev,
functionType: v,
}));
}}
TextFieldProps={{
id: "functionType",
className: "labelHorizontal",
sx: { "& .MuiInputBase-root": { width: 200 } },
fullWidth: false,
SelectProps: {
renderValue: () => {
if (cloudLogFilters?.functionType?.length === 1) {
return `Type (${cloudLogFilters.functionType[0]})`;
} else if (cloudLogFilters?.functionType?.length) {
return `Type (${cloudLogFilters.functionType.length})`;
} else {
return `Type`;
}
},
},
}}
itemRenderer={(option) => <>{upperCase(option.value)}</>}
<TableToolbarButton
onClick={() => mutate()}
title="Refresh"
icon={
isValidating ? (
<CircularProgress size={15} thickness={4} />
) : (
<RefreshIcon />
)
}
disabled={isValidating}
/>
<MultiSelect
multiple
aria-label="Source:"
options={["backend-scripts", "backend-function", "hooks"]}
value={cloudLogFilters.loggingSource ?? []}
onChange={(v) => {
setCloudLogFilters((prev) => ({
...prev,
loggingSource: v,
}));
}}
TextFieldProps={{
id: "loggingSource",
className: "labelHorizontal",
sx: { "& .MuiInputBase-root": { width: 200 } },
fullWidth: false,
SelectProps: {
renderValue: () => {
if (cloudLogFilters?.loggingSource?.length === 1) {
return `Source (${cloudLogFilters.loggingSource[0]})`;
} else if (cloudLogFilters?.loggingSource?.length) {
return `Source (${cloudLogFilters.loggingSource.length})`;
} else {
return `Source`;
}
</>
)}
</Stack>
</>
}
>
{cloudLogFilters.type === "build" ? (
<BuildLogs />
) : (
<Box
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
overflowY: "visible",
}}
>
{["extension", "webhook", "column", "audit"].includes(
cloudLogFilters.type
) ? (
<Stack
width={"100%"}
direction="row"
spacing={2}
justifyContent="flex-start"
alignItems="center"
sx={{
overflowX: "auto",
overflowY: "hidden",
margin: "8px 0",
flex: "0 0 32px",
}}
>
{cloudLogFilters.type === "extension" ? (
<>
<MultiSelect
multiple
aria-label={"Extension"}
labelPlural="extensions"
options={
Array.isArray(tableSchema.extensionObjects)
? tableSchema.extensionObjects.map((x) => ({
label: x.name,
value: x.name,
type: x.type,
}))
: []
}
value={cloudLogFilters.extension ?? []}
onChange={(v) =>
setCloudLogFilters((prev) => ({ ...prev, extension: v }))
}
TextFieldProps={{
id: "extension",
className: "labelHorizontal",
sx: {
width: "100%",
"& .MuiInputBase-root": { width: "100%" },
},
},
}}
itemRenderer={(option) => <>{upperCase(option.value)}</>}
/>
fullWidth: false,
placeholder: "Extension",
SelectProps: {
renderValue: () => {
if (cloudLogFilters?.extension?.length === 1) {
return `Extension (${cloudLogFilters.extension[0]})`;
} else if (cloudLogFilters?.extension?.length) {
return `Extension (${cloudLogFilters.extension.length})`;
} else {
return `Extension`;
}
},
},
}}
itemRenderer={(option) => (
<>
{option.label}&nbsp;<code>{option.type}</code>
</>
)}
/>
</>
) : null}
{cloudLogFilters.type === "webhook" ? (
<MultiSelect
multiple
aria-label="Webhook:"
@@ -394,7 +285,10 @@ export default function CloudLogsModal({ onClose }: ITableModalProps) {
TextFieldProps={{
id: "webhook",
className: "labelHorizontal",
sx: { "& .MuiInputBase-root": { width: 180 } },
sx: {
width: "100%",
"& .MuiInputBase-root": { width: "100%" },
},
fullWidth: false,
SelectProps: {
renderValue: () => {
@@ -412,133 +306,145 @@ export default function CloudLogsModal({ onClose }: ITableModalProps) {
</>
)}
/>
<MultiSelect
multiple
aria-label={"Extension"}
labelPlural="extensions"
options={
Array.isArray(tableSchema.extensionObjects)
? tableSchema.extensionObjects.map((x) => ({
label: x.name,
value: x.name,
type: x.type,
}))
: []
}
value={cloudLogFilters.extension ?? []}
onChange={(v) =>
setCloudLogFilters((prev) => ({ ...prev, extension: v }))
}
TextFieldProps={{
id: "extension",
className: "labelHorizontal",
sx: { "& .MuiInputBase-root": { width: 180 } },
fullWidth: false,
placeholder: "Extension",
SelectProps: {
renderValue: () => {
if (cloudLogFilters?.extension?.length === 1) {
return `Extension (${cloudLogFilters.extension[0]})`;
} else if (cloudLogFilters?.extension?.length) {
return `Extension (${cloudLogFilters.extension.length})`;
} else {
return `Extension`;
}
) : null}
{cloudLogFilters.type === "column" ? (
<>
<MultiSelect
multiple
aria-label={"Column"}
options={Object.entries(tableSchema.columns ?? {}).map(
([key, config]) => ({
label: config.name,
value: key,
type: config.type,
})
)}
value={cloudLogFilters.column ?? []}
onChange={(v) =>
setCloudLogFilters((prev) => ({ ...prev, column: v }))
}
TextFieldProps={{
id: "column",
className: "labelHorizontal",
sx: {
width: "100%",
"& .MuiInputBase-root": { width: "100%" },
},
},
}}
itemRenderer={(option) => (
<>
{option.label}&nbsp;<code>{option.type}</code>
</>
)}
/>
<MultiSelect
multiple
aria-label={"Column"}
options={Object.entries(tableSchema.columns ?? {}).map(
([key, config]) => ({
label: config.name,
value: key,
type: config.type,
})
)}
value={cloudLogFilters.column ?? []}
onChange={(v) =>
setCloudLogFilters((prev) => ({ ...prev, column: v }))
}
TextFieldProps={{
id: "column",
className: "labelHorizontal",
sx: { "& .MuiInputBase-root": { width: 200 } },
fullWidth: false,
placeholder: "Column",
SelectProps: {
renderValue: () => {
if (cloudLogFilters?.column?.length === 1) {
return `Column (${cloudLogFilters.column[0]})`;
} else if (cloudLogFilters?.column?.length) {
return `Column (${cloudLogFilters.column.length})`;
} else {
return `Column`;
}
fullWidth: false,
placeholder: "Column",
SelectProps: {
renderValue: () => {
if (cloudLogFilters?.column?.length === 1) {
return `Column (${cloudLogFilters.column[0]})`;
} else if (cloudLogFilters?.column?.length) {
return `Column (${cloudLogFilters.column.length})`;
} else {
return `Column`;
}
},
},
},
}}
itemRenderer={(option) => (
<>
{option.label}&nbsp;<code>{option.value}</code>&nbsp;
<code>{option.type}</code>
</>
)}
/>
<MultiSelect
aria-label="Severity"
labelPlural="severity levels"
options={Object.keys(SEVERITY_LEVELS_ROWY)}
value={cloudLogFilters.severity ?? []}
onChange={(severity) =>
setCloudLogFilters((prev) => ({ ...prev, severity }))
}
TextFieldProps={{
style: { width: 130 },
placeholder: "Severity",
SelectProps: {
renderValue: () => {
if (
!Array.isArray(cloudLogFilters.severity) ||
cloudLogFilters.severity.length === 0
)
return `Severity`;
}}
itemRenderer={(option) => (
<>
{option.label}&nbsp;<code>{option.value}</code>&nbsp;
<code>{option.type}</code>
</>
)}
/>
</>
) : null}
{cloudLogFilters.type === "audit" ? (
<>
<TextField
id="auditRowId"
label="Row ID:"
value={cloudLogFilters.auditRowId}
onChange={(e) =>
setCloudLogFilters((prev) => ({
...prev,
auditRowId: e.target.value,
}))
}
InputProps={{
startAdornment: (
<InputAdornment position="start">
{tableSettings.collection}/
</InputAdornment>
),
}}
className="labelHorizontal"
sx={{
width: "100%",
"& .MuiInputBase-root, & .MuiInputBase-input": {
width: "100%",
typography: "body2",
fontFamily: "mono",
},
"& .MuiInputAdornment-positionStart": {
m: "0 !important",
pointerEvents: "none",
},
"& .MuiInputBase-input": { pl: 0 },
"& .MuiFormLabel-root": {
whiteSpace: "nowrap",
},
}}
/>
</>
) : null}
<MultiSelect
aria-label="Severity"
labelPlural="severity levels"
options={Object.keys(SEVERITY_LEVELS_ROWY)}
value={cloudLogFilters.severity ?? []}
onChange={(severity) =>
setCloudLogFilters((prev) => ({ ...prev, severity }))
}
TextFieldProps={{
style: { width: 200 },
placeholder: "Severity",
SelectProps: {
renderValue: () => {
if (
!Array.isArray(cloudLogFilters.severity) ||
cloudLogFilters.severity.length === 0
)
return `Severity`;
if (cloudLogFilters.severity.length === 1)
return (
<>
Severity{" "}
<CloudLogSeverityIcon
severity={cloudLogFilters.severity[0]}
style={{ marginTop: -2, marginBottom: -7 }}
/>
</>
);
if (cloudLogFilters.severity.length === 1)
return (
<>
Severity{" "}
<CloudLogSeverityIcon
severity={cloudLogFilters.severity[0]}
style={{ marginTop: -2, marginBottom: -7 }}
/>
</>
);
return `Severity (${cloudLogFilters.severity.length})`;
},
return `Severity (${cloudLogFilters.severity.length})`;
},
}}
itemRenderer={(option) => (
<>
<CloudLogSeverityIcon
severity={option.value}
sx={{ mr: 1 }}
/>
{startCase(option.value.toLowerCase())}
</>
)}
/>
</Stack>
) : null}
</Box>
},
}}
itemRenderer={(option) => (
<>
<CloudLogSeverityIcon
severity={option.value}
sx={{ mr: 1 }}
/>
{startCase(option.value.toLowerCase())}
</>
)}
/>
<TimeRangeSelect
aria-label="Time range"
value={cloudLogFilters.timeRange}
onChange={(value) =>
setCloudLogFilters((c) => ({ ...c, timeRange: value }))
}
/>
</Stack>
) : null}
<Box
sx={{
overflowY: "scroll",
@@ -580,7 +486,7 @@ export default function CloudLogsModal({ onClose }: ITableModalProps) {
Icon={LogsIcon}
message="No logs"
description={
cloudLogFilters.type === "rowy"
cloudLogFilters.type !== "audit"
? "There are no logs matching the filters"
: cloudLogFilters.type === "audit" &&
tableSettings.audit === false

View File

@@ -19,7 +19,9 @@ export default function TimeRangeSelect({
...props
}: ITimeRangeSelectProps) {
return (
<fieldset style={{ appearance: "none", padding: 0, border: 0 }}>
<fieldset
style={{ appearance: "none", padding: 0, border: 0, display: "flex" }}
>
{value && value.type !== "range" && (
<TextField
aria-label={`Custom ${value.type} value`}

View File

@@ -12,43 +12,21 @@ export const cloudLogFetcher = (
// https://cloud.google.com/logging/docs/view/logging-query-language
let logQuery: string[] = [];
if (["extension", "webhook", "column"].includes(cloudLogFilters.type)) {
// mandatory filter to remove unwanted gcp diagnostic logs
logQuery.push(
["backend-scripts", "backend-function", "hooks"]
.map((loggingSource) => {
return `jsonPayload.loggingSource = "${loggingSource}"`;
})
.join(encodeURIComponent(" OR "))
);
}
switch (cloudLogFilters.type) {
case "rowy":
case "extension":
logQuery.push(`logName = "projects/${projectId}/logs/rowy-logging"`);
if (cloudLogFilters?.functionType?.length)
logQuery.push(
cloudLogFilters.functionType
.map((functionType) => {
return `jsonPayload.functionType = "${functionType}"`;
})
.join(encodeURIComponent(" OR "))
);
if (cloudLogFilters?.loggingSource?.length) {
logQuery.push(
cloudLogFilters.loggingSource
.map((loggingSource) => {
return `jsonPayload.loggingSource = "${loggingSource}"`;
})
.join(encodeURIComponent(" OR "))
);
} else {
// mandatory filter to remove unwanted gcp diagnostic logs
logQuery.push(
["backend-scripts", "backend-function", "hooks"]
.map((loggingSource) => {
return `jsonPayload.loggingSource = "${loggingSource}"`;
})
.join(encodeURIComponent(" OR "))
);
}
if (cloudLogFilters?.webhook?.length) {
logQuery.push(
cloudLogFilters.webhook
.map((id) => `jsonPayload.url : "${id}"`)
.join(encodeURIComponent(" OR "))
);
}
if (cloudLogFilters?.extension?.length)
if (cloudLogFilters?.extension?.length) {
logQuery.push(
cloudLogFilters.extension
.map((extensionName) => {
@@ -56,7 +34,25 @@ export const cloudLogFetcher = (
})
.join(encodeURIComponent(" OR "))
);
if (cloudLogFilters?.column?.length)
} else {
logQuery.push(`jsonPayload.functionType = "extension"`);
}
break;
case "webhook":
if (cloudLogFilters?.webhook?.length) {
logQuery.push(
cloudLogFilters.webhook
.map((id) => `jsonPayload.url : "${id}"`)
.join(encodeURIComponent(" OR "))
);
} else {
logQuery.push(`jsonPayload.functionType = "hooks"`);
}
break;
case "column":
if (cloudLogFilters?.column?.length) {
logQuery.push(
cloudLogFilters.column
.map((column) => {
@@ -64,6 +60,21 @@ export const cloudLogFetcher = (
})
.join(encodeURIComponent(" OR "))
);
} else {
logQuery.push(
[
"connector",
"derivative-script",
"action",
"derivative-function",
"defaultValue",
]
.map((functionType) => {
return `jsonPayload.functionType = "${functionType}"`;
})
.join(encodeURIComponent(" OR "))
);
}
break;
case "audit":

View File

@@ -107,8 +107,7 @@ export default function ExtensionList({
onClick={() => {
setModal("cloudLogs");
setCloudLogFilters({
type: "rowy",
functionType: ["extension"],
type: "extension",
timeRange: { type: "days", value: 7 },
extension: [extensionObject.name],
});

View File

@@ -126,8 +126,7 @@ export default function WebhookList({
onClick={() => {
setModal("cloudLogs");
setCloudLogFilters({
type: "rowy",
functionType: ["hooks"],
type: "webhook",
timeRange: { type: "days", value: 7 },
webhook: [webhook.endpoint],
});