Skip to content

Commit ba9826e

Browse files
committed
setting up settings
1 parent 7bcc749 commit ba9826e

File tree

5 files changed

+221
-31
lines changed

5 files changed

+221
-31
lines changed

components/dashboard/src/components/forms/SelectInputField.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { InputField } from "./InputField";
1111

1212
type Props = {
1313
label?: ReactNode;
14-
value: string;
14+
value: React.SelectHTMLAttributes<HTMLSelectElement>["value"];
1515
id?: string;
1616
hint?: ReactNode;
1717
error?: ReactNode;
@@ -67,7 +67,7 @@ export const SelectInputField: FunctionComponent<Props> = memo(
6767
);
6868

6969
type SelectInputProps = {
70-
value: string;
70+
value: React.SelectHTMLAttributes<HTMLSelectElement>["value"];
7171
className?: string;
7272
id?: string;
7373
disabled?: boolean;

components/dashboard/src/repositories/detail/ConfigurationDetailPrebuilds.tsx

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,56 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { FC, useState } from "react";
7+
import { FC, useCallback, useState } from "react";
88
import { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
99
import { ConfigurationSettingsField } from "./ConfigurationSettingsField";
1010
import { Heading3, Subheading } from "@podkit/typography/Headings";
1111
import { Switch } from "@podkit/switch/Switch";
1212
import { TextMuted } from "@podkit/typography/TextMuted";
13+
import { PrebuildSettingsForm } from "./prebuilds/PrebuildSettingsForm";
14+
import { useConfigurationMutation } from "../../data/configurations/configuration-queries";
15+
import { useToast } from "../../components/toasts/Toasts";
1316

1417
type Props = {
1518
configuration: Configuration;
1619
};
1720
export const ConfigurationDetailPrebuilds: FC<Props> = ({ configuration }) => {
18-
// TODO: hook this up to just use configuration as state and wire up optimistic update for mutation
21+
const { toast } = useToast();
22+
const updateConfiguration = useConfigurationMutation();
23+
1924
const [enabled, setEnabled] = useState(!!configuration.prebuildSettings?.enabled);
2025

26+
const updateEnabled = useCallback(
27+
(newEnabled: boolean) => {
28+
setEnabled(newEnabled);
29+
updateConfiguration.mutate(
30+
{
31+
configurationId: configuration.id,
32+
prebuildSettings: {
33+
...configuration.prebuildSettings,
34+
enabled: newEnabled,
35+
},
36+
},
37+
{
38+
onError: (err) => {
39+
toast(
40+
<>
41+
<span>
42+
{newEnabled
43+
? "There was a problem enabling prebuilds"
44+
: "There was a problem disabling prebuilds"}
45+
</span>
46+
{err?.message && <p>{err.message}</p>}
47+
</>,
48+
);
49+
setEnabled(!newEnabled);
50+
},
51+
},
52+
);
53+
},
54+
[configuration.id, configuration.prebuildSettings, toast, updateConfiguration],
55+
);
56+
2157
return (
2258
<>
2359
<ConfigurationSettingsField>
@@ -26,10 +62,10 @@ export const ConfigurationDetailPrebuilds: FC<Props> = ({ configuration }) => {
2662

2763
<div className="flex gap-4 mt-6">
2864
{/* TODO: wrap this in a SwitchInputField that handles the switch, label and description and htmlFor/id automatically */}
29-
<Switch checked={enabled} onCheckedChange={setEnabled} id="prebuilds-enabled" />
65+
<Switch checked={enabled} onCheckedChange={updateEnabled} id="prebuilds-enabled" />
3066
<div className="flex flex-col">
3167
<label className="font-semibold" htmlFor="prebuilds-enabled">
32-
Prebuilds are enabled
68+
{enabled ? "Prebuilds are enabled" : "Prebuilds are disabled"}
3369
</label>
3470
<TextMuted>
3571
Enabling requires permissions to configure repository webhooks.{" "}
@@ -46,6 +82,8 @@ export const ConfigurationDetailPrebuilds: FC<Props> = ({ configuration }) => {
4682
</div>
4783
</div>
4884
</ConfigurationSettingsField>
85+
86+
{enabled && <PrebuildSettingsForm configuration={configuration} />}
4987
</>
5088
);
5189
};
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { BranchMatchingStrategy, Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
8+
import { FC, FormEvent, useCallback, useState } from "react";
9+
import { ConfigurationSettingsField } from "../ConfigurationSettingsField";
10+
import { Heading3, Subheading } from "@podkit/typography/Headings";
11+
import { InputField } from "../../../components/forms/InputField";
12+
import { PartialConfiguration, useConfigurationMutation } from "../../../data/configurations/configuration-queries";
13+
import { useToast } from "../../../components/toasts/Toasts";
14+
import { SelectInputField } from "../../../components/forms/SelectInputField";
15+
import { TextInputField } from "../../../components/forms/TextInputField";
16+
import { WorkspaceClassOptions } from "../shared/WorkspaceClassOptions";
17+
import { LoadingButton } from "@podkit/buttons/LoadingButton";
18+
19+
type Props = {
20+
configuration: Configuration;
21+
};
22+
23+
export const PrebuildSettingsForm: FC<Props> = ({ configuration }) => {
24+
const { toast } = useToast();
25+
const updateConfiguration = useConfigurationMutation();
26+
27+
const [interval, setInterval] = useState<string>(`${configuration.prebuildSettings?.prebuildInterval ?? 20}`);
28+
const [branchStrategy, setBranchStrategy] = useState<BranchMatchingStrategy>(
29+
configuration.prebuildSettings?.branchStrategy ?? BranchMatchingStrategy.DEFAULT_BRANCH,
30+
);
31+
const [branchMatchingPattern, setBranchMatchingPattern] = useState<string>(
32+
configuration.prebuildSettings?.branchMatchingPattern || "**",
33+
);
34+
const [workspaceClass, setWorkspaceClass] = useState<string>(
35+
configuration.prebuildSettings?.workspaceClass || "g1-standard",
36+
);
37+
38+
const handleSubmit = useCallback(
39+
(e: FormEvent) => {
40+
e.preventDefault();
41+
42+
const newInterval = Math.abs(Math.min(Number.parseInt(interval), 100)) || 0;
43+
44+
const updatedConfig: PartialConfiguration = {
45+
configurationId: configuration.id,
46+
prebuildSettings: {
47+
...configuration.prebuildSettings,
48+
prebuildInterval: newInterval,
49+
branchStrategy,
50+
branchMatchingPattern,
51+
workspaceClass,
52+
},
53+
};
54+
55+
updateConfiguration.mutate(updatedConfig, {
56+
onSuccess: () => {
57+
toast("Your prebuild settings were updated.");
58+
},
59+
});
60+
},
61+
[
62+
branchMatchingPattern,
63+
branchStrategy,
64+
configuration.id,
65+
configuration.prebuildSettings,
66+
interval,
67+
toast,
68+
updateConfiguration,
69+
workspaceClass,
70+
],
71+
);
72+
73+
// TODO: Figure out if there's a better way to deal with grpc enums in the UI
74+
const handleBranchStrategyChange = useCallback((val) => {
75+
// This is pretty hacky, trying to coerce value into a number and then cast it to the enum type
76+
// Would be better if we treated these as strings instead of special enums
77+
setBranchStrategy(parseInt(val, 10) as BranchMatchingStrategy);
78+
}, []);
79+
80+
return (
81+
<ConfigurationSettingsField>
82+
<form onSubmit={handleSubmit}>
83+
<Heading3>Prebuild Settings</Heading3>
84+
<Subheading className="max-w-lg">These settings will be applied on every Prebuild.</Subheading>
85+
86+
<InputField
87+
label="Commit interval"
88+
hint="The number of commits to be skipped between prebuild runs."
89+
id="prebuild-interval"
90+
>
91+
<input
92+
type="number"
93+
id="prebuild-interval"
94+
min="0"
95+
max="100"
96+
step="5"
97+
value={interval}
98+
onChange={({ target }) => setInterval(target.value)}
99+
/>
100+
</InputField>
101+
102+
<SelectInputField
103+
label="Branch Filter"
104+
hint="Run prebuilds on the selected branches only."
105+
value={branchStrategy}
106+
onChange={handleBranchStrategyChange}
107+
>
108+
<option value={BranchMatchingStrategy.ALL_BRANCHES}>All branches</option>
109+
<option value={BranchMatchingStrategy.DEFAULT_BRANCH}>Default branch</option>
110+
<option value={BranchMatchingStrategy.MATCHED_BRANCHES}>Match branches by pattern</option>
111+
</SelectInputField>
112+
113+
{branchStrategy === BranchMatchingStrategy.MATCHED_BRANCHES && (
114+
<TextInputField
115+
label="Branch name pattern"
116+
hint="Glob patterns separated by commas are supported."
117+
value={branchMatchingPattern}
118+
onChange={setBranchMatchingPattern}
119+
/>
120+
)}
121+
122+
<WorkspaceClassOptions value={workspaceClass} onChange={setWorkspaceClass} />
123+
124+
<LoadingButton className="mt-8" type="submit" loading={updateConfiguration.isLoading}>
125+
Save
126+
</LoadingButton>
127+
</form>
128+
</ConfigurationSettingsField>
129+
);
130+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { FC } from "react";
8+
import { useWorkspaceClasses } from "../../../data/workspaces/workspace-classes-query";
9+
import { LoadingState } from "@podkit/loading/LoadingState";
10+
import { cn } from "@podkit/lib/cn";
11+
import Alert from "../../../components/Alert";
12+
import { Label } from "@podkit/forms/Label";
13+
import { RadioGroup, RadioGroupItem } from "@podkit/forms/RadioListField";
14+
15+
type Props = {
16+
value: string;
17+
className?: string;
18+
onChange: (newValue: string) => void;
19+
};
20+
export const WorkspaceClassOptions: FC<Props> = ({ value, className, onChange }) => {
21+
const { data: classes, isLoading } = useWorkspaceClasses();
22+
23+
if (isLoading) {
24+
return <LoadingState />;
25+
}
26+
27+
if (!classes) {
28+
return <Alert type="error">There was a problem loading workspace classes.</Alert>;
29+
}
30+
31+
return (
32+
<RadioGroup value={value} onValueChange={onChange} className={cn("mt-4", className)}>
33+
{classes.map((wsClass) => (
34+
<Label className="flex items-start space-x-2 my-2" key={wsClass.id}>
35+
<RadioGroupItem value={wsClass.id} />
36+
<div className="flex flex-col space-y-2">
37+
<span className="font-bold">{wsClass.displayName}</span>
38+
<span>{wsClass.description}</span>
39+
</div>
40+
</Label>
41+
))}
42+
</RadioGroup>
43+
);
44+
};

components/dashboard/src/repositories/detail/workspaces/WorkpaceSizeOptions.tsx

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,12 @@
66

77
import type { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
88
import React, { useCallback, useState } from "react";
9-
import { useWorkspaceClasses } from "../../../data/workspaces/workspace-classes-query";
10-
import { Label } from "@podkit/forms/Label";
11-
import { RadioGroup, RadioGroupItem } from "@podkit/forms/RadioListField";
129
import { Heading3, Subheading } from "@podkit/typography/Headings";
1310
import { ConfigurationSettingsField } from "../ConfigurationSettingsField";
1411
import { useToast } from "../../../components/toasts/Toasts";
1512
import { LoadingButton } from "@podkit/buttons/LoadingButton";
16-
import { LoadingState } from "@podkit/loading/LoadingState";
1713
import { useConfigurationMutation } from "../../../data/configurations/configuration-queries";
14+
import { WorkspaceClassOptions } from "../shared/WorkspaceClassOptions";
1815

1916
interface Props {
2017
configuration: Configuration;
@@ -27,8 +24,6 @@ export const ConfigurationWorkspaceSizeOptions = ({ configuration }: Props) => {
2724
const classChanged = selectedValue !== configuration.workspaceSettings?.workspaceClass;
2825

2926
const updateConfiguration = useConfigurationMutation();
30-
const { data: classes, isError, isLoading } = useWorkspaceClasses();
31-
3227
const { toast } = useToast();
3328

3429
const setWorkspaceClass = useCallback(
@@ -56,31 +51,14 @@ export const ConfigurationWorkspaceSizeOptions = ({ configuration }: Props) => {
5651
[configuration.id, selectedValue, toast, updateConfiguration],
5752
);
5853

59-
if (isError || !classes) {
60-
return <div>Something went wrong</div>;
61-
}
62-
63-
if (isLoading) {
64-
return <LoadingState />;
65-
}
66-
6754
return (
6855
<ConfigurationSettingsField>
6956
<form onSubmit={setWorkspaceClass}>
7057
<Heading3>Workspace Size Options</Heading3>
7158
<Subheading>Choose the size of your workspace based on the resources you need.</Subheading>
7259

73-
<RadioGroup value={selectedValue} onValueChange={setSelectedValue} className="mt-4">
74-
{classes.map((wsClass) => (
75-
<Label className="flex items-start space-x-2 my-2">
76-
<RadioGroupItem value={wsClass.id} />
77-
<div className="flex flex-col space-y-2">
78-
<span className="font-bold">{wsClass.displayName}</span>
79-
<span>{wsClass.description}</span>
80-
</div>
81-
</Label>
82-
))}
83-
</RadioGroup>
60+
<WorkspaceClassOptions value={selectedValue} onChange={setSelectedValue} />
61+
8462
<LoadingButton
8563
className="mt-8"
8664
type="submit"

0 commit comments

Comments
 (0)