335d587c89
- Salesforce Composite Batch API per describe SObject: le describe sono ora raggruppate in chunk da 25 e inviate come singole POST a /composite/batch, riducendo le chiamate API da N a ceil(N/25); per 200 SObject: da 201 a 9 chiamate. - Discovery entita' REST in parallelo: DiscoverEntitySummariesAsync e DiscoverEntitiesAsync avviate simultaneamente; la lista entita' diventa interattiva subito dopo le summaries, i dettagli completano in background con StateHasChanged() per aggiornare l'UI istantaneamente. - Fix scheduler - preservazione ExternalIdRelationshipsJson e DefaultValuesJson: in DataCoupler.razor.cs entrambi i blocchi di update profilo esistente (riattivazione profilo inattivo e sovrascrittura profilo attivo) omettevano questi campi nella copia, causandone l'azzeramento silenzioso ad ogni re-salvataggio. Ora entrambi i percorsi propagano correttamente i campi JSON. - Fix scheduler - esclusione campi sorgente External ID dal mapping normale: in ScheduledProfileExecutionService.TransformRecordForRest i campi sorgente usati nelle External ID Relationships venivano inclusi anche nel loop di field mapping standard, generando dati duplicati nell'entita' destinazione. Ora il comportamento e' allineato alla UI manuale (TransformRecordToRestEntity). - Aggiornata documentazione: README.md, AGENTS.md, copilot-instructions.md
250 lines
8.7 KiB
C#
250 lines
8.7 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.Components.Forms;
|
|
using Microsoft.JSInterop;
|
|
using Microsoft.Extensions.Logging;
|
|
using CredentialManager.Models;
|
|
using DataConnection.REST.Interfaces;
|
|
using DataConnection.REST.Models;
|
|
using DataConnection.CredentialManagement.Interfaces;
|
|
using DataConnection.Interfaces;
|
|
using Data_Coupler.Services;
|
|
using Data_Coupler.Models;
|
|
|
|
namespace Data_Coupler.Pages;
|
|
|
|
public partial class DataCoupler : ComponentBase
|
|
{
|
|
// ===== PROPRIETÀ REST =====
|
|
|
|
// Credenziali REST
|
|
protected List<RestApiCredential> restApiCredentials = new();
|
|
protected string selectedRestCredential = "";
|
|
|
|
// Stato connessioni REST
|
|
protected bool isConnectingRest = false;
|
|
protected bool isRestConnected = false;
|
|
protected string restErrorMessage = "";
|
|
|
|
// REST discovery
|
|
protected List<RestEntitySummary> restEntities = new();
|
|
protected RestEntitySummary? selectedRestEntity = null;
|
|
protected RestEntityInfo? restEntityDetails = null;
|
|
protected string restSearchTerm = "";
|
|
|
|
// Proprietà di mapping REST
|
|
protected string selectedRestProperty = "";
|
|
|
|
// Servizi REST
|
|
protected IRestMetadataDiscovery? currentRestDiscovery = null;
|
|
protected IRestServiceClient? currentRestClient = null;
|
|
|
|
// ===== METODI REST =====
|
|
|
|
/// <summary>
|
|
/// Carica le credenziali REST API
|
|
/// </summary>
|
|
protected async Task LoadRestCredentials()
|
|
{
|
|
try
|
|
{
|
|
restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nel caricamento delle credenziali REST");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gestisce il cambio di credenziale REST
|
|
/// </summary>
|
|
private void OnRestCredentialChanged(ChangeEventArgs e)
|
|
{
|
|
var newCredential = e.Value?.ToString() ?? "";
|
|
|
|
// Clear the cache if we're switching to a different credential
|
|
if (!string.IsNullOrEmpty(selectedRestCredential) && selectedRestCredential != newCredential)
|
|
{
|
|
ConnectionFactory.ClearRestClientCache(selectedRestCredential);
|
|
Logger.LogInformation("Cleared REST client cache for credential: {CredentialName}", selectedRestCredential);
|
|
}
|
|
|
|
selectedRestCredential = newCredential;
|
|
ResetRestState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resetta lo stato REST
|
|
/// </summary>
|
|
protected void ResetRestState()
|
|
{
|
|
isRestConnected = false;
|
|
restEntities.Clear();
|
|
selectedRestEntity = null;
|
|
restEntityDetails = null;
|
|
restSearchTerm = "";
|
|
restErrorMessage = "";
|
|
currentRestDiscovery = null;
|
|
currentRestClient = null;
|
|
|
|
// Clear mappings when resetting REST state - handled by main class
|
|
// ClearAllMappings();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connette al servizio REST API
|
|
/// </summary>
|
|
protected async Task ConnectToRestApi()
|
|
{
|
|
if (string.IsNullOrEmpty(selectedRestCredential))
|
|
return;
|
|
|
|
isConnectingRest = true;
|
|
restErrorMessage = "";
|
|
|
|
try
|
|
{
|
|
// Trova la credenziale
|
|
var credential = restApiCredentials.FirstOrDefault(c => c.Name == selectedRestCredential);
|
|
if (credential == null)
|
|
{
|
|
restErrorMessage = "Credenziale REST API non trovata";
|
|
return;
|
|
}
|
|
|
|
// Test della connessione
|
|
var (success, message) = await CredentialService.TestRestApiConnectionAsync(credential.Name);
|
|
if (!success)
|
|
{
|
|
restErrorMessage = $"Connessione fallita: {message}";
|
|
return;
|
|
}
|
|
|
|
// Crea i client REST usando il factory con le credenziali complete
|
|
currentRestClient = await ConnectionFactory.CreateRestServiceClientAsync(selectedRestCredential);
|
|
currentRestDiscovery = await ConnectionFactory.CreateRestMetadataDiscoveryAsync(selectedRestCredential);
|
|
|
|
Logger.LogInformation("Iniziando autenticazione per il servizio REST {ServiceType} con credenziale: {CredentialName}", credential.ServiceType, selectedRestCredential);
|
|
|
|
// Autenticazione prima del discovery
|
|
var authResult = await currentRestClient.AuthenticateAsync();
|
|
if (!authResult)
|
|
{
|
|
Logger.LogWarning("Autenticazione fallita per il servizio REST {ServiceType}", credential.ServiceType);
|
|
restErrorMessage = "Autenticazione fallita per il servizio REST";
|
|
return;
|
|
}
|
|
|
|
Logger.LogInformation("Autenticazione completata con successo per il servizio REST {ServiceType}", credential.ServiceType);
|
|
|
|
// Avvia entrambe le discovery in parallelo:
|
|
// - DiscoverEntitySummariesAsync è veloce (1 API call) → sblocca la UI subito
|
|
// - DiscoverEntitiesAsync è pesante (batch describe) → completa in background
|
|
Logger.LogInformation("Avvio discovery parallela: entity summaries + entity details (batch)...");
|
|
|
|
var summariesTask = currentRestDiscovery.DiscoverEntitySummariesAsync();
|
|
var entitiesTask = currentRestDiscovery.DiscoverEntitiesAsync();
|
|
|
|
// Attendi le summaries (veloci) e rendi la UI interattiva immediatamente
|
|
restEntities = await summariesTask;
|
|
isRestConnected = true;
|
|
StateHasChanged();
|
|
Logger.LogInformation("Entity summaries completate: {EntityCount} entità. UI interattiva.", restEntities.Count);
|
|
|
|
// Attendi i dettagli completi (già in esecuzione in parallelo)
|
|
try
|
|
{
|
|
availableRelationshipObjects = await entitiesTask;
|
|
Logger.LogInformation("Entity details (batch) completati: {Count} oggetti disponibili per External ID Relationships.", availableRelationshipObjects.Count);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogWarning(ex, "Impossibile completare il caricamento dei dettagli entità per External ID Relationships");
|
|
availableRelationshipObjects = new List<RestEntityInfo>();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nella connessione al servizio REST {ServiceType}", selectedRestCredential);
|
|
restErrorMessage = $"Errore nella connessione: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
isConnectingRest = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seleziona un'entità REST
|
|
/// </summary>
|
|
protected async Task SelectRestEntity(RestEntitySummary entity)
|
|
{
|
|
selectedRestEntity = entity;
|
|
|
|
// Clear mappings when changing entity - handled by main class
|
|
// ClearAllMappings();
|
|
|
|
try
|
|
{
|
|
if (currentRestDiscovery != null)
|
|
{
|
|
// Discovery dei dettagli dell'entità
|
|
restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name);
|
|
}
|
|
else
|
|
{
|
|
restErrorMessage = "Servizio di discovery REST non disponibile";
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name);
|
|
restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filtra le entità REST in base al termine di ricerca
|
|
/// </summary>
|
|
private IEnumerable<RestEntitySummary> GetFilteredRestEntities()
|
|
{
|
|
if (string.IsNullOrEmpty(restSearchTerm))
|
|
return restEntities;
|
|
|
|
return restEntities.Where(entity =>
|
|
entity.Name.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase) ||
|
|
(!string.IsNullOrEmpty(entity.Label) && entity.Label.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applica il filtro alle entità REST
|
|
/// </summary>
|
|
private async Task FilterRestEntities(ChangeEventArgs e)
|
|
{
|
|
restSearchTerm = e.Value?.ToString() ?? "";
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pulisce la ricerca delle entità REST
|
|
/// </summary>
|
|
private async Task ClearRestSearch()
|
|
{
|
|
restSearchTerm = "";
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seleziona una proprietà REST per il mapping
|
|
/// </summary>
|
|
protected void SelectRestProperty(string propertyName)
|
|
{
|
|
selectedRestProperty = propertyName;
|
|
}
|
|
}
|