UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

695 lines 28.8 kB
/** * IntelligentFieldMapper - Handles complex cross-table field resolution for the Dynamic JSON Query Engine * * This class solves the fundamental problem of mapping logical fields like "flags.environment" * to actual database schema with proper JOINs and field references. */ import { getLogger } from '../logging/Logger.js'; export class IntelligentFieldMapper { schemaMap; constructor() { this.schemaMap = this.initializeSchemaMap(); } /** * Resolve a logical field reference to actual database structure */ resolveField(fieldPath, primaryEntity, context = {}) { getLogger().debug({ fieldPath, primaryEntity, contextFilters: context.filters?.length || 0 }, 'IntelligentFieldMapper: Resolving field path'); // Handle simple direct column references first if (!fieldPath.includes('.')) { return this.resolveDirectField(fieldPath, primaryEntity); } // Parse complex field path (e.g., "flags.environment", "experiments.variations.traffic") const pathParts = fieldPath.split('.'); const [entityPart, fieldPart, ...nestedParts] = pathParts; // Handle cross-table references if (entityPart !== primaryEntity) { // CRITICAL: Handle common entity reference patterns without explicit context // AI agents often use plural entity names directly without specifying the parent entity // Map of entity references that should be resolved based on context const contextualEntityMappings = { 'variations': (pe, parts) => this.resolveVariationRelation(pe, parts), 'variation': (pe, parts) => this.resolveVariationRelation(pe, parts), 'audiences': (pe, parts) => this.resolveAudienceRelation(pe, parts), 'audience': (pe, parts) => this.resolveAudienceRelation(pe, parts), 'metrics': (pe, parts) => this.resolveMetricsRelation(pe, parts), 'metric': (pe, parts) => this.resolveMetricsRelation(pe, parts), 'pages': (pe, parts) => this.resolvePagesRelation(pe, parts), 'page': (pe, parts) => this.resolvePagesRelation(pe, parts), 'events': (pe, parts) => this.resolveEventsRelation(pe, parts), 'event': (pe, parts) => this.resolveEventsRelation(pe, parts), 'rules': (pe, parts) => this.resolveRulesRelation(pe, parts), 'rule': (pe, parts) => this.resolveRulesRelation(pe, parts), 'environments': (pe, parts) => this.resolveEnvironmentsRelation(pe, parts), 'environment': (pe, parts) => this.resolveEnvironmentsRelation(pe, parts), 'attributes': (pe, parts) => this.resolveAttributesRelation(pe, parts), 'attribute': (pe, parts) => this.resolveAttributesRelation(pe, parts), 'variables': (pe, parts) => this.resolveVariablesRelation(pe, parts), 'variable': (pe, parts) => this.resolveVariablesRelation(pe, parts), 'changes': (pe, parts) => this.resolveChangesRelation(pe, parts), 'whitelist': (pe, parts) => this.resolveWhitelistRelation(pe, parts), 'fields': (pe, parts) => this.resolveFieldsRelation(pe, parts), }; // Check if this is a known entity reference pattern const resolver = contextualEntityMappings[entityPart]; if (resolver) { try { const result = resolver(primaryEntity, [fieldPart, ...nestedParts].filter(Boolean)); if (result) return result; } catch (e) { // If resolver fails, fall through to generic resolution getLogger().debug({ entityPart, primaryEntity, error: e instanceof Error ? e.message : String(e) }, 'IntelligentFieldMapper: Contextual resolver failed, trying generic resolution'); } } return this.resolveCrossTableField(entityPart, fieldPart, primaryEntity, nestedParts); } // Handle nested field within primary entity return this.resolveNestedField(fieldPart, primaryEntity, nestedParts); } /** * Get all required JOINs for a set of field mappings */ getRequiredJoins(mappings) { const joins = new Map(); for (const mapping of mappings) { if (mapping.requiredJoin) { const joinDef = this.getJoinDefinition(mapping.requiredJoin); if (joinDef) { joins.set(mapping.requiredJoin, joinDef); } } } return Array.from(joins.values()); } /** * Resolve direct field on primary table */ resolveDirectField(field, primaryEntity) { const schema = this.schemaMap.get(primaryEntity); if (!schema) { throw new Error(`Unknown entity type: ${primaryEntity}`); } // CRITICAL FIX: Handle special cross-table fields that don't use dot notation // These are fields that logically belong to the entity but are stored in related tables if ((primaryEntity === 'flags' || primaryEntity === 'flag') && field === 'environment') { return { sqlField: 'flag_environments.environment_key', requiredJoin: 'flag_environments', additionalConditions: [] }; } // Apply field name mappings const mappedField = this.applyFieldMappings(field, primaryEntity); if (mappedField) { return { sqlField: `${schema.table}.${mappedField}` }; } // Check if it's a valid column if (schema.columns?.includes(field)) { return { sqlField: `${schema.table}.${field}` }; } // Check if it's a JSON field that needs processing if (schema.jsonColumns?.includes(field)) { return { sqlField: `${schema.table}.${field}`, requiresJsonProcessing: true }; } throw new Error(`Unknown field: ${field} on ${primaryEntity}`); } /** * Resolve cross-table field references (e.g., flags.environment) */ resolveCrossTableField(targetEntity, field, primaryEntity, nestedParts) { getLogger().debug({ targetEntity, field, primaryEntity, nestedParts }, 'IntelligentFieldMapper: Resolving cross-table field'); // Handle the specific case: flags.environment if (primaryEntity === 'flags' && targetEntity === 'flags' && field === 'environment') { return { sqlField: 'flag_environments.environment_key', requiredJoin: 'flag_environments', additionalConditions: [] }; } // Handle experiments.variations if (primaryEntity === 'experiments' && targetEntity === 'experiments' && field === 'variations') { // Web Experimentation: variations are in data_json if (nestedParts.length > 0) { // Handle nested access like experiments.variations.key const nestedField = nestedParts.join('.'); return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: `variations.${nestedField}` }; } // Count variations in JSON array return { sqlField: 'JSON_ARRAY_LENGTH(data_json, "$.variations")', requiresJsonProcessing: true }; } // Handle flags.environments (plural - all environments) if (primaryEntity === 'flags' && targetEntity === 'flags' && field === 'environments') { if (nestedParts.length > 0) { // Handle nested like flags.environments.production.enabled const envName = nestedParts[0]; const envField = nestedParts.slice(1).join('.'); return { sqlField: `flag_environments.${envField || 'enabled'}`, requiredJoin: 'flag_environments', additionalConditions: [`flag_environments.environment_key = '${envName}'`] }; } // Return environment data as JSON return { sqlField: 'flag_environments.data_json', requiredJoin: 'flag_environments', requiresJsonProcessing: true }; } // Handle audience relationships if (field === 'audiences' || field === 'audience') { return this.resolveAudienceRelation(primaryEntity, nestedParts); } // Handle variations relationships if (field === 'variations' || field === 'variation') { return this.resolveVariationRelation(primaryEntity, nestedParts); } // Generic entity-to-entity resolution return this.resolveGenericRelation(targetEntity, field, primaryEntity, nestedParts); } /** * Resolve nested field within primary entity's JSON data */ resolveNestedField(field, primaryEntity, nestedParts) { const schema = this.schemaMap.get(primaryEntity); if (!schema) { throw new Error(`Unknown entity type: ${primaryEntity}`); } // Check if this is a JSON field access if (schema.jsonColumns?.includes('data_json')) { const fullPath = [field, ...nestedParts].join('.'); return { sqlField: `${schema.table}.data_json`, requiresJsonProcessing: true, jsonataPath: `$.${fullPath}` }; } throw new Error(`Cannot resolve nested field: ${field} in ${primaryEntity}`); } /** * Resolve audience-related field references */ resolveAudienceRelation(primaryEntity, nestedParts) { if (primaryEntity === 'experiments') { if (nestedParts.length === 0) { // Count audiences return { sqlField: 'COUNT(DISTINCT audiences.id)', requiredJoin: 'experiment_audiences' }; } // Access audience field const audienceField = nestedParts.join('.'); return { sqlField: `audiences.${audienceField}`, requiredJoin: 'experiment_audiences' }; } throw new Error(`Audience relation not supported for ${primaryEntity}`); } /** * Resolve variation-related field references */ resolveVariationRelation(primaryEntity, nestedParts) { if (primaryEntity === 'flags') { // Feature Experimentation: variations are in separate table if (nestedParts.length === 0) { // Count variations return { sqlField: 'COUNT(DISTINCT variations.key)', requiredJoin: 'variations' }; } // Access variation field const variationField = nestedParts.join('.'); if (variationField === 'traffic_allocation' || variationField === 'weight') { return { sqlField: 'variations.weight', requiredJoin: 'variations' }; } return { sqlField: `variations.${variationField}`, requiredJoin: 'variations' }; } else if (primaryEntity === 'experiments') { // Web Experimentation: variations are in data_json if (nestedParts.length === 0) { // Count variations in JSON array return { sqlField: 'JSON_ARRAY_LENGTH(data_json, "$.variations")', requiresJsonProcessing: true }; } // Access variation field from JSON const variationField = nestedParts.join('.'); return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: `variations.${variationField}` }; } throw new Error(`Variation relation not supported for ${primaryEntity}`); } /** * Resolve metrics-related field references */ resolveMetricsRelation(primaryEntity, nestedParts) { if (primaryEntity === 'experiments' || primaryEntity === 'campaigns') { if (nestedParts.length === 0) { // Count metrics return { sqlField: 'JSON_ARRAY_LENGTH(data_json, "$.metrics")', requiresJsonProcessing: true }; } // Access specific metric field const metricField = nestedParts.join('.'); return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: `metrics.${metricField}` }; } throw new Error(`Metrics relation not supported for ${primaryEntity}`); } /** * Resolve pages-related field references */ resolvePagesRelation(primaryEntity, nestedParts) { if (primaryEntity === 'experiments' || primaryEntity === 'campaigns') { if (nestedParts.length === 0) { // Count pages return { sqlField: 'JSON_ARRAY_LENGTH(data_json, "$.page_ids")', requiresJsonProcessing: true }; } // For specific page access, would need JOIN to pages table throw new Error(`Direct page field access not supported. Use page_ids array instead.`); } throw new Error(`Pages relation not supported for ${primaryEntity}`); } /** * Resolve events-related field references */ resolveEventsRelation(primaryEntity, nestedParts) { if (primaryEntity === 'experiments') { // Events are referenced through metrics if (nestedParts.length === 0) { return { sqlField: 'JSON_ARRAY_LENGTH(data_json, "$.metrics")', requiresJsonProcessing: true }; } // Access event through metrics const eventField = nestedParts.join('.'); return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: `metrics[*].event_id` }; } throw new Error(`Events relation not supported for ${primaryEntity}`); } /** * Resolve rules-related field references */ resolveRulesRelation(primaryEntity, nestedParts) { if (primaryEntity === 'flags') { // Rules are in a separate table if (nestedParts.length === 0) { return { sqlField: 'COUNT(DISTINCT rules.id)', requiredJoin: 'rules' }; } const ruleField = nestedParts.join('.'); return { sqlField: `rules.${ruleField}`, requiredJoin: 'rules' }; } throw new Error(`Rules relation not supported for ${primaryEntity}`); } /** * Resolve environments-related field references */ resolveEnvironmentsRelation(primaryEntity, nestedParts) { if (primaryEntity === 'flags') { if (nestedParts.length === 0) { // Count environments return { sqlField: 'COUNT(DISTINCT flag_environments.environment_key)', requiredJoin: 'flag_environments' }; } // Handle specific environment access like environments.production.enabled if (nestedParts.length >= 2) { const [envName, envField] = nestedParts; return { sqlField: `flag_environments.${envField || 'enabled'}`, requiredJoin: 'flag_environments', additionalConditions: [`flag_environments.environment_key = '${envName}'`] }; } // General environment field access const envField = nestedParts.join('.'); return { sqlField: `flag_environments.${envField}`, requiredJoin: 'flag_environments' }; } else if (primaryEntity === 'experiments') { // Experiments have environments in JSON if (nestedParts.length === 0) { return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: 'environments' }; } const envPath = nestedParts.join('.'); return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: `environments.${envPath}` }; } throw new Error(`Environments relation not supported for ${primaryEntity}`); } /** * Resolve attributes-related field references */ resolveAttributesRelation(primaryEntity, nestedParts) { // Attributes are typically referenced through audience conditions if (primaryEntity === 'audiences') { if (nestedParts.length === 0) { // Can't directly count attributes from conditions JSON throw new Error(`Direct attribute counting not supported. Attributes are embedded in audience conditions.`); } // Attributes are in the conditions JSON return { sqlField: 'audiences.conditions', requiresJsonProcessing: true, jsonataPath: `$[?(@.type == "custom_attribute")]` }; } throw new Error(`Attributes relation not supported for ${primaryEntity}`); } /** * Resolve variables-related field references */ resolveVariablesRelation(primaryEntity, nestedParts) { if (primaryEntity === 'flags') { if (nestedParts.length === 0) { // Count variable definitions return { sqlField: 'JSON_ARRAY_LENGTH(flags.data_json, "$.variable_definitions")', requiresJsonProcessing: true }; } // Access variable definition field const varField = nestedParts.join('.'); return { sqlField: 'flags.data_json', requiresJsonProcessing: true, jsonataPath: `variable_definitions.${varField}` }; } throw new Error(`Variables relation not supported for ${primaryEntity}`); } /** * Resolve changes-related field references (array field in experiments/campaigns) */ resolveChangesRelation(primaryEntity, nestedParts) { if (primaryEntity === 'experiments' || primaryEntity === 'campaigns') { if (nestedParts.length === 0) { return { sqlField: 'JSON_ARRAY_LENGTH(data_json, "$.changes")', requiresJsonProcessing: true }; } const changeField = nestedParts.join('.'); return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: `changes.${changeField}` }; } throw new Error(`Changes relation not supported for ${primaryEntity}`); } /** * Resolve whitelist-related field references */ resolveWhitelistRelation(primaryEntity, nestedParts) { if (primaryEntity === 'experiments') { if (nestedParts.length === 0) { return { sqlField: 'JSON_ARRAY_LENGTH(data_json, "$.whitelist")', requiresJsonProcessing: true }; } const whitelistField = nestedParts.join('.'); return { sqlField: 'data_json', requiresJsonProcessing: true, jsonataPath: `whitelist.${whitelistField}` }; } throw new Error(`Whitelist relation not supported for ${primaryEntity}`); } /** * Resolve fields-related field references (extension fields) */ resolveFieldsRelation(primaryEntity, nestedParts) { // Extensions entity type is not supported in the current analytics engine // This resolver is here for future expansion when extensions are added throw new Error(`Fields relation not supported for ${primaryEntity}. Extensions entity type is not yet implemented.`); } /** * Resolve generic entity relationships */ resolveGenericRelation(targetEntity, field, primaryEntity, nestedParts) { // This would be expanded to handle other cross-table relationships // For now, throw an error for unsupported relations throw new Error(`Cross-table relation not supported: ${targetEntity}.${field} from ${primaryEntity}`); } /** * Get JOIN definition for a required join */ getJoinDefinition(joinName) { const joinDefinitions = { 'flag_environments': { alias: 'flag_environments', table: 'flag_environments', condition: 'flags.project_id = flag_environments.project_id AND flags.key = flag_environments.flag_key', type: 'LEFT' }, 'variations': { alias: 'variations', table: 'variations', condition: 'flags.project_id = variations.project_id AND flags.key = variations.flag_key', type: 'LEFT' }, 'experiment_audiences': { alias: 'audiences', table: 'audiences', condition: 'EXISTS (SELECT 1 FROM json_each(experiments.data_json, "$.audience_ids") WHERE value = audiences.id)', type: 'LEFT' }, 'rules': { alias: 'rules', table: 'rules', condition: 'flags.project_id = rules.project_id AND flags.key = rules.flag_key', type: 'LEFT' }, 'pages': { alias: 'pages', table: 'pages', condition: 'pages.project_id = experiments.project_id AND EXISTS (SELECT 1 FROM json_each(experiments.data_json, "$.page_ids") WHERE value = pages.id)', type: 'LEFT' }, 'events': { alias: 'events', table: 'events', condition: 'events.project_id = experiments.project_id AND EXISTS (SELECT 1 FROM json_each(experiments.data_json, "$.metrics") m, json_each(m.value, "$.event_id") WHERE value = events.id)', type: 'LEFT' } }; return joinDefinitions[joinName] || null; } /** * Apply field name mappings for common aliases */ applyFieldMappings(field, primaryEntity) { const commonMappings = { 'created': 'created_time', 'updated': 'updated_time', 'modified': 'updated_time', 'last_modified': 'updated_time', 'is_archived': 'archived', 'enabled': 'archived', // Inverse logic 'active': 'archived' // Inverse logic }; // Entity-specific mappings (partial mapping for supported entities) const entityMappings = { 'flags': { 'flag_key': 'key', 'flag_name': 'name' }, 'flag': { 'flag_key': 'key', 'flag_name': 'name' }, 'experiments': { 'experiment_id': 'id', 'experiment_name': 'name' }, 'experiment': { 'experiment_id': 'id', 'experiment_name': 'name' }, 'audiences': { 'audience_id': 'id', 'audience_name': 'name' }, 'audience': { 'audience_id': 'id', 'audience_name': 'name' }, 'campaigns': { 'campaign_id': 'id', 'campaign_name': 'name' }, 'campaign': { 'campaign_id': 'id', 'campaign_name': 'name' }, 'pages': { 'page_id': 'id', 'page_name': 'name' }, 'page': { 'page_id': 'id', 'page_name': 'name' }, 'projects': { 'project_name': 'name' }, 'project': { 'project_name': 'name' } }; // Check entity-specific mappings first const entitySpecific = entityMappings[primaryEntity]; if (entitySpecific && entitySpecific[field]) { return entitySpecific[field]; } // Check common mappings return commonMappings[field] || null; } /** * Initialize the schema map with current database structure */ initializeSchemaMap() { const schemaMap = new Map(); // Flags schema schemaMap.set('flags', { table: 'flags', columns: ['project_id', 'key', 'id', 'name', 'description', 'archived', 'created_time', 'updated_time', 'data_json'], jsonColumns: ['data_json'], joins: { environments: { table: 'flag_environments', on: 'flags.project_id = flag_environments.project_id AND flags.key = flag_environments.flag_key', type: 'LEFT' }, variations: { table: 'variations', on: 'flags.project_id = variations.project_id AND flags.key = variations.flag_key', type: 'LEFT' }, rules: { table: 'rules', on: 'flags.project_id = rules.project_id AND flags.key = rules.flag_key', type: 'LEFT' }, rulesets: { table: 'rulesets', on: 'flags.project_id = rulesets.project_id AND flags.key = rulesets.flag_key', type: 'LEFT' } } }); // Experiments schema schemaMap.set('experiments', { table: 'experiments', columns: ['id', 'project_id', 'name', 'description', 'status', 'type', 'flag_key', 'environment', 'archived', 'created_time', 'updated_time', 'data_json'], jsonColumns: ['data_json'], joins: { results: { table: 'experiment_results', on: 'experiments.id = experiment_results.experiment_id', type: 'LEFT' }, campaigns: { table: 'campaigns', on: 'JSON_EXTRACT(experiments.data_json, "$.campaign_id") = campaigns.id', type: 'LEFT' }, audiences: { table: 'audiences', on: 'EXISTS (SELECT 1 FROM json_each(experiments.data_json, "$.audience_ids") WHERE value = audiences.id)', type: 'LEFT' } } }); // Audiences schema schemaMap.set('audiences', { table: 'audiences', columns: ['id', 'project_id', 'name', 'description', 'conditions', 'archived', 'created_time', 'last_modified', 'data_json'], jsonColumns: ['conditions', 'data_json'], joins: { experiments: { table: 'experiments', on: 'EXISTS (SELECT 1 FROM json_each(experiments.data_json, "$.audience_ids") WHERE value = audiences.id)', type: 'LEFT' } } }); // Add other entity schemas as needed... return schemaMap; } } //# sourceMappingURL=IntelligentFieldMapper-INCOMPLETE.js.map