Skip to content

Conversation

@ibolton336
Copy link
Member

@ibolton336 ibolton336 commented Feb 2, 2026

Screen.Recording.2026-02-02.at.3.38.17.PM.mov
  • UI-side validation in ProfileEditorForm that blocks saves when no targets are selected
  • Shows inline error message and "Fix errors to save" status
  • The existing invalidLabelSelector error still catches truly empty selectors

Resolves #1228

Summary by CodeRabbit

  • New Features

    • Tooltip support for showing warning messages on configuration buttons.
    • Auto-save for profile edits when validations pass.
  • Improvements

    • Status indicators updated with clearer warning icon and message ("Fix errors to save") and automatic save feedback.
    • Global configuration-based warning detection replaces prior local check.
    • Stronger validation and feedback for profile names, targets, and rules.

@ibolton336 ibolton336 requested a review from a team as a code owner February 2, 2026 20:38
@snyk-io
Copy link

snyk-io bot commented Feb 2, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@ibolton336 ibolton336 changed the title 🐛 add profile-no-targets validation and prevent auto-save with error 🐛 add profile-no-targets validation and prevent auto save with error Feb 2, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

AnalysisPage removes a local profile-invalid flag and derives profileValidationErrors from rawConfigErrors (types: no-active-profile, invalid-label-selector, no-custom-rules), exposing hasProfileValidationErrors to ServerStatusToggle as its warning condition. ConfigButton conditionally wraps its button in a PatternFly Tooltip when hasWarning and a warningMessage exist. ProfileEditorForm adds name validation helpers (validateName, isNameValid), a canSaveProfile guard, validateAndSave flow, debounced/blur handling, handlers for default-rules toggle and removing custom rules that trigger auto-save when valid, and updates status UI to show ExclamationTriangleIcon and a “Fix errors to save” message on validation failure.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Modifications to AnalysisPage and ConfigButton extend beyond the stated objective of blocking saves in ProfileEditorForm. Extract or remove unrelated AnalysisPage and ConfigButton changes or document them as separate objectives.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the primary change: blocking save when no targets are selected with appropriate emoji prefix.
Description check ✅ Passed Description provides a concise summary of UI validation changes and references the resolved issue.
Linked Issues check ✅ Passed Implementation adds inline warning and blocks saving with no targets selected, satisfying issue #1228 requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Failure to add the new IP will result in interrupted reviews.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
webview-ui/src/components/AnalysisPage/ConfigButton/ConfigButton.tsx (1)

24-41: ⚠️ Potential issue | 🟡 Minor

Avoid empty aria-label and missing tooltip when warningMessage is "".

An empty string passes ?? unchanged and fails the tooltip condition, which can yield an empty label and no warning affordance. Prefer a truthy fallback check.

🔧 Suggested fix
-      aria-label={
-        hasWarning ? (warningMessage ?? "Configuration needs attention") : "Configuration"
-      }
+      aria-label={
+        hasWarning
+          ? (warningMessage || "Configuration needs attention")
+          : "Configuration"
+      }
...
-  if (hasWarning && warningMessage) {
-    return <Tooltip content={warningMessage}>{button}</Tooltip>;
-  }
+  if (hasWarning) {
+    return (
+      <Tooltip content={warningMessage || "Configuration needs attention"}>
+        {button}
+      </Tooltip>
+    );
+  }
🤖 Fix all issues with AI agents
In `@shared/src/labelSelector.ts`:
- Around line 6-12: The current hasTargetInLabelSelector function uses
/konveyor\.io\/target=\S+/ which treats negated selectors like
"!konveyor.io/target=eap7" as matches; add tests for negated-only selectors
(e.g., "!konveyor.io/target=eap7" and "!konveyor.io/target=eap7 &&
!konveyor.io/target=eap6") and decide intended behavior: if negated-only should
not count as a positive target, change hasTargetInLabelSelector to ignore
patterns preceded by "!" (e.g., use a regex that ensures the match is not
preceded by "!" or perform a simple parse that skips tokens starting with "!"
before testing for "konveyor.io/target="), update unit tests accordingly to
assert the chosen behavior, and keep function name hasTargetInLabelSelector as
the entry point to locate the change.

In `@webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx`:
- Around line 190-229: canSaveProfile and validateAndSave currently ignore the
profile name validity, allowing autosave of empty/duplicate names; update the
logic to include a name validation check (use the existing name validator
function, e.g., validateProfileName or validateName) so that
canSaveProfile(profileToCheck, targets) returns false when the profile name is
invalid and validateAndSave(updatedProfile, targets) also gates saving on name
validity; update callers (updateLabelSelector, handleDefaultRulesChange,
handleRemoveCustomRule) to rely on the amended canSaveProfile/validateAndSave
behavior so autosave is prevented unless name, targets, and rules all validate.
🧹 Nitpick comments (3)
shared/src/__tests__/labelSelector.test.ts (1)

191-235: Good test coverage, consider adding edge case tests.

The tests comprehensively cover the main scenarios. Consider adding tests for these edge cases to document expected behavior:

  1. Negated targets: !konveyor.io/target=eap7 - clarifies whether exclusions count as "having targets"
  2. Empty target value: konveyor.io/target= - verifies the \S+ requirement
  3. Partial match in different context: some-other-konveyor.io/target=value - ensures the pattern matches correctly
💡 Suggested additional test cases
+  it("should return true for negated targets (exclusion still indicates target configuration)", () => {
+    expect(hasTargetInLabelSelector("!konveyor.io/target=eap7")).toBe(true);
+  });
+
+  it("should return false for empty target value", () => {
+    expect(hasTargetInLabelSelector("konveyor.io/target=")).toBe(false);
+  });
vscode/core/src/utilities/configuration.ts (2)

146-154: Potential duplicate errors for empty label selectors.

When profile.labelSelector is empty or whitespace-only, both invalidLabelSelector (line 148) and profileNoTargets (line 153) errors will be added since hasTargetInLabelSelector returns false for empty strings.

If showing both errors is intentional (to communicate both that the selector is invalid AND that targets are needed), this is fine. Otherwise, consider making the target check conditional on having a valid (non-empty) label selector:

💡 Potential refinement to avoid duplicate errors
   // Check label selector
   if (!profile.labelSelector?.trim()) {
     draft.configErrors.push(createConfigError.invalidLabelSelector());
-  }
-
-  // Check for no target technologies in label selector
-  if (!hasTargetInLabelSelector(profile.labelSelector)) {
+  } else if (!hasTargetInLabelSelector(profile.labelSelector)) {
+    // Check for no target technologies only if label selector is non-empty
     draft.configErrors.push(createConfigError.profileNoTargets());
   }

219-227: Same consideration applies here for updateActiveProfileValidity.

Similar to updateConfigErrors, this function may add both invalidLabelSelector and profileNoTargets errors when the label selector is empty. Consider applying the same conditional logic if only one error should be shown.

@ibolton336 ibolton336 changed the title 🐛 add profile-no-targets validation and prevent auto save with error 🐛 Block save when no targets are selected Feb 2, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx`:
- Around line 286-299: The status indicator currently only checks
targetsValidation and rulesValidation, causing it to ignore nameValidation;
update the conditional that determines the icon and status message in the
ProfileEditorForm component (the logic using targetsValidation, rulesValidation,
isSaving) to also consider nameValidation so that when nameValidation ===
"error" the UI shows the error state (ExclamationTriangleIcon and "Fix errors to
save") and not the success/saving messages; ensure both the icon branch and the
message branch include nameValidation in the combined error check.
🧹 Nitpick comments (2)
webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx (2)

215-221: Consider using validateAndSave for consistency.

This handler uses canSaveProfile while updateLabelSelector uses validateAndSave. Using validateAndSave here would make the approach consistent and ensure validation state is set synchronously rather than relying on the useEffect.

♻️ Suggested refactor for consistency
   const handleDefaultRulesChange = (_e: React.FormEvent, checked: boolean) => {
     const updated = { ...localProfile, useDefaultRules: checked };
     setLocalProfile(updated);
-    if (canSaveProfile(updated, selectedTargets)) {
-      debouncedChange(updated);
-    }
+    validateAndSave(updated, selectedTargets);
   };

223-230: Same consistency suggestion applies here.

Like handleDefaultRulesChange, this could use validateAndSave for a uniform approach across all handlers.

♻️ Suggested refactor for consistency
   const handleRemoveCustomRule = (index: number) => {
     const updatedRules = localProfile.customRules.filter((_, i) => i !== index);
     const newProfile = { ...localProfile, customRules: updatedRules };
     setLocalProfile(newProfile);
-    if (canSaveProfile(newProfile, selectedTargets)) {
-      debouncedChange(newProfile);
-    }
+    validateAndSave(newProfile, selectedTargets);
   };

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx`:
- Around line 240-246: The remove/close handlers currently allow modifying a
read-only profile; update handleRemoveCustomRule (and the close handler
referenced around the same block) to no-op when the profile is read-only by
checking localProfile.readOnly (or profile.readOnly) along with existing
isDisabled checks—i.e., early-return if localProfile.readOnly || isDisabled
before computing updatedRules or calling debouncedChange—so removal and close
actions are gated for read-only profiles.
- Around line 141-178: handleBlur trims the name for debouncedChange but does
not update localProfile, leaving the input showing trailing spaces; after
successful validateName(localProfile) update the local state with the trimmed
name (e.g., call setLocalProfile({ ...localProfile, name:
localProfile.name.trim() })) and then call debouncedChange with that trimmed
profile so both the persisted value and the rendered input are synchronized;
keep validateName and isNameValid logic unchanged.

Comment on lines +141 to 178
// Validates profile name and updates validation state
const validateName = (profileToCheck: AnalysisProfile): boolean => {
const trimmedName = profileToCheck.name.trim();
const isDuplicate =
trimmedName !== profile.name && allProfiles.some((p) => p.name === trimmedName);
const isEmpty = trimmedName === "";

if (isEmpty) {
setNameValidation("error");
setNameErrorMsg("Profile name is required.");
return;
return false;
}

if (isDuplicate) {
setNameValidation("error");
setNameErrorMsg("A profile with this name already exists.");
return;
return false;
}

setNameValidation("default");
setNameErrorMsg(null);
debouncedChange({ ...localProfile, name: trimmedName });
return true;
};

// Checks name validity without updating state (for canSaveProfile)
const isNameValid = (profileToCheck: AnalysisProfile): boolean => {
const trimmedName = profileToCheck.name.trim();
const isDuplicate =
trimmedName !== profile.name && allProfiles.some((p) => p.name === trimmedName);
const isEmpty = trimmedName === "";
return !isEmpty && !isDuplicate;
};

const handleBlur = () => {
if (validateName(localProfile)) {
debouncedChange({ ...localProfile, name: localProfile.name.trim() });
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Sync trimmed names back into local state on blur.

handleBlur saves a trimmed name but leaves localProfile.name unchanged, so the input can display trailing spaces while the persisted value is trimmed. Consider updating local state to the trimmed value when saving.

🛠️ Proposed fix
 const handleBlur = () => {
-  if (validateName(localProfile)) {
-    debouncedChange({ ...localProfile, name: localProfile.name.trim() });
-  }
+  const trimmedName = localProfile.name.trim();
+  const updated = { ...localProfile, name: trimmedName };
+  if (validateName(updated)) {
+    setLocalProfile(updated);
+    debouncedChange(updated);
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Validates profile name and updates validation state
const validateName = (profileToCheck: AnalysisProfile): boolean => {
const trimmedName = profileToCheck.name.trim();
const isDuplicate =
trimmedName !== profile.name && allProfiles.some((p) => p.name === trimmedName);
const isEmpty = trimmedName === "";
if (isEmpty) {
setNameValidation("error");
setNameErrorMsg("Profile name is required.");
return;
return false;
}
if (isDuplicate) {
setNameValidation("error");
setNameErrorMsg("A profile with this name already exists.");
return;
return false;
}
setNameValidation("default");
setNameErrorMsg(null);
debouncedChange({ ...localProfile, name: trimmedName });
return true;
};
// Checks name validity without updating state (for canSaveProfile)
const isNameValid = (profileToCheck: AnalysisProfile): boolean => {
const trimmedName = profileToCheck.name.trim();
const isDuplicate =
trimmedName !== profile.name && allProfiles.some((p) => p.name === trimmedName);
const isEmpty = trimmedName === "";
return !isEmpty && !isDuplicate;
};
const handleBlur = () => {
if (validateName(localProfile)) {
debouncedChange({ ...localProfile, name: localProfile.name.trim() });
}
};
// Validates profile name and updates validation state
const validateName = (profileToCheck: AnalysisProfile): boolean => {
const trimmedName = profileToCheck.name.trim();
const isDuplicate =
trimmedName !== profile.name && allProfiles.some((p) => p.name === trimmedName);
const isEmpty = trimmedName === "";
if (isEmpty) {
setNameValidation("error");
setNameErrorMsg("Profile name is required.");
return false;
}
if (isDuplicate) {
setNameValidation("error");
setNameErrorMsg("A profile with this name already exists.");
return false;
}
setNameValidation("default");
setNameErrorMsg(null);
return true;
};
// Checks name validity without updating state (for canSaveProfile)
const isNameValid = (profileToCheck: AnalysisProfile): boolean => {
const trimmedName = profileToCheck.name.trim();
const isDuplicate =
trimmedName !== profile.name && allProfiles.some((p) => p.name === trimmedName);
const isEmpty = trimmedName === "";
return !isEmpty && !isDuplicate;
};
const handleBlur = () => {
const trimmedName = localProfile.name.trim();
const updated = { ...localProfile, name: trimmedName };
if (validateName(updated)) {
setLocalProfile(updated);
debouncedChange(updated);
}
};
🤖 Prompt for AI Agents
In `@webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx` around lines
141 - 178, handleBlur trims the name for debouncedChange but does not update
localProfile, leaving the input showing trailing spaces; after successful
validateName(localProfile) update the local state with the trimmed name (e.g.,
call setLocalProfile({ ...localProfile, name: localProfile.name.trim() })) and
then call debouncedChange with that trimmed profile so both the persisted value
and the rendered input are synchronized; keep validateName and isNameValid logic
unchanged.

…lid profiles

- Add 'profile-no-targets' ConfigError type to warn when active profile has no targets
- Add hasTargetInLabelSelector() utility to check for targets in label selector
- Update backend validation to check for no targets in active profile
- Update ProfileEditorForm to not auto-save when validation fails
- Move validation logic out of render method into helper functions
- Centralize profile warnings through ConfigAlerts instead of one-off alerts
- Add tooltip to ConfigButton when there are config warnings
- Simplify AnalysisPage to use rawConfigErrors for all warning states
- Add tests for hasTargetInLabelSelector function

Signed-off-by: Ian Bolton <[email protected]>
The UI already prevents saving profiles without targets, making the
backend validation redundant. The existing invalidLabelSelector error
handles truly empty selectors.

- Remove profile-no-targets ConfigError type
- Remove hasTargetInLabelSelector() utility and tests
- Remove profile-no-targets checks from backend validation
- Keep UI-side validation in ProfileEditorForm (blocks saves, shows error)

Signed-off-by: Ian Bolton <[email protected]>
- Add validateName() function that validates and updates state
- Add isNameValid() pure function for checking without side effects
- Update canSaveProfile() to check name, targets, and rules
- Update validateAndSave() to gate saving on name validity
- Update auto-save status indicator to show name validation errors
- Prevents autosave of empty or duplicate profile names

Signed-off-by: Ian Bolton <[email protected]>
The ServerStatusToggle was using rawConfigErrors.length > 0 which
included all config errors like genai-disabled. This caused the
Start button to be disabled even when the profile was valid.

Now filters to only profile validation errors:
- no-active-profile
- invalid-label-selector
- no-custom-rules

This fixes the e2e test failure where the Start button was disabled.

Signed-off-by: Ian Bolton <[email protected]>
- Add early return in handleRemoveCustomRule for read-only/disabled profiles
- Update onClose condition to check profile.readOnly in addition to isDisabled
- Ensures read-only profiles cannot have their custom rules modified

Signed-off-by: Ian Bolton <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx`:
- Around line 205-230: validateAndSave currently calls isNameValid (which only
checks validity) so a bad name blocks saving without updating validation state;
change validateAndSave to call validateName(updatedProfile) and use its result
to set validation state before deciding to save. Specifically, inside
validateAndSave replace the isProfileNameValid = isNameValid(...) call with a
call to validateName(updatedProfile) (or invoke whatever validateName
returns/side-effects to update UI state), then proceed to check isTargetsValid
and isRulesValid and call debouncedChange(updatedProfile) only if all are true;
updateLabelSelector remains the same but will now trigger validateAndSave which
will surface name errors via validateName.

Comment on lines +205 to +230
// Helper to check if a profile can be saved (all validation passes)
const canSaveProfile = (profileToCheck: AnalysisProfile, targets: string[]): boolean => {
const hasValidName = isNameValid(profileToCheck);
const hasTargets = targets.length > 0;
const hasRules =
profileToCheck.useDefaultRules || (profileToCheck.customRules?.length ?? 0) > 0;
return hasValidName && hasTargets && hasRules;
};

// Validates and optionally saves if valid
const validateAndSave = (updatedProfile: AnalysisProfile, targets: string[]) => {
const isProfileNameValid = isNameValid(updatedProfile);
const isTargetsValid = validateTargets(targets);
const isRulesValid = validateRules(updatedProfile);

if (isProfileNameValid && isTargetsValid && isRulesValid) {
debouncedChange(updatedProfile);
}
};

const updateLabelSelector = (sources: string[], targets: string[]) => {
const selector = buildLabelSelector(sources, targets);
const updatedProfile = { ...localProfile, labelSelector: selector };
setLocalProfile(updatedProfile);
validateAndSave(updatedProfile, targets);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Surface name validation when validateAndSave blocks a save.

validateAndSave uses isNameValid (no state updates), so a name-invalid edit can block saves while the status still appears “saved.” Consider using validateName here so the UI reflects the error.

🔧 Suggested fix
-  const isProfileNameValid = isNameValid(updatedProfile);
+  const isProfileNameValid = validateName(updatedProfile);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Helper to check if a profile can be saved (all validation passes)
const canSaveProfile = (profileToCheck: AnalysisProfile, targets: string[]): boolean => {
const hasValidName = isNameValid(profileToCheck);
const hasTargets = targets.length > 0;
const hasRules =
profileToCheck.useDefaultRules || (profileToCheck.customRules?.length ?? 0) > 0;
return hasValidName && hasTargets && hasRules;
};
// Validates and optionally saves if valid
const validateAndSave = (updatedProfile: AnalysisProfile, targets: string[]) => {
const isProfileNameValid = isNameValid(updatedProfile);
const isTargetsValid = validateTargets(targets);
const isRulesValid = validateRules(updatedProfile);
if (isProfileNameValid && isTargetsValid && isRulesValid) {
debouncedChange(updatedProfile);
}
};
const updateLabelSelector = (sources: string[], targets: string[]) => {
const selector = buildLabelSelector(sources, targets);
const updatedProfile = { ...localProfile, labelSelector: selector };
setLocalProfile(updatedProfile);
validateAndSave(updatedProfile, targets);
};
// Validates and optionally saves if valid
const validateAndSave = (updatedProfile: AnalysisProfile, targets: string[]) => {
const isProfileNameValid = validateName(updatedProfile);
const isTargetsValid = validateTargets(targets);
const isRulesValid = validateRules(updatedProfile);
if (isProfileNameValid && isTargetsValid && isRulesValid) {
debouncedChange(updatedProfile);
}
};
🤖 Prompt for AI Agents
In `@webview-ui/src/components/ProfileManager/ProfileEditorForm.tsx` around lines
205 - 230, validateAndSave currently calls isNameValid (which only checks
validity) so a bad name blocks saving without updating validation state; change
validateAndSave to call validateName(updatedProfile) and use its result to set
validation state before deciding to save. Specifically, inside validateAndSave
replace the isProfileNameValid = isNameValid(...) call with a call to
validateName(updatedProfile) (or invoke whatever validateName
returns/side-effects to update UI state), then proceed to check isTargetsValid
and isRulesValid and call debouncedChange(updatedProfile) only if all are true;
updateLabelSelector remains the same but will now trigger validateAndSave which
will surface name errors via validateName.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Start button disabled with no warning when no profile is configured correctly

2 participants