mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
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:
parent
ba167df617
commit
95b60c2777
7 changed files with 215 additions and 36 deletions
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
68
app/vmui/packages/vmui/src/hooks/useDropzone.ts
Normal file
68
app/vmui/packages/vmui/src/hooks/useDropzone.ts
Normal 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;
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Reference in a new issue