How to package SearchKit Displays in an extension
Use Case
Ship an extension with SearchKit-based tabs for the Contact Summary screen.
Requirements
Using a Search Display as a contact summary tab requires 3 interconnected entities:
- A SavedSearch containing the api params for the search.
- A SearchDisplay linked to the
SavedSearch
by id (saved_search_id = $savedSearch.id
). - An Afform linked to both the
SavedSearch
and theSearchDisplay
by name (via embedded tag).
Solutions
1. The Status Quo
Afforms can already be packaged as .aff.html/.aff.json
files, and the Afform API & GUI have excellent handling for packaged Afforms to be overridden and reverted by site admins. If an extension upgrade provides updates to a packaged Afform, overrides will not be affected but the default version will be upgraded automatically.
DAO-based entities like SavedSearch
and SearchDisplay
can be packaged as .mgd.php
files which will be added to the database via hook_civicrm_managed
. However, the CRM_Core_ManagedEntities
system doesn't provide any way for admins to override and then revert back to the packaged version, nor to reconcile potentially overridden entities with new packaged versions during extension upgrades. There are also an issue where the system doesn't guerantee write order, and in this case we need it to write the SavedSearch
prior to the SearchDisplay
, which contains a foreign key to the former.
Currently there is a workaround using chaining and 'update' = 'never'
, for example as used by the Deduper Extension.
CRM_Core_ManagedEntities
offers us 2 modes for packaging SavedSearch
and SearchDisplay
entities, neither is ideal:
-
'update' = 'always'
will essentially lock the entities. Any changes to the search made by the site admin will be overwritten on a regular basis. This solves the extension upgrade problem at the cost of configurability. -
'update' = 'never'
will drop the entities into the database and then never touch them again. This is the lesser of two evils because at least this way they are configurable.
The bottom line: The status quo allows us to package searches and displays. They will be user-configurable but not revertable (if the admin makes a mess of them, he'd have to delete them all and then uninstall/reinstall the extension). During extension upgrades, the packaged Afforms will update, but changes to .mgd.php
files will do nothing unless the extension is uninstalled, searches are deleted, and then it's reinstalled.
PackagedEntity
class
2. Create APIv4 This would do something like the Afform
APIv4 entity: read from the database and also from json files. This would need to be opt-in on a per-entity basis (starting with SavedSearch
and SearchDisplay
) and would be mostly for configuration entities, as a file/DB hybrid would not be able to perform joins in the GET action.
Tasks
- Add an APIv4 trait
PackagedEntity
- Opt-in
SavedSearch
andSearchDisplay
to use that trait - Add a specialized
get
action which would read from json files and the database. - Add calculated fields comparable to Afform's
has_base
&has_local
toget
output.
Pros
- Would give excellent feature parity with Afform's packaged systems.
- Reverting and upgrades would be simple.
Cons
- Involves extra file i/o and directory scanning with each
get
- feasible with hundreds of records but not many thousands. - Restricted to APIv4 as the packaged entities wouldn't exist as far as the DAO is concerned.
- Entities have to opt-in (whereas
mgd
works on any entity). - Retrofitting an existing API over would involve a slight BC breakage as the new
get
method would be missingJOIN
, andHAVING
clauses.
CRM_Core_ManagedEntities
3. Improve Make the manager aware of whether a record has been manually updated. Add a new 'update' = 'auto'
mode which would intelligently push updates from .mgd.php
only to untouched records. Add a way to revert a record.
Tasks
- Add an
is_overridden
column to thecivicrm_managed
table. - Add some type of trigger to set that column when manual changes are made to a managed entity.
- Add some type of
Revert
api action (currently theManaged
class doesn't even have an API so this will take some thought). - Add a way to know if a database record has a corresponding managed entry (maybe a calculated field or maybe a DFK join - both would be slightly tricky).
Pros
- Fully backward compatible with existing entities.
- Improves the existing "Managed" system instead of inventing a new one.
- Wouldn't be a drag on performance.
Cons
- Might be a lot of work.