Skip to content

Commit f7f09ff

Browse files
committed
Add resource filtering
1 parent fd84ed9 commit f7f09ff

File tree

1 file changed

+82
-60
lines changed

1 file changed

+82
-60
lines changed

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

Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import type { ProvisionerTiming } from "api/typesGenerated";
2-
import {
3-
Chart,
4-
type Duration,
5-
type ChartProps,
6-
type Timing,
7-
duration,
8-
} from "./Chart/Chart";
2+
import { Chart, type Duration, type Timing, duration } from "./Chart/Chart";
93
import { useState, type FC } from "react";
104
import type { Interpolation, Theme } from "@emotion/react";
115
import ChevronRight from "@mui/icons-material/ChevronRight";
126
import { YAxisSidePadding, YAxisWidth } from "./Chart/YAxis";
137
import { SearchField } from "components/SearchField/SearchField";
148

9+
// TODO: Export provisioning stages from the BE to the generated types.
1510
// We control the stages to be displayed in the chart so we can set the correct
1611
// colors and labels.
1712
const provisioningStages = [
@@ -21,73 +16,39 @@ const provisioningStages = [
2116
{ name: "apply" },
2217
];
2318

19+
// The advanced view is an expanded view of the stage, allowing the user to see
20+
// which resources within a stage are taking the most time. It supports resource
21+
// filtering and displays bars with different colors representing various states
22+
// such as created, deleted, etc.
23+
type TimingView =
24+
| { name: "basic" }
25+
| {
26+
name: "advanced";
27+
selectedStage: string;
28+
parentSection: string;
29+
filter: string;
30+
};
31+
2432
type WorkspaceTimingsProps = {
2533
provisionerTimings: readonly ProvisionerTiming[];
2634
};
2735

28-
type TimingView =
29-
| { type: "basic" }
30-
// The advanced view enables users to filter results based on the XAxis label
31-
| { type: "advanced"; selectedStage: string; parentSection: string };
32-
3336
export const WorkspaceTimings: FC<WorkspaceTimingsProps> = ({
3437
provisionerTimings,
3538
}) => {
36-
const [view, setView] = useState<TimingView>({ type: "basic" });
37-
let data: ChartProps["data"] = [];
38-
39-
if (view.type === "basic") {
40-
data = [
41-
{
42-
name: "provisioning",
43-
timings: provisioningStages.map((stage) => {
44-
// Get all the timing durations for a stage
45-
const durations = provisionerTimings
46-
.filter((t) => t.stage === stage.name)
47-
.map(extractDuration);
48-
const stageDuration = duration(durations);
49-
50-
// Mount the timing data that is required by the chart
51-
const stageTiming: Timing = {
52-
label: stage.name,
53-
count: durations.length,
54-
...stageDuration,
55-
};
56-
return stageTiming;
57-
}),
58-
},
59-
];
60-
}
61-
62-
if (view.type === "advanced") {
63-
data = [
64-
{
65-
name: `${view.selectedStage} stage`,
66-
timings: provisionerTimings
67-
.filter((t) => t.stage === view.selectedStage)
68-
.map((t) => {
69-
console.log("-> RESOURCE", t);
70-
return {
71-
label: t.resource,
72-
count: 0, // Resource timings don't have inner timings
73-
...extractDuration(t),
74-
} as Timing;
75-
}),
76-
},
77-
];
78-
}
39+
const [view, setView] = useState<TimingView>({ name: "basic" });
7940

8041
return (
8142
<div css={styles.panelBody}>
82-
{view.type === "advanced" && (
43+
{view.name === "advanced" && (
8344
<div css={styles.toolbar}>
8445
<ul css={styles.breadcrumbs}>
8546
<li>
8647
<button
8748
type="button"
8849
css={styles.breadcrumbButton}
8950
onClick={() => {
90-
setView({ type: "basic" });
51+
setView({ name: "basic" });
9152
}}
9253
>
9354
{view.parentSection}
@@ -101,26 +62,84 @@ export const WorkspaceTimings: FC<WorkspaceTimingsProps> = ({
10162

10263
<SearchField
10364
css={styles.searchField}
65+
value={view.filter}
10466
placeholder="Filter results..."
105-
onChange={(q: string) => {}}
67+
onChange={(q: string) => {
68+
setView((v) => ({
69+
...v,
70+
filter: q,
71+
}));
72+
}}
10673
/>
10774
</div>
10875
)}
10976

11077
<Chart
111-
data={data}
78+
data={selectChartData(view, provisionerTimings)}
11279
onBarClick={(stage, section) => {
11380
setView({
114-
type: "advanced",
81+
name: "advanced",
11582
selectedStage: stage,
11683
parentSection: section,
84+
filter: "",
11785
});
11886
}}
11987
/>
12088
</div>
12189
);
12290
};
12391

92+
export const selectChartData = (
93+
view: TimingView,
94+
timings: readonly ProvisionerTiming[],
95+
) => {
96+
switch (view.name) {
97+
case "basic": {
98+
const groupedTimingsByStage = provisioningStages.map((stage) => {
99+
const durations = timings
100+
.filter((t) => t.stage === stage.name)
101+
.map(extractDuration);
102+
const stageDuration = duration(durations);
103+
const stageTiming: Timing = {
104+
label: stage.name,
105+
count: durations.length,
106+
...stageDuration,
107+
};
108+
return stageTiming;
109+
});
110+
111+
return [
112+
{
113+
name: "provisioning",
114+
timings: groupedTimingsByStage,
115+
},
116+
];
117+
}
118+
119+
case "advanced": {
120+
const selectedStageTimings = timings
121+
.filter(
122+
(t) =>
123+
t.stage === view.selectedStage && t.resource.includes(view.filter),
124+
)
125+
.map((t) => {
126+
return {
127+
label: t.resource,
128+
count: 0, // Resource timings don't have inner timings
129+
...extractDuration(t),
130+
} as Timing;
131+
});
132+
133+
return [
134+
{
135+
name: `${view.selectedStage} stage`,
136+
timings: selectedStageTimings,
137+
},
138+
];
139+
}
140+
}
141+
};
142+
124143
const extractDuration = (t: ProvisionerTiming): Duration => {
125144
return {
126145
startedAt: new Date(t.started_at),
@@ -148,6 +167,7 @@ const styles = {
148167
alignItems: "center",
149168
gap: 4,
150169
lineHeight: 1,
170+
flexShrink: 0,
151171

152172
"& li": {
153173
display: "block",
@@ -182,6 +202,8 @@ const styles = {
182202
},
183203
}),
184204
searchField: (theme) => ({
205+
width: "100%",
206+
185207
"& fieldset": {
186208
border: 0,
187209
borderRadius: 0,

0 commit comments

Comments
 (0)