@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
689 lines • 29.3 kB
JavaScript
/**
* JSONataProcessor - Handles complex JSON transformations using JSONata
*/
import jsonata from 'jsonata';
export class JSONataProcessor {
expressions = new Map();
// Pre-compiled common patterns for performance
COMMON_PATTERNS = {
// Flag patterns
flagsWithVariables: '$[variable_definitions != {}]',
flagsWithSpecificVariable: (varName) => `$[variable_definitions.${varName} exists]`,
flagVariationCount: '{ "flag_key": key, "variation_count": variations.$count() }',
flagsWithCdnVariable: '$[variable_definitions.cdnVariationSettings exists]',
// Enhanced Flag Environment patterns (Phase 2)
flagsGroupedByEnvironment: `$group(environments.key)~>$keys()`,
flagsEnabledInEnvironment: (envName) => `$[environments[key="${envName}"].enabled = true]`,
flagEnvironmentComplexity: `{
"flag_key": key,
"environments": environments.{
"name": key,
"enabled": enabled,
"variation_count": rollout_rules.$count(),
"traffic_allocation": rollout_rules.percentage_included~>$sum()
}
}`,
flagsWithMultipleEnvironments: '$[environments.$count() > 1]',
flagsEnabledInProduction: '$[environments[key="production"].enabled = true]',
flagsDisabledEverywhere: '$[environments[enabled=true].$count() = 0]',
// Variation patterns
variationsWithCustomValues: 'variations[variable_values != {}]',
variationWeightDistribution: `variations.{
"key": key,
"weight": weight / 100,
"has_custom_code": actions[type = "custom_code"] exists
}`,
variationsByVariable: (varName) => `variations[variable_values.${varName} exists]`,
// Ruleset patterns
rulesetsWithComplexTargeting: '$[rules[audience_conditions.$count() > 1]]',
ruleTrafficAllocation: 'rules.{ "rule_key": key, "traffic": percentage_included / 10000 }',
rulesetsEnabled: '$[enabled = true]',
// Experiment patterns
experimentsWithMultipleAudiences: (limit) => `$[audience_ids.$count() > ${limit}]`,
experimentPageComplexity: `{
"experiment": name,
"page_count": page_ids.$count(),
"has_url_targeting": url_targeting exists
}`,
experimentsByStatus: (status) => `$[status = "${status}"]`,
// Audience patterns
audienceComplexity: `{
"audience": name,
"condition_count": conditions.($type() = "array" ? $count() : 1),
"uses_custom_attributes": conditions.$string().$contains("custom_attribute")
}`,
audiencesWithCustomAttributes: '$[conditions.$string().$contains("custom_attribute")]',
// Cross-entity patterns
entitiesWithArchived: '$[archived = true]',
entitiesByProject: (projectId) => `$[project_id = "${projectId}"]`,
recentlyUpdated: (days) => {
const date = new Date();
date.setDate(date.getDate() - days);
return `$[updated_time > "${date.toISOString()}"]`;
},
// Phase 2: Advanced cross-entity analysis patterns
entityCountsByType: `$group($type)~>$values()~>{"entity_type": $[0], "count": $count()}`,
entitiesWithComplexTargeting: `$[
$exists(audience_conditions) and
($type(audience_conditions) = "array" ? audience_conditions.$count() > 1 : true) or
$exists(url_targeting) or
$exists(page_ids) and page_ids.$count() > 1
]`,
flagExperimentMatrix: `{
"flag_key": key,
"has_experiments": experiments.$count() > 0,
"experiment_types": experiments.type~>$distinct(),
"environments_with_experiments": environments[experiments.$count() > 0].key
}`,
audienceUsageAnalysis: `{
"audience_id": id,
"audience_name": name,
"used_in_experiments": experiments.$count(),
"used_in_flags": flags.$count(),
"condition_complexity": $type(conditions) = "array" ? conditions.$count() : 1,
"last_usage": $max([experiments.updated_time, flags.updated_time])
}`,
// Phase 2: Environment-specific analysis
environmentHealthCheck: `environments.{
"environment": key,
"enabled_flags": $parent[enabled=true].$count(),
"total_flags": $parent.$count(),
"health_score": $parent[enabled=true].$count() / $parent.$count(),
"flags_with_experiments": $parent[experiments.$count() > 0].$count(),
"avg_traffic_allocation": $parent.rollout_rules.percentage_included~>$average()
}`,
// Phase 2: Performance analysis patterns
performanceMetrics: `{
"entity_count": $count(),
"avg_complexity": variations.$count()~>$average(),
"memory_footprint": $string().$length()~>$sum(),
"last_activity": updated_time~>$max(),
"activity_distribution": $group(updated_time~>$substring(0,10))~>$each(function($v, $k) {
{"date": $k, "activity_count": $v.$count()}
})
}`
};
/**
* Process SQL results through JSONata for complex filtering/transformation
*/
async processResults(sqlResults, jsonataExpression, context) {
try {
// Parse data_json fields if present
const parsedResults = this.parseJsonFields(sqlResults);
// Compile expression with caching
let expression = this.expressions.get(jsonataExpression);
if (!expression) {
expression = jsonata(jsonataExpression);
// Register custom functions
this.registerCustomFunctions(expression);
// Cache compiled expression
this.expressions.set(jsonataExpression, expression);
}
// Apply expression to results
const processed = await expression.evaluate(parsedResults, context);
return processed;
}
catch (error) {
throw new Error(`JSONata processing failed: ${error.message}`);
}
}
/**
* Build JSONata expression from enhanced intent
*/
buildExpression(intent) {
const { jsonFilters, transforms, aggregations } = intent;
let expression = '$';
// Add filters
if (jsonFilters && jsonFilters.length > 0) {
const filterExpressions = jsonFilters.map(f => this.buildFilterExpression(f));
expression += `[${filterExpressions.join(' and ')}]`;
}
// Add transformations
if (transforms && transforms.length > 0) {
expression = this.buildTransformExpression(expression, transforms);
}
// Add aggregations
if (aggregations && aggregations.length > 0) {
expression = this.wrapWithAggregations(expression, aggregations);
}
return expression;
}
/**
* Apply a processing pipeline step
*/
async applyProcessingStep(data, step, context) {
switch (step.type) {
case 'filter':
return this.applyFilterStep(data, step, context);
case 'transform':
return this.applyTransformStep(data, step, context);
case 'aggregate':
return this.applyAggregateStep(data, step, context);
case 'sort':
return this.applySortStep(data, step);
case 'limit':
return this.applyLimitStep(data, step);
default:
return data;
}
}
/**
* Get a pre-built expression pattern
*/
getPattern(patternName, ...args) {
const pattern = this.COMMON_PATTERNS[patternName];
if (typeof pattern === 'function') {
return pattern.apply(null, args);
}
return pattern;
}
/**
* Phase 2: Build complex JSONata expressions from natural language intent
*/
buildComplexExpression(intent) {
const { primaryEntity, action, groupBy, filters } = intent;
// Handle environment grouping specifically
if (groupBy && groupBy.includes('environment')) {
return this.buildEnvironmentGroupingExpression(intent);
}
// Handle complexity analysis
if (action === 'analyze' && intent.metrics?.includes('complexity')) {
return this.buildComplexityAnalysisExpression(intent);
}
// Handle cross-entity correlation
if (intent.relatedEntities && intent.relatedEntities.length > 0) {
return this.buildCrossEntityExpression(intent);
}
// Handle traffic analysis
if (intent.metrics?.some(m => m.includes('traffic') || m.includes('allocation'))) {
return this.buildTrafficAnalysisExpression(intent);
}
// Default to basic grouping expression
return this.buildBasicGroupingExpression(intent);
}
buildEnvironmentGroupingExpression(intent) {
const { primaryEntity, filters } = intent;
let expression = '$';
// Add filters first
if (filters && filters.length > 0) {
const filterExprs = filters
.filter((f) => !f.field.includes('environment')) // Handle env filters separately
.map((f) => this.buildQueryFilterExpression(f));
if (filterExprs.length > 0) {
expression += `[${filterExprs.join(' and ')}]`;
}
}
// Use the custom groupByEnvironment function
return `${expression}~>$groupByEnvironment()`;
}
buildComplexityAnalysisExpression(intent) {
return `$.{
"entity": key || name || id,
"type": "${intent.primaryEntity}",
"complexity": $analyzeComplexity($),
"environments": environments ? $environmentHealth(environments) : null,
"traffic": $trafficAnalysis($)
}[complexity.level != "simple"]^(complexity.score)`;
}
buildCrossEntityExpression(intent) {
const { primaryEntity, relatedEntities } = intent;
return `{
"primary_entity": "${primaryEntity}",
"correlation_analysis": $crossEntityCorrelation($, "project_id"),
"entity_breakdown": $group(type || "${primaryEntity}")~>$each(function($v, $k) {
{
"entity_type": $k,
"count": $v.$count(),
"avg_complexity": $v.$analyzeComplexity($).$average(score),
"samples": $v[0..2].{
"id": id || key,
"name": name,
"complexity": $analyzeComplexity($).level
}
}
}),
"activity_timeline": $timeSeriesAnalysis($, "updated_time")
}`;
}
buildTrafficAnalysisExpression(intent) {
return `$.{
"entity": key || name || id,
"traffic_analysis": $trafficAnalysis($),
"environment_health": environments ? $environmentHealth(environments) : null,
"allocation_issues": $trafficAnalysis($).allocation_gaps > 0,
"recommendation": $trafficAnalysis($).is_fully_allocated ? "optimal" : "needs_adjustment"
}[allocation_issues = true or traffic_analysis.total_traffic > 0]^(traffic_analysis.total_traffic)`;
}
buildBasicGroupingExpression(intent) {
const { groupBy, filters } = intent;
let expression = '$';
// Apply filters
if (filters && filters.length > 0) {
const filterExprs = filters.map((f) => this.buildQueryFilterExpression(f));
expression += `[${filterExprs.join(' and ')}]`;
}
// Apply grouping
if (groupBy && groupBy.length > 0) {
const groupField = groupBy[0];
expression = `${expression}~>$group(${groupField})~>$each(function($v, $k) {
{
"${groupField}": $k,
"count": $v.$count(),
"entities": $v[0..4].{
"id": id || key,
"name": name
}
}
})`;
}
return expression;
}
parseJsonFields(results) {
return results.map(row => {
const parsed = { ...row };
// Parse data_json if it's a string
if (parsed.data_json && typeof parsed.data_json === 'string') {
try {
parsed.data_json = JSON.parse(parsed.data_json);
// Flatten data_json properties to top level for easier access
Object.assign(parsed, parsed.data_json);
}
catch (e) {
// Keep original if parse fails
}
}
// Parse other JSON fields
['conditions', 'variables', 'audience_conditions', 'variations', 'metrics'].forEach(field => {
if (parsed[field] && typeof parsed[field] === 'string') {
try {
parsed[field] = JSON.parse(parsed[field]);
}
catch (e) {
// Keep original if parse fails
}
}
});
return parsed;
});
}
registerCustomFunctions(expression) {
// Register utility functions
expression.registerFunction('countIf', (array, predicate) => {
const predicateExpr = jsonata(predicate);
return array.filter(item => predicateExpr.evaluate(item)).length;
});
expression.registerFunction('percentOf', (value, total) => {
return total > 0 ? (value / total) * 100 : 0;
});
expression.registerFunction('hasVariable', (obj, varName) => {
return obj?.variable_definitions?.[varName] !== undefined ||
obj?.variable_values?.[varName] !== undefined;
});
expression.registerFunction('daysSince', (dateStr) => {
const date = new Date(dateStr);
const now = new Date();
const diffTime = Math.abs(now.getTime() - date.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
});
expression.registerFunction('flatten', (obj) => {
const result = {};
function recurse(cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
}
else if (Array.isArray(cur)) {
result[prop] = cur;
}
else {
let isEmpty = true;
for (const p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop + '.' + p : p);
}
if (isEmpty && prop) {
result[prop] = {};
}
}
}
recurse(obj, '');
return result;
});
// Phase 2: Enhanced functions for complex nested queries
expression.registerFunction('groupByEnvironment', (entities) => {
const groups = {};
for (const entity of entities) {
if (entity.environments && Array.isArray(entity.environments)) {
for (const env of entity.environments) {
const envKey = env.key || env.name || 'unknown';
if (!groups[envKey])
groups[envKey] = [];
groups[envKey].push({
...entity,
environment_status: env.enabled ? 'enabled' : 'disabled',
environment_config: env
});
}
}
}
return Object.entries(groups).map(([environment, items]) => ({
environment,
count: items.length,
entities: items
}));
});
expression.registerFunction('analyzeComplexity', (entity) => {
let complexity = 0;
let factors = [];
// Count variations
if (entity.variations && Array.isArray(entity.variations)) {
const varCount = entity.variations.length;
complexity += varCount * 2;
factors.push(`${varCount} variations`);
}
// Count audiences
if (entity.audience_ids && Array.isArray(entity.audience_ids)) {
const audCount = entity.audience_ids.length;
complexity += audCount * 3;
factors.push(`${audCount} audiences`);
}
// Count environments
if (entity.environments && Array.isArray(entity.environments)) {
const envCount = entity.environments.length;
complexity += envCount;
factors.push(`${envCount} environments`);
}
// Check for custom targeting
if (entity.url_targeting || entity.page_ids) {
complexity += 5;
factors.push('custom targeting');
}
return {
score: complexity,
level: complexity < 5 ? 'simple' : complexity < 15 ? 'moderate' : 'complex',
factors
};
});
expression.registerFunction('environmentHealth', (environments) => {
if (!Array.isArray(environments))
return { status: 'unknown' };
const enabled = environments.filter(env => env.enabled).length;
const total = environments.length;
const healthScore = total > 0 ? enabled / total : 0;
return {
enabled_count: enabled,
total_count: total,
health_score: Math.round(healthScore * 100),
status: healthScore > 0.8 ? 'healthy' : healthScore > 0.5 ? 'warning' : 'critical',
environments: environments.map(env => ({
name: env.key || env.name,
enabled: env.enabled,
traffic: env.rollout_rules ?
env.rollout_rules.reduce((sum, rule) => sum + (rule.percentage_included || 0), 0) : 0
}))
};
});
expression.registerFunction('trafficAnalysis', (entity) => {
let totalTraffic = 0;
let distribution = {};
// Analyze variations traffic
if (entity.variations && Array.isArray(entity.variations)) {
entity.variations.forEach((variation) => {
const weight = variation.weight || 0;
totalTraffic += weight;
distribution[variation.key || variation.name] = weight;
});
}
// Analyze rules traffic
if (entity.rules && Array.isArray(entity.rules)) {
entity.rules.forEach((rule) => {
const traffic = rule.percentage_included || 0;
totalTraffic += traffic;
distribution[rule.key || 'rule'] = traffic;
});
}
return {
total_traffic: totalTraffic,
distribution,
is_fully_allocated: totalTraffic >= 10000, // 100% in basis points
allocation_gaps: Math.max(0, 10000 - totalTraffic)
};
});
expression.registerFunction('timeSeriesAnalysis', (entities, timeField) => {
const timeSeries = {};
entities.forEach(entity => {
const timestamp = entity[timeField];
if (timestamp) {
const date = new Date(timestamp).toISOString().split('T')[0]; // YYYY-MM-DD
timeSeries[date] = (timeSeries[date] || 0) + 1;
}
});
const dates = Object.keys(timeSeries).sort();
return dates.map(date => ({
date,
count: timeSeries[date],
cumulative: dates.slice(0, dates.indexOf(date) + 1)
.reduce((sum, d) => sum + timeSeries[d], 0)
}));
});
expression.registerFunction('crossEntityCorrelation', (entities, correlateBy) => {
const correlations = {};
entities.forEach(entity => {
const key = entity[correlateBy];
if (key) {
if (!correlations[key]) {
correlations[key] = {
count: 0,
entities: [],
types: new Set(),
avg_complexity: 0
};
}
correlations[key].count++;
correlations[key].entities.push(entity.id || entity.key);
correlations[key].types.add(entity.type || 'unknown');
// Add complexity if available
const complexity = this.calculateEntityComplexity(entity);
correlations[key].avg_complexity =
(correlations[key].avg_complexity * (correlations[key].count - 1) + complexity) /
correlations[key].count;
}
});
return Object.entries(correlations).map(([key, data]) => ({
[correlateBy]: key,
count: data.count,
entity_types: Array.from(data.types),
avg_complexity: Math.round(data.avg_complexity * 100) / 100,
entities: data.entities.slice(0, 10) // Limit for readability
}));
});
}
calculateEntityComplexity(entity) {
let complexity = 1; // Base complexity
if (entity.variations)
complexity += Array.isArray(entity.variations) ? entity.variations.length : 1;
if (entity.audience_ids)
complexity += Array.isArray(entity.audience_ids) ? entity.audience_ids.length : 1;
if (entity.environments)
complexity += Array.isArray(entity.environments) ? entity.environments.length : 1;
if (entity.rules)
complexity += Array.isArray(entity.rules) ? entity.rules.length : 1;
return complexity;
}
buildFilterExpression(filter) {
const path = filter.path;
switch (filter.operator) {
case 'exists':
return `${path} exists`;
case 'not_exists':
return `not(${path} exists)`;
case 'eq':
return `${path} = ${JSON.stringify(filter.value)}`;
case 'ne':
return `${path} != ${JSON.stringify(filter.value)}`;
case 'gt':
return `${path} > ${filter.value}`;
case 'lt':
return `${path} < ${filter.value}`;
case 'contains':
if (filter.type === 'array') {
return `${JSON.stringify(filter.value)} in ${path}`;
}
else {
return `$contains(${path}, ${JSON.stringify(filter.value)})`;
}
case 'matches':
return `$match(${path}, /${filter.value}/)`;
default:
return 'true';
}
}
// Separate method for QueryFilter (used by AnalyticsEngine)
buildQueryFilterExpression(filter) {
const field = filter.field;
const value = filter.value;
switch (filter.operator) {
case 'eq':
return `${field} = ${JSON.stringify(value)}`;
case 'ne':
return `${field} != ${JSON.stringify(value)}`;
case 'gt':
return `${field} > ${value}`;
case 'lt':
return `${field} < ${value}`;
case 'gte':
return `${field} >= ${value}`;
case 'lte':
return `${field} <= ${value}`;
case 'in':
if (Array.isArray(value)) {
return `${field} in [${value.map(v => JSON.stringify(v)).join(', ')}]`;
}
return `${field} = ${JSON.stringify(value)}`;
case 'not_in':
if (Array.isArray(value)) {
return `not(${field} in [${value.map(v => JSON.stringify(v)).join(', ')}])`;
}
return `${field} != ${JSON.stringify(value)}`;
case 'contains':
return `$contains(${field}, ${JSON.stringify(value)})`;
case 'not_contains':
return `not($contains(${field}, ${JSON.stringify(value)}))`;
case 'exists':
return `${field} exists`;
case 'not_exists':
return `not(${field} exists)`;
case 'array_contains':
return `${JSON.stringify(value)} in ${field}`;
case 'array_length':
return `${field}.$count() = ${value}`;
case 'json_contains':
if (filter.jsonPath) {
return `${field}.${filter.jsonPath} exists`;
}
return `${field} exists`;
default:
return 'true';
}
}
buildTransformExpression(base, transforms) {
let expression = base;
for (const transform of transforms) {
switch (transform.type) {
case 'extract':
const fields = transform.config.fields;
expression = `(${expression}).{ ${fields.map(f => `"${f}": ${f}`).join(', ')} }`;
break;
case 'compute':
const computations = transform.config.computations;
const compStr = Object.entries(computations)
.map(([key, expr]) => `"${key}": ${expr}`)
.join(', ');
expression = `(${expression}).{ *, ${compStr} }`;
break;
case 'flatten':
expression = `(${expression}).$flatten()`;
break;
case 'pivot':
const { rows, columns, values } = transform.config;
expression = `(${expression}).$groupBy(${rows}).$pivot(${columns}, ${values})`;
break;
}
}
return expression;
}
wrapWithAggregations(expression, aggregations) {
const aggMap = {
count: '$count()',
sum: '$sum()',
avg: '$average()',
min: '$min()',
max: '$max()',
distinct: '$distinct()',
percent: '$percentOf($count(), $$.length)'
};
const aggExprs = aggregations
.filter(agg => aggMap[agg])
.map(agg => `"${agg}": ${aggMap[agg]}`);
if (aggExprs.length === 0) {
return expression;
}
return `{ "data": ${expression}, ${aggExprs.join(', ')} }`;
}
async applyFilterStep(data, step, context) {
const filters = step.params;
let expression = '$';
if (filters.length > 0) {
const filterExprs = filters.map(f => this.buildFilterExpression(f));
expression += `[${filterExprs.join(' and ')}]`;
}
return this.processResults(data, expression, context);
}
async applyTransformStep(data, step, context) {
let expression = '$';
switch (step.operation) {
case 'aggregate':
const metrics = step.params.metrics;
const metricExprs = Object.entries(metrics)
.map(([key, expr]) => `"${key}": ${expr}`)
.join(', ');
expression = `{ ${metricExprs} }`;
break;
case 'pivot':
const { rows, columns, values } = step.params;
expression = `$.$groupBy(${rows}).$pivot(${columns}, ${values})`;
break;
default:
return data;
}
return this.processResults(data, expression, context);
}
async applyAggregateStep(data, step, context) {
const aggregations = step.params;
const expression = this.wrapWithAggregations('$', aggregations);
const result = await this.processResults(data, expression, context);
// Aggregations typically return a single result object
return Array.isArray(result) ? result : [result];
}
applySortStep(data, step) {
const orderBy = step.params;
return [...data].sort((a, b) => {
for (const order of orderBy) {
const aVal = this.getNestedValue(a, order.field);
const bVal = this.getNestedValue(b, order.field);
if (aVal < bVal)
return order.direction === 'asc' ? -1 : 1;
if (aVal > bVal)
return order.direction === 'asc' ? 1 : -1;
}
return 0;
});
}
applyLimitStep(data, step) {
const { limit, offset } = step.params;
return data.slice(offset || 0, (offset || 0) + limit);
}
getNestedValue(obj, path) {
return path.split('.').reduce((curr, prop) => curr?.[prop], obj);
}
}
//# sourceMappingURL=JSONataProcessor.js.map