Skip to content

feat(RHINENG-20858): Use data driven forms Policy wizard#2724

Merged
LightOfHeaven1994 merged 1 commit intoRedHatInsights:masterfrom
LightOfHeaven1994:RHINENG-20858
Mar 5, 2026
Merged

feat(RHINENG-20858): Use data driven forms Policy wizard#2724
LightOfHeaven1994 merged 1 commit intoRedHatInsights:masterfrom
LightOfHeaven1994:RHINENG-20858

Conversation

@LightOfHeaven1994
Copy link
Copy Markdown
Contributor

@LightOfHeaven1994 LightOfHeaven1994 commented Jan 21, 2026

This PR brings data-driven-forms for Create policy wizard. Changes are shown when compliance.data-driven-forms feature flag is enabled (currently it's set to true in Stage).

To test change:

  1. Have feature flag on/off
  2. Test if everything related to create policy wizard works as before (you can create/modify policy, validation happens correctly, etc). You can compare behaviour vs Stage/Prod.
  3. There are 2 small design changes for data-driven-forms implementation RHINENG-24153 and RHINENG-24152

Secure Coding Practices Checklist GitHub Link

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

Summary by Sourcery

Introduce a data-driven-forms-based Create Policy wizard gated by feature flag and wire it into the existing Create Policy route.

New Features:

  • Add a new Create Policy wizard implementation built on data-driven-forms with dedicated steps for SCAP policy selection, details, systems selection, rules tailoring, review, and a progress-based finish screen.

Enhancements:

  • Refactor policy type components into PresentationalComponents for reuse in both legacy and new wizards.
  • Update routing to load a wrapper that chooses between the legacy and data-driven-forms wizards based on a feature flag.
  • Reuse the shared useProfileRuleIds hook from the Utilities hooks module in EditPolicyProfilesRules.

Build:

  • Add data-driven-forms core and PatternFly 4 mapper dependencies to support the new wizard implementation.

@LightOfHeaven1994 LightOfHeaven1994 marked this pull request as ready for review February 23, 2026 11:54
@LightOfHeaven1994 LightOfHeaven1994 requested a review from a team as a code owner February 23, 2026 11:54
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In RulesStepContent.onSelect, the branch for updatedSelectedRuleRefIds.length > 0 assumes the osMinorVersion already exists in the array and accesses updatedSelectedRuleRefIds[index] without checking for index === -1, which will throw if a new minor version is selected after others have been added; add a fallback to create a new entry when not found.
  • In FinishedStep's EmptyStateWithErrors, the errors.map uses key={error} and renders {error} directly, which will log [object Object] and trigger key warnings when errors contain objects (as GraphQL errors typically do); use a stable primitive key and render a specific field such as error.message instead.
  • The CreatePolicyWrapper component contains a console.log('Is data driven forms enabled?', isDDFEnabled); which will log on every render in production; consider removing or gating this behind a development check.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `RulesStepContent.onSelect`, the branch for `updatedSelectedRuleRefIds.length > 0` assumes the `osMinorVersion` already exists in the array and accesses `updatedSelectedRuleRefIds[index]` without checking for `index === -1`, which will throw if a new minor version is selected after others have been added; add a fallback to create a new entry when not found.
- In `FinishedStep`'s `EmptyStateWithErrors`, the `errors.map` uses `key={error}` and renders `{error}` directly, which will log `[object Object]` and trigger key warnings when `errors` contain objects (as GraphQL errors typically do); use a stable primitive key and render a specific field such as `error.message` instead.
- The `CreatePolicyWrapper` component contains a `console.log('Is data driven forms enabled?', isDDFEnabled);` which will log on every render in production; consider removing or gating this behind a development check.

## Individual Comments

### Comment 1
<location> `src/SmartComponents/CreatePolicyDDF/components/RulesStep.js:99-108` </location>
<code_context>
+      };
+    }, {});
+
+  const onSelect = useCallback(
+    (
+      _securityGuideId,
+      { os_minor_version: osMinorVersion },
+      newSelectedRuleIds,
+    ) => {
+      const updatedSelectedRuleRefIds = structuredClone(
+        selectedRuleRefIds || [],
+      );
+
+      if (updatedSelectedRuleRefIds.length === 0) {
+        if (newSelectedRuleIds.length === 0) {
+          return;
+        }
+
+        updatedSelectedRuleRefIds.push({
+          osMinorVersion,
+          ruleRefIds: newSelectedRuleIds,
+        });
+      } else {
+        const index = updatedSelectedRuleRefIds.findIndex(
+          ({ osMinorVersion: _osMinorVersion }) =>
+            _osMinorVersion === osMinorVersion,
</code_context>

<issue_to_address>
**issue (bug_risk):** Selecting rules for a previously unseen osMinorVersion can cause an out-of-bounds array access.

In `onSelect`, after `findIndex`, you access `updatedSelectedRuleRefIds[index]` without handling the `index === -1` case. If a user selects rules for an `os_minor_version` not yet in `selectedRuleRefIds`, this will cause an invalid read when dereferencing `.ruleRefIds`. You should handle `index === -1` by inserting a new entry for that `osMinorVersion`, similar to the branch used when the list is initially empty.
</issue_to_address>

### Comment 2
<location> `src/SmartComponents/CreatePolicyDDF/components/FinishedStep.js:125-129` </location>
<code_context>
+        const tailorings = fetchPolicyResponse.data;
+        dispatchProgress();
+
+        for (const tailoring of tailorings) {
+          const rulesToAssign = selectedRuleRefIds.find(
+            ({ osMinorVersion }) =>
+              Number(osMinorVersion) === tailoring.os_minor_version,
+          ).ruleRefIds;
+
+          await assignRules({
</code_context>

<issue_to_address>
**issue (bug_risk):** Assigning rules to tailorings assumes a matching selectedRuleRefIds entry for every tailoring.

Within the `tailorings` loop, `selectedRuleRefIds.find(...)` may return `undefined`, but `.ruleRefIds` is accessed unconditionally. If a tailoring exists for an `os_minor_version` that isn’t in `selectedRuleRefIds`, this will throw. Please add a guard (e.g., treat missing entries as an empty rule list, or skip/log them) to handle this mismatch safely.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/SmartComponents/CreatePolicyDDF/components/RulesStep.js
Comment thread src/SmartComponents/CreatePolicyDDF/components/FinishedStep.js
@LightOfHeaven1994 LightOfHeaven1994 self-assigned this Feb 23, 2026
@LightOfHeaven1994 LightOfHeaven1994 added the enhancement New feature or request label Feb 23, 2026
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 311 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.43%. Comparing base (7b92265) to head (ec3c06c).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
...Components/CreatePolicyDDF/components/RulesStep.js 0.00% 82 Missing ⚠️
...ponents/CreatePolicyDDF/components/FinishedStep.js 0.00% 76 Missing ⚠️
...mponents/CreatePolicyDDF/components/DetailsStep.js 0.00% 54 Missing ⚠️
...CreatePolicyDDF/components/CreateSCAPPolicyStep.js 0.00% 36 Missing ⚠️
...mponents/CreatePolicyDDF/components/SystemsStep.js 0.00% 23 Missing ⚠️
...omponents/CreatePolicyDDF/components/ReviewStep.js 0.00% 14 Missing ⚠️
src/SmartComponents/CreatePolicyDDF/schema.js 0.00% 10 Missing ⚠️
...rtComponents/CreatePolicyDDF/CreatePolicyWizard.js 0.00% 6 Missing ⚠️
...SmartComponents/CreatePolicyDDF/componentMapper.js 0.00% 6 Missing ⚠️
...martComponents/CreatePolicy/CreatePolicyWrapper.js 0.00% 4 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (7b92265) and HEAD (ec3c06c). Click for more details.

HEAD has 2 uploads less than BASE
Flag BASE (7b92265) HEAD (ec3c06c)
jest 1 0
cypress 1 0
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2724      +/-   ##
==========================================
- Coverage   65.83%   60.43%   -5.41%     
==========================================
  Files         247      258      +11     
  Lines        3401     3806     +405     
  Branches      987     1108     +121     
==========================================
+ Hits         2239     2300      +61     
- Misses       1162     1506     +344     
Flag Coverage Δ
combined 60.43% <0.00%> (-5.41%) ⬇️
cypress ?
jest ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RedHatInsights RedHatInsights deleted a comment from sourcery-ai Bot Feb 23, 2026
@LightOfHeaven1994 LightOfHeaven1994 marked this pull request as draft February 23, 2026 14:02
@LightOfHeaven1994 LightOfHeaven1994 marked this pull request as ready for review February 25, 2026 10:09
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 4 issues, and left some high level feedback:

  • In CreatePolicyWrapper you still have a console.log for the feature flag; this should be removed or switched to the app’s logging/telemetry mechanism before merging to avoid noisy console output in production.
  • In DetailsStepContent, the threshold local state is always initialized to 100 and never synchronized from the form values, so revisiting the step in the wizard will show 100 even if the user previously entered a different threshold; consider initializing and updating the local state from values.complianceThreshold to keep the UI and form state consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CreatePolicyWrapper` you still have a `console.log` for the feature flag; this should be removed or switched to the app’s logging/telemetry mechanism before merging to avoid noisy console output in production.
- In `DetailsStepContent`, the `threshold` local state is always initialized to `100` and never synchronized from the form values, so revisiting the step in the wizard will show 100 even if the user previously entered a different threshold; consider initializing and updating the local state from `values.complianceThreshold` to keep the UI and form state consistent.

## Individual Comments

### Comment 1
<location path="src/SmartComponents/CreatePolicyDDF/components/RulesStep.js" line_range="141-150" />
<code_context>
+    [change, selectedRuleRefIds],
+  );
+
+  useDeepCompareEffect(() => {
+    const newSelectedRuleRefIds = osMinorVersionCounts?.reduce(
+      (accum, { osMinorVersion }) => {
+        if (
+          selectedRuleRefIds?.some(
+            (entry) => entry.osMinorVersion === osMinorVersion,
+          )
+        ) {
+          return accum;
+        }
+        const refIdsPerMinorVersion = profilesAndRuleIds?.find(
+          ({ osMinorVersion: profileOsMinorVersion }) =>
+            profileOsMinorVersion === osMinorVersion,
+        ).ruleIds;
+
+        return [
+          ...accum,
+          ...(refIdsPerMinorVersion?.length
+            ? [{ osMinorVersion, ruleRefIds: refIdsPerMinorVersion }]
+            : []),
+        ];
+      },
+      [],
+    );
+    if (newSelectedRuleRefIds?.length !== 0) {
+      change('selectedRuleRefIds', [
+        ...(selectedRuleRefIds || []),
</code_context>
<issue_to_address>
**issue (bug_risk):** New selectedRuleRefIds calculation can spread `undefined`, causing a runtime error.

Because `reduce` is called without an explicit initial array, `newSelectedRuleRefIds` can be `undefined` when `osMinorVersionCounts` is `undefined`. In that case, `newSelectedRuleRefIds?.length !== 0` is `true` and the code enters the `if`, but `...[...newSelectedRuleRefIds]` will throw at runtime. You can avoid this either by giving `reduce` an initial empty array or by guarding the condition, e.g. `if (newSelectedRuleRefIds && newSelectedRuleRefIds.length !== 0)`.
</issue_to_address>

### Comment 2
<location path="src/SmartComponents/CreatePolicyDDF/components/FinishedStep.js" line_range="74-79" />
<code_context>
+        businessObjective,
+        complianceThreshold,
+        systems,
+        selectedRuleRefIds,
+        valueOverrides = {},
+      } = values;
+
+      const cloneFromProfileId = profile.id;
+      const minorVersions = selectedRuleRefIds.map(
+        ({ osMinorVersion }) => osMinorVersion,
+      );
</code_context>
<issue_to_address>
**issue (bug_risk):** Assuming `selectedRuleRefIds` is always defined can lead to a crash on policy creation.

In `createPolicyAsync`, `selectedRuleRefIds` is destructured from `values` and then used as `selectedRuleRefIds.map(...)`. If it ever comes through as `undefined`, this will throw and abort policy creation. Please either default it to an empty array in the destructuring (`selectedRuleRefIds = []`) or add a guard before calling `.map`.
</issue_to_address>

### Comment 3
<location path="src/SmartComponents/CreatePolicyDDF/components/SystemsStep.js" line_range="32" />
<code_context>
+const EmptyState = ({ osMajorVersion, onGoToStep1 }) => {
+  return (
+    <React.Fragment>
+      <Content className="pf-v6-u-mb-md">
+        <Content component="p">
+          You do not have any <b>RHEL {osMajorVersion}</b> systems connected to{' '}
+          Red Hat Lightspeed and enabled for Compliance.
+          <br />
+        </Content>
+      </Content>
+      <Content className="pf-v6-u-mb-md">
+        <Content component="p">
+          Choose a different RHEL version , or connect{' '}
+          <b>RHEL {osMajorVersion}</b> systems to Red Hat Lightspeed.
+        </Content>
</code_context>
<issue_to_address>
**nitpick (typo):** Minor punctuation/spacing issue in the Systems step empty state text.

The text currently reads `Choose a different RHEL version , or connect ...` with an extra space before the comma. Please update it to `version, or` to fix the spacing in the UI string.

```suggestion
          Choose a different RHEL version, or connect{' '}
```
</issue_to_address>

### Comment 4
<location path="src/SmartComponents/CreatePolicyDDF/schema.js" line_range="25" />
<code_context>
+              component: 'create-scap-policy-step',
+              name: 'scap-policy-selection',
+              validate: [
+                (value) =>
+                  value !== 'valid'
+                    ? 'Profile selection validation failed'
</code_context>
<issue_to_address>
**issue (complexity):** Consider introducing a shared `stepValidityValidator` with an explicit `{ isValid: boolean }` value shape to replace the repeated `'valid'` string checks and inline lambdas.

You can reduce the “magic string” coupling and repetition without changing behavior by making the validity contract explicit and centralized.

### 1. Replace `'valid'` sentinel with an explicit object shape

Define a shared validator that checks an explicit `isValid` flag instead of a string:

```js
// validation.js
export const stepValidityValidator = (value) =>
  value?.isValid === true ? undefined : 'Step validation pending or failed';
```

Then update the schema to use this validator instead of inline functions:

```js
import { componentTypes } from '@data-driven-forms/react-form-renderer';
import { stepValidityValidator } from './validation';

const schema = {
  fields: [
    {
      component: componentTypes.WIZARD,
      name: 'create-policy-wizard',
      // ...
      fields: [
        {
          name: 'step-create-scap-policy',
          title: 'Create SCAP policy',
          nextStep: 'step-details',
          fields: [
            {
              component: 'create-scap-policy-step',
              name: 'scap-policy-selection',
              validate: [stepValidityValidator],
            },
          ],
        },
        {
          name: 'step-details',
          title: 'Details',
          nextStep: 'step-systems',
          fields: [
            {
              component: 'details-step',
              name: 'details-content',
              validate: [stepValidityValidator],
            },
          ],
        },
        {
          name: 'step-rules',
          title: 'Rules',
          nextStep: 'step-review',
          fields: [
            {
              component: 'rules-step',
              name: 'rules-content',
              validate: [stepValidityValidator],
            },
          ],
        },
        // ...
      ],
    },
  ],
};

export default schema;
```

In your step components, switch from the string sentinel to the explicit shape:

```js
// before:
// input.onChange('valid');

// after:
input.onChange({ isValid: true });

// to mark invalid/pending:
input.onChange({ isValid: false });
```

You can still customize messages per step if needed:

```js
const makeStepValidityValidator = (message) => (value) =>
  value?.isValid === true ? undefined : message;

{
  component: 'create-scap-policy-step',
  name: 'scap-policy-selection',
  validate: [makeStepValidityValidator('Profile selection validation failed')],
}
```

This keeps your current gating behavior but:

- Removes the non-obvious `'valid'` protocol.
- Documents the contract via the `value` shape (`{ isValid: boolean }`).
- Centralizes validation logic instead of repeating inline lambdas.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/SmartComponents/CreatePolicyDDF/components/RulesStep.js
Comment thread src/SmartComponents/CreatePolicyDDF/components/FinishedStep.js
Comment thread src/SmartComponents/CreatePolicyDDF/components/SystemsStep.js Outdated
Comment thread src/SmartComponents/CreatePolicyDDF/schema.js Outdated
Copy link
Copy Markdown
Member

@bastilian bastilian left a comment

Choose a reason for hiding this comment

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

Works awesome! Thank you @LightOfHeaven1994!

- Add DDF implementation for Create policy wizard
- Move common pieces to utilities/presential-components
- Use feature flag
@LightOfHeaven1994 LightOfHeaven1994 enabled auto-merge (squash) March 5, 2026 11:33
@LightOfHeaven1994 LightOfHeaven1994 merged commit 9d1c4f7 into RedHatInsights:master Mar 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants