Skip to content

Commit fd84ed9

Browse files
committed
Add basic view and breadcrumbs
1 parent 62152ce commit fd84ed9

File tree

5 files changed

+191
-68
lines changed

5 files changed

+191
-68
lines changed

site/src/modules/workspaces/WorkspaceTiming/Chart/Bar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const styles = {
5656
height: 32,
5757
display: "flex",
5858
padding: 0,
59+
minWidth: 8,
5960

6061
"&:not(:disabled)": {
6162
cursor: "pointer",

site/src/modules/workspaces/WorkspaceTiming/Chart/Chart.tsx

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ import {
1515
barsSpacing,
1616
columnWidth,
1717
contentSidePadding,
18-
intervalDimension,
1918
XAxisHeight,
2019
} from "./constants";
2120
import { Bar } from "./Bar";
2221

22+
// When displaying the chart we must consider the time intervals to display the
23+
// data. For example, if the total time is 10 seconds, we should display the
24+
// data in 200ms intervals. However, if the total time is 1 minute, we should
25+
// display the data in 5 seconds intervals. To achieve this, we define the
26+
// dimensions object that contains the time intervals for the chart.
27+
const dimensions = {
28+
small: 500,
29+
default: 5_000,
30+
};
31+
2332
export type ChartProps = {
2433
data: DataSection[];
2534
onBarClick: (label: string, section: string) => void;
@@ -54,8 +63,33 @@ export type Timing = Duration & {
5463
};
5564

5665
export const Chart: FC<ChartProps> = ({ data, onBarClick }) => {
57-
const totalDuration = calcTotalDuration(data.flatMap((d) => d.timings));
58-
const intervals = createIntervals(totalDuration, intervalDimension);
66+
const totalDuration = duration(data.flatMap((d) => d.timings));
67+
const totalTime = durationTime(totalDuration);
68+
// Use smaller dimensions for the chart if the total time is less than 10
69+
// seconds; otherwise, use default intervals.
70+
const dimension = totalTime < 10_000 ? dimensions.small : dimensions.default;
71+
72+
// XAxis intervals
73+
const intervalsCount = Math.ceil(totalTime / dimension);
74+
const intervals = Array.from(
75+
{ length: intervalsCount },
76+
(_, i) => i * dimension + dimension,
77+
);
78+
79+
// Helper function to convert time into pixel size, used for setting bar width
80+
// and offset
81+
const calcSize = (time: number): number => {
82+
return (columnWidth * time) / dimension;
83+
};
84+
85+
const formatTime = (time: number): string => {
86+
if (dimension === dimensions.small) {
87+
return `${time.toLocaleString()}ms`;
88+
}
89+
return `${(time / 1_000).toLocaleString(undefined, {
90+
maximumFractionDigits: 2,
91+
})}s`;
92+
};
5993

6094
return (
6195
<div css={styles.chart}>
@@ -65,7 +99,10 @@ export const Chart: FC<ChartProps> = ({ data, onBarClick }) => {
6599
<YAxisCaption>{section.name}</YAxisCaption>
66100
<YAxisLabels>
67101
{section.timings.map((t) => (
68-
<YAxisLabel key={t.label} id={`${t.label}-label`}>
102+
<YAxisLabel
103+
key={t.label}
104+
id={`${encodeURIComponent(t.label)}-label`}
105+
>
69106
{t.label}
70107
</YAxisLabel>
71108
))}
@@ -75,28 +112,28 @@ export const Chart: FC<ChartProps> = ({ data, onBarClick }) => {
75112
</YAxis>
76113

77114
<div css={styles.main}>
78-
<XAxis labels={intervals.map(formatAsTimer)} />
115+
<XAxis labels={intervals.map(formatTime)} />
79116
<div css={styles.content}>
80117
{data.map((section) => {
81118
return (
82119
<div key={section.name} css={styles.bars}>
83120
{section.timings.map((t) => {
84-
// The time this timing started relative to the initial timing
85-
const offset = diffInSeconds(
86-
t.startedAt,
87-
totalDuration.startedAt,
88-
);
89-
const size = secondsToPixel(durationToSeconds(t));
121+
const offset =
122+
t.startedAt.getTime() - totalDuration.startedAt.getTime();
123+
const size = calcSize(durationTime(t));
90124
return (
91125
<Bar
92126
key={t.label}
93-
x={secondsToPixel(offset)}
127+
x={calcSize(offset)}
94128
width={size}
95-
afterLabel={`${durationToSeconds(t).toFixed(2)}s`}
129+
afterLabel={formatTime(durationTime(t))}
96130
aria-labelledby={`${t.label}-label`}
97131
ref={applyBarHeightToLabel}
98132
disabled={t.count <= 1}
99133
onClick={() => {
134+
if (t.count <= 1) {
135+
return;
136+
}
100137
onBarClick(t.label, section.name);
101138
}}
102139
>
@@ -130,41 +167,22 @@ const applyBarHeightToLabel = (bar: HTMLDivElement | null) => {
130167
// #coder_metadata.container_info[0]) will fail because it is not a valid
131168
// selector. To handle this, we need to query by the id attribute and escape
132169
// it with quotes.
133-
const label = document.querySelector<HTMLSpanElement>(`[id="${labelId}"]`);
170+
const label = document.querySelector<HTMLSpanElement>(
171+
`[id="${encodeURIComponent(labelId)}"]`,
172+
);
134173
if (!label) {
135174
return;
136175
}
137176
label.style.height = `${bar.clientHeight}px`;
138177
};
139178

140-
// Format a number in seconds to 00:00:00 format
141-
const formatAsTimer = (seconds: number): string => {
142-
const hours = Math.floor(seconds / 3600);
143-
const minutes = Math.floor((seconds % 3600) / 60);
144-
const remainingSeconds = seconds % 60;
145-
146-
return `${hours.toString().padStart(2, "0")}:${minutes
147-
.toString()
148-
.padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
149-
};
150-
151-
const durationToSeconds = (duration: Duration): number => {
152-
return (duration.endedAt.getTime() - duration.startedAt.getTime()) / 1000;
153-
};
154-
155-
// Create the intervals to be used in the XAxis
156-
const createIntervals = (duration: Duration, range: number): number[] => {
157-
const intervals = Math.ceil(durationToSeconds(duration) / range);
158-
return Array.from({ length: intervals }, (_, i) => i * range + range);
159-
};
160-
161-
const secondsToPixel = (seconds: number): number => {
162-
return (columnWidth * seconds) / intervalDimension;
179+
const durationTime = (duration: Duration): number => {
180+
return duration.endedAt.getTime() - duration.startedAt.getTime();
163181
};
164182

165183
// Combine multiple durations into a single duration by using the initial start
166184
// time and the final end time.
167-
export const calcTotalDuration = (durations: readonly Duration[]): Duration => {
185+
export const duration = (durations: readonly Duration[]): Duration => {
168186
const sortedDurations = durations
169187
.slice()
170188
.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
@@ -177,10 +195,6 @@ export const calcTotalDuration = (durations: readonly Duration[]): Duration => {
177195
return { startedAt: start, endedAt: end };
178196
};
179197

180-
const diffInSeconds = (b: Date, a: Date): number => {
181-
return (b.getTime() - a.getTime()) / 1000;
182-
};
183-
184198
const styles = {
185199
chart: {
186200
display: "flex",
@@ -216,6 +230,8 @@ const styles = {
216230
flexDirection: "column",
217231
flex: 1,
218232
borderLeft: `1px solid ${theme.palette.divider}`,
233+
height: "fit-content",
234+
minHeight: "100%",
219235
}),
220236
content: {
221237
flex: 1,

site/src/modules/workspaces/WorkspaceTiming/Chart/YAxis.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { barsSpacing, XAxisHeight } from "./constants";
55
// Predicting the caption height is necessary to add appropriate spacing to the
66
// grouped bars, ensuring alignment with the sidebar labels.
77
export const YAxisCaptionHeight = 20;
8+
export const YAxisWidth = 200;
9+
export const YAxisSidePadding = 16;
810

911
export const YAxis: FC<HTMLProps<HTMLDivElement>> = (props) => {
1012
return <div css={styles.root} {...props} />;
@@ -23,14 +25,18 @@ export const YAxisLabels: FC<HTMLProps<HTMLUListElement>> = (props) => {
2325
};
2426

2527
export const YAxisLabel: FC<HTMLProps<HTMLLIElement>> = (props) => {
26-
return <li {...props} css={styles.label} />;
28+
return (
29+
<li {...props} css={styles.label}>
30+
<span>{props.children}</span>
31+
</li>
32+
);
2733
};
2834

2935
const styles = {
3036
root: {
31-
width: 200,
37+
width: YAxisWidth,
3238
flexShrink: 0,
33-
padding: 16,
39+
padding: YAxisSidePadding,
3440
paddingTop: XAxisHeight,
3541
},
3642
caption: (theme) => ({
@@ -51,10 +57,15 @@ const styles = {
5157
textAlign: "right",
5258
},
5359
label: {
54-
display: "block",
55-
maxWidth: "100%",
56-
overflow: "hidden",
57-
textOverflow: "ellipsis",
58-
whiteSpace: "nowrap",
60+
display: "flex",
61+
alignItems: "center",
62+
63+
"& > *": {
64+
display: "block",
65+
width: "100%",
66+
overflow: "hidden",
67+
textOverflow: "ellipsis",
68+
whiteSpace: "nowrap",
69+
},
5970
},
6071
} satisfies Record<string, Interpolation<Theme>>;

site/src/modules/workspaces/WorkspaceTiming/Chart/constants.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,3 @@ export const contentSidePadding = 4;
1919
*/
2020
export const columnWidth = 130;
2121

22-
/**
23-
* Time interval used to calculate the XAxis dimension.
24-
*/
25-
export const intervalDimension = 5;

0 commit comments

Comments
 (0)