From 0de2e120fd93976d689ff0ca11e159159aeeade0 Mon Sep 17 00:00:00 2001 From: ykulynych Date: Tue, 22 Sep 2026 13:06:45 +0200 Subject: [PATCH 1/3] PDPS-2095: Unblock admin render from blocking sync checks Fixes a 504 timeout on every tab of the admin page, caused by synchronous blocking work during render. - AdminPage: read cached background-sync status and cached labelsets during render instead of running a full upstream reconcile/recover pass and a live labelsets fetch. Automatic sync and cache refresh still happen via the existing AJAX poll and explicit save/manual sync actions, just not during page load. - ApiClient: add get_labelsets_cached(), a cache-only reader with no upstream HTTP call, used by the render path above. - Scheduler: count_actions() now issues a real SQL COUNT via ActionScheduler::store()->query_actions(..., 'count') instead of loading every matching action row into memory just to count them. --- src/Admin/AdminPage.php | 39 +++++++++++++++++++++++++++++++++++--- src/Api/ApiClient.php | 14 ++++++++++++++ src/Indexing/Scheduler.php | 28 ++++++++++++++++++--------- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/Admin/AdminPage.php b/src/Admin/AdminPage.php index 42d76e5..22750ca 100644 --- a/src/Admin/AdminPage.php +++ b/src/Admin/AdminPage.php @@ -97,8 +97,8 @@ public function enqueue_assets( string $hook_suffix ): void { 'nonce' => wp_create_nonce( 'progress_agentic_rag_manual_sync' ), 'initialSyncStatus' => $this->manual_sync->status(), 'initialDeleteStatus' => $this->manual_sync->delete_status(), - 'initialBackgroundSyncStatus' => $this->manual_sync->ensure_automatic_sync(), - 'mapping' => $this->mapping_data(), + 'initialBackgroundSyncStatus' => $this->manual_sync->background_sync_status(), + 'mapping' => $this->mapping_data_cached(), 'strings' => [ 'closeDeleteProgress' => __( 'Close delete progress', 'progress-agentic-rag-connector' ), 'closeSyncProgress' => __( 'Close sync progress', 'progress-agentic-rag-connector' ), @@ -585,7 +585,7 @@ private function render_indexation(): void { $api_connected = $this->settings->get_api_is_reachable(); $scheduler_available = $this->manual_sync->scheduler_available(); $scheduler_status = $this->manual_sync->scheduler_status(); - $background_sync_status = $this->manual_sync->ensure_automatic_sync(); + $background_sync_status = $this->manual_sync->background_sync_status(); $manual_sync_status = $this->manual_sync->status(); $delete_status = $this->manual_sync->delete_status(); $delete_running = 'running' === ( $delete_status['status'] ?? '' ); @@ -914,6 +914,39 @@ private function mapping_data(): array { ]; } + /** + * Same shape as mapping_data(), but never performs an upstream HTTP request. + * + * Used in the admin page boot path (enqueue_assets) where any blocking + * call would stall every tab. The taxonomy-labeling tab still calls + * mapping_data() server-side and get_labelsets() refreshes the cache. + * + * @return array + */ + private function mapping_data_cached(): array { + $taxonomies = []; + + foreach ( get_taxonomies( [ 'public' => true ], 'objects' ) as $taxonomy_name => $taxonomy ) { + $terms = []; + foreach ( $this->taxonomy_terms( [ $taxonomy_name ] )[ $taxonomy_name ] ?? [] as $term ) { + $terms[] = [ + 'id' => (int) $term->term_id, + 'name' => (string) $term->name, + ]; + } + + $taxonomies[ $taxonomy_name ] = [ + 'label' => isset( $taxonomy->labels->name ) ? (string) $taxonomy->labels->name : (string) $taxonomy_name, + 'terms' => $terms, + ]; + } + + return [ + 'taxonomies' => $taxonomies, + 'labelsets' => $this->api_client->get_labelsets_cached(), + ]; + } + /** * @param list $taxonomy_names Taxonomy names. * diff --git a/src/Api/ApiClient.php b/src/Api/ApiClient.php index ead7296..f0b5747 100644 --- a/src/Api/ApiClient.php +++ b/src/Api/ApiClient.php @@ -252,6 +252,20 @@ public function get_labelsets(): array { return $labelsets; } + /** + * Return cached labelsets only, without performing any upstream HTTP request. + * + * Use this in render paths that must not block (e.g. admin page boot). + * `get_labelsets()` remains the source of truth for fresh data. + * + * @return list + */ + public function get_labelsets_cached(): array { + $cache = $this->settings->get_labelsets_cache(); + + return is_array( $cache['labelsets'] ?? null ) ? array_values( $cache['labelsets'] ) : []; + } + /** * @return list */ diff --git a/src/Indexing/Scheduler.php b/src/Indexing/Scheduler.php index 1b4d8b4..ec445af 100644 --- a/src/Indexing/Scheduler.php +++ b/src/Indexing/Scheduler.php @@ -110,21 +110,31 @@ public function count_actions( string $hook, string $group, string $status ): in return 0; } - return count( - as_get_scheduled_actions( - array( - 'hook' => $hook, - 'group' => $group, - 'status' => $status, - 'per_page' => -1, - ) - ) + $args = array( + 'hook' => $hook, + 'group' => $group, + 'status' => $status, ); + + // as_get_scheduled_actions() has no 'count' mode — it always hydrates every + // matching action. Only the underlying store's query_actions() supports a + // real COUNT query, via its second, undocumented-by-wrapper argument. Fall + // back to the (slower) wrapper when only the as_* function shims exist. + if ( class_exists( '\ActionScheduler' ) ) { + $count = \ActionScheduler::store()->query_actions( $args, 'count' ); + + return is_numeric( $count ) ? (int) $count : 0; + } + + return count( as_get_scheduled_actions( array_merge( $args, [ 'per_page' => -1 ] ) ) ); } /** * Get scheduled actions by status. * + * Only use this when you genuinely need to inspect action args. For pure + * counts, use count_actions() — it avoids loading every row into memory. + * * @param string $hook Action hook. * @param string $group Action Scheduler group. * @param string $status Action status. From 8b26b36ee4dffd6273df4c040dcffacd557935f7 Mon Sep 17 00:00:00 2001 From: ykulynych Date: Wed, 23 Sep 2026 12:44:35 +0200 Subject: [PATCH 2/3] PDPS-2095: Unblock taxonomy page rendering, fix cold start --- assets/js/admin.js | 17 ++++++++++++++ progress-agentic-rag.php | 4 ++-- readme.txt | 9 +++++++- src/Admin/AdminPage.php | 48 +++++++++++++--------------------------- 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/assets/js/admin.js b/assets/js/admin.js index ab7931e..8525789 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -607,6 +607,23 @@ }; if ( addMappingButton && taxonomySelect && mappingContainer ) { + if ( ! ( mappingConfig.labelsets || [] ).length ) { + // Cold-cache page load: refresh labelsets in the background instead of + // blocking render (see AdminPage::get_labelsets() AJAX handler). + request( 'progress_agentic_rag_get_labelsets' ) + .then( ( data ) => { + mappingConfig.labelsets = Array.isArray( data.labelsets ) ? data.labelsets : []; + + mappingContainer.querySelectorAll( '[data-progress-agentic-rag-labelset-select], [data-progress-agentic-rag-fallback-labelset-select]' ).forEach( ( select ) => { + const current = select.value; + select.innerHTML = ''; + select.appendChild( labelsetOptions() ); + select.value = current; + } ); + } ) + .catch( () => {} ); + } + addMappingButton.addEventListener( 'click', () => { const taxonomy = taxonomySelect.value; diff --git a/progress-agentic-rag.php b/progress-agentic-rag.php index 8cd4f76..4a48b77 100644 --- a/progress-agentic-rag.php +++ b/progress-agentic-rag.php @@ -8,7 +8,7 @@ * Plugin Name: Progress Agentic RAG connector * Plugin URI: https://github.com/nuclia/wordpress-plugin * Description: Index WordPress content into Progress Agentic RAG and power knowledge-base search. - * Version: 1.0.1 + * Version: 1.0.2 * Requires at least: 6.8 * Requires PHP: 8.1 * Author: Progress Software @@ -30,7 +30,7 @@ } // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Public plugin constants already use the Progress Agentic RAG prefix. -define( 'PROGRESS_AGENTIC_RAG_VERSION', '1.0.1' ); +define( 'PROGRESS_AGENTIC_RAG_VERSION', '1.0.2' ); define( 'PROGRESS_AGENTIC_RAG_MIN_PHP_VERSION', '8.1' ); define( 'PROGRESS_AGENTIC_RAG_MIN_WP_VERSION', '6.8' ); define( 'PROGRESS_AGENTIC_RAG_FILE', __FILE__ ); diff --git a/readme.txt b/readme.txt index 17e54a2..f8015fd 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: progresssoftware Tags: search, ai, knowledge-base, rag, indexing Requires at least: 6.8 Tested up to: 7.0 -Stable tag: 1.0.1 +Stable tag: 1.0.2 Requires PHP: 8.1 License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -43,6 +43,13 @@ No. Service credentials stay server-side, and public widget requests use the ser == Changelog == += 1.0.2 = + +* Fix: Admin pages no longer perform blocking upstream sync/labelset calls + during render, which caused 504 timeouts on slow or large sites. The + taxonomy labeling tab now loads instantly from cache and refreshes its + labelset dropdown in the background instead of fetching it synchronously. + = 1.0.1 = * Fix: Advanced Custom Fields (ACF) data (e.g. Branch page locations, opening hours, contact diff --git a/src/Admin/AdminPage.php b/src/Admin/AdminPage.php index 22750ca..d82b603 100644 --- a/src/Admin/AdminPage.php +++ b/src/Admin/AdminPage.php @@ -50,6 +50,7 @@ public function register(): void { add_action( 'wp_ajax_progress_agentic_rag_label_reprocess_cancel', [ $this, 'cancel_label_reprocess' ] ); add_action( 'wp_ajax_progress_agentic_rag_label_reprocess_status', [ $this, 'label_reprocess_status' ] ); add_action( 'wp_ajax_progress_agentic_rag_get_labelset_labels', [ $this, 'get_labelset_labels' ] ); + add_action( 'wp_ajax_progress_agentic_rag_get_labelsets', [ $this, 'get_labelsets' ] ); add_action( 'wp_ajax_progress_agentic_rag_test_connection', [ $this, 'test_connection' ] ); add_action( 'wp_ajax_progress_agentic_rag_retry_failed_sync', [ $this, 'retry_failed_sync' ] ); add_action( 'wp_ajax_progress_agentic_rag_delete_synced_resource', [ $this, 'delete_synced_resource' ] ); @@ -373,6 +374,16 @@ public function get_labelset_labels(): void { wp_send_json_success( [ 'labels' => $this->api_client->get_labelset_labels( $labelset ) ] ); } + public function get_labelsets(): void { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( [ 'message' => __( 'You do not have permission to load labelsets.', 'progress-agentic-rag-connector' ) ], 403 ); + } + + check_ajax_referer( 'progress_agentic_rag_manual_sync' ); + + wp_send_json_success( [ 'labelsets' => $this->api_client->get_labelsets() ] ); + } + public function test_connection(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'You do not have permission to test the connection.', 'progress-agentic-rag-connector' ) ], 403 ); @@ -628,7 +639,7 @@ private function render_taxonomy_labeling(): void { $taxonomies = get_taxonomies( [ 'public' => true ], 'objects' ); $taxonomy_label_map = $this->settings->get_taxonomy_label_map(); $taxonomy_label_map_option = SettingsRepository::OPTION_TAXONOMY_LABEL_MAP; - $labelsets = $this->api_client->get_labelsets(); + $labelsets = $this->api_client->get_labelsets_cached(); $taxonomy_terms = $this->taxonomy_terms( array_keys( $taxonomies ) ); $labelset_labels = $this->labelset_labels_for_mapping( $taxonomy_label_map ); $taxonomy_mapping_warnings = $this->taxonomy_mapping_warnings( $taxonomy_label_map, $labelsets, $taxonomy_terms, $labelset_labels, (int) ( $sync_status['total_mapped'] ?? 0 ) ); @@ -888,38 +899,9 @@ private function connection_test_result( string $zone, string $kbid, string $tok } /** - * @return array - */ - private function mapping_data(): array { - $taxonomies = []; - - foreach ( get_taxonomies( [ 'public' => true ], 'objects' ) as $taxonomy_name => $taxonomy ) { - $terms = []; - foreach ( $this->taxonomy_terms( [ $taxonomy_name ] )[ $taxonomy_name ] ?? [] as $term ) { - $terms[] = [ - 'id' => (int) $term->term_id, - 'name' => (string) $term->name, - ]; - } - - $taxonomies[ $taxonomy_name ] = [ - 'label' => isset( $taxonomy->labels->name ) ? (string) $taxonomy->labels->name : (string) $taxonomy_name, - 'terms' => $terms, - ]; - } - - return [ - 'taxonomies' => $taxonomies, - 'labelsets' => $this->api_client->get_labelsets(), - ]; - } - - /** - * Same shape as mapping_data(), but never performs an upstream HTTP request. - * - * Used in the admin page boot path (enqueue_assets) where any blocking - * call would stall every tab. The taxonomy-labeling tab still calls - * mapping_data() server-side and get_labelsets() refreshes the cache. + * Never performs an upstream HTTP request; the taxonomy-labeling tab's + * "Add mapping" dropdown self-heals via the get_labelsets() AJAX handler + * when the cache is cold (see assets/js/admin.js). * * @return array */ From 6f74493dd083d6b46f65009db56b5e528b8aedb8 Mon Sep 17 00:00:00 2001 From: ykulynych Date: Thu, 24 Sep 2026 13:52:43 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=C2=A0PDPS-2095:=20Fix=20labelset=20dropdow?= =?UTF-8?q?n=20and=20label=20checkboxes=20blanking=20on=20cold=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/js/admin.js | 57 +++++++++++++++++- src/Admin/AdminPage.php | 90 +++++++++++++++------------- src/Api/ApiClient.php | 14 +++++ templates/admin/taxonomy-mapping.php | 31 ++++++++-- tests/phpunit/AdminPageTest.php | 7 +-- 5 files changed, 141 insertions(+), 58 deletions(-) diff --git a/assets/js/admin.js b/assets/js/admin.js index 8525789..842de8b 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -454,7 +454,7 @@ 'nuclia_taxonomy_label_map[' + taxonomy + '][fallback][labels][]' : 'nuclia_taxonomy_label_map[' + taxonomy + '][terms][' + termId + '][]'; - const renderLabelCheckboxes = ( container, taxonomy, labels, termId = '', fallback = false ) => { + const renderLabelCheckboxes = ( container, taxonomy, labels, termId = '', fallback = false, checkedValues = null ) => { container.replaceChildren(); if ( ! labels.length ) { @@ -471,6 +471,9 @@ input.type = 'checkbox'; input.value = label; input.name = labelCheckboxName( taxonomy, termId, fallback ); + if ( checkedValues && checkedValues.includes( label ) ) { + input.checked = true; + } text.textContent = label; wrapper.append( input, text ); @@ -487,6 +490,31 @@ return Array.isArray( data.labels ) ? data.labels : []; }; + // Cold cache: server rendered from saved state only. Fetch confirmed + // labels and merge them in without discarding checked values. + const refreshSavedLabelset = async ( select ) => { + const labelset = select.dataset.selectedLabelset || ''; + if ( ! labelset ) { + return; + } + + const taxonomy = select.dataset.taxonomy; + const isFallback = select.matches( '[data-progress-agentic-rag-fallback-labelset-select]' ); + const containers = isFallback ? + [ mappingContainer.querySelector( '[data-progress-agentic-rag-fallback-labels][data-taxonomy="' + selectorValue( taxonomy ) + '"]' ) ] : + Array.from( mappingContainer.querySelectorAll( '[data-progress-agentic-rag-label-checkboxes][data-taxonomy="' + selectorValue( taxonomy ) + '"]' ) ); + + try { + const labels = await loadMappingLabels( labelset ); + containers.filter( Boolean ).forEach( ( container ) => { + const checked = ( container.dataset.checkedLabels || '' ).split( '|' ).filter( Boolean ); + renderLabelCheckboxes( container, taxonomy, labels, container.dataset.termId || '', isFallback, checked ); + } ); + } catch ( error ) { + // Keep the server-rendered state on failure. + } + }; + const buildMappingBlock = ( taxonomy ) => { const taxonomyConfig = mappingConfig.taxonomies[ taxonomy ]; const block = document.createElement( 'div' ); @@ -615,15 +643,38 @@ mappingConfig.labelsets = Array.isArray( data.labelsets ) ? data.labelsets : []; mappingContainer.querySelectorAll( '[data-progress-agentic-rag-labelset-select], [data-progress-agentic-rag-fallback-labelset-select]' ).forEach( ( select ) => { - const current = select.value; + // Saved value is the source of truth, not the select's current value. + const selected = select.dataset.selectedLabelset || ''; select.innerHTML = ''; select.appendChild( labelsetOptions() ); - select.value = current; + select.value = selected; + + const label = select.closest( 'label' ) || select; + if ( label.nextElementSibling && label.nextElementSibling.hasAttribute( 'data-progress-agentic-rag-labelset-missing' ) ) { + label.nextElementSibling.remove(); + } + + // Confirmed list now available: a missing saved value is truly gone. + if ( selected && ! mappingConfig.labelsets.includes( selected ) ) { + const warning = document.createElement( 'p' ); + warning.className = 'progress-agentic-rag__mapping-muted'; + warning.setAttribute( 'data-progress-agentic-rag-labelset-missing', '' ); + warning.textContent = ( config.strings.mappingLabelsetMissing || 'Labelset "%s" was not found upstream.' ).replace( '%s', selected ); + label.insertAdjacentElement( 'afterend', warning ); + } } ); } ) .catch( () => {} ); } + // Label lists have their own staleness, separate from the labelset list; + // always top up (the AJAX handler is a no-op if already warm). + mappingContainer.querySelectorAll( '[data-progress-agentic-rag-labelset-select][data-selected-labelset], [data-progress-agentic-rag-fallback-labelset-select][data-selected-labelset]' ).forEach( ( select ) => { + if ( select.dataset.selectedLabelset ) { + refreshSavedLabelset( select ); + } + } ); + addMappingButton.addEventListener( 'click', () => { const taxonomy = taxonomySelect.value; diff --git a/src/Admin/AdminPage.php b/src/Admin/AdminPage.php index d82b603..c7299b1 100644 --- a/src/Admin/AdminPage.php +++ b/src/Admin/AdminPage.php @@ -101,50 +101,52 @@ public function enqueue_assets( string $hook_suffix ): void { 'initialBackgroundSyncStatus' => $this->manual_sync->background_sync_status(), 'mapping' => $this->mapping_data_cached(), 'strings' => [ - 'closeDeleteProgress' => __( 'Close delete progress', 'progress-agentic-rag-connector' ), - 'closeSyncProgress' => __( 'Close sync progress', 'progress-agentic-rag-connector' ), + 'closeDeleteProgress' => __( 'Close delete progress', 'progress-agentic-rag-connector' ), + 'closeSyncProgress' => __( 'Close sync progress', 'progress-agentic-rag-connector' ), /* translators: %d: synced resource count. */ - 'confirmDelete' => __( 'Delete %d synced resource(s) from Progress Agentic RAG and clear their local sync mappings? This cannot be undone.', 'progress-agentic-rag-connector' ), + 'confirmDelete' => __( 'Delete %d synced resource(s) from Progress Agentic RAG and clear their local sync mappings? This cannot be undone.', 'progress-agentic-rag-connector' ), /* translators: %d: synced resource count. */ - 'confirmReprocess' => __( 'Update labels for %d synced resource(s) with the current taxonomy mapping?', 'progress-agentic-rag-connector' ), - 'deleteFailed' => __( 'Synced resources could not be deleted.', 'progress-agentic-rag-connector' ), - 'deleteModalLabel' => __( 'Synced resource delete', 'progress-agentic-rag-connector' ), - 'deleteModalTitle' => __( 'Deleting synced resources', 'progress-agentic-rag-connector' ), - 'deleteTotalLabel' => __( 'Total resources', 'progress-agentic-rag-connector' ), - 'deleteDoneLabel' => __( 'Deleted', 'progress-agentic-rag-connector' ), - 'deleteCurrentLabel' => __( 'Current resource', 'progress-agentic-rag-connector' ), - 'deleteRunning' => __( 'Delete in progress', 'progress-agentic-rag-connector' ), - 'deleting' => __( 'Preparing synced resource deletion.', 'progress-agentic-rag-connector' ), - 'failed' => __( 'Manual sync could not start.', 'progress-agentic-rag-connector' ), - 'running' => __( 'Sync in progress', 'progress-agentic-rag-connector' ), - 'syncModalLabel' => __( 'Manual sync', 'progress-agentic-rag-connector' ), - 'syncModalTitle' => __( 'Syncing selected content', 'progress-agentic-rag-connector' ), - 'syncTotalLabel' => __( 'Total entities', 'progress-agentic-rag-connector' ), - 'syncDoneLabel' => __( 'Synced', 'progress-agentic-rag-connector' ), - 'syncCurrentLabel' => __( 'Current entity', 'progress-agentic-rag-connector' ), - 'sync' => __( 'Sync manually', 'progress-agentic-rag-connector' ), - 'reprocessFailed' => __( 'Label reprocessing could not start.', 'progress-agentic-rag-connector' ), - 'starting' => __( 'Preparing manual sync.', 'progress-agentic-rag-connector' ), - 'complete' => __( 'Manual sync complete.', 'progress-agentic-rag-connector' ), - 'mappingSelectLabelset' => __( 'Select a labelset', 'progress-agentic-rag-connector' ), - 'mappingRemove' => __( 'Remove', 'progress-agentic-rag-connector' ), - 'mappingLabelset' => __( 'Labelset', 'progress-agentic-rag-connector' ), - 'mappingTerm' => __( 'Term', 'progress-agentic-rag-connector' ), - 'mappingLabels' => __( 'Progress Agentic RAG labels', 'progress-agentic-rag-connector' ), - 'mappingFallback' => __( 'Fallback labels (when no terms assigned)', 'progress-agentic-rag-connector' ), - 'mappingSelectLabels' => __( 'Select a labelset to load labels.', 'progress-agentic-rag-connector' ), - 'mappingLoadingLabels' => __( 'Loading labels...', 'progress-agentic-rag-connector' ), - 'mappingNoLabels' => __( 'No labels available.', 'progress-agentic-rag-connector' ), - 'mappingLabelsFailed' => __( 'Labels could not be loaded.', 'progress-agentic-rag-connector' ), - 'mappingNoTerms' => __( 'No terms available for this taxonomy.', 'progress-agentic-rag-connector' ), - 'testConnection' => __( 'Testing connection...', 'progress-agentic-rag-connector' ), - 'connectionTestFailed' => __( 'Connection test could not run.', 'progress-agentic-rag-connector' ), - 'retryFailed' => __( 'Failed sync items could not be retried.', 'progress-agentic-rag-connector' ), - 'diagnosticsCopied' => __( 'Diagnostics copied.', 'progress-agentic-rag-connector' ), - 'diagnosticsFailed' => __( 'Diagnostics could not be exported.', 'progress-agentic-rag-connector' ), - 'confirmDeleteSingle' => __( 'Delete this resource from Progress Agentic RAG and clear its local sync mapping?', 'progress-agentic-rag-connector' ), - 'deleteSingleFailed' => __( 'The synced resource could not be removed.', 'progress-agentic-rag-connector' ), - 'deleteSingleComplete' => __( 'Synced resource removed.', 'progress-agentic-rag-connector' ), + 'confirmReprocess' => __( 'Update labels for %d synced resource(s) with the current taxonomy mapping?', 'progress-agentic-rag-connector' ), + 'deleteFailed' => __( 'Synced resources could not be deleted.', 'progress-agentic-rag-connector' ), + 'deleteModalLabel' => __( 'Synced resource delete', 'progress-agentic-rag-connector' ), + 'deleteModalTitle' => __( 'Deleting synced resources', 'progress-agentic-rag-connector' ), + 'deleteTotalLabel' => __( 'Total resources', 'progress-agentic-rag-connector' ), + 'deleteDoneLabel' => __( 'Deleted', 'progress-agentic-rag-connector' ), + 'deleteCurrentLabel' => __( 'Current resource', 'progress-agentic-rag-connector' ), + 'deleteRunning' => __( 'Delete in progress', 'progress-agentic-rag-connector' ), + 'deleting' => __( 'Preparing synced resource deletion.', 'progress-agentic-rag-connector' ), + 'failed' => __( 'Manual sync could not start.', 'progress-agentic-rag-connector' ), + 'running' => __( 'Sync in progress', 'progress-agentic-rag-connector' ), + 'syncModalLabel' => __( 'Manual sync', 'progress-agentic-rag-connector' ), + 'syncModalTitle' => __( 'Syncing selected content', 'progress-agentic-rag-connector' ), + 'syncTotalLabel' => __( 'Total entities', 'progress-agentic-rag-connector' ), + 'syncDoneLabel' => __( 'Synced', 'progress-agentic-rag-connector' ), + 'syncCurrentLabel' => __( 'Current entity', 'progress-agentic-rag-connector' ), + 'sync' => __( 'Sync manually', 'progress-agentic-rag-connector' ), + 'reprocessFailed' => __( 'Label reprocessing could not start.', 'progress-agentic-rag-connector' ), + 'starting' => __( 'Preparing manual sync.', 'progress-agentic-rag-connector' ), + 'complete' => __( 'Manual sync complete.', 'progress-agentic-rag-connector' ), + 'mappingSelectLabelset' => __( 'Select a labelset', 'progress-agentic-rag-connector' ), + 'mappingRemove' => __( 'Remove', 'progress-agentic-rag-connector' ), + 'mappingLabelset' => __( 'Labelset', 'progress-agentic-rag-connector' ), + 'mappingTerm' => __( 'Term', 'progress-agentic-rag-connector' ), + 'mappingLabels' => __( 'Progress Agentic RAG labels', 'progress-agentic-rag-connector' ), + 'mappingFallback' => __( 'Fallback labels (when no terms assigned)', 'progress-agentic-rag-connector' ), + 'mappingSelectLabels' => __( 'Select a labelset to load labels.', 'progress-agentic-rag-connector' ), + 'mappingLoadingLabels' => __( 'Loading labels...', 'progress-agentic-rag-connector' ), + 'mappingNoLabels' => __( 'No labels available.', 'progress-agentic-rag-connector' ), + 'mappingLabelsFailed' => __( 'Labels could not be loaded.', 'progress-agentic-rag-connector' ), + 'mappingNoTerms' => __( 'No terms available for this taxonomy.', 'progress-agentic-rag-connector' ), + /* translators: %s: labelset name. */ + 'mappingLabelsetMissing' => __( 'Labelset "%s" was not found upstream.', 'progress-agentic-rag-connector' ), + 'testConnection' => __( 'Testing connection...', 'progress-agentic-rag-connector' ), + 'connectionTestFailed' => __( 'Connection test could not run.', 'progress-agentic-rag-connector' ), + 'retryFailed' => __( 'Failed sync items could not be retried.', 'progress-agentic-rag-connector' ), + 'diagnosticsCopied' => __( 'Diagnostics copied.', 'progress-agentic-rag-connector' ), + 'diagnosticsFailed' => __( 'Diagnostics could not be exported.', 'progress-agentic-rag-connector' ), + 'confirmDeleteSingle' => __( 'Delete this resource from Progress Agentic RAG and clear its local sync mapping?', 'progress-agentic-rag-connector' ), + 'deleteSingleFailed' => __( 'The synced resource could not be removed.', 'progress-agentic-rag-connector' ), + 'deleteSingleComplete' => __( 'Synced resource removed.', 'progress-agentic-rag-connector' ), ], ] ); @@ -952,6 +954,8 @@ private function taxonomy_terms( array $taxonomy_names ): array { } /** + * Cache-only; JS tops up stale labels via get_labelset_labels() after load. + * * @param array $mapping Taxonomy label mapping. * * @return array> @@ -978,7 +982,7 @@ private function labelset_labels_for_mapping( array $mapping ): array { $labels = []; foreach ( array_keys( $labelsets ) as $labelset ) { - $labels[ $labelset ] = $this->api_client->get_labelset_labels( $labelset ); + $labels[ $labelset ] = $this->api_client->get_labelset_labels_cached( $labelset ); } return $labels; diff --git a/src/Api/ApiClient.php b/src/Api/ApiClient.php index f0b5747..35264ba 100644 --- a/src/Api/ApiClient.php +++ b/src/Api/ApiClient.php @@ -266,6 +266,20 @@ public function get_labelsets_cached(): array { return is_array( $cache['labelsets'] ?? null ) ? array_values( $cache['labelsets'] ) : []; } + /** + * Cached labels only, no upstream request; use in non-blocking render paths. + * + * @return list + */ + public function get_labelset_labels_cached( string $labelset ): array { + $labelset = trim( $labelset ); + if ( '' === $labelset ) { + return []; + } + + return $this->settings->get_labelset_labels_cache( $labelset ); + } + /** * @return list */ diff --git a/templates/admin/taxonomy-mapping.php b/templates/admin/taxonomy-mapping.php index d4ebfd1..1ceb8f8 100644 --- a/templates/admin/taxonomy-mapping.php +++ b/templates/admin/taxonomy-mapping.php @@ -49,6 +49,25 @@ $available_labels = '' !== $taxonomy_labelset && isset( $labelset_labels[ $taxonomy_labelset ] ) ? $labelset_labels[ $taxonomy_labelset ] : []; $fallback_available = '' !== $fallback_labelset && isset( $labelset_labels[ $fallback_labelset ] ) ? $labelset_labels[ $fallback_labelset ] : []; $terms = $taxonomy_terms[ $taxonomy_key ] ?? []; + + // Keep saved/checked labels visible until the labelset's list is fetched. + if ( empty( $available_labels ) ) { + $saved_term_labels = []; + foreach ( $term_map as $term_saved_labels ) { + $term_saved_labels = is_array( $term_saved_labels ) ? $term_saved_labels : ( '' !== $term_saved_labels ? [ $term_saved_labels ] : [] ); + foreach ( $term_saved_labels as $saved_label ) { + $saved_term_labels[ (string) $saved_label ] = true; + } + } + $available_labels = array_keys( $saved_term_labels ); + } + if ( empty( $fallback_available ) ) { + $fallback_available = $fallback_labels; + } + + // Keep a saved value selected until $labelsets is populated and can confirm it. + $taxonomy_labelset_options = ( '' !== $taxonomy_labelset && empty( $labelsets ) ) ? [ $taxonomy_labelset ] : $labelsets; + $fallback_labelset_options = ( '' !== $fallback_labelset && empty( $labelsets ) ) ? [ $fallback_labelset ] : $labelsets; ?>
@@ -60,9 +79,9 @@