Skip to content

Commit 2f04aae

Browse files
committed
✨ Manage profiles UX improvements
Signed-off-by: Ian Bolton <[email protected]>
1 parent 89bfd9f commit 2f04aae

File tree

2 files changed

+122
-15
lines changed

2 files changed

+122
-15
lines changed

webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,36 @@ import {
1818
Tooltip,
1919
StackItem,
2020
Stack,
21+
Title,
22+
Icon,
2123
} from "@patternfly/react-core";
22-
import { ExclamationCircleIcon } from "@patternfly/react-icons";
24+
import {
25+
ExclamationCircleIcon,
26+
CheckCircleIcon,
27+
StarIcon,
28+
InfoCircleIcon,
29+
} from "@patternfly/react-icons";
2330
import { useExtensionStateContext } from "../../context/ExtensionStateContext";
2431
import { AnalysisProfile, CONFIGURE_CUSTOM_RULES } from "@editor-extensions/shared";
2532
import { ConfirmDialog } from "../ConfirmDialog/ConfirmDialog";
2633
import { CreatableMultiSelectField } from "./CreatableMultiSelectField";
2734

2835
function useDebouncedCallback(callback: (...args: any[]) => void, delay: number) {
2936
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
30-
return (...args: any[]) => {
31-
if (timeoutRef.current) {
32-
clearTimeout(timeoutRef.current);
33-
}
34-
timeoutRef.current = setTimeout(() => callback(...args), delay);
37+
const [isPending, setIsPending] = useState(false);
38+
39+
return {
40+
callback: (...args: any[]) => {
41+
setIsPending(true);
42+
if (timeoutRef.current) {
43+
clearTimeout(timeoutRef.current);
44+
}
45+
timeoutRef.current = setTimeout(() => {
46+
callback(...args);
47+
setIsPending(false);
48+
}, delay);
49+
},
50+
isPending,
3551
};
3652
}
3753

@@ -52,6 +68,8 @@ export const ProfileEditorForm: React.FC<{
5268

5369
const { dispatch } = useExtensionStateContext();
5470

71+
const { callback: debouncedChange, isPending: isSaving } = useDebouncedCallback(onChange, 300);
72+
5573
useEffect(() => {
5674
setLocalProfile(profile);
5775
setNameValidation("default");
@@ -76,8 +94,6 @@ export const ProfileEditorForm: React.FC<{
7694
setSelectedTargets(parsedTargets);
7795
}, [profile]);
7896

79-
const debouncedChange = useDebouncedCallback(onChange, 300);
80-
8197
const handleInputChange = (value: string, field: keyof AnalysisProfile) => {
8298
const updated = { ...localProfile, [field]: value };
8399
setLocalProfile(updated);
@@ -118,6 +134,55 @@ export const ProfileEditorForm: React.FC<{
118134

119135
return (
120136
<Form isWidthLimited>
137+
{/* Active Profile Header */}
138+
{isActive && (
139+
<Alert
140+
variant="info"
141+
title={
142+
<Flex alignItems={{ default: "alignItemsCenter" }}>
143+
<FlexItem>
144+
<Icon>
145+
<StarIcon color="var(--pf-v5-global--info-color--100)" />
146+
</Icon>
147+
</FlexItem>
148+
<FlexItem>Active Profile</FlexItem>
149+
</Flex>
150+
}
151+
isInline
152+
style={{ marginBottom: "1rem" }}
153+
>
154+
This is your active analysis profile. It will be used for all new analyses. Changes are
155+
saved automatically, no action needed.
156+
</Alert>
157+
)}
158+
159+
{/* Auto-save Status */}
160+
<Flex
161+
justifyContent={{ default: "justifyContentSpaceBetween" }}
162+
alignItems={{ default: "alignItemsCenter" }}
163+
style={{ marginBottom: "1rem" }}
164+
>
165+
<FlexItem>
166+
<Title headingLevel="h3" size="lg">
167+
Profile Settings
168+
</Title>
169+
</FlexItem>
170+
<FlexItem>
171+
<Flex alignItems={{ default: "alignItemsCenter" }}>
172+
<FlexItem>
173+
<Icon>
174+
<CheckCircleIcon color="var(--pf-v5-global--success-color--100)" />
175+
</Icon>
176+
</FlexItem>
177+
<FlexItem>
178+
<span style={{ fontSize: "0.875rem", color: "var(--pf-v5-global--Color--200)" }}>
179+
{isSaving ? "Saving..." : "Changes saved automatically"}
180+
</span>
181+
</FlexItem>
182+
</Flex>
183+
</FlexItem>
184+
</Flex>
185+
121186
{nameValidation === "error" && (
122187
<FormAlert>
123188
<Alert
@@ -159,7 +224,15 @@ export const ProfileEditorForm: React.FC<{
159224
}}
160225
initialOptions={targetOptions}
161226
/>
227+
<FormHelperText>
228+
<HelperText>
229+
<HelperTextItem icon={<InfoCircleIcon />}>
230+
Technologies you want to migrate to (e.g., Spring Boot, Quarkus)
231+
</HelperTextItem>
232+
</HelperText>
233+
</FormHelperText>
162234
</FormGroup>
235+
163236
<FormGroup label="Source Technologies" fieldId="sources">
164237
<CreatableMultiSelectField
165238
fieldId="sources"
@@ -170,6 +243,13 @@ export const ProfileEditorForm: React.FC<{
170243
}}
171244
initialOptions={sourceOptions}
172245
/>
246+
<FormHelperText>
247+
<HelperText>
248+
<HelperTextItem icon={<InfoCircleIcon />}>
249+
Technologies you&apos;re migrating from (e.g., Java EE, WebLogic)
250+
</HelperTextItem>
251+
</HelperText>
252+
</FormHelperText>
173253
</FormGroup>
174254

175255
<FormGroup label="Use Default Rules" fieldId="use-default-rules">
@@ -183,7 +263,15 @@ export const ProfileEditorForm: React.FC<{
183263
debouncedChange(updated);
184264
}}
185265
/>
266+
<FormHelperText>
267+
<HelperText>
268+
<HelperTextItem icon={<InfoCircleIcon />}>
269+
Include Konveyor&apos;s built-in migration rules
270+
</HelperTextItem>
271+
</HelperText>
272+
</FormHelperText>
186273
</FormGroup>
274+
187275
<FormGroup label="Custom Rules" fieldId="custom-rules">
188276
<Stack hasGutter>
189277
<StackItem isFilled>
@@ -199,6 +287,13 @@ export const ProfileEditorForm: React.FC<{
199287
>
200288
Select Custom Rules…
201289
</Button>
290+
<FormHelperText>
291+
<HelperText>
292+
<HelperTextItem icon={<InfoCircleIcon />}>
293+
Add your own custom migration rules
294+
</HelperTextItem>
295+
</HelperText>
296+
</FormHelperText>
202297
</StackItem>
203298
<StackItem>
204299
<LabelGroup aria-label="Custom Rules">
@@ -229,13 +324,19 @@ export const ProfileEditorForm: React.FC<{
229324

230325
<Flex spaceItems={{ default: "spaceItemsMd" }}>
231326
<FlexItem>
232-
<Button
233-
variant="secondary"
234-
onClick={() => onMakeActive(profile.id)}
235-
isDisabled={isActive}
236-
>
237-
Make Active
238-
</Button>
327+
{isActive ? (
328+
<Tooltip content="This profile is already active and will be used for analyses">
329+
<Button variant="secondary" isDisabled={true} icon={<StarIcon />}>
330+
Active Profile
331+
</Button>
332+
</Tooltip>
333+
) : (
334+
<Tooltip content="Set this profile as active to use it for new analyses">
335+
<Button variant="secondary" onClick={() => onMakeActive(profile.id)}>
336+
Make Active
337+
</Button>
338+
</Tooltip>
339+
)}
239340
</FlexItem>
240341
<FlexItem>
241342
<Button
@@ -247,6 +348,7 @@ export const ProfileEditorForm: React.FC<{
247348
</Button>
248349
</FlexItem>
249350
</Flex>
351+
250352
<ConfirmDialog
251353
isOpen={isDeleteDialogOpen}
252354
title="Delete profile?"

webview-ui/src/components/ProfileManager/ProfileList.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ export const ProfileList: React.FC<{
106106
key="make-active"
107107
onClick={() => onMakeActive(profile.id)}
108108
isDisabled={active === profile.id}
109+
description={
110+
active === profile.id
111+
? "This profile is already active. No action needed."
112+
: ""
113+
}
109114
>
110115
{active === profile.id ? "Active" : "Make Active"}
111116
</DropdownItem>

0 commit comments

Comments
 (0)