Skip to content

Commit

Permalink
Major overhaul of charts (#44)
Browse files Browse the repository at this point in the history
* Add loading indicator, refresh button

And keep track of a global state

* Add show promql toggle

* Revert change to icon

* Remove useRequestData hook

* Simplify

* Fix loading

* Fix alignment issue

* Persist timerange in extension instead of panel

* Cleanup

* Tweak color scheme and add click outside handler

For datepicker

* Add copy to clipboard

* Persist showingQuery in extension

This way switching tabs means queries no longer disappear (as they are
hidden by default right now)

Not just in the react app. This way when you switch tabs data is

* Improve formatting for some queries

* Refactor src/chart/utils

* Additional styling tweaks

* upgrade to typescript 5

* Refactor so more styling is shared

Between SingleChart and the Multi/Functions chart

* Fix lint issues

* Remove loading delay

* Feedback on pr

* Update changelog

* Fix build
  • Loading branch information
flenter authored Jun 8, 2023
1 parent 39bd758 commit d13655d
Show file tree
Hide file tree
Showing 41 changed files with 1,095 additions and 413 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [Unreleased]
- Add refresh button
- clean up styling
- add date picker to single chart views
- add toggle for showing/hiding PromQL queries
- add support for the "module" label for certain graphs

## [0.4.0]
- Add date picker to the function metrics panel
- [chore] update TypeScript plugin dependency
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,15 @@
"@msgpack/msgpack": "^3.0.0-beta2",
"byte-base64": "^1.1.0",
"fiberplane-charts": "git+https://git@github.com/fiberplane/fiberplane.git#workspace=fiberplane-charts",
"framer-motion": "^10.12.16",
"node-fetch": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"react-syntax-highlighter": "^15.5.0",
"styled-components": "^5.3.10",
"typescript": "^4.9.5"
"typescript": "^5.0.0",
"valtio": "^1.10.5"
},
"packageManager": "yarn@3.5.0"
}
41 changes: 33 additions & 8 deletions src/chartPanel.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as vscode from "vscode";
import type { TimeRange } from "fiberplane-charts";

import { formatProviderError } from "./providerRuntime/errors";
import type { MessageFromWebview, MessageToWebview } from "./charts";
import { OPEN_PANEL_COMMAND } from "./constants";
import type { Prometheus } from "./prometheus";
import { getNonce, getTitle } from "./utils";
import { createDefaultTimeRange, getNonce, getTitle } from "./utils";

/**
* Options for the kind of chart to display.
Expand All @@ -19,6 +20,11 @@ export type PanelOptions =
| SingleChartOptions
| { type: "function_graphs"; functionName: string; moduleName?: string };

export type GlobalGraphSettings = {
timeRange: TimeRange;
showingQuery: boolean;
};

type ChartPanel = {
/**
* Reveals the panel.
Expand Down Expand Up @@ -63,7 +69,11 @@ function createChartPanel(
prometheus: Prometheus,
options: PanelOptions,
): ChartPanel {
let currentOptions = options;
let currentOptions: PanelOptions & GlobalGraphSettings = {
...options,
timeRange: createDefaultTimeRange(),
showingQuery: false,
};
const panel = vscode.window.createWebviewPanel(
"autometricsChart",
getTitle(options),
Expand All @@ -76,9 +86,11 @@ function createChartPanel(
}

function update(options: PanelOptions) {
currentOptions = options;
const timeRange = currentOptions?.timeRange || createDefaultTimeRange();
const showingQuery = currentOptions?.showingQuery || false;
currentOptions = { ...options, timeRange, showingQuery };
panel.title = getTitle(options);
postMessage({ type: "show_panel", options });
postMessage({ type: "show_panel", options: currentOptions });
}

panel.webview.onDidReceiveMessage(
Expand All @@ -92,7 +104,7 @@ function createChartPanel(
prometheus
.fetchTimeseries(query, timeRange)
.then((data) => {
postMessage({ type: "show_data", timeRange, data, id });
postMessage({ type: "show_data", data, id });
})
.catch((error: unknown) => {
const errorMessage = formatProviderError(error);
Expand All @@ -102,12 +114,25 @@ function createChartPanel(
id,
error: errorMessage,
});
vscode.window.showErrorMessage(
`Could not query Prometheus. Query: ${query} Error: ${errorMessage}`,
);

if (options.type !== "function_graphs") {
vscode.window.showErrorMessage(
`Could not query Prometheus. Query: ${query} Error: ${errorMessage}`,
);
}
});
return;
}
case "update_time_range": {
const { timeRange } = message;
currentOptions = { ...currentOptions, timeRange };
return;
}
case "update_showing_query": {
const { showingQuery } = message;
currentOptions = { ...currentOptions, showingQuery };
return;
}
}
},
undefined,
Expand Down
20 changes: 13 additions & 7 deletions src/charts/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ButtonHTMLAttributes } from "react";
import { ButtonHTMLAttributes, forwardRef } from "react";
import styled from "styled-components";

type ButtonStyle = "primary" | "secondary";
Expand All @@ -7,14 +7,17 @@ type Props = {
buttonStyle?: ButtonStyle;
} & ButtonHTMLAttributes<HTMLButtonElement>;

export function Button(props: Props): JSX.Element {
export const Button = forwardRef(function Button(
props: Props,
ref: React.ForwardedRef<HTMLButtonElement>,
): JSX.Element {
const { children, className = "", buttonStyle = "primary", ...rest } = props;
return (
<StyledButton {...rest} className={`${className} ${buttonStyle}`}>
<StyledButton {...rest} ref={ref} className={`${className} ${buttonStyle}`}>
{children}
</StyledButton>
);
}
});

const StyledButton = styled.button`
--button-foreground: var(--vscode-button-foreground);
Expand All @@ -23,19 +26,22 @@ const StyledButton = styled.button`
border: none;
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
/* width: 100%; */
text-align: center;
outline: 1px solid transparent;
outline-offset: 2px !important;
color: var(--button-foreground);
background: var(--button-background);
&:hover {
[disabled] {
pointer-events: none;
}
&:not([disabled]):hover {
cursor: pointer;
background: var(--button-hoverBackground);
}
&:focus {
&:not([disabled]):focus {
outline-color: var(--vscode-focusBorder);
}
Expand Down
53 changes: 50 additions & 3 deletions src/charts/components/CodeBlock/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,57 @@
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { styles } from "./prismStyles";
import styled from "styled-components";
import { Button } from "../Button";
import { Copy } from "./Copy";
import { useRef } from "react";
import { pxToEm } from "../../utils";

export function CodeBlock({ query }: { query: string }) {
const ref = useRef<HTMLDivElement | null>(null);
return (
<SyntaxHighlighter language="promql" style={styles}>
{query}
</SyntaxHighlighter>
<Container>
<CodeContainer ref={ref}>
<SyntaxHighlighter language="promql" style={styles}>
{query}
</SyntaxHighlighter>
</CodeContainer>
<StyledButton
onClick={() => {
navigator.clipboard.writeText(query);
if (ref.current && window.getSelection) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(ref.current);
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
return;
}
}}
>
<Copy width="17" height="17" />
</StyledButton>
</Container>
);
}

const Container = styled.div`
display: grid;
grid-template-columns: auto 38px;
background:var(--vscode-dropdown-background, --vscode-editor-background, #ffffff);
margin: 0;
margin-top: ${pxToEm(13)};
border-radius: ${pxToEm(4)};
border: 1px solid var(--vscode-dropdown-border, --vscode-widget-border, #ccc);
`;

const CodeContainer = styled.div`
overflow: auto;
`;

const StyledButton = styled(Button)`
border-radius: var(--vscode-corner-size, ${pxToEm(4)});
border-top-left-radius: 0;
border-bottom-left-radius: 0;
`;
27 changes: 27 additions & 0 deletions src/charts/components/CodeBlock/Copy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react";
export const Copy = (props: React.SVGAttributes<HTMLOrSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={26}
height={26}
fill="none"
viewBox="0 0 26 26"
{...props}
>
<title>Copy</title>
<path
fill="currentColor"
fillOpacity={0.4}
fillRule="evenodd"
d="M21 5H8.714v12.286H21V5ZM8.714 3h-2v16.286H23V3H8.714Z"
clipRule="evenodd"
/>
<path
fill="currentColor"
fillOpacity={0.4}
fillRule="evenodd"
d="M5 7v14h14v2H3V7h2Z"
clipRule="evenodd"
/>
</svg>
);
9 changes: 2 additions & 7 deletions src/charts/components/CodeBlock/prismStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,10 @@ export const styles: { [key: string]: React.CSSProperties } = {
wordWrap: "normal",
fontFamily: "var(--vscode-editor-font-family, monospace)",
fontSize: "var(--vscode-editor-font-size, 14px)",
color: "var(--vscode-foreground, #76d9e6)",
color: "var(--vscode-dropdown-foreground, --vscode-foreground, #76d9e6)",
textShadow: "none",
background: "var(--vscode-editor-background, #ffffff)",
padding: "12px",
borderRadius: "4px",
border: "1px solid var(--vscode-widget-border, #ccc)",
overflow: "auto",
margin: "0",
position: "relative",
},
'pre > code[class*="language-"]': {
Expand All @@ -42,8 +39,6 @@ export const styles: { [key: string]: React.CSSProperties } = {
background: "var(--vscode-editor-background, #ffffff)",
padding: "0.15em 0.2em 0.05em",
borderRadius: ".3em",
border: "0.13em solid #7a6652",
boxShadow: "1px 1px 0.3em -0.1em #000 inset",
},
'pre[class*="language-"] code': {
whiteSpace: "pre",
Expand Down
36 changes: 29 additions & 7 deletions src/charts/components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TimeRange } from "fiberplane-charts";
import { Button } from "../Button";
import styled from "styled-components";
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import { DatePickerContent } from "./DatePickerContent";
import { useHandler } from "../../hooks";
import { Clock } from "./Clock";
Expand All @@ -15,21 +15,47 @@ type Props = {

export function DatePicker(props: Props) {
const [opened, setOpened] = useState(false);
const contentRef = useRef<HTMLDivElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);

const handler = useHandler((timeRange: TimeRange) => {
setOpened(false);
props.onChange(timeRange);
});

useEffect(() => {
const handler = (event: MouseEvent) => {
if (!contentRef.current) {
return;
}

if (
contentRef.current.contains(event.target as Node) ||
buttonRef.current?.contains(event.target as Node)
) {
return;
}

setOpened(false);
};

document.addEventListener("click", handler);
return () => document.removeEventListener("click", handler);
}, []);

return (
<>
<StyledButton buttonStyle="secondary" onClick={() => setOpened(!opened)}>
<StyledButton
buttonStyle="secondary"
onClick={() => setOpened(!opened)}
ref={buttonRef}
>
<Clock />
{props.timeRange.from} - {props.timeRange.to}
<CaretDown />
</StyledButton>
{opened && (
<Content>
<Content ref={contentRef}>
<DatePickerContent timeRange={props.timeRange} onChange={handler} />
</Content>
)}
Expand All @@ -47,9 +73,6 @@ const StyledButton = styled(Button)`
background: var(--vscode-editorWidget-background, transparent);
color: var(--vscode-editorWidget-foreground, transparent);
border: 1px solid var(--vscode-editorWidget-border, transparent);
/* background: var(--vscode-dropdown-background, transparent);
color: var(--vscode-dropdown-foreground, transparent);
border: 1px solid var(--vscode-dropdown-border, transparent); */
display: flex;
align-items: center;
gap: ${pxToEm(10)};
Expand All @@ -60,6 +83,5 @@ const StyledButton = styled(Button)`
&:hover {
background: var(--vscode-editorWidget-foreground, transparent);
color: var(--vscode-editorWidget-background, transparent);
/* background: var(--vscode-editorWidget-border, transparent); */
}
`;
7 changes: 2 additions & 5 deletions src/charts/components/DatePicker/DatePickerContent.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import * as React from "react";
import { TimeRange, Timestamp } from "fiberplane-charts";
import { useHandler } from "../../hooks";
import { MonthTable } from "./MonthTable";
import styled, { css } from "styled-components";
import { Button } from "../Button";
import { pxToEm, validateTimeRange } from "../../utils";
import { FormEvent, useRef, useState } from "react";
import { pxToEm } from "../../utils";
import { useState } from "react";
import { DatePickerForm } from "./DatePickerForm";
import { TimeRangePresets } from "./TimeRangePresets";

Expand Down
17 changes: 17 additions & 0 deletions src/charts/components/ErrorMessage/ErrorIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react";
export const ErrorIcon = (props: React.SVGAttributes<HTMLOrSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={23}
height={20}
fill="none"
viewBox="0 0 23 20"
{...props}
>
<title>Error icon</title>
<path
fill="currentColor"
d="M22.906 19.464 11.921.178a.35.35 0 0 0-.608 0L5.816 9.821.323 19.464a.362.362 0 0 0 0 .356.35.35 0 0 0 .303.18h21.978a.35.35 0 0 0 .303-.18.363.363 0 0 0 0-.356ZM12.67 17.143H10.56V15h2.11v2.143Zm0-3.572H10.56V8.096h2.11v5.475Z"
/>
</svg>
);
Loading

0 comments on commit d13655d

Please sign in to comment.