vmui: support for drag'n'drop in the "Trace analyzer" page (#3971)

vmui: add drag-and-drop support for the trace analyzer page
This commit is contained in:
Yury Molodov 2023-03-20 11:07:18 +01:00 committed by Aliaksandr Valialkin
parent ba167df617
commit 95b60c2777
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
7 changed files with 215 additions and 36 deletions

View file

@ -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<RecursiveProps> = ({ trace, totalMsec }) => {
const { isDarkTheme } = useAppState();
const { isMobile } = useDeviceDetect();
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
const messageRef = useRef<HTMLDivElement>(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<HTMLButtonElement>) => {
e.stopPropagation();
setShowFullMessage(prev => !prev);
};
const handleListClick = (level: number) => () => {
setOpenLevels((prevState:OpenLevels) => {
@ -54,11 +73,28 @@ const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
<div className="vm-nested-nav-header__progress">
<LineProgress value={progress}/>
</div>
<div className="vm-nested-nav-header__message">
{trace.message}
<div
className={classNames({
"vm-nested-nav-header__message": true,
"vm-nested-nav-header__message_show-full": showFullMessage,
})}
ref={messageRef}
>
<span>{trace.message}</span>
</div>
<div className="vm-nested-nav-header__duration">
{`duration: ${trace.duration} ms`}
<div className="vm-nested-nav-header-bottom">
<div className="vm-nested-nav-header-bottom__duration">
{`duration: ${trace.duration} ms`}
</div>
{(isExpanded || showFullMessage) && (
<Button
variant="text"
size="small"
onClick={handleClickShowMore}
>
{showFullMessage ? "Hide" : "Show more"}
</Button>
)}
</div>
</div>
{openLevels[trace.idValue] && <div>

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,68 @@
import { useState, useEffect } from "preact/compat";
const useDropzone = (node: HTMLElement | null): {dragging: boolean, files: File[]} => {
const [files, setFiles] = useState<File[]>([]);
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;

View file

@ -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<HTMLInputElement>) => void;
}
const TraceUploadButtons: FC<TraceUploadButtonsProps> = ({ onOpenModal, onChange }) => (
<div className="vm-trace-page-controls">
<Button
variant="outlined"
onClick={onOpenModal}
>
Paste JSON
</Button>
<Tooltip title="The file must contain tracing information in JSON format">
<Button>
Upload Files
<input
id="json"
type="file"
accept="application/json"
multiple
title=" "
onChange={onChange}
/>
</Button>
</Tooltip>
</div>
);
export default TraceUploadButtons;

View file

@ -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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
setErrors([]);
const files = Array.from(e.target.files || []);
handleReadFiles(files);
e.target.value = "";
};
@ -78,29 +83,12 @@ const TracePage: FC = () => {
setSearchParams({});
}, []);
const UploadButtons = () => (
<div className="vm-trace-page-controls">
<Button
variant="outlined"
onClick={handleOpenModal}
>
Paste JSON
</Button>
<Tooltip title="The file must contain tracing information in JSON format">
<Button>
Upload Files
<input
id="json"
type="file"
accept="application/json"
multiple
title=" "
onChange={handleChange}
/>
</Button>
</Tooltip>
</div>
);
const { files, dragging } = useDropzone(document.body);
useEffect(() => {
handleReadFiles(files);
}, [files]);
return (
<div className="vm-trace-page">
@ -126,7 +114,12 @@ const TracePage: FC = () => {
))}
</div>
<div>
{hasTraces && <UploadButtons/>}
{hasTraces && (
<TraceUploadButtons
onOpenModal={handleOpenModal}
onChange={handleChange}
/>
)}
</div>
</div>
@ -158,8 +151,13 @@ const TracePage: FC = () => {
</a>
{"\n"}
Tracing graph will be displayed after file upload.
{"\n"}
Attach files by dragging & dropping, selecting or pasting them.
</p>
<UploadButtons/>
<TraceUploadButtons
onOpenModal={handleOpenModal}
onChange={handleChange}
/>
</div>
)}
@ -177,6 +175,10 @@ const TracePage: FC = () => {
/>
</Modal>
)}
{dragging && (
<div className="vm-trace-page__dropzone"/>
)}
</div>
);
};

View file

@ -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;
}
}

View file

@ -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).