Skip to content

feat: add configurable dialog action#2350

Open
minottic wants to merge 5 commits into
masterfrom
generalize_dialog
Open

feat: add configurable dialog action#2350
minottic wants to merge 5 commits into
masterfrom
generalize_dialog

Conversation

@minottic
Copy link
Copy Markdown
Member

@minottic minottic commented Apr 22, 2026

Description

It adds a new action called dialog which can be configured in config.json allowing to set the shape of the dialog (fields, type of fields and required properties) and selecting a follow-up action.

Motivation

This will later make the cart and the overview datasets actions configurable, effectively replacing the hardcoded job submissions and enabling easy extensions to published data

dialog configuration:
image

generated form

image

Changes:

Please provide a list of the changes implemented by this PR

  • Extend dialog component with an "additionalFields" property where one can set additional user filled fields. This keeps the old "data" fields to avoid changing upstream
  • add support for dialog action in configurable actions
  • refactor actions for better maintainability
  • add tests for new actions and missing tests for registered functions
  • adds an output to actions enabling to control from the component which uses the action what to do on component end (e.g. clearBatch on dialog submission)
  • adds inputs for button type (raised or not) and color which can be configured from the component
  • adds a generic function to access data from actionItems

Tests included

  • Included for each change/fix?
  • Passing? (Merge will not be approved unless this is checked)

Documentation

  • swagger documentation updated [required]
  • official documentation updated [nice-to-have]

official documentation info

If you have updated the official documentation, please provide PR # and URL of the pages where the updates are included

Backend version

  • Does it require a specific version of the backend
  • which version of the backend is required:

Summary by Sourcery

Introduce a configurable dialog-based action type and richer action evaluation to the configurable actions framework, along with button styling controls and expanded configuration typing.

New Features:

  • Add a new "dialog" action type that opens a configurable dialog and can trigger follow-up actions such as XHR, form submit, or JSON download.
  • Allow configurable actions and dialogs to define dynamic additional fields and options, with dialog input fed back into action variables and payloads.
  • Expose button style configuration (raised/flat and color) for configurable action buttons and propagate it through the actions container component.
  • Type configurable action definitions and action items more strictly, including strongly typed dataset-based items and support for batch actions in app configuration.

Enhancements:

  • Refactor the configurable action component to centralize variable resolution, authorization token handling, expression evaluation, and dataset selector support.
  • Extend visibility and enable/disable expressions with higher-level keywords like dataset ownership, admin status, publication flags, and helper functions for list length and maximum downloadable size checks.
  • Improve the generic dialog component to support dynamic field rendering, validation, and better-structured data passing.
  • Adjust configurable action button markup to use standard Angular Material buttons and align tests with the new structure.

Documentation:

  • Update technical and reference documentation to describe the new "dialog" action type, onSuccess chaining, dialog configuration structure, and extended selector and expression keywords.
  • Update non-technical documentation to mention dialog-based actions as a user-facing capability.

Tests:

  • Add and extend unit tests for configurable actions to cover dialog flow, selector resolution, visibility/enabled expressions, and edge cases like missing max download size configuration.
  • Add dialog component tests for cancel behavior and dynamic field rendering and validation.
  • Add test configurations and fixtures for new dialog actions and stricter action typing.

@minottic minottic requested a review from a team as a code owner April 22, 2026 19:21
Copy link
Copy Markdown
Contributor

@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 3 issues, and left some high level feedback:

  • The new selector implementations in buildDatasetStaticMap don’t yet cover all the keywords documented in configurable-actions-technical.md (e.g. #DatasetsPidEmptyFilesMap, #DatasetsTotalSize, #DatasetsTotalPackedSize), so either add support for these tokens or remove them from the docs to avoid configuration surprises.
  • authorizationHandlers is used by resolve() for any #... token, but it only handles auth-related keywords; if a misconfigured payload or header accidentally contains a dataset selector like #Dataset0Pid directly, it will silently pass through unchanged—consider narrowing where authorizationHandlers is applied or failing fast on unknown # tokens to make misconfigurations easier to detect.
  • In DialogComponent you define a reactive FormGroup (form) but the template uses template-driven forms (ngForm/ngModel), so either remove the unused FormGroup or wire the template up to it to keep the implementation consistent and avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new selector implementations in `buildDatasetStaticMap` don’t yet cover all the keywords documented in `configurable-actions-technical.md` (e.g. `#DatasetsPidEmptyFilesMap`, `#DatasetsTotalSize`, `#DatasetsTotalPackedSize`), so either add support for these tokens or remove them from the docs to avoid configuration surprises.
- `authorizationHandlers` is used by `resolve()` for any `#...` token, but it only handles auth-related keywords; if a misconfigured payload or header accidentally contains a dataset selector like `#Dataset0Pid` directly, it will silently pass through unchanged—consider narrowing where `authorizationHandlers` is applied or failing fast on unknown `#` tokens to make misconfigurations easier to detect.
- In `DialogComponent` you define a reactive `FormGroup` (`form`) but the template uses template-driven forms (`ngForm`/`ngModel`), so either remove the unused `FormGroup` or wire the template up to it to keep the implementation consistent and avoid confusion.

## Individual Comments

### Comment 1
<location path="src/app/shared/modules/configurable-actions/configurable-action.component.ts" line_range="419-423" />
<code_context>
+    return data;
+  }
+
+  private actionFinishedEmit(success: boolean, resultOrError?: Error) {
+    this.actionFinished.emit({
+      success,
+      result: success ? resultOrError : undefined,
+      error: !success ? resultOrError : undefined,
+    });
+  }
</code_context>
<issue_to_address>
**issue:** The `actionFinishedEmit` parameter type is too narrow and will conflict with successful XHR responses.

In `typeXhr` you pass the parsed JSON response as the second argument to `actionFinishedEmit`, but that parameter is typed as `Error`. This doesn’t match usage and will either fail type-checking or require casts. Consider widening the parameter type, e.g.:

```ts
private actionFinishedEmit(success: boolean, payload?: unknown) {
  this.actionFinished.emit({
    success,
    result: success ? payload : undefined,
    error: !success ? (payload as Error) : undefined,
  });
}
```

or split success and error into separate, explicitly typed parameters.
</issue_to_address>

### Comment 2
<location path="src/app/shared/modules/configurable-actions/configurable-action.component.ts" line_range="359-376" />
<code_context>
-            duration: 2000,
-          },
-        );
+  private typeDialog() {
+    const dialogRef = this.dialog.open(DialogComponent, {
+      width: this.actionConfig.dialog?.width || "450px",
+      data: this.prepareDialogData(),
+    });
+
+    dialogRef.afterClosed().subscribe((result: Record<string, any>) => {
+      if (!result) return;
+      const dialogRes: Record<string, any> = {};
+      this.actionConfig.dialog?.fields?.forEach((f) => {
+        const v = result[f.key];
+        dialogRes[f.key] =
+          v && typeof v === "object" && "option" in v ? v.option : v;
       });
+      this.variables["dialog"] = dialogRes;
+      this.executeNextStep(this.actionConfig.onSuccess || "xhr");
+    });
+  }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Defaulting `onSuccess` for dialogs to `"xhr"` may trigger unintended follow-up requests.

In `typeDialog`, `executeNextStep` is always called with `this.actionConfig.onSuccess || "xhr"`, so any dialog without `onSuccess` configured will fall back to an XHR using the same `ActionConfig`. For dialogs meant only for confirmation (no follow-up action), this can trigger an unintended network request. Consider only invoking `executeNextStep` when `onSuccess` is set:

```ts
if (this.actionConfig.onSuccess) {
  this.executeNextStep(this.actionConfig.onSuccess);
}
```

so configs explicitly opt in to a follow-up step.

```suggestion
  private typeDialog() {
    const dialogRef = this.dialog.open(DialogComponent, {
      width: this.actionConfig.dialog?.width || "450px",
      data: this.prepareDialogData(),
    });

    dialogRef.afterClosed().subscribe((result: Record<string, any>) => {
      if (!result) return;
      const dialogRes: Record<string, any> = {};
      this.actionConfig.dialog?.fields?.forEach((f) => {
        const v = result[f.key];
        dialogRes[f.key] =
          v && typeof v === "object" && "option" in v ? v.option : v;
      });
      this.variables["dialog"] = dialogRes;

      if (this.actionConfig.onSuccess) {
        this.executeNextStep(this.actionConfig.onSuccess);
      }
    });
  }
```
</issue_to_address>

### Comment 3
<location path="src/app/shared/modules/dialog/dialog.component.ts" line_range="35-36" />
<code_context>
   standalone: false,
 })
 export class DialogComponent {
+  form: FormGroup = new FormGroup({});
+  fieldKeys: string[] = [];
+
   constructor(
</code_context>
<issue_to_address>
**suggestion:** Reactive `FormGroup` is declared but not used while the template relies on template-driven forms.

`form` and `fieldKeys` are never used in the template, which currently relies on `ngForm`/`ngModel`. This introduces an unused reactive-form setup and mixes patterns. Please either remove these properties or update the template to use the reactive form consistently.

Suggested implementation:

```typescript
export class DialogComponent {
  constructor(

```

You should also:
1. Remove the `FormGroup` (and possibly `FormControl`, `FormBuilder`, etc., if present) import from the top of `dialog.component.ts` since it's no longer used.
2. Ensure there are no remaining references to `form` or `fieldKeys` elsewhere in the file (methods, lifecycle hooks).
</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/app/shared/modules/configurable-actions/configurable-action.component.ts Outdated
Comment thread src/app/shared/modules/dialog/dialog.component.ts Outdated
@minottic
Copy link
Copy Markdown
Member Author

even if this does some refactor of configurable-actions, I believe some more refactor is necessary, but that would introduce breaking changes. In particular, I believe we should only support computation in the variables block and everywhere else only reference what defined in variables. For example, we should favor

  "variables": {
    "pid": "#Dataset0Pid",
    "files": "#Dataset0FilesPath",
    "totalSize": "#Dataset0FilesTotalSize",
    "folder": "#Dataset0SourceFolder"
    "maxSize": "#MaxDownloadableSize(@totalSize)"
  },
  "enabled": "@maxSize",

inplace of

  "variables": {
    "pid": "#Dataset0Pid",
    "files": "#Dataset0FilesPath",
    "totalSize": "#Dataset0FilesTotalSize",
    "folder": "#Dataset0SourceFolder"
  },
  "enabled": "#MaxDownloadableSize(@totalSize)",

this has the advantage of moving the resolution to one single field allowing for more control

@minottic minottic force-pushed the generalize_dialog branch 5 times, most recently from 0f79676 to f997b85 Compare April 22, 2026 20:07
New action type dialog that allows defining a dialog in config and emits on close

This includes a big refactor (improvement) of configurable action
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.

1 participant