From 95b60c2777ba28e4dc87d01150eb8aa4faf65b3c Mon Sep 17 00:00:00 2001 From: Yury Molodov Date: Mon, 20 Mar 2023 11:07:18 +0100 Subject: [PATCH] vmui: support for drag'n'drop in the "Trace analyzer" page (#3971) vmui: add drag-and-drop support for the trace analyzer page --- .../TraceQuery/NestedNav/NestedNav.tsx | 46 +++++++++++-- .../TraceQuery/NestedNav/style.scss | 25 ++++++- .../packages/vmui/src/hooks/useDropzone.ts | 68 +++++++++++++++++++ .../TraceUploadButtons/TraceUploadButtons.tsx | 35 ++++++++++ .../vmui/src/pages/TracePage/index.tsx | 60 ++++++++-------- .../vmui/src/pages/TracePage/style.scss | 15 ++++ docs/CHANGELOG.md | 2 + 7 files changed, 215 insertions(+), 36 deletions(-) create mode 100644 app/vmui/packages/vmui/src/hooks/useDropzone.ts create mode 100644 app/vmui/packages/vmui/src/pages/TracePage/TraceUploadButtons/TraceUploadButtons.tsx diff --git a/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/NestedNav.tsx b/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/NestedNav.tsx index 73d081ed6b..89528fcb38 100644 --- a/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/NestedNav.tsx +++ b/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/NestedNav.tsx @@ -1,4 +1,5 @@ -import React, { FC, useState } from "preact/compat"; +import React, { FC, useEffect, useRef, useState } from "preact/compat"; +import { MouseEvent } from "react"; import LineProgress from "../../Main/LineProgress/LineProgress"; import Trace from "../Trace"; import { ArrowDownIcon } from "../../Main/Icons"; @@ -6,6 +7,7 @@ import "./style.scss"; import classNames from "classnames"; import { useAppState } from "../../../state/common/StateContext"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; +import Button from "../../Main/Button/Button"; interface RecursiveProps { trace: Trace; @@ -20,6 +22,23 @@ const NestedNav: FC = ({ trace, totalMsec }) => { const { isDarkTheme } = useAppState(); const { isMobile } = useDeviceDetect(); const [openLevels, setOpenLevels] = useState({} as OpenLevels); + const messageRef = useRef(null); + + const [isExpanded, setIsExpanded] = useState(false); + const [showFullMessage, setShowFullMessage] = useState(false); + + useEffect(() => { + if (!messageRef.current) return; + const contentElement = messageRef.current; + const child = messageRef.current.children[0]; + const { height } = child.getBoundingClientRect(); + setIsExpanded(height > contentElement.clientHeight); + }, [trace]); + + const handleClickShowMore = (e: MouseEvent) => { + e.stopPropagation(); + setShowFullMessage(prev => !prev); + }; const handleListClick = (level: number) => () => { setOpenLevels((prevState:OpenLevels) => { @@ -54,11 +73,28 @@ const NestedNav: FC = ({ trace, totalMsec }) => {
-
- {trace.message} +
+ {trace.message}
-
- {`duration: ${trace.duration} ms`} +
+
+ {`duration: ${trace.duration} ms`} +
+ {(isExpanded || showFullMessage) && ( + + )}
{openLevels[trace.idValue] &&
diff --git a/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/style.scss b/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/style.scss index 932dffd017..05f35b6b61 100644 --- a/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/style.scss +++ b/app/vmui/packages/vmui/src/components/TraceQuery/NestedNav/style.scss @@ -43,12 +43,33 @@ } &__message { + position: relative; grid-column: 2; + line-height: 130%; + overflow: hidden; + text-overflow: ellipsis; + display: -moz-box; + -moz-box-orient: vertical; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + line-clamp: 3; + + &_show-full { + display: block; + overflow: visible; + } } - &__duration { + &-bottom { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; grid-column: 2; - color: $color-text-secondary; + + &__duration { + color: $color-text-secondary; + } } } } diff --git a/app/vmui/packages/vmui/src/hooks/useDropzone.ts b/app/vmui/packages/vmui/src/hooks/useDropzone.ts new file mode 100644 index 0000000000..7395a53bd9 --- /dev/null +++ b/app/vmui/packages/vmui/src/hooks/useDropzone.ts @@ -0,0 +1,68 @@ +import { useState, useEffect } from "preact/compat"; + +const useDropzone = (node: HTMLElement | null): {dragging: boolean, files: File[]} => { + const [files, setFiles] = useState([]); + const [dragging, setDragging] = useState(false); + + const handleAddFiles = (fileList: FileList) => { + const filesArray = Array.from(fileList || []); + setFiles(filesArray); + }; + + // handle drag events + const handleDrag = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (e.type === "dragenter" || e.type === "dragover") { + setDragging(true); + } else if (e.type === "dragleave") { + setDragging(false); + } + }; + + // triggers when file is dropped + const handleDrop = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + + setDragging(false); + if (e?.dataTransfer?.files && e.dataTransfer.files[0]) { + handleAddFiles(e.dataTransfer.files); + } + }; + + // triggers when file is pasted + const handlePaste = (e: ClipboardEvent) => { + const items = e.clipboardData?.items; + if (!items) return; + const jsonFiles = Array.from(items) + .filter(item => item.type === "application/json") + .map(item => item.getAsFile()) + .filter(file => file !== null) as File[]; + setFiles(jsonFiles); + }; + + useEffect(() => { + node?.addEventListener("dragenter", handleDrag); + node?.addEventListener("dragleave", handleDrag); + node?.addEventListener("dragover", handleDrag); + node?.addEventListener("drop", handleDrop); + node?.addEventListener("paste", handlePaste); + + return () => { + node?.removeEventListener("dragenter", handleDrag); + node?.removeEventListener("dragleave", handleDrag); + node?.removeEventListener("dragover", handleDrag); + node?.removeEventListener("drop", handleDrop); + node?.removeEventListener("paste", handlePaste); + }; + }, [node]); + + return { + files, + dragging, + }; +}; + +export default useDropzone; diff --git a/app/vmui/packages/vmui/src/pages/TracePage/TraceUploadButtons/TraceUploadButtons.tsx b/app/vmui/packages/vmui/src/pages/TracePage/TraceUploadButtons/TraceUploadButtons.tsx new file mode 100644 index 0000000000..a4ed9606d7 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/TracePage/TraceUploadButtons/TraceUploadButtons.tsx @@ -0,0 +1,35 @@ +import React, { FC } from "preact/compat"; +import Button from "../../../components/Main/Button/Button"; +import Tooltip from "../../../components/Main/Tooltip/Tooltip"; +import { ChangeEvent } from "react"; + +interface TraceUploadButtonsProps { + onOpenModal: () => void; + onChange: (e: ChangeEvent) => void; +} + +const TraceUploadButtons: FC = ({ onOpenModal, onChange }) => ( +
+ + + + +
+); + +export default TraceUploadButtons; diff --git a/app/vmui/packages/vmui/src/pages/TracePage/index.tsx b/app/vmui/packages/vmui/src/pages/TracePage/index.tsx index b8307fa23b..b3e6429a0b 100644 --- a/app/vmui/packages/vmui/src/pages/TracePage/index.tsx +++ b/app/vmui/packages/vmui/src/pages/TracePage/index.tsx @@ -2,7 +2,6 @@ import React, { FC, useEffect, useMemo, useState } from "preact/compat"; import { ChangeEvent } from "react"; import Trace from "../../components/TraceQuery/Trace"; import TracingsView from "../../components/TraceQuery/TracingsView"; -import Tooltip from "../../components/Main/Tooltip/Tooltip"; import Button from "../../components/Main/Button/Button"; import Alert from "../../components/Main/Alert/Alert"; import "./style.scss"; @@ -11,6 +10,8 @@ import Modal from "../../components/Main/Modal/Modal"; import JsonForm from "./JsonForm/JsonForm"; import { ErrorTypes } from "../../types"; import { useSearchParams } from "react-router-dom"; +import useDropzone from "../../hooks/useDropzone"; +import TraceUploadButtons from "./TraceUploadButtons/TraceUploadButtons"; const TracePage: FC = () => { const [openModal, setOpenModal] = useState(false); @@ -46,9 +47,7 @@ const TracePage: FC = () => { } }; - const handleChange = (e: ChangeEvent) => { - setErrors([]); - const files = Array.from(e.target.files || []); + const handleReadFiles = (files: File[]) => { files.map(f => { const reader = new FileReader(); const filename = f?.name || ""; @@ -58,6 +57,12 @@ const TracePage: FC = () => { }; reader.readAsText(f); }); + }; + + const handleChange = (e: ChangeEvent) => { + setErrors([]); + const files = Array.from(e.target.files || []); + handleReadFiles(files); e.target.value = ""; }; @@ -78,29 +83,12 @@ const TracePage: FC = () => { setSearchParams({}); }, []); - const UploadButtons = () => ( -
- - - - -
- ); + + const { files, dragging } = useDropzone(document.body); + + useEffect(() => { + handleReadFiles(files); + }, [files]); return (
@@ -126,7 +114,12 @@ const TracePage: FC = () => { ))}
- {hasTraces && } + {hasTraces && ( + + )}
@@ -158,8 +151,13 @@ const TracePage: FC = () => { {"\n"} Tracing graph will be displayed after file upload. + {"\n"} + Attach files by dragging & dropping, selecting or pasting them.

- +
)} @@ -177,6 +175,10 @@ const TracePage: FC = () => { /> )} + + {dragging && ( +
+ )}
); }; diff --git a/app/vmui/packages/vmui/src/pages/TracePage/style.scss b/app/vmui/packages/vmui/src/pages/TracePage/style.scss index 56e05b0c63..33e8eba090 100644 --- a/app/vmui/packages/vmui/src/pages/TracePage/style.scss +++ b/app/vmui/packages/vmui/src/pages/TracePage/style.scss @@ -75,4 +75,19 @@ line-height: 1.8; } } + + &__dropzone { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: inset $color-primary 0 0 10px; + opacity: 0.5; + z-index: 100; + pointer-events: none; + } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a8cea53764..7a0bf17b34 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -24,6 +24,8 @@ created by v1.90.0 or newer versions. The solution is to upgrade to v1.90.0 or n * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [VictoriaMetrics remote write protocol](https://docs.victoriametrics.com/vmagent.html#victoriametrics-remote-write-protocol) when [sending / receiving data to / from Kafka](https://docs.victoriametrics.com/vmagent.html#kafka-integration). This protocol allows saving egress network bandwidth costs when sending data from `vmagent` to `Kafka` located in another datacenter or availability zone. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1225). * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `--kafka.consumer.topic.concurrency` command-line flag. It controls the number of Kafka consumer workers to use by `vmagent`. It should eliminate the need to start multiple `vmagent` instances to improve data transfer rate. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1957). * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [Kafka producer and consumer](https://docs.victoriametrics.com/vmagent.html#kafka-integration) on `arm64` machines. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2271). +* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add support for drag'n'drop and paste from clipboard in the "Trace analyzer" page. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3971). +* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): Hide messages longer than 3 lines in the trace. You can view the full message by clicking on the `show more` button. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3971). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): Add the ability to manually input date and time when selecting a time range. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3968). * BUGFIX: prevent from slow [snapshot creating](https://docs.victoriametrics.com/#how-to-work-with-snapshots) under high data ingestion rate. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3551).