Skip to content

Commit d6b6ad7

Browse files
jeffpaulwhyisjakedkotterraftaar1191
authored
Abilities Explorer: move to DataViews and REST (from #1074) (#1181)
Changed - Rebuilt the Abilities Explorer on DataViews and three `ai/v1` REST routes to retrieve and run abilities. Added - An `ai.abilitiesExplorer.fields` JavaScript filter for adding read-only table columns to the Abilities Explorer page. Co-authored-by: whyisjake <whyisjake@git.wordpress.org> Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org> Co-authored-by: dkotter <dkotter@git.wordpress.org> Co-authored-by: raftaar1191 <raftaar1191@git.wordpress.org>
1 parent 5bfb356 commit d6b6ad7

40 files changed

Lines changed: 6217 additions & 2334 deletions

‎docs/experiments/abilities-explorer.md‎

Lines changed: 95 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2,142 +2,138 @@
22

33
## Summary
44

5-
The Abilities Explorer experiment provides an admin interface for discovering, inspecting, testing, and documenting all abilities registered via the WordPress Abilities API. It adds a menu item ("Abilities Explorer") under the "Tools" menu in the WordPress admin that displays a searchable, filterable table of all registered abilities with the ability to view details and invoke abilities directly from the UI.
5+
The Abilities Explorer experiment adds an admin screen for finding, inspecting and testing every ability registered through the WordPress Abilities API. It lives at **Tools → Abilities Explorer** (`tools.php?page=ai-abilities-explorer`) and requires the `manage_options` capability.
66

7-
## Overview
7+
The screen is a React application built on `@wordpress/dataviews`, backed by three `ai/v1` REST routes.
88

9-
### For End Users
9+
## What the screen does
1010

11-
When enabled, the Abilities Explorer adds an "Abilities Explorer" menu item under the "Tools" menu in the WordPress admin sidebar. This provides:
11+
### List
1212

13-
**Key Features:**
13+
- Statistics for Total, Core, Plugins and Theme, counted by each ability's origin. They are computed in the browser from the whole list, so search and filters do not change them.
14+
- A table of every registered ability, including abilities that are not opted into REST, with Name, Slug and Provider columns. Search covers name, slug and description. The table sorts by name, slug and provider, shows 20 rows per page, and has a **Refresh** button.
15+
- Filters:
16+
- **Provider** (single choice). Core, Plugin and Theme match the ability's origin; any other option is a custom `meta.provider` label and matches that exact label. An ability with a custom label from a plugin therefore appears under both "Plugin" and its own label.
17+
- **Category**. Each category label is listed once and rendered as text.
18+
- Row actions under each ability's name, shown on hover or keyboard focus as in `WP_List_Table` (always shown on small screens): **View** and **Test**.
1419

15-
- Dashboard with statistics showing total abilities by provider (Core, Plugin, Theme)
16-
- Searchable, sortable table of all registered abilities
17-
- Filter abilities by provider type
18-
- Detailed view for each ability showing input/output schemas
19-
- Built-in test runner to invoke abilities with custom JSON input
20-
- JSON validation against input schemas
21-
- Copy-to-clipboard functionality for schemas
20+
### Detail view and test runner
2221

23-
### For Developers
22+
- The detail view shows the description, provider, input schema, output schema and raw data. Each JSON block has a Copy button.
23+
- The test runner prefills example input generated from the input schema, validates it in the browser (**Validate Input**), invokes the ability (**Invoke Ability**) and clears the result and validation panels (**Clear Result**). The JSON input is labelled "Ability test input (JSON)".
24+
- Validation checks the input's top-level type, required fields, and each top-level property's type, `enum`, `minimum` and `maximum`. Nested objects are not validated.
2425

25-
The experiment consists of four main classes:
26+
### Views and links
2627

27-
1. **Abilities_Explorer** (`Abilities_Explorer.php`): Main experiment class handling registration and asset loading
28-
2. **Admin_Page** (`Admin_Page.php`): Manages admin menu, page rendering, and AJAX handlers
29-
3. **Ability_Handler** (`Ability_Handler.php`): Interfaces with the WordPress Abilities API to fetch, format, validate, and invoke abilities
30-
4. **Ability_Table** (`Ability_Table.php`): Extends `WP_List_Table` for the abilities listing
28+
Views are addressed by the same query arguments as before, so existing links keep working:
3129

32-
## Architecture & Implementation
30+
- List: `tools.php?page=ai-abilities-explorer`
31+
- Detail: `&action=view&ability=<name>`
32+
- Test runner: `&action=test&ability=<name>`
3333

34-
### Key Hooks & Entry Points
34+
Moving between views updates the URL, so browser Back and Forward work, and each view has a single `h1`, "Abilities Explorer".
3535

36-
- `WordPress\AI\Experiments\Abilities_Explorer\Abilities_Explorer::register()` wires everything once enabled:
37-
- `admin_enqueue_scripts` → enqueues JS/CSS on the `toplevel_page_ai-abilities-explorer` screen
38-
- `admin_menu` → adds the top-level "Abilities" menu page
39-
- `wp_ajax_ai_ability_explorer_invoke` → handles AJAX ability invocation
36+
The list remembers its layout, visible columns, sort and page size in the browser's local storage (`ai.abilitiesExplorer.view`). Search, filters and the current page are not saved.
4037

41-
### Assets & Data Flow
38+
## REST routes
4239

43-
1. **PHP Side:**
44-
- `enqueue_assets()` loads `experiments/abilities-explorer` bundle and localizes `window.AbilityExplorer` with:
45-
- `enabled`: Whether the experiment is enabled
46-
- `ajaxUrl`: WordPress AJAX URL
47-
- `nonce`: Security nonce for AJAX requests
48-
- `strings`: Localized UI strings
40+
All three routes are registered only while the experiment is enabled. They share one permission check: the user must have `manage_options` and must be authenticated by the logged-in cookie with the REST nonce. Requests authenticated any other way, including application passwords, are refused with a 403. None of the routes is registered as an ability.
4941

50-
2. **JavaScript Side:**
51-
- Handles test runner functionality (invoke, validate, clear)
52-
- Real-time JSON validation in the input textarea
53-
- Copy-to-clipboard for schemas
54-
- Auto-formatting of JSON input
42+
Ability names are passed as a parameter, never in the path, because names contain `/`.
5543

56-
### Admin Page Views
44+
| Method | Route | Parameters | Returns |
45+
|---|---|---|---|
46+
| `GET` | `ai/v1/abilities` | none | `items` (summary fields and `meta` for every ability) |
47+
| `GET` | `ai/v1/abilities/item` | `name` | One ability with `input_schema`, `output_schema`, `raw_data` and `example_input` |
48+
| `POST` | `ai/v1/abilities/invoke` | `name`, `input` (a JSON string; empty means no input) | `success` with `data`, or `success: false` with `error.code`, `error.message` and `error.data` |
5749

58-
The admin page supports three views:
50+
The invoke route answers 404 for an unknown ability and 400 for malformed JSON or input that fails the Explorer's schema check. The ability's own outcome, including its permission check and core's input validation, is a 200 with `success: false`. Invoking writes no AI request log row.
5951

60-
1. **List View** (`action=list`, default): Statistics dashboard + searchable table
61-
2. **Detail View** (`action=view&ability=slug`): Full ability details with schemas
62-
3. **Test Runner** (`action=test&ability=slug`): Interactive ability testing interface
52+
A field of a list item that cannot be encoded as JSON is sent as `null` and named in that item's `unencodable_fields`, so one ability cannot stop the list from loading.
6353

64-
### Provider Detection
54+
## Behavior changes from the previous screen
6555

66-
Abilities are categorized by provider:
56+
The screen used to be a PHP `WP_List_Table` with admin-ajax handlers. Compared with that version:
6757

68-
- **Core**: Abilities with `wordpress`, `wp`, or `core` namespace prefix
69-
- **Theme**: Abilities matching the active theme's stylesheet or template name
70-
- **Plugin**: All other abilities (default)
58+
- **Invoke runs under REST.** Abilities are invoked from a REST request, so `is_admin()` is `false` during the call. The route loads `wp-admin/includes/admin.php` first, so admin functions such as `get_plugins()` remain available.
59+
- **Errors show a code, message and data.** A failed invoke shows the error's `code`, `message` and `data` in the error panel. The old `trace` field, which was always empty, is gone.
60+
- **Back to List keeps the view's layout.** Returning from the detail view or the test runner keeps the list's layout, visible columns, sort and page size. Search and filters reset.
61+
- **The list, detail and runner come from REST.** The screen builds all three from REST requests, where `is_admin()` is `false`. An ability that a plugin registers only when `is_admin()` is `true` no longer appears.
62+
- **The routes accept only an administrator's browser session.** The `ai/v1` Explorer routes require `manage_options`, cookie authentication from a logged-in session, and a valid `wp_rest` nonce (the `X-WP-Nonce` header, or the `_wpnonce` parameter). Application passwords and other non-cookie authentication are refused, so scripts and custom clients cannot call them.
7163

72-
### Input Validation
73-
74-
The experiment performs JSON Schema validation:
75-
76-
- Required field checking
77-
- Type validation (string, number, integer, boolean, array, object)
78-
- Real-time JSON syntax validation in the UI
64+
## Permissions
7965

80-
## Using the Test Runner
66+
- The screen and all three routes require `manage_options`.
67+
- Invoking an ability still runs that ability's own permission check and input validation through `WP_Ability::execute()`.
8168

82-
### Testing an Ability
69+
## Testing
8370

84-
1. Navigate to **Abilities Explorer** in the "Tools" menu in the admin menu
85-
2. Find the ability you want to test
86-
3. Click the **Test** button
87-
4. Edit the JSON input (pre-populated with example values from the schema)
88-
5. Click **Validate Input** to check JSON syntax and schema compliance
89-
6. Click **Invoke Ability** to execute
90-
7. View the result in the Result section
71+
### Automated
9172

92-
### Example Input Generation
73+
- PHP: `tests/Integration/Includes/Experiments/Abilities_Explorer/` (`Admin_PageTest`, `Ability_HandlerTest`, `Abilities_ExplorerTest`, `REST/Abilities_ControllerTest`).
74+
- End to end: `tests/e2e/specs/experiments/abilities-explorer-*.spec.js` (list, runner, navigation), with helpers in `tests/e2e/utils/abilities-explorer.js`, fixture abilities from `tests/e2e-testing/e2e-testing.php` and the field extension fixture in `tests/e2e-plugins/abilities-explorer-field`.
9375

94-
The test runner automatically generates example input based on the ability's input schema:
76+
### Manual
9577

96-
- Uses `default` values if specified
97-
- Falls back to `example` values
98-
- Generates type-appropriate defaults (empty string, 0, false, empty array/object)
78+
1. Go to **Settings → AI** and enable **Abilities Explorer**.
79+
2. Open **Tools → Abilities Explorer**. Check the statistics, search, the Provider and Category filters, and sorting.
80+
3. Use **View** on a row. Check the schemas and the Copy buttons.
81+
4. Use **Test** on a row. Edit the input, then use **Validate Input**, **Invoke Ability** and **Clear Result**.
9982

100-
## Permissions
83+
## Adding fields to the table
10184

102-
- Requires `manage_options` capability to access the admin page
103-
- AJAX requests verify nonce and capability before invoking abilities
85+
Other plugins can add read-only columns to the Abilities Explorer list through the `ai.abilitiesExplorer.fields` JavaScript filter. The list is built with `@wordpress/dataviews`, and each column is a DataViews field.
10486

105-
## Testing
87+
### Enqueueing the script
10688

107-
### Manual Testing
89+
Enqueue the script on the Explorer screen (its hook suffix is `tools_page_ai-abilities-explorer`) and make it depend on `wp-hooks`. Do not depend on the Explorer's own script handle: the Explorer bundle loads deferred, and its handle is not a public API. The filter is read when the list renders and again whenever a callback is added to or removed from it, so the column shows whether your script runs before or after the Explorer.
10890

109-
1. **Enable the experiment:**
110-
- Go to `Settings → AI`
111-
- Toggle **Abilities Explorer** to enabled
91+
```php
92+
add_action(
93+
'admin_enqueue_scripts',
94+
static function ( string $hook_suffix ): void {
95+
if ( 'tools_page_ai-abilities-explorer' !== $hook_suffix ) {
96+
return;
97+
}
11298

113-
2. **Test the list view:**
114-
- Navigate to **Abilities Explorer** in the "Tools" menu in the admin menu
115-
- Verify statistics are displayed
116-
- Search for abilities by name or description
117-
- Filter by provider type
118-
- Sort columns
99+
wp_enqueue_script(
100+
'my-plugin-explorer-field',
101+
plugins_url( 'explorer-field.js', __FILE__ ),
102+
array( 'wp-hooks' ),
103+
'1.0.0',
104+
true
105+
);
106+
}
107+
);
108+
```
119109

120-
3. **Test the detail view:**
121-
- Click on an ability name or **View** button
122-
- Verify input/output schemas are displayed
123-
- Test copy buttons
110+
### Adding a field
124111

125-
4. **Test the test runner:**
126-
- Click **Test** on any ability
127-
- Modify the JSON input
128-
- Click **Validate Input**
129-
- Click **Invoke Ability**
130-
- Verify results are displayed
112+
The filter receives the current fields, built-ins first, and returns the full list. Append your field and return the array.
131113

132-
## Notes & Considerations
114+
```js
115+
wp.hooks.addFilter(
116+
'ai.abilitiesExplorer.fields',
117+
'my-plugin/slug-length',
118+
( fields ) => [
119+
...fields,
120+
{
121+
id: 'my-plugin/slug-length',
122+
label: 'Slug length',
123+
type: 'integer',
124+
enableSorting: true,
125+
getValue: ( { item } ) => item.slug.length,
126+
},
127+
]
128+
);
129+
```
133130

134-
### Requirements
131+
Each row (`item`) is one ability as returned by `GET ai/v1/abilities`, so a field can read properties such as `slug`, `name`, `provider`, `category` and `meta`. Several of them, including `name`, `provider`, `category` and `meta`, can be `null`, for example when the server could not encode that part of an ability, so read them with a fallback. Prefix the field ID with your plugin's namespace so it cannot clash with another extension.
135132

136-
- Requires the WordPress Abilities API (`wp_get_abilities`, `wp_get_ability` functions)
137-
- Users must have `manage_options` capability
133+
### What a field may carry
138134

139-
### Limitations
135+
An extension field keeps only these DataViews properties: `id`, `type`, `label`, `header`, `description`, `render`, `getValue`, `getValueFormatted`, `sort`, `format`, `elements`, `filterBy`, `enableSorting`, `enableHiding` and `enableGlobalSearch`. Anything else is dropped, including edit controls (`Edit`, `setValue`, `isValid`) and anything like `actions`, and every extension field is marked `readOnly`. Extension fields never add row actions.
140136

141-
- Provider detection is heuristic-based and may not be accurate for all abilities
142-
- Input validation is basic JSON Schema validation (required fields, types)
143-
- Does not support nested object validation
137+
- **Built-in fields win.** A field whose ID matches a built-in field (`name`, `slug`, `provider`, `category`, `description`) is ignored, and returning a list without the built-ins does not remove them. If two extensions use the same ID, the first one wins.
138+
- **Failures fall back.** If a filter callback throws or returns something other than an array, the list shows only the built-in fields. A `getValue`, `getValueFormatted` or `sort` callback that throws yields an empty value (or no reordering), and a `render` callback that throws renders an empty cell, so one extension cannot break the table.
139+
- **Saved views keep your column.** A new field is added to the visible columns the first time the Explorer sees it, including on a first visit with nothing saved. After that it is the user's choice to hide it. If your plugin is deactivated, its column's place in the user's saved view is kept, and it shows again when the plugin is reactivated.

‎includes/Experiments/Abilities_Explorer/Abilities_Explorer.php‎

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace WordPress\AI\Experiments\Abilities_Explorer;
1515

1616
use WordPress\AI\Abstracts\Abstract_Feature;
17-
use WordPress\AI\Asset_Loader;
17+
use WordPress\AI\Experiments\Abilities_Explorer\REST\Abilities_Controller;
1818
use WordPress\AI\Experiments\Experiment_Category;
1919

2020
if ( ! defined( 'ABSPATH' ) ) {
@@ -53,45 +53,9 @@ protected function load_metadata(): array {
5353
* {@inheritDoc}
5454
*/
5555
public function register(): void {
56-
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
57-
58-
// @todo: evaluate standardization after triaging existing comments.
5956
$admin_page = new Admin_Page();
6057
$admin_page->init();
61-
}
62-
63-
/**
64-
* Enqueues and localizes the admin script and styles.
65-
*
66-
* @since 0.2.0
67-
*
68-
* @param string $hook_suffix The current admin page hook suffix.
69-
*/
70-
public function enqueue_assets( string $hook_suffix ): void {
71-
// Load asset in Abilities Explorer page only.
72-
if ( 'tools_page_ai-abilities-explorer' !== $hook_suffix ) {
73-
return;
74-
}
7558

76-
Asset_Loader::enqueue_script( 'abilities_explorer', 'experiments/abilities-explorer' );
77-
Asset_Loader::enqueue_style( 'abilities_explorer', 'experiments/abilities-explorer' );
78-
Asset_Loader::localize_script(
79-
'abilities_explorer',
80-
'AbilityExplorer',
81-
array(
82-
'enabled' => $this->is_enabled(),
83-
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
84-
'nonce' => wp_create_nonce( 'ai_ability_explorer_invoke' ),
85-
'strings' => array(
86-
'invoking' => esc_html__( 'Invoking ability...', 'ai' ),
87-
'success' => esc_html__( 'Success!', 'ai' ),
88-
'error' => esc_html__( 'Error', 'ai' ),
89-
'invalidJson' => esc_html__( 'Invalid JSON input', 'ai' ),
90-
'confirmInvoke' => esc_html__( 'Are you sure you want to invoke this ability?', 'ai' ),
91-
'copySuccess' => esc_html__( 'Copied!', 'ai' ),
92-
'copyError' => esc_html__( 'Failed to copy', 'ai' ),
93-
),
94-
)
95-
);
59+
( new Abilities_Controller() )->init();
9660
}
9761
}

0 commit comments

Comments
 (0)