# Task: Reduce the Tags relationship-panel row menu to "Unlink" only in EspoCRM ## Goal This EspoCRM instance has a custom many-to-many relationship to a custom `Tag` entity, exposed as a **"Tags" bottom relationship panel** on three entity types: **Opportunity**, **Contact**, and **Account**. On each of these three entities the link is named **`cTags`**. (The inline **Create (+)** button on these panels was already removed in a previous task via `relationshipPanels.cTags.create = false` — leave that in place.) On a record's Tags panel, each attached tag row has a small dropdown (triangle/▾) that currently offers **four** actions: **View**, **Edit**, **Unlink**, **Remove** (German UI: **Ansehen**, **Bearbeiten**, **Link entfernen**, **Löschen**). We want each tag row's dropdown to offer **only "Unlink"** (German: **"Link entfernen"**). The actions **View**, **Edit**, and **Remove/Delete** must be gone. This must apply to **all** users, including regular (non-admin) users, on **all three** entities (Opportunity, Contact, Account). Do **not** change anything else (no ACLs, no other panels/fields/entities, and do not touch the `Tag` entity itself — creating, viewing, editing and deleting tags from the **Tag** entity's own list/detail views must keep working normally). ## Environment - EspoCRM (version **9.3.8**, Apache variant) runs as a **rootless Podman pod** named `espocrm_pod` on this server. The web container is expected to be `espocrm_ctr` (confirm with `podman ps`). - EspoCRM application root inside the web container: `/var/www/html`. The app runs as user **`www-data`**. - Customizations live under `/var/www/html/` and must be on the **persisted** bind-mount so they survive container recreation. Two locations are used here: - Backend metadata: `/var/www/html/custom/Espo/Custom/Resources/metadata/clientDefs/` (this already holds `Opportunity.json`, `Contact.json`, `Account.json` from the +button task). - Frontend custom view: `/var/www/html/client/custom/src/` (this is the official location for custom client-side views; it is part of the same persisted `/var/www/html` that already keeps your `custom/` metadata across restarts). ## Mechanism (verified against EspoCRM 9.3.8 source) The per-row dropdown of a relationship panel is rendered by the view **`views/record/row-actions/relationship`**. Its `getActionList()` builds the menu like this (paraphrased from `client/src/views/record/row-actions/relationship.js`): - **View** — pushed **unconditionally** (always present; there is **no** flag to disable it). - **Edit** — pushed only if `this.options.acl.edit && !this.options.editDisabled`. - **Unlink** — pushed only if `!this.options.unlinkDisabled`. - **Remove** — pushed only if `this.options.acl.delete && !this.options.removeDisabled`. The panel (`client/src/views/record/panels/relationship.js`) feeds these flags from the panel metadata and lets you swap the whole row-actions view: ```js this.rowActionsView = this.defs.readOnly ? false : (this.defs.rowActionsView || this.rowActionsView); // ... rowActionsOptions: { unlinkDisabled: unlinkDisabled, editDisabled: this.defs.editDisabled, removeDisabled: this.defs.removeDisabled, }, ``` Consequence: - `editDisabled: true` and `removeDisabled: true` (in `clientDefs..relationshipPanels.cTags`) would hide **Edit** and **Remove** — but **not View**, because View is hard-coded. - To leave **only Unlink**, we point the panel's **`rowActionsView`** (a supported `relationshipPanels.` parameter) at a small **custom row-actions view** that returns only the Unlink action. This removes View, Edit and Remove in one go and is the clean, upgrade-safe approach. Custom client views live under `client/custom/src/...` and are referenced with the **`custom:`** prefix (per the official EspoCRM docs, "Custom views"). No build step is required; a cache rebuild plus a hard browser reload picks them up. ### Confidence / self-verify before relying on it The action name `unlinkRelated`, the four-action structure, and the `rowActionsView` parameter are taken from the 9.3.8 source. Before relying on them, confirm against this instance: ``` podman exec espocrm_ctr sh -lc "grep -n 'rowActionsView\|unlinkDisabled\|editDisabled\|removeDisabled' /var/www/html/client/src/views/record/panels/relationship.js" podman exec espocrm_ctr sh -lc "grep -n 'quickView\|quickEdit\|unlinkRelated\|removeRelated' /var/www/html/client/src/views/record/row-actions/relationship.js" ``` You should see View pushed unconditionally, Unlink guarded by `unlinkDisabled`, and the panel reading `this.defs.rowActionsView`. If this version differs, adjust the custom view's `getActionList()` to match the real action name(s) for "Unlink". ## Steps 1. **Confirm container and link name.** - `podman ps` → identify the web container (expected `espocrm_ctr`). - Confirm each of `Opportunity`, `Contact`, `Account` has a many-to-many link named `cTags`: ``` podman exec espocrm_ctr sh -lc "grep -rn 'cTags' /var/www/html/custom/Espo/Custom/Resources/metadata/entityDefs/" ``` 2. **Create the custom row-actions view.** Create the directory and file (inside the web container, on the persisted `/var/www/html`): `/var/www/html/client/custom/src/views/record/row-actions/tags-unlink-only.js` ```js define('custom:views/record/row-actions/tags-unlink-only', ['views/record/row-actions/relationship'], (RelationshipRowActionsView) => { return class extends RelationshipRowActionsView { getActionList() { const list = []; // Keep only the "Unlink" action (German UI: "Link entfernen"). // View / Edit / Remove are intentionally omitted. if (!this.options.unlinkDisabled) { list.push({ action: 'unlinkRelated', label: 'Unlink', data: { id: this.model.id, }, groupIndex: 0, }); } // Preserve any explicitly configured extra row actions (none by default). this.getAdditionalActionList().forEach(item => list.push(item)); return list; } }; }); ``` Make sure the directories exist, e.g.: ``` podman exec espocrm_ctr sh -lc "mkdir -p /var/www/html/client/custom/src/views/record/row-actions" ``` 3. **Point the three Tags panels at the custom view.** For `Opportunity`, `Contact`, and `Account`, add the `rowActionsView` key to the existing `cTags` panel in: `/var/www/html/custom/Espo/Custom/Resources/metadata/clientDefs/.json` The resulting `cTags` block must look like this (note: **keep** the existing `"create": false` from the previous task): ```json { "relationshipPanels": { "cTags": { "create": false, "rowActionsView": "custom:views/record/row-actions/tags-unlink-only" } } } ``` - Target files: - `/var/www/html/custom/Espo/Custom/Resources/metadata/clientDefs/Opportunity.json` - `/var/www/html/custom/Espo/Custom/Resources/metadata/clientDefs/Contact.json` - `/var/www/html/custom/Espo/Custom/Resources/metadata/clientDefs/Account.json` - **MERGE into the existing JSON.** Preserve all other keys; in particular keep `relationshipPanels.cTags.create = false`. Only add the single `rowActionsView` key inside `relationshipPanels.cTags`. Do not overwrite the file. Produce valid JSON. 4. **Fix ownership** so the web user owns the new/changed files (run as root inside the container): ``` podman exec espocrm_ctr chown -R www-data:www-data /var/www/html/client/custom /var/www/html/custom/Espo/Custom/Resources/metadata/clientDefs ``` 5. **Clear cache and rebuild** (run as the web user): ``` podman exec -u www-data espocrm_ctr php command.php rebuild ``` If `command.php` is not present in this version, use: ``` podman exec -u www-data espocrm_ctr bin/command rebuild ``` (Alternatively, an admin can run Administration → Rebuild in the web UI.) 6. **Verify in the browser** (do a hard reload, Ctrl+Shift+R, to drop the client cache; ideally test as a regular non-admin user, and also as admin): - Open an **Opportunity**, a **Contact**, and an **Account** record. On the **Tags** panel, open the ▾ dropdown of an attached tag. It must now show **only "Link entfernen" (Unlink)**. **Ansehen / Bearbeiten / Löschen** (View / Edit / Remove) must be gone. - **"Link entfernen"** must still work (it detaches the tag from the record but does not delete the Tag itself). - The **Tag** entity's own list/detail views must still allow viewing, editing and deleting tags normally (this task does not touch the Tag entity or any ACL). ## Rollback 1. Remove the `rowActionsView` key from `relationshipPanels.cTags` in the three `clientDefs` files (leave `create: false` if you still want the +button hidden). 2. Optionally delete `/var/www/html/client/custom/src/views/record/row-actions/tags-unlink-only.js`. 3. Rebuild again (step 5) and hard-reload the browser.