diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 3334e04dd..ccaef05d7 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -6,7 +6,6 @@ use Classifai\Features\RecommendedContent; use Classifai\Services\ServicesManager; use Classifai\Taxonomy\TaxonomyFactory; -use Classifai\Helpers\CredentialReuse; use Classifai\Providers\CredentialObfuscator; use function Classifai\get_asset_info; @@ -306,62 +305,6 @@ public function register_routes() { ], ] ); - - register_rest_route( - 'classifai/v1', - 'credential-reuse/(?P[a-zA-Z0-9_-]+)', - [ - [ - 'methods' => \WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_reusable_credentials' ], - 'args' => array( - 'feature_id' => array( - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Feature ID to get reusable credentials for.', 'classifai' ), - ), - ), - 'permission_callback' => [ $this, 'get_settings_permissions_check' ], - ], - ] - ); - - register_rest_route( - 'classifai/v1', - 'credential-reuse/copy', - [ - [ - 'methods' => \WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'copy_credentials' ], - 'args' => array( - 'source_feature_id' => array( - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Source feature ID to copy credentials from.', 'classifai' ), - ), - 'target_feature_id' => array( - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Target feature ID to copy credentials to.', 'classifai' ), - ), - 'provider_id' => array( - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Provider ID to copy.', 'classifai' ), - ), - ), - 'permission_callback' => [ $this, 'get_settings_permissions_check' ], - ], - ] - ); } /** @@ -581,52 +524,6 @@ public function check_embedding_generation_status( \WP_REST_Request $request ): return rest_ensure_response( $response ); } - /** - * Get reusable credentials for a feature. - * - * @param \WP_REST_Request $request The full request object. - * @return \WP_REST_Response - */ - public function get_reusable_credentials( \WP_REST_Request $request ): \WP_REST_Response { - $feature_id = $request->get_param( 'feature_id' ); - $reusable = CredentialReuse::get_reusable_credentials( $feature_id ); - - // Add provider display names for better UX. - foreach ( $reusable as $provider_id => &$provider_data ) { - $provider_data['provider_display_name'] = CredentialReuse::get_provider_display_name( $provider_id ); - } - - return rest_ensure_response( $reusable ); - } - - /** - * Copy credentials between features. - * - * @param \WP_REST_Request $request The full request object. - * @return \WP_REST_Response|\WP_Error - */ - public function copy_credentials( \WP_REST_Request $request ) { - $source_feature_id = $request->get_param( 'source_feature_id' ); - $target_feature_id = $request->get_param( 'target_feature_id' ); - $provider_id = $request->get_param( 'provider_id' ); - - $success = CredentialReuse::copy_provider_credentials( - $source_feature_id, - $target_feature_id, - $provider_id - ); - - if ( ! $success ) { - return new \WP_Error( - 'copy_failed', - __( 'Failed to copy credentials.', 'classifai' ), - [ 'status' => 400 ] - ); - } - - return rest_ensure_response( [ 'success' => true ] ); - } - /** * Add a link to the ClassifAI registration page in the plugin row. */ diff --git a/includes/Classifai/Helpers/CredentialReuse.php b/includes/Classifai/Helpers/CredentialReuse.php deleted file mode 100644 index bed09a8e6..000000000 --- a/includes/Classifai/Helpers/CredentialReuse.php +++ /dev/null @@ -1,361 +0,0 @@ - [ - 'openai_chatgpt', - 'openai_embeddings', - 'openai_moderation', - 'openai_dalle', - 'openai_speech_to_text', - 'openai_text_to_speech', - ], - 'azure' => [ - 'azure_openai', - 'azure_ai_vision', - 'azure_speech', - ], - 'ollama' => [ - 'ollama', - 'ollama_embeddings', - 'ollama_multimodal', - ], - ]; - - /** - * Get all configured providers across all features. - * - * @since 3.6.0 - * - * @return array Array of configured providers with their credentials. - */ - public static function get_configured_providers(): array { - $configured_providers = []; - $features = self::get_all_features(); - - foreach ( $features as $feature_id => $feature_instance ) { - $settings = $feature_instance->get_settings(); - $provider_id = $settings['provider'] ?? ''; - - if ( empty( $provider_id ) || empty( $settings[ $provider_id ]['authenticated'] ) ) { - continue; - } - - $configured_providers[ $provider_id ] = [ - 'feature_id' => $feature_id, - 'feature_label' => $feature_instance->get_label(), - 'credentials' => $settings[ $provider_id ], - ]; - } - return $configured_providers; - } - - /** - * Get the provider group for a given provider ID. - * - * @since 3.6.0 - * - * @param string $provider_id The provider ID. - * @return string|null The provider group name or null if not found. - */ - private static function get_provider_group( string $provider_id ): ?string { - foreach ( self::$provider_groups as $group_name => $providers ) { - if ( in_array( $provider_id, $providers, true ) ) { - return $group_name; - } - } - return null; - } - - /** - * Get all providers in the same group as the given provider. - * - * @since 3.6.0 - * - * @param string $provider_id The provider ID. - * @return array Array of provider IDs in the same group. - */ - private static function get_providers_in_same_group( string $provider_id ): array { - $group = self::get_provider_group( $provider_id ); - if ( ! $group ) { - return [ $provider_id ]; - } - return self::$provider_groups[ $group ]; - } - - /** - * Check if a provider is compatible with a feature. - * - * @since 3.6.0 - * - * @param string $provider_id The provider ID to check. - * @param string $feature_id The feature ID to check against. - * @return bool True if compatible. - */ - public static function is_provider_compatible( string $provider_id, string $feature_id ): bool { - $features = self::get_all_features(); - - if ( ! isset( $features[ $feature_id ] ) ) { - return false; - } - - $feature_providers = $features[ $feature_id ]->get_providers(); - return array_key_exists( $provider_id, $feature_providers ); - } - - /** - * Check if any provider in the same group is compatible with a feature. - * - * @since 3.6.0 - * - * @param string $provider_id The provider ID to check. - * @param string $feature_id The feature ID to check against. - * @return bool True if any provider in the same group is compatible. - */ - private static function is_provider_group_compatible( string $provider_id, string $feature_id ): bool { - $providers_in_group = self::get_providers_in_same_group( $provider_id ); - - foreach ( $providers_in_group as $group_provider_id ) { - if ( self::is_provider_compatible( $group_provider_id, $feature_id ) ) { - return true; - } - } - - return false; - } - - /** - * Get the best matching provider ID for a feature from a provider group. - * - * @since 3.6.0 - * - * @param string $source_provider_id The source provider ID. - * @param string $feature_id The target feature ID. - * @return string|null The best matching provider ID or null if none found. - */ - private static function get_best_matching_provider( string $source_provider_id, string $feature_id ): ?string { - $providers_in_group = self::get_providers_in_same_group( $source_provider_id ); - - // First, try to find an exact match - if ( self::is_provider_compatible( $source_provider_id, $feature_id ) ) { - return $source_provider_id; - } - - // Then, try to find any compatible provider in the same group - foreach ( $providers_in_group as $group_provider_id ) { - if ( self::is_provider_compatible( $group_provider_id, $feature_id ) ) { - return $group_provider_id; - } - } - - return null; - } - - /** - * Get reusable credentials for a feature. - * - * @since 3.6.0 - * - * @param string $feature_id The feature ID to get credentials for. - * @return array Array of compatible providers with existing credentials. - */ - public static function get_reusable_credentials( string $feature_id ): array { - $configured_providers = self::get_configured_providers(); - $reusable = []; - - foreach ( $configured_providers as $provider_id => $provider_data ) { - // Skip if this is the same feature. - if ( $provider_data['feature_id'] === $feature_id ) { - continue; - } - - // Check if provider is directly compatible with the target feature. - if ( self::is_provider_compatible( $provider_id, $feature_id ) ) { - $reusable[ $provider_id ] = $provider_data; - continue; - } - - // Check if any provider in the same group is compatible. - if ( self::is_provider_group_compatible( $provider_id, $feature_id ) ) { - $best_match = self::get_best_matching_provider( $provider_id, $feature_id ); - if ( $best_match ) { - // Create a modified provider data entry with the best matching provider ID - $reusable[ $best_match ] = [ - 'feature_id' => $provider_data['feature_id'], - 'feature_label' => $provider_data['feature_label'], - 'credentials' => $provider_data['credentials'], - 'source_provider_id' => $provider_id, - ]; - } - } - } - - /** - * Filter the reusable credentials for a feature. - * - * @since 3.6.0 - * @hook classifai_reusable_credentials - * - * @param array $reusable Array of reusable credentials. - * @param string $feature_id The feature ID. - * - * @return array Filtered reusable credentials. - */ - return apply_filters( 'classifai_reusable_credentials', $reusable, $feature_id ); - } - - /** - * Copy credentials from one feature to another. - * - * @since 3.6.0 - * - * @param string $source_feature_id Source feature ID. - * @param string $target_feature_id Target feature ID. - * @param string $provider_id Provider ID to copy. - * @return bool Success status. - */ - public static function copy_provider_credentials( string $source_feature_id, string $target_feature_id, string $provider_id ): bool { - $features = self::get_all_features(); - - if ( ! isset( $features[ $source_feature_id ] ) || ! isset( $features[ $target_feature_id ] ) ) { - return false; - } - - $source_settings = $features[ $source_feature_id ]->get_settings(); - $target_settings = $features[ $target_feature_id ]->get_settings(); - - // Find the source provider credentials - $source_credentials = null; - - // First, try to find the exact provider - if ( ! empty( $source_settings[ $provider_id ] ) ) { - $source_credentials = $source_settings[ $provider_id ]; - } else { - // If not found, look for any provider in the same group - $providers_in_group = self::get_providers_in_same_group( $provider_id ); - foreach ( $providers_in_group as $group_provider_id ) { - if ( ! empty( $source_settings[ $group_provider_id ] ) ) { - $source_credentials = $source_settings[ $group_provider_id ]; - break; - } - } - } - - if ( ! $source_credentials ) { - return false; - } - - // Copy the provider credentials to the target provider - $target_settings[ $provider_id ] = $source_credentials; - $target_settings['provider'] = $provider_id; - - // Update the target feature settings. - update_option( $features[ $target_feature_id ]->get_option_name(), $target_settings ); - - /** - * Fires after credentials are copied between features. - * - * @since 3.6.0 - * @hook classifai_credentials_copied - * - * @param string $source_feature_id Source feature ID. - * @param string $target_feature_id Target feature ID. - * @param string $provider_id Provider ID that was copied. - */ - do_action( 'classifai_credentials_copied', $source_feature_id, $target_feature_id, $provider_id ); - - return true; - } - - /** - * Get all feature instances. - * - * @since 3.6.0 - * - * @return array Array of feature instances. - */ - private static function get_all_features(): array { - $services = get_plugin()->services; - $features = []; - - if ( empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { - return $features; - } - - /** @var ServicesManager $service_manager */ - $service_manager = $services['service_manager']; - - foreach ( $service_manager->service_classes as $service ) { - foreach ( $service->feature_classes as $feature ) { - $features[ $feature::ID ] = $feature; - } - } - - return $features; - } - - /** - * Get a user-friendly provider name. - * - * @since 3.6.0 - * - * @param string $provider_id The provider ID. - * @return string The formatted provider name. - */ - public static function get_provider_display_name( string $provider_id ): string { - $provider_names = [ - 'openai_chatgpt' => __( 'OpenAI ChatGPT', 'classifai' ), - 'openai_dalle' => __( 'OpenAI Images', 'classifai' ), - 'openai_embeddings' => __( 'OpenAI Embeddings', 'classifai' ), - 'openai_moderation' => __( 'OpenAI Moderation', 'classifai' ), - 'openai_speech_to_text' => __( 'OpenAI Speech to Text', 'classifai' ), - 'openai_text_to_speech' => __( 'OpenAI Text to Speech', 'classifai' ), - 'azure_openai' => __( 'Azure OpenAI', 'classifai' ), - 'azure_ai_vision' => __( 'Azure AI Vision', 'classifai' ), - 'azure_speech' => __( 'Microsoft Azure AI Speech', 'classifai' ), - 'aws_polly' => __( 'Amazon Polly', 'classifai' ), - 'google_gemini_api' => __( 'Google AI Gemini API', 'classifai' ), - 'ibm_watson_nlu' => __( 'IBM Watson NLU', 'classifai' ), - 'ollama' => __( 'Ollama', 'classifai' ), - 'ollama_embeddings' => __( 'Ollama Embeddings', 'classifai' ), - 'ollama_multimodal' => __( 'Ollama Multimodal', 'classifai' ), - 'xai_grok' => __( 'xAI Grok', 'classifai' ), - ]; - - return $provider_names[ $provider_id ] ?? ucwords( str_replace( '_', ' ', $provider_id ) ); - } - - /** - * Get provider groups for external use. - * - * @since 3.6.0 - * - * @return array Array of provider groups. - */ - public static function get_provider_groups(): array { - return self::$provider_groups; - } -} diff --git a/src/js/settings/components/credential-reuse/index.js b/src/js/settings/components/credential-reuse/index.js deleted file mode 100644 index f7daf8d68..000000000 --- a/src/js/settings/components/credential-reuse/index.js +++ /dev/null @@ -1,258 +0,0 @@ -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { useState, useEffect } from '@wordpress/element'; -import { - Modal, - Button, - Notice, - CheckboxControl, - Flex, - FlexItem, - Icon, -} from '@wordpress/components'; -import apiFetch from '@wordpress/api-fetch'; - -/** - * CredentialReuseModal component. - * - * Shows a modal asking the user if they want to reuse existing provider credentials - * when enabling a new feature. - * - * @param {Object} props Component props. - * @param {boolean} props.isOpen Whether the modal is open. - * @param {Function} props.onClose Callback when modal is closed. - * @param {string} props.featureName Name of the feature being enabled. - * @param {string} props.featureLabel Display label of the feature being enabled. - * @param {Function} props.onCredentialsReused Callback when credentials are reused. - * @return {React.ReactElement|null} The modal component or null if not open. - */ -export const CredentialReuseModal = ( { - isOpen, - onClose, - featureName, - featureLabel, - onCredentialsReused, -} ) => { - const [ reusableCredentials, setReusableCredentials ] = useState( {} ); - const [ selectedProvider, setSelectedProvider ] = useState( '' ); - const [ dontAskAgain, setDontAskAgain ] = useState( false ); - const [ isLoading, setIsLoading ] = useState( false ); - const [ isLoadingCredentials, setIsLoadingCredentials ] = useState( false ); - - useEffect( () => { - if ( isOpen && featureName ) { - fetchReusableCredentials(); - } - }, [ isOpen, featureName ] ); - - /** - * Fetch reusable credentials for the current feature. - */ - const fetchReusableCredentials = async () => { - setIsLoadingCredentials( true ); - try { - const response = await apiFetch( { - path: `/classifai/v1/credential-reuse/${ featureName }`, - } ); - setReusableCredentials( response ); - - // Auto-select the first available provider - const providerIds = Object.keys( response ); - if ( providerIds.length > 0 ) { - setSelectedProvider( providerIds[ 0 ] ); - } - } catch ( error ) { - // Error handled gracefully - } finally { - setIsLoadingCredentials( false ); - } - }; - - /** - * Handle reusing credentials. - */ - const handleReuseCredentials = async () => { - if ( ! selectedProvider ) { - return; - } - - setIsLoading( true ); - try { - const sourceFeatureId = - reusableCredentials[ selectedProvider ].feature_id; - - await apiFetch( { - path: '/classifai/v1/credential-reuse/copy', - method: 'POST', - data: { - source_feature_id: sourceFeatureId, - target_feature_id: featureName, - provider_id: selectedProvider, - }, - } ); - - // Save "don't ask again" preference - if ( dontAskAgain ) { - window.localStorage.setItem( - 'classifai_dont_ask_credential_reuse', - 'true' - ); - } - - onCredentialsReused( selectedProvider ); - onClose(); - } catch ( error ) { - // Error handled gracefully - } finally { - setIsLoading( false ); - } - }; - - /** - * Handle skipping credential reuse. - */ - const handleSkip = () => { - // Save "don't ask again" preference if checked - if ( dontAskAgain ) { - window.localStorage.setItem( - 'classifai_dont_ask_credential_reuse', - 'true' - ); - } - onClose(); - }; - - // Only return null if the modal is not open. - if ( ! isOpen ) { - return null; - } - - const providers = Object.keys( reusableCredentials ); - - return ( - - { isLoadingCredentials && ( -
- - { __( 'Checking for existing credentials…', 'classifai' ) } -
- ) } - { ! isLoadingCredentials && providers.length > 0 && ( - <> - - { sprintf( - /* translators: %s: Feature label */ - __( - 'We found existing Provider credentials that can be used with %s. Would you like to reuse them?', - 'classifai' - ), - featureLabel || 'this feature' - ) } - - -
- { providers.map( ( providerId ) => { - const provider = reusableCredentials[ providerId ]; - return ( - - ); - } ) } -
- - - - - - - - - - - - - ) } - { ! isLoadingCredentials && providers.length === 0 && ( - <> - - { sprintf( - /* translators: %s: Feature label */ - __( - 'No compatible existing credentials were found for %s.', - 'classifai' - ), - featureLabel || 'this feature' - ) } - - - - - - - - ) } -
- ); -}; diff --git a/src/js/settings/components/feature-settings/enable-feature.js b/src/js/settings/components/feature-settings/enable-feature.js index 733d2fc5e..2e0485328 100644 --- a/src/js/settings/components/feature-settings/enable-feature.js +++ b/src/js/settings/components/feature-settings/enable-feature.js @@ -4,8 +4,6 @@ import { __ } from '@wordpress/i18n'; import { ToggleControl } from '@wordpress/components'; import { decodeEntities } from '@wordpress/html-entities'; -import { useState } from '@wordpress/element'; -import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies @@ -13,7 +11,6 @@ import apiFetch from '@wordpress/api-fetch'; import { getFeature } from '../../utils/utils'; import { SettingsRow } from '../settings-row'; import { useFeatureSettings } from '../../data/hooks'; -import { CredentialReuseModal } from '../credential-reuse'; /** * Enable Feature Toggle component. @@ -24,73 +21,18 @@ import { CredentialReuseModal } from '../credential-reuse'; export const EnableToggleControl = ( { children } ) => { const { featureName, getFeatureSettings, setFeatureSettings } = useFeatureSettings(); - const [ showCredentialModal, setShowCredentialModal ] = useState( false ); const status = getFeatureSettings( 'status' ) || '0'; const feature = getFeature( featureName ); /** - * Handle toggle change with credential reuse check. + * Handle toggle change. * * @param {boolean} value Whether the feature should be enabled. */ const handleToggleChange = ( value ) => { - // Enable/disable the feature immediately setFeatureSettings( { status: value ? '1' : '0', } ); - - // Check for reusable credentials in the background (only when enabling) - if ( value && status === '0' ) { - checkForReusableCredentials(); - } - }; - - /** - * Check for reusable credentials in the background. - */ - const checkForReusableCredentials = async () => { - // Check if user has disabled the prompt - const dontAsk = - window.localStorage.getItem( - 'classifai_dont_ask_credential_reuse' - ) === 'true'; - - if ( dontAsk ) { - return; - } - - try { - const reusableCredentials = await apiFetch( { - path: `/classifai/v1/credential-reuse/${ featureName }`, - } ); - - const providers = Object.keys( reusableCredentials ); - - if ( providers.length > 0 ) { - // Show credential reuse modal only if there are reusable credentials - setShowCredentialModal( true ); - } - } catch ( error ) { - // Error handled gracefully - } - }; - - /** - * Handle credentials being reused. - * - */ - const handleCredentialsReused = () => { - // Feature is already enabled, credentials have been copied by the API - // Trigger a settings refresh to show the updated provider in the UI - window.location.reload(); - }; - - /** - * Handle modal close. - */ - const handleModalClose = () => { - setShowCredentialModal( false ); - // Feature is already enabled, no need to change status }; if ( children && 'function' === typeof children ) { @@ -102,26 +44,16 @@ export const EnableToggleControl = ( { children } ) => { ); return ( - <> - - - - - + - + ); }; diff --git a/src/js/settings/components/service-settings/index.js b/src/js/settings/components/service-settings/index.js index f27924cf9..395ab03bd 100644 --- a/src/js/settings/components/service-settings/index.js +++ b/src/js/settings/components/service-settings/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { NavLink, useParams, useNavigate } from 'react-router-dom'; +import { NavLink, useParams } from 'react-router-dom'; /** * WordPress dependencies @@ -28,7 +28,6 @@ import { store as noticesStore } from '@wordpress/notices'; */ import { STORE_NAME } from '../../data/store'; import { isProviderConfigurationNeeded } from '../../utils/utils'; -import { CredentialReuseModal } from '../credential-reuse'; const { features } = window.classifAISettings; /** @@ -57,8 +56,6 @@ const ConfigureProviderNotice = () => ( */ export const ServiceSettings = () => { const [ enabled, setEnabled ] = useState( false ); - const [ showCredentialModal, setShowCredentialModal ] = useState( false ); - const [ currentFeature, setCurrentFeature ] = useState( null ); const { setCurrentService, setIsSaving, setSettings } = useDispatch( STORE_NAME ); const { @@ -72,7 +69,6 @@ export const ServiceSettings = () => { ); const { service } = useParams(); - const navigate = useNavigate(); const isInitialPageLoad = useRef( true ); const { settings, getFeatureSettings } = useSelect( ( select ) => { @@ -92,48 +88,12 @@ export const ServiceSettings = () => { const serviceFeatures = features[ service ] || {}; /** - * Check for reusable credentials in the background. - * - * @param {string} featureName The feature name to check for. - */ - const checkForReusableCredentials = async ( featureName ) => { - // Check if user has disabled the prompt - const dontAsk = - window.localStorage.getItem( - 'classifai_dont_ask_credential_reuse' - ) === 'true'; - - if ( dontAsk ) { - return; - } - - try { - const reusableCredentials = await apiFetch( { - path: `/classifai/v1/credential-reuse/${ featureName }`, - } ); - - const providers = Object.keys( reusableCredentials ); - - if ( providers.length > 0 ) { - // Show credential reuse modal only if there are reusable credentials - setCurrentFeature( featureName ); - setShowCredentialModal( true ); - } - } catch ( error ) { - // Error handled gracefully - } - }; - - /** - * Handle toggle change with credential reuse check. + * Handle toggle change. * * @param {boolean} value Whether the feature should be enabled. * @param {string} featureName The feature name being toggled. */ const handleToggleChange = ( value, featureName ) => { - const currentStatus = getFeatureSettings( 'status', featureName ); - - // Enable/disable the feature immediately setEnabled( value ); wp.data.dispatch( STORE_NAME ).setFeatureSettings( { @@ -141,32 +101,6 @@ export const ServiceSettings = () => { }, featureName ); - - // Check for reusable credentials in the background (only when enabling) - if ( value && currentStatus === '0' ) { - checkForReusableCredentials( featureName ); - } - }; - - /** - * Handle credentials being reused. - */ - const handleCredentialsReused = () => { - // Feature is already enabled, credentials have been copied by the API - // Navigate to the feature settings page and reload to show the configuration - if ( currentFeature ) { - // Navigate to feature settings and reload immediately - navigate( currentFeature ); - window.location.reload(); - } - }; - - /** - * Handle modal close. - */ - const handleModalClose = () => { - setShowCredentialModal( false ); - setCurrentFeature( null ); }; const saveSettings = () => { @@ -295,18 +229,6 @@ export const ServiceSettings = () => { ) ) } - - ); }; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index a9d99e873..553a41e53 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -644,78 +644,3 @@ .classifai-settings__main-title { margin-top: 2.5rem; } - -// Start: Credential Reuse Modal -.classifai-credential-reuse-modal { - .components-modal__content { - min-width: 500px; - } -} - -.classifai-loading { - display: flex; - align-items: center; - gap: 8px; - padding: 16px 0; - color: #757575; - - .components-icon { - animation: spin 1s linear infinite; - } -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -.classifai-provider-selection { - margin: 16px 0; - - .classifai-provider-option { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 8px; - padding: 12px; - border: 1px solid #ddd; - border-radius: 4px; - cursor: pointer; - transition: border-color 0.2s, background-color 0.2s; - - &:hover { - border-color: var(--classifai-admin-theme-color); - background-color: #f9f9f9; - } - - input[type="radio"] { - margin: 0; - } - - &:has(input[type="radio"]:checked) { - border-color: var(--classifai-admin-theme-color); - background-color: #f0f6fc; - } - } - - .classifai-provider-info { - display: flex; - flex-direction: column; - gap: 4px; - - strong { - color: #1d2327; - font-weight: 600; - } - - .classifai-feature-label { - font-size: 13px; - color: #757575; - } - } -} -// End: Credential Reuse Modal diff --git a/tests/cypress/integration/admin/credential-reuse-modal.test.js b/tests/cypress/integration/admin/credential-reuse-modal.test.js deleted file mode 100644 index f312ce72e..000000000 --- a/tests/cypress/integration/admin/credential-reuse-modal.test.js +++ /dev/null @@ -1,203 +0,0 @@ -describe( 'Credential Reuse Modal Tests', () => { - beforeEach( () => { - cy.login(); - // Clear localStorage before each test to ensure clean state - cy.window().then( ( win ) => { - win.localStorage.removeItem( 'classifai_dont_ask_credential_reuse' ); - } ); - } ); - - describe( 'Modal Display Behavior', () => { - it( 'Should display credential reuse modal when enabling a feature without localStorage flag', () => { - // Visit settings page - cy.visitFeatureSettings( 'language_processing/feature_title_generation' ); - - // Enable the credential reuse modal (ensure it's not disabled) - cy.enableCredentialReuseModal(); - - // Mock the API response to simulate available credentials - cy.intercept( 'GET', '**/wp-json/classifai/v1/credential-reuse/feature_title_generation**', { - statusCode: 200, - body: { - openai_chatgpt: { - feature_id: 'feature_excerpt_generation', - feature_label: 'Excerpt Generation', - provider_display_name: 'OpenAI ChatGPT', - credentials: { - authenticated: true - } - } - } - } ).as( 'checkCredentials' ); - - // Disable if already enabled. - cy.disableFeatureIfEnabled(); - - cy.enableFeature( false ); - - // Wait for API call - cy.wait( '@checkCredentials' ); - - // Check modal is visible - cy.get( '.components-modal__header' ).should( 'be.visible' ); - cy.get( '.components-modal__header-heading' ).should( 'contain', 'Reuse Existing Credentials' ); - } ); - - it( 'Should not display modal when localStorage flag is set', () => { - // Set the flag first - cy.disableCredentialReuseModal(); - - // Visit settings page - cy.visitFeatureSettings( 'language_processing/feature_title_generation' ); - - // Mock the API response - cy.intercept( 'GET', '**/wp-json/classifai/v1/credential-reuse/feature_title_generation**', { - statusCode: 200, - body: { - openai_chatgpt: { - feature_id: 'feature_excerpt_generation', - feature_label: 'Excerpt Generation', - provider_display_name: 'OpenAI ChatGPT', - credentials: { - authenticated: true - } - } - } - } ).as( 'checkCredentials' ); - - // Disable if already enabled. - cy.disableFeatureIfEnabled(); - - // Enable the feature - cy.enableFeature( false ); - - // API should not be called when flag is set - cy.get( '.components-modal__header' ).should( 'not.exist' ); - } ); - - it( 'Should not display modal when no compatible credentials are available', () => { - // Visit settings page - cy.visitFeatureSettings( 'language_processing/feature_title_generation' ); - - // Enable the credential reuse modal - cy.enableCredentialReuseModal(); - - // Mock empty response - cy.intercept( 'GET', '**/wp-json/classifai/v1/credential-reuse/feature_title_generation**', { - statusCode: 200, - body: {} - } ).as( 'checkCredentials' ); - - // Disable if already enabled. - cy.disableFeatureIfEnabled(); - - // Enable the feature - cy.enableFeature( false ); - - // Wait for API call - cy.wait( '@checkCredentials' ); - - // Modal should not appear - cy.get( '.components-modal__header' ).should( 'not.exist' ); - } ); - } ); - - describe( 'Modal Interaction', () => { - beforeEach( () => { - // Setup common mocks - cy.intercept( 'GET', '**/wp-json/classifai/v1/credential-reuse/feature_title_generation**', { - statusCode: 200, - body: { - openai_chatgpt: { - feature_id: 'feature_excerpt_generation', - feature_label: 'Excerpt Generation', - provider_display_name: 'OpenAI ChatGPT', - credentials: { - authenticated: true - } - }, - ollama_embeddings: { - feature_id: 'feature_excerpt_generation', - feature_label: 'Excerpt Generation', - provider_display_name: 'Ollama Embeddings', - credentials: { - authenticated: true - } - } - } - } ).as( 'checkCredentials' ); - } ); - - it( 'Should close modal when X button is clicked', () => { - cy.visitFeatureSettings( 'language_processing/feature_title_generation' ); - cy.enableCredentialReuseModal(); - - // Disable if already enabled. - cy.disableFeatureIfEnabled(); - - // Trigger modal - cy.enableFeature( false ); - cy.wait( '@checkCredentials' ); - - // Click X button - cy.get( 'button[aria-label="Close"]' ).click(); - - // Modal should close - cy.get( '.components-modal__header' ).should( 'not.exist' ); - } ); - - it( 'Should save localStorage flag when "Don\'t ask again" is checked', () => { - cy.visitFeatureSettings( 'language_processing/feature_title_generation' ); - cy.enableCredentialReuseModal(); - - // Disable if already enabled. - cy.disableFeatureIfEnabled(); - - // Trigger modal - cy.enableFeature( false ); - cy.wait( '@checkCredentials' ); - - // Check "Don't ask again" checkbox - cy.get( '.components-modal__content' ).within( () => { - cy.get( 'input[type="checkbox"]' ).last().check(); - } ); - - // Click Reuse - cy.get( 'button' ).contains( 'Reuse' ).click(); - - cy.wait( 3000 ); - - // Verify localStorage was set - cy.window().then( ( win ) => { - expect( win.localStorage.getItem( 'classifai_dont_ask_credential_reuse' ) ).to.equal( 'true' ); - } ); - } ); - - it( 'Should maintain feature enabled state after modal interaction', () => { - cy.visitFeatureSettings( 'language_processing/feature_title_generation' ); - cy.enableCredentialReuseModal(); - - cy.intercept( 'POST', '**/wp-json/classifai/v1/credential-reuse/copy**', { - success: true - } ).as( 'applyCredentials' ); - - // Disable if already enabled. - cy.disableFeatureIfEnabled(); - - // Enable feature - cy.enableFeature( false ); - cy.wait( '@checkCredentials' ); - - // Check features are displayed - cy.get( '.components-modal__content' ).within( () => { - cy.get( 'label' ).should( 'contain', 'Excerpt Generation' ); - cy.get( 'label' ).should( 'contain', 'Ollama Embeddings' ); - cy.get( '.classifai-provider-selection label' ).should( 'have.length', 2 ); - } ); - - // As this is a mock response, the creds will not be copied. - cy.get( 'button' ).contains( 'Reuse' ).click(); - cy.get( '.components-modal__header' ).should( 'not.exist' ); - } ); - } ); -} ); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index f2b60e16f..90fb2de35 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -642,10 +642,7 @@ Cypress.Commands.add( 'saveGeneralSettings', () => { /** * Enable Feature. */ -Cypress.Commands.add( 'enableFeature', ( disableCredentialReuseModal = true ) => { - if ( disableCredentialReuseModal ) { - cy.disableCredentialReuseModal(); - } +Cypress.Commands.add( 'enableFeature', () => { cy.get( '.classifai-enable-feature-toggle input[type="checkbox"]' ).check(); } ); @@ -874,26 +871,6 @@ Cypress.Commands.add( 'classicCreateProduct', ( { title, content } ) => { cy.get( '#content' ).type( content ); } ); -/** - * Disable credential reuse modal for all tests. - * This prevents the modal from appearing when enabling features. - */ -Cypress.Commands.add( 'disableCredentialReuseModal', () => { - cy.window().then( ( win ) => { - win.localStorage.setItem( 'classifai_dont_ask_credential_reuse', 'true' ); - } ); -} ); - -/** - * Enable credential reuse modal (for testing the modal itself). - * This allows the modal to appear when enabling features. - */ -Cypress.Commands.add( 'enableCredentialReuseModal', () => { - cy.window().then( ( win ) => { - win.localStorage.removeItem( 'classifai_dont_ask_credential_reuse' ); - } ); -} ); - /** * Disable feature if already enabled. */