Skip to content

Commit

Permalink
vmui: add explore tab for exploration of metrics, which belong to a p…
Browse files Browse the repository at this point in the history
…articular job/instance (#3470)

* feat: add "Explore" page

* feat: add graphs for explore page

* vmui: add explore tab for exploration of metrics, which belong to a particular job/instance

* refactor: rename variables

* refactor: extract graph to ExploreMetricItemGraph.tsx

* feat: add searchable for Select.tsx

* feat: improve metrics explorer

* feat: set document title by page

* feat: add page to view icons

* fix: improve styles

* fix: add encodeURIComponent to query
  • Loading branch information
Loori-R committed Dec 22, 2022
1 parent f6ac045 commit f6d31f5
Show file tree
Hide file tree
Showing 37 changed files with 1,020 additions and 50 deletions.
1 change: 1 addition & 0 deletions app/vmui/packages/vmui/.env
@@ -0,0 +1 @@
FAST_REFRESH=false
3 changes: 2 additions & 1 deletion app/vmui/packages/vmui/README.md
Expand Up @@ -9,7 +9,8 @@ In the project directory, you can run:
### `npm start`

Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.\
Open [http://localhost:3000/#/icons](http://localhost:3000/#/icons) to view the icons used in the project.

The page will reload if you make edits.\
You will also see any lint errors in the console.
Expand Down
10 changes: 10 additions & 0 deletions app/vmui/packages/vmui/src/App.tsx
Expand Up @@ -10,6 +10,8 @@ import TopQueries from "./pages/TopQueries";
import ThemeProvider from "./components/Main/ThemeProvider/ThemeProvider";
import Spinner from "./components/Main/Spinner/Spinner";
import TracePage from "./pages/TracePage";
import ExploreMetrics from "./pages/ExploreMetrics";
import PreviewIcons from "./components/Main/Icons/PreviewIcons";

const App: FC = () => {

Expand Down Expand Up @@ -50,6 +52,14 @@ const App: FC = () => {
path={router.trace}
element={<TracePage/>}
/>
<Route
path={router.metrics}
element={<ExploreMetrics/>}
/>
<Route
path={router.icons}
element={<PreviewIcons/>}
/>
</Route>
</Routes>
</AppContextProvider>
Expand Down
16 changes: 16 additions & 0 deletions app/vmui/packages/vmui/src/api/explore-metrics.ts
@@ -0,0 +1,16 @@
import { TimeParams } from "../types";

export const getJobsUrl = (server: string, period: TimeParams): string =>
`${server}/api/v1/label/job/values?start=${period.start}&end=${period.end}`;

export const getInstancesUrl = (server: string, period: TimeParams, job: string): string =>
`${server}/api/v1/label/instance/values?match[]={job="${encodeURIComponent(job)}"}&start=${period.start}&end=${period.end}`;

export const getNamesUrl = (server: string, job: string, instance: string): string => {
const match = Object.entries({ job, instance })
.filter(val => val[1])
.map(([key, val]) => `${key}="${val}"`)
.join(",");

return `${server}/api/v1/label/__name__/values?match[]={${encodeURIComponent(match)}}`;
};
Expand Up @@ -8,7 +8,7 @@ const Footer: FC = () => {

return <footer className="vm-footer">
<a
className="vm-footer__link vm-footer__website"
className="vm__link vm-footer__website"
target="_blank"
href="https://victoriametrics.com/"
rel="noreferrer"
Expand All @@ -17,7 +17,7 @@ const Footer: FC = () => {
victoriametrics.com
</a>
<a
className="vm-footer__link"
className="vm__link"
target="_blank"
href="https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new"
rel="noreferrer"
Expand Down
10 changes: 0 additions & 10 deletions app/vmui/packages/vmui/src/components/Layout/Footer/style.scss
Expand Up @@ -17,16 +17,6 @@
gap: 6px;
}

&__link {
transition: color 200ms ease;
cursor: pointer;

&:hover {
color: $color-primary;
text-decoration: underline;
}
}

&__copyright {
text-align: right;
flex-grow: 1;
Expand Down
14 changes: 9 additions & 5 deletions app/vmui/packages/vmui/src/components/Layout/Header/Header.tsx
Expand Up @@ -28,25 +28,29 @@ const Header: FC = () => {
const { search, pathname } = useLocation();
const routes = useMemo(() => ([
{
label: "Custom panel",
label: routerOptions[router.home].title,
value: router.home,
},
{
label: "Dashboards",
label: routerOptions[router.dashboards].title,
value: router.dashboards,
hide: appModeEnable
},
{
label: "Cardinality",
label: routerOptions[router.cardinality].title,
value: router.cardinality,
},
{
label: "Top queries",
label: routerOptions[router.topQueries].title,
value: router.topQueries,
},
{
label: "Trace analyzer",
label: routerOptions[router.trace].title,
value: router.trace,
},
{
label: routerOptions[router.metrics].title,
value: router.metrics,
}
]), [appModeEnable]);

Expand Down
12 changes: 10 additions & 2 deletions app/vmui/packages/vmui/src/components/Layout/Layout.tsx
@@ -1,14 +1,22 @@
import Header from "./Header/Header";
import React, { FC } from "preact/compat";
import { Outlet } from "react-router-dom";
import React, { FC, useEffect } from "preact/compat";
import { Outlet, useLocation } from "react-router-dom";
import "./style.scss";
import { getAppModeEnable } from "../../utils/app-mode";
import classNames from "classnames";
import Footer from "./Footer/Footer";
import { routerOptions } from "../../router";

const Layout: FC = () => {
const appModeEnable = getAppModeEnable();

const { pathname } = useLocation();
useEffect(() => {
const defaultTitle = "VM UI";
const routeTitle = routerOptions[pathname]?.title;
document.title = routeTitle ? `${routeTitle} - ${defaultTitle}` : defaultTitle;
}, [pathname]);

return <section className="vm-container">
<Header/>
<div
Expand Down
Expand Up @@ -10,6 +10,9 @@ interface AutocompleteProps {
anchor: Ref<HTMLElement>
disabled?: boolean
maxWords?: number
minLength?: number
fullWidth?: boolean
noOptionsText?: string
onSelect: (val: string) => void,
onOpenAutocomplete?: (val: boolean) => void
}
Expand All @@ -20,6 +23,9 @@ const Autocomplete: FC<AutocompleteProps> = ({
anchor,
disabled,
maxWords = 1,
minLength = 2,
fullWidth,
noOptionsText,
onSelect,
onOpenAutocomplete
}) => {
Expand All @@ -39,6 +45,10 @@ const Autocomplete: FC<AutocompleteProps> = ({
}
}, [openAutocomplete, options, value]);

const displayNoOptionsText = useMemo(() => {
return noOptionsText && !foundOptions.length;
}, [noOptionsText,foundOptions]);

const handleCloseAutocomplete = () => {
setOpenAutocomplete(false);
};
Expand Down Expand Up @@ -84,7 +94,7 @@ const Autocomplete: FC<AutocompleteProps> = ({

useEffect(() => {
const words = (value.match(/[a-zA-Z_:.][a-zA-Z0-9_:.]*/gm) || []).length;
setOpenAutocomplete(value.length > 2 && words <= maxWords);
setOpenAutocomplete(value.length > minLength && words <= maxWords);
}, [value]);

useEffect(() => {
Expand Down Expand Up @@ -113,11 +123,13 @@ const Autocomplete: FC<AutocompleteProps> = ({
buttonRef={anchor}
placement="bottom-left"
onClose={handleCloseAutocomplete}
fullWidth={fullWidth}
>
<div
className="vm-autocomplete"
ref={wrapperEl}
>
{displayNoOptionsText && <div className="vm-autocomplete__no-options">{noOptionsText}</div>}
{foundOptions.map((option, i) =>
<div
className={classNames({
Expand Down
Expand Up @@ -3,4 +3,10 @@
.vm-autocomplete {
max-height: 300px;
overflow: auto;

&__no-options {
padding: $padding-global;
text-align: center;
color: $color-text-disabled;
}
}
38 changes: 38 additions & 0 deletions app/vmui/packages/vmui/src/components/Main/Icons/PreviewIcons.tsx
@@ -0,0 +1,38 @@
import React, { FC } from "preact/compat";
import * as icons from "./index";
import { useSnack } from "../../../contexts/Snackbar";
import "./style.scss";

const PreviewIcons: FC = () => {
const { showInfoMessage } = useSnack();

const handleClickIcon = (copyValue: string) => {
navigator.clipboard.writeText(`<${copyValue}/>`);
showInfoMessage({ text: `<${copyValue}/> has been copied`, type: "success" });
};

const createHandlerClickIcon = (key: string) => () => {
handleClickIcon(key);
};

return (
<div className="vm-preview-icons">
{Object.entries(icons).map(([iconKey, icon]) => (
<div
className="vm-preview-icons-item"
onClick={createHandlerClickIcon(iconKey)}
key={iconKey}
>
<div className="vm-preview-icons-item__svg">
{icon()}
</div>
<div className="vm-preview-icons-item__name">
{`<${iconKey}/>`}
</div>
</div>
))}
</div>
);
};

export default PreviewIcons;
20 changes: 11 additions & 9 deletions app/vmui/packages/vmui/src/components/Main/Icons/index.tsx
Expand Up @@ -116,15 +116,6 @@ export const ArrowDownIcon = () => (
</svg>
);

export const ArrowUpIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="m12 8-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"></path>
</svg>
);

export const ArrowDropDownIcon = () => (
<svg
viewBox="0 0 24 24"
Expand Down Expand Up @@ -318,3 +309,14 @@ export const DragIcon = () => (
<path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"></path>
</svg>
);

export const SearchIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
></path>
</svg>
);
53 changes: 53 additions & 0 deletions app/vmui/packages/vmui/src/components/Main/Icons/style.scss
@@ -0,0 +1,53 @@
@use "src/styles/variables" as *;

.vm-preview-icons {
display: grid;
align-items: flex-start;
justify-content: center;
grid-template-columns: repeat(auto-fill, 100px);
gap: $padding-global;

&-item {
display: grid;
grid-template-rows: 1fr auto;
align-items: stretch;
justify-content: center;
gap: $padding-small;
height: 100px;
padding: $padding-global $padding-small;
border-radius: $border-radius-small;
border: 1px solid transparent;
cursor: pointer;
transition: box-shadow 200ms ease-in-out;

&:hover {
box-shadow: rgba(0, 0, 0, 0.16) 0 1px 4px;
}

&:active &__svg {
transform: scale(0.9);
}

&__name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
font-size: $font-size-small;
line-height: 2;
}

&__svg {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
transition: transform 100ms ease-out;

svg {
width: auto;
height: 24px
}
}
}
}
@@ -1,4 +1,4 @@
@import 'src/styles/variables';
@use "src/styles/variables" as *;

$padding-modal: 22px;

Expand Down
13 changes: 9 additions & 4 deletions app/vmui/packages/vmui/src/components/Main/Popper/Popper.tsx
Expand Up @@ -12,7 +12,8 @@ interface PopperProps {
placement?: "bottom-right" | "bottom-left" | "top-left" | "top-right"
animation?: string
offset?: {top: number, left: number}
clickOutside?: boolean
clickOutside?: boolean,
fullWidth?: boolean
}

const Popper: FC<PopperProps> = ({
Expand All @@ -23,7 +24,8 @@ const Popper: FC<PopperProps> = ({
onClose,
animation,
offset = { top: 6, left: 0 },
clickOutside = true
clickOutside = true,
fullWidth
}) => {

const [isOpen, setIsOpen] = useState(true);
Expand Down Expand Up @@ -68,7 +70,8 @@ const Popper: FC<PopperProps> = ({

const position = {
top: 0,
left: 0
left: 0,
width: "auto"
};

const needAlignRight = placement === "bottom-right" || placement === "top-right";
Expand Down Expand Up @@ -96,8 +99,10 @@ const Popper: FC<PopperProps> = ({
if (isOverflowRight) position.left = buttonPos.right - popperSize.width - offsetLeft;
if (isOverflowLeft) position.left = buttonPos.left + offsetLeft;

if (fullWidth) position.width = `${buttonPos.width}px`;

return position;
},[buttonRef, placement, isOpen, children]);
},[buttonRef, placement, isOpen, children, fullWidth]);

if (clickOutside) useClickOutside(popperRef, () => setIsOpen(false), buttonRef);

Expand Down

0 comments on commit f6d31f5

Please sign in to comment.