From f0f1f0a9d10235027f57d7f8e6a3644521eb2654 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 1 Jun 2022 15:03:17 +1000 Subject: [PATCH 1/5] TypeChangeModal: add potential data loss warning --- src/components/ColumnModals/TypeChangeModal.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/ColumnModals/TypeChangeModal.tsx b/src/components/ColumnModals/TypeChangeModal.tsx index e7609d6e..8fe554df 100644 --- a/src/components/ColumnModals/TypeChangeModal.tsx +++ b/src/components/ColumnModals/TypeChangeModal.tsx @@ -4,9 +4,11 @@ import { IColumnModalProps } from "."; import Modal from "@src/components/Modal"; import FieldsDropdown from "./FieldsDropdown"; +import { Alert, AlertTitle } from "@mui/material"; import { tableScope, updateColumnAtom } from "@src/atoms/tableScope"; import { FieldType } from "@src/constants/fields"; +import { getFieldProp } from "@src/components/fields"; import { analytics, logEvent } from "analytics"; export default function TypeChangeModal({ @@ -20,7 +22,20 @@ export default function TypeChangeModal({ } + children={ + <> + + + {getFieldProp("dataType", column.type) !== + getFieldProp("dataType", newType) && ( + + Potential data loss + {getFieldProp("name", newType)} has an incompatible data type. + Selecting this can result in data loss. + + )} + + } actions={{ primary: { onClick: () => { From 43959dd6312bed10effad5998f4db992a11335c2 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 1 Jun 2022 15:18:26 +1000 Subject: [PATCH 2/5] ColumnSelect: display column icon when selected --- src/components/TableToolbar/ColumnSelect.tsx | 43 ++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/components/TableToolbar/ColumnSelect.tsx b/src/components/TableToolbar/ColumnSelect.tsx index c241cca5..bd6f84cd 100644 --- a/src/components/TableToolbar/ColumnSelect.tsx +++ b/src/components/TableToolbar/ColumnSelect.tsx @@ -1,7 +1,7 @@ import { useAtom } from "jotai"; import MultiSelect, { MultiSelectProps } from "@rowy/multiselect"; -import { Stack, Typography } from "@mui/material"; +import { Stack, StackProps, Typography } from "@mui/material"; import { globalScope, altPressAtom } from "@src/atoms/globalScope"; import { tableScope, tableColumnsOrderedAtom } from "@src/atoms/tableScope"; @@ -30,7 +30,7 @@ export default function ColumnSelect({ filterColumns ? tableColumnsOrdered.filter(filterColumns) : tableColumnsOrdered - ).map(({ key, name, type, index, fixed }) => ({ + ).map(({ key, name, type, index }) => ({ value: key, label: name, type, @@ -44,14 +44,39 @@ export default function ColumnSelect({ labelPlural="columns" {...(props as any)} itemRenderer={(option: ColumnOption) => } + TextFieldProps={{ + ...props.TextFieldProps, + SelectProps: { + ...props.TextFieldProps?.SelectProps, + renderValue: () => { + if (Array.isArray(props.value) && props.value.length > 1) + return `${props.value.length} columns`; + + const value = Array.isArray(props.value) + ? props.value[0] + : props.value; + const option = options.find((o) => o.value === value); + return option ? ( + + ) : ( + value + ); + }, + }, + }} /> ); } -export function ColumnItem({ - option, - children, -}: React.PropsWithChildren<{ option: ColumnOption }>) { +export interface IColumnItemProps extends Partial { + option: ColumnOption; + children?: React.ReactNode; +} + +export function ColumnItem({ option, children, ...props }: IColumnItemProps) { const [altPress] = useAtom(altPressAtom, globalScope); return ( @@ -59,7 +84,11 @@ export function ColumnItem({ direction="row" alignItems="center" gap={1} - sx={{ color: "text.secondary", width: "100%" }} + {...props} + sx={[ + { color: "text.secondary", width: "100%" }, + ...(Array.isArray(props.sx) ? props.sx : props.sx ? [props.sx] : []), + ]} > {getFieldProp("icon", option.type)} From dd0164b1ef15e42267ff3b790f18702ac934b6d6 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 1 Jun 2022 21:52:04 +1000 Subject: [PATCH 3/5] add CloudLogs --- package.json | 3 + .../CloudLogs/BuildLogs/BuildLogList.tsx | 107 ++++++ .../CloudLogs/BuildLogs/BuildLogRow.tsx | 68 ++++ .../CloudLogs/BuildLogs/BuildLogs.tsx | 210 +++++++++++ .../CloudLogs/BuildLogs/BuildLogsSnack.tsx | 181 +++++++++ .../TableToolbar/CloudLogs/BuildLogs/index.ts | 2 + .../CloudLogs/BuildLogs/useBuildLogs.ts | 48 +++ .../TableToolbar/CloudLogs/CloudLogItem.tsx | 257 +++++++++++++ .../TableToolbar/CloudLogs/CloudLogList.tsx | 85 +++++ .../CloudLogs/CloudLogSeverityIcon.tsx | 100 +++++ .../CloudLogs/CloudLogSubheader.tsx | 21 ++ .../TableToolbar/CloudLogs/CloudLogs.tsx | 39 ++ .../TableToolbar/CloudLogs/CloudLogsModal.tsx | 346 ++++++++++++++++++ .../CloudLogs/TimeRangeSelect.tsx | 96 +++++ .../TableToolbar/CloudLogs/index.ts | 2 + .../TableToolbar/CloudLogs/utils.ts | 104 ++++++ src/components/TableToolbar/TableToolbar.tsx | 10 +- src/types/table.d.ts | 2 +- src/utils/ui.ts | 5 + yarn.lock | 28 ++ 20 files changed, 1707 insertions(+), 7 deletions(-) create mode 100644 src/components/TableToolbar/CloudLogs/BuildLogs/BuildLogList.tsx create mode 100644 src/components/TableToolbar/CloudLogs/BuildLogs/BuildLogRow.tsx create mode 100644 src/components/TableToolbar/CloudLogs/BuildLogs/BuildLogs.tsx create mode 100644 src/components/TableToolbar/CloudLogs/BuildLogs/BuildLogsSnack.tsx create mode 100644 src/components/TableToolbar/CloudLogs/BuildLogs/index.ts create mode 100644 src/components/TableToolbar/CloudLogs/BuildLogs/useBuildLogs.ts create mode 100644 src/components/TableToolbar/CloudLogs/CloudLogItem.tsx create mode 100644 src/components/TableToolbar/CloudLogs/CloudLogList.tsx create mode 100644 src/components/TableToolbar/CloudLogs/CloudLogSeverityIcon.tsx create mode 100644 src/components/TableToolbar/CloudLogs/CloudLogSubheader.tsx create mode 100644 src/components/TableToolbar/CloudLogs/CloudLogs.tsx create mode 100644 src/components/TableToolbar/CloudLogs/CloudLogsModal.tsx create mode 100644 src/components/TableToolbar/CloudLogs/TimeRangeSelect.tsx create mode 100644 src/components/TableToolbar/CloudLogs/index.ts create mode 100644 src/components/TableToolbar/CloudLogs/utils.ts create mode 100644 src/utils/ui.ts diff --git a/package.json b/package.json index 5b5b59a3..ed563663 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@rowy/multiselect": "^0.3.0", "@tinymce/tinymce-react": "^3", "algoliasearch": "^4.13.1", + "ansi-to-react": "^6.1.6", "buffer": "^6.0.3", "compare-versions": "^4.1.3", "csv-parse": "^5.0.4", @@ -39,6 +40,7 @@ "monaco-editor-auto-typings": "^0.4.0", "notistack": "^2.0.4", "path-browserify": "^1.0.1", + "pb-util": "^1.0.3", "quicktype-core": "^6.0.71", "react": "^18.0.0", "react-color-palette": "^6.2.0", @@ -58,6 +60,7 @@ "react-router-dom": "^6.3.0", "react-router-hash-link": "^2.4.3", "react-scripts": "^5.0.0", + "react-usestateref": "^1.0.8", "remark-gfm": "^3.0.1", "stream-browserify": "^3.0.0", "swr": "^1.3.0", diff --git a/src/components/TableToolbar/CloudLogs/BuildLogs/BuildLogList.tsx b/src/components/TableToolbar/CloudLogs/BuildLogs/BuildLogList.tsx new file mode 100644 index 00000000..0c4267f0 --- /dev/null +++ b/src/components/TableToolbar/CloudLogs/BuildLogs/BuildLogList.tsx @@ -0,0 +1,107 @@ +import { useEffect, useRef } from "react"; +import useStateRef from "react-usestateref"; +import { throttle } from "lodash-es"; + +import { Box } from "@mui/material"; + +import BuildLogRow from "./BuildLogRow"; +import CircularProgressOptical from "@src/components/CircularProgressOptical"; + +import { isTargetInsideBox } from "@src/utils/ui"; + +export interface IBuildLogListProps + extends React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + > { + logs: Record[]; + status: string; + value: number; + index: number; +} + +export default function BuildLogList({ + logs, + status, + value, + index, + ...props +}: IBuildLogListProps) { + // useStateRef is necessary to resolve the state syncing issue + // https://stackoverflow.com/a/63039797/12208834 + const [liveStreaming, setLiveStreaming, liveStreamingStateRef] = + useStateRef(true); + const liveStreamingRef = useRef(); + const isActive = value === index; + + const handleScroll = throttle(() => { + const target = document.querySelector("#live-stream-target")!; + const scrollBox = document.querySelector("#live-stream-scroll-box")!; + const liveStreamTargetVisible = isTargetInsideBox(target, scrollBox); + if (liveStreamTargetVisible !== liveStreamingStateRef.current) { + setLiveStreaming(liveStreamTargetVisible); + } + }, 500); + + const scrollToLive = () => { + const liveStreamTarget = document.querySelector("#live-stream-target"); + liveStreamTarget?.scrollIntoView?.({ + behavior: "smooth", + }); + }; + + useEffect(() => { + if (liveStreaming && isActive && status === "BUILDING") { + if (!liveStreamingRef.current) { + scrollToLive(); + } else { + setTimeout(scrollToLive, 100); + } + } + }, [logs, value]); + + useEffect(() => { + if (isActive) { + const liveStreamScrollBox = document.querySelector( + "#live-stream-scroll-box" + ); + liveStreamScrollBox!.addEventListener("scroll", () => { + handleScroll(); + }); + } + }, [value]); + + return ( +