# custom-knime-workflows

A Strapi 5 plugin for managing KNIME workflow integrations. It provides content-types for storing KNIME server configurations and job definitions, a service layer for triggering remote KNIME jobs over HTTP, and a lightweight admin UI for navigating directly to the relevant Content Manager collections.

---

## Overview

The plugin acts as the bridge between Strapi and a KNIME Server REST API. A *Configuration* stores connection credentials; a *Job* references a specific workflow on that server together with an optional AJV JSON Schema that validates the request payload; a *Job Request* is created each time a workflow is triggered and stores both the request and the KNIME response for auditing.

---

## Directory structure

```
custom-knime-workflows/
├── package.json
├── README.md
├── admin/
│   ├── jsconfig.json
│   └── src/
│       ├── index.js                    # Plugin registration + registerTrads
│       ├── pluginId.js                 # PLUGIN_ID = 'custom-knime-workflows'
│       ├── components/
│       │   └── PluginIcon.jsx          # Rocket icon from @strapi/icons
│       ├── pages/
│       │   ├── App.jsx                 # Root router (only HomePage)
│       │   └── HomePage.jsx            # Landing page: 3 SectionCards → Content Manager
│       ├── shared/
│       │   └── constants.js            # UIDs: CONFIGURATION_UID, JOB_UID, JOB_REQUEST_UID
│       ├── translations/
│       │   ├── en.json
│       │   ├── it.json
│       │   └── es.json
│       └── utils/
│           └── getTranslation.js       # Namespaces a key: `${PLUGIN_ID}.${id}`
└── server/
    ├── jsconfig.json
    └── src/
        ├── index.js                    # Aggregates all server exports
        ├── register.js                 # Registers components + document middleware
        ├── bootstrap.js                # No-op
        ├── destroy.js                  # No-op
        ├── config/
        │   └── index.js                # Empty config (no required env vars)
        ├── components/
        │   ├── job-request-payload.json         # Component: key-value payload item
        │   └── job-request-payload-log.json     # Component: request+response audit log
        ├── content-types/
        │   ├── index.js
        │   ├── configuration/schema.json
        │   ├── job/schema.json
        │   └── job-request/schema.json
        ├── controllers/
        │   ├── index.js
        │   └── knimeRequest.js         # createRequest · getRequests · updateRequestStatus
        ├── routes/
        │   ├── index.js
        │   ├── admin.js                # Empty (no admin-only routes)
        │   └── content-api.js          # 3 public routes (⚠ auth: false – see notes)
        ├── services/
        │   ├── index.js
        │   └── knime-request.js        # Core logic: validate → create → call KNIME → update
        ├── middlewares/
        │   └── index.js                # Empty
        └── policies/
            └── index.js                # Empty
```

---

## Content-types

All three content-types are visible in the Strapi Content Manager (`content-manager.visible: true`) and hidden from the Content-Type Builder.

### `plugin::custom-knime-workflows.configuration`

Stores connection details for a KNIME Server instance.

| Field        | Type   | Notes                                  |
|--------------|--------|----------------------------------------|
| `name`       | string | Required. Friendly label.              |
| `jobsBaseUrl`| string | Required. KNIME REST v4 jobs endpoint. |
| `username`   | string | Required. HTTP Basic Auth user.        |
| `password`   | string | Required. Stored in plain text – consider encrypting at rest. |

i18n-enabled (non-localised fields).

### `plugin::custom-knime-workflows.job`

Defines a single KNIME workflow to be triggered.

| Field             | Type     | Notes                                                  |
|-------------------|----------|--------------------------------------------------------|
| `name`            | string   | Required. Human-readable identifier.                   |
| `jobId`           | uid      | Required. KNIME Server job UUID.                       |
| `description`     | text     | Optional.                                              |
| `configuration`   | relation | oneToOne → `configuration`. Required.                  |
| `payloadTemplate` | json     | Optional. JSON Schema used by AJV to validate payloads.|

### `plugin::custom-knime-workflows.job-request`

Audit log for each workflow execution.

| Field        | Type      | Notes                                                              |
|--------------|-----------|--------------------------------------------------------------------|
| `identifier` | string    | Auto-generated: `{job.name}-{timestamp}`. Unique.                  |
| `job`        | relation  | oneToOne → `job`. Required.                                        |
| `user`       | relation  | oneToOne → `users-permissions.user`. Required.                     |
| `payload`    | component | Repeatable `job-request-payload` (key/value pairs).                |
| `errored`    | boolean   | `true` if the KNIME API call failed.                               |
| `auditLog`   | component | Single `job-request-payload-log` storing `request` + `response` JSON.|

---

## Server components (registered programmatically)

Components are registered in `register.js` via `strapi.get('components').add(...)` because Strapi does not auto-scan plugin component directories.

| UID                                          | Fields                               |
|----------------------------------------------|--------------------------------------|
| `custom-knime-workflows.job-request-payload` | `key` (string), `value` (string), `pairKey` (string, hidden in CM) |
| `custom-knime-workflows.job-request-payload-log` | `request` (json), `response` (json) |

The `register.js` document middleware also auto-populates `identifier` and `pairKey` on write operations for `job-request`.

---

## API routes

All routes are under `/api/custom-knime-workflows/` (Strapi content-api routing).

| Method | Path                                    | Handler                           | Auth   |
|--------|-----------------------------------------|-----------------------------------|--------|
| POST   | `/knime-request`                        | `knimeRequest.createRequest`      | ⚠ false |
| GET    | `/knime-requests`                       | `knimeRequest.getRequests`        | ⚠ false |
| PUT    | `/knime-request/:requestId/processed`   | `knimeRequest.updateRequestStatus`| ⚠ false |

> **⚠ Important:** All three routes currently have `auth: false`. This must be addressed before production deployment — enable authentication and configure role-based permissions in the Strapi Users & Permissions plugin.

---

## Service: `knime-request`

The service in `server/src/services/knime-request.js` implements three methods:

### `createRequest({ user, job, payload, stateUser })`

1. Validates the payload against `job.payloadTemplate` using **AJV** (if a schema is defined).
2. Converts the payload object to an array of `{key, value}` components.
3. Creates a `job-request` record with `errored: false`.
4. Injects `requestId` (the new record's `documentId`) into the KNIME body.
5. Calls the KNIME REST API: `POST {jobsBaseUrl}/{job.jobId}?reset=true&async=true&timeout=-1` using HTTP Basic Auth.
6. Updates the `job-request` record's `auditLog` with the response (or marks `errored: true` on failure).

### `getRequests({ page, pageSize, user })`

Returns paginated `job-request` records sorted by `createdAt` descending, populating `job`, `user`, `payload`, and `auditLog`.

### `updateRequestStatus({ requestId, requestBody })`

Locates a `job-request` by `documentId` and updates its `auditLog.response` with the provided body. Used as a callback endpoint when KNIME finishes an async job.

---

## Admin UI

The admin side registers a menu entry under the plugin's namespace (icon: `Rocket`). The single page (`HomePage`) displays three `SectionCard` components that navigate to the Strapi Content Manager:

| Section       | Actions                          | Target UID                                  |
|---------------|----------------------------------|---------------------------------------------|
| Configurations| Create · View all                | `plugin::custom-knime-workflows.configuration` |
| Jobs          | Create · View all                | `plugin::custom-knime-workflows.job`           |
| Job Requests  | View all                         | `plugin::custom-knime-workflows.job-request`  |

Navigation uses `react-router-dom`'s `useNavigate` pointing to `/content-manager/collection-types/{uid}` and `/content-manager/collection-types/{uid}/create`.

### Translations

Translations are loaded via `registerTrads` in `admin/src/index.js`. All three locales are supported:

| File      | Locale    |
|-----------|-----------|
| `en.json` | English   |
| `it.json` | Italian   |
| `es.json` | Spanish   |

Translation key prefix: `custom-knime-workflows.*`

---

## Development

```bash
# Build the plugin (required before running Strapi)
npm run build

# Watch mode (auto-rebuild on change)
npm run watch
```

Run together with the main Strapi app using the root-level dev script:

```bash
# From Backend/
npm run develop:all
```

---

## Known issues / TODOs

| Area              | Issue                                                                 |
|-------------------|-----------------------------------------------------------------------|
| Route auth        | All content-api routes have `auth: false` — must be secured.         |
| Password storage  | `configuration.password` is stored as plain text.                    |
| `entityService`   | The service uses `strapi.entityService` (Strapi v4 API). Consider migrating to `strapi.documents` for full Strapi v5 compatibility. |
| AJV version dup   | `package.json` lists `ajv-formats` twice — remove the duplicate entry.|

