@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
610 lines (608 loc) • 23.4 kB
JavaScript
/**
* AnalyticalFunctions - Pre-built query templates for common analytics tasks
*/
import { TEMPLATE_CATEGORIES } from './constants.js';
export class AnalyticalFunctions {
templates = new Map();
constructor() {
this.initializeTemplates();
}
/**
* Get all available templates
*/
getTemplates() {
return Array.from(this.templates.values());
}
/**
* Get templates by category
*/
getTemplatesByCategory(category) {
return this.getTemplates().filter(t => t.category === category);
}
/**
* Get a specific template
*/
getTemplate(id) {
return this.templates.get(id);
}
/**
* Apply a template with parameters
*/
applyTemplate(templateId, params) {
const template = this.templates.get(templateId);
if (!template) {
throw new Error(`Template not found: ${templateId}`);
}
// Validate parameters
this.validateTemplateParams(template, params);
// Build intent from template
return this.buildIntentFromTemplate(template, params);
}
/**
* Register a custom template
*/
registerTemplate(template) {
this.templates.set(template.id, template);
}
initializeTemplates() {
// Variable Analysis Templates
this.templates.set('variable_analysis', {
id: 'variable_analysis',
name: 'Flag Variable Analysis',
description: 'Analyze flags that use a specific variable',
category: TEMPLATE_CATEGORIES.VARIABLE_ANALYSIS,
parameters: [
{
name: 'variable_name',
type: 'string',
required: true,
description: 'Name of the variable to analyze (e.g., cdnVariationSettings)'
},
{
name: 'include_empty',
type: 'boolean',
required: false,
default: false,
description: 'Include flags where the variable exists but has no value'
},
{
name: 'group_by',
type: 'array',
required: false,
default: ['environment'],
description: 'Fields to group results by'
}
],
baseQuery: `
SELECT
f.key as flag_key,
f.name as flag_name,
f.project_id,
fe.environment_key,
COUNT(DISTINCT v.key) as variation_count,
COUNT(DISTINCT JSON_EXTRACT(v.variables, '$.{variable_name}')) as variations_with_variable
FROM flags f
LEFT JOIN flag_environments fe ON f.project_id = fe.project_id AND f.key = fe.flag_key
LEFT JOIN variations v ON f.project_id = v.project_id AND f.key = v.flag_key
WHERE JSON_EXTRACT(f.data_json, '$.variable_definitions.{variable_name}') IS NOT NULL
GROUP BY f.key, f.name, f.project_id, fe.environment_key
`,
jsonataExpression: `
$[variable_definitions.{variable_name} exists].{
"flag": key,
"name": name,
"environment": environment,
"variable_type": variable_definitions.{variable_name}.type,
"default_value": variable_definitions.{variable_name}.default_value,
"variations_using": variations[variable_values.{variable_name} exists].$count(),
"variations_total": variations.$count()
}
`,
examples: [
'Find all flags using cdnVariationSettings variable',
'Show flags with payment_enabled variable grouped by project'
]
});
this.templates.set('variable_usage_summary', {
id: 'variable_usage_summary',
name: 'Variable Usage Summary',
description: 'Summary of all variables used across flags',
category: TEMPLATE_CATEGORIES.VARIABLE_ANALYSIS,
parameters: [
{
name: 'project_id',
type: 'string',
required: false,
description: 'Filter by specific project'
}
],
baseQuery: `
SELECT
JSON_EXTRACT(value.value, '$.key') as variable_name,
JSON_EXTRACT(value.value, '$.type') as variable_type,
COUNT(DISTINCT f.key) as flag_count
FROM flags f,
json_each(f.data_json, '$.variable_definitions') as value
WHERE 1=1
GROUP BY variable_name, variable_type
ORDER BY flag_count DESC
`,
jsonataExpression: `
$**.variable_definitions.$spread().(
$keys := $keys($);
$keys.{
"variable": $,
"type": $lookup($parent, $).type,
"flags_using": $count($parent.^[variable_definitions.$exists($)])
}
)
`
});
// Complexity Analysis Templates
this.templates.set('flag_complexity', {
id: 'flag_complexity',
name: 'Flag Complexity Analysis',
description: 'Identify complex flags based on variations, rules, and variables',
category: TEMPLATE_CATEGORIES.COMPLEXITY_ANALYSIS,
parameters: [
{
name: 'min_complexity_score',
type: 'number',
required: false,
default: 50,
description: 'Minimum complexity score to include'
}
],
baseQuery: `
SELECT
f.key,
f.name,
f.project_id,
COUNT(DISTINCT v.key) as variation_count,
COUNT(DISTINCT r.id) as rule_count,
JSON_ARRAY_LENGTH(f.data_json, '$.variable_definitions') as variable_count,
COUNT(DISTINCT fe.environment_key) as environment_count,
(COUNT(DISTINCT v.key) * 10 +
COUNT(DISTINCT r.id) * 15 +
JSON_ARRAY_LENGTH(f.data_json, '$.variable_definitions') * 5 +
COUNT(DISTINCT fe.environment_key) * 5) as complexity_score
FROM flags f
LEFT JOIN variations v ON f.project_id = v.project_id AND f.key = v.flag_key
LEFT JOIN rules r ON f.project_id = r.project_id AND f.key = r.flag_key
LEFT JOIN flag_environments fe ON f.project_id = fe.project_id AND f.key = fe.flag_key
WHERE f.archived = 0
GROUP BY f.key, f.name, f.project_id
HAVING complexity_score >= ?
ORDER BY complexity_score DESC
`,
jsonataExpression: `
$.{
"flag": key,
"name": name,
"complexity_score": (
variations.$count() * 10 +
rules.$count() * 15 +
$keys(variable_definitions).$count() * 5 +
environments.$count() * 5
),
"breakdown": {
"variations": variations.$count(),
"rules": rules.$count(),
"variables": $keys(variable_definitions).$count(),
"environments": environments.$count()
}
}[$$.complexity_score >= {min_complexity_score}]
`
});
this.templates.set('audience_complexity', {
id: 'audience_complexity',
name: 'Audience Complexity Analysis',
description: 'Analyze audience targeting complexity',
category: TEMPLATE_CATEGORIES.COMPLEXITY_ANALYSIS,
parameters: [],
baseQuery: `
SELECT
a.id,
a.name,
a.project_id,
JSON_ARRAY_LENGTH(a.conditions) as condition_count,
COUNT(DISTINCT e.id) as experiment_count,
CASE
WHEN a.conditions LIKE '%custom_attribute%' THEN 1
ELSE 0
END as uses_custom_attributes
FROM audiences a
LEFT JOIN experiments e ON
EXISTS (SELECT 1 FROM json_each(e.data_json, '$.audience_ids')
WHERE value = a.id)
WHERE a.archived = 0
GROUP BY a.id, a.name, a.project_id, a.conditions
ORDER BY condition_count DESC
`,
jsonataExpression: `
$.{
"audience": name,
"id": id,
"condition_complexity": conditions.(
$type($) = "array" ? $count($) :
$type($) = "object" ? $keys($).$count() : 1
),
"uses_custom": $contains($string(conditions), "custom_attribute"),
"experiment_usage": experiments.$count()
}
`
});
// Performance Trends Templates
this.templates.set('experiment_performance_trends', {
id: 'experiment_performance_trends',
name: 'Experiment Performance Trends',
description: 'Analyze experiment performance over time',
category: TEMPLATE_CATEGORIES.PERFORMANCE_TRENDS,
parameters: [
{
name: 'time_range',
type: 'object',
required: false,
default: { value: 30, unit: 'days' },
description: 'Time range for analysis'
},
{
name: 'status',
type: 'string',
required: false,
validation: { enum: ['running', 'paused', 'not_started', 'concluded'] }
}
],
baseQuery: `
SELECT
e.id,
e.name,
e.status,
e.type,
e.created_time,
e.updated_time,
er.confidence_level,
er.total_count as visitor_count,
DATE(e.created_time) as start_date,
JULIANDAY('now') - JULIANDAY(e.created_time) as days_running
FROM experiments e
LEFT JOIN experiment_results er ON e.id = er.experiment_id
WHERE e.created_time >= datetime('now', '-{time_value} {time_unit}')
AND e.archived = 0
ORDER BY e.created_time DESC
`,
jsonataExpression: `
$[created_time >= $now() - {time_range}].{
"experiment": name,
"status": status,
"days_running": $daysSince(created_time),
"performance": {
"confidence": confidence_level,
"visitors": visitor_count
}
}
`
});
// Audience Usage Templates
this.templates.set('audience_usage_analysis', {
id: 'audience_usage_analysis',
name: 'Audience Usage Analysis',
description: 'Analyze how audiences are used across experiments',
category: TEMPLATE_CATEGORIES.AUDIENCE_USAGE,
parameters: [
{
name: 'min_usage',
type: 'number',
required: false,
default: 0,
description: 'Minimum number of experiments using the audience'
}
],
baseQuery: `
SELECT
a.id,
a.name,
a.project_id,
COUNT(DISTINCT e.id) as experiment_count,
GROUP_CONCAT(DISTINCT e.status) as experiment_statuses,
MAX(e.updated_time) as last_used
FROM audiences a
LEFT JOIN experiments e ON
EXISTS (SELECT 1 FROM json_each(e.data_json, '$.audience_ids')
WHERE value = a.id)
WHERE a.archived = 0
GROUP BY a.id, a.name, a.project_id
HAVING experiment_count >= ?
ORDER BY experiment_count DESC
`,
jsonataExpression: `
audiences.{
"audience": name,
"id": id,
"usage": {
"experiment_count": experiments.$count(),
"active_experiments": experiments[status = "running"].$count(),
"last_used": experiments.updated_time.$max()
},
"recommendations": experiments.$count() = 0 ?
["Consider archiving unused audience"] :
experiments.$count() > 10 ?
["High usage audience - ensure conditions are optimized"] : []
}
`
});
this.templates.set('audience_overlap', {
id: 'audience_overlap',
name: 'Audience Overlap Analysis',
description: 'Find experiments using multiple audiences',
category: TEMPLATE_CATEGORIES.AUDIENCE_USAGE,
parameters: [],
baseQuery: `
SELECT
e.id,
e.name,
e.status,
JSON_ARRAY_LENGTH(e.data_json, '$.audience_ids') as audience_count,
e.data_json
FROM experiments e
WHERE JSON_ARRAY_LENGTH(e.data_json, '$.audience_ids') > 1
AND e.archived = 0
ORDER BY audience_count DESC
`,
jsonataExpression: `
experiments[audience_ids.$count() > 1].{
"experiment": name,
"audience_count": audience_ids.$count(),
"audiences": audience_ids.(
$lookup($$.audiences, $).name
),
"complexity_warning": audience_ids.$count() > 3 ?
"High audience complexity may reduce reach" : null
}
`
});
// Change Tracking Templates
this.templates.set('recent_changes', {
id: 'recent_changes',
name: 'Recent Changes',
description: 'Track recent changes across all entity types',
category: TEMPLATE_CATEGORIES.CHANGE_TRACKING,
parameters: [
{
name: 'days_back',
type: 'number',
required: false,
default: 7,
description: 'Number of days to look back'
},
{
name: 'entity_type',
type: 'string',
required: false,
description: 'Filter by entity type'
}
],
baseQuery: `
SELECT
ch.entity_type,
ch.entity_id,
ch.entity_name,
ch.action,
ch.timestamp,
ch.changed_by,
ch.change_summary
FROM change_history ch
WHERE ch.timestamp >= datetime('now', '-{days_back} days')
AND ch.archived = 0
ORDER BY ch.timestamp DESC
LIMIT 100
`,
jsonataExpression: `
change_history[timestamp >= $now() - {days_back} * 86400000].{
"entity": entity_type & ": " & entity_name,
"action": action,
"when": timestamp,
"who": changed_by,
"summary": change_summary
}
`
});
// Cross-Entity Templates
this.templates.set('entity_relationships', {
id: 'entity_relationships',
name: 'Entity Relationship Analysis',
description: 'Analyze relationships between different entity types',
category: TEMPLATE_CATEGORIES.CROSS_ENTITY,
parameters: [
{
name: 'entity_id',
type: 'string',
required: true,
description: 'ID of the entity to analyze relationships for'
},
{
name: 'entity_type',
type: 'string',
required: true,
validation: { enum: ['flag', 'experiment', 'audience'] }
}
],
baseQuery: `
-- This is a complex query that varies based on entity_type
-- The actual query is built dynamically
SELECT * FROM flags WHERE key = ?
`,
jsonataExpression: `
$.{
"entity": name,
"type": "{entity_type}",
"relationships": {
"experiments": experiments.$count(),
"audiences": audiences.$count(),
"variations": variations.$count(),
"rules": rules.$count()
}
}
`
});
this.templates.set('unused_entities', {
id: 'unused_entities',
name: 'Unused Entities Report',
description: 'Find entities that are not being used',
category: TEMPLATE_CATEGORIES.CROSS_ENTITY,
parameters: [],
baseQuery: `
-- Union query to find all unused entities
SELECT 'audience' as entity_type, id, name, project_id
FROM audiences a
WHERE NOT EXISTS (
SELECT 1 FROM experiments e
WHERE EXISTS (SELECT 1 FROM json_each(e.data_json, '$.audience_ids')
WHERE value = a.id)
) AND a.archived = 0
UNION ALL
SELECT 'event' as entity_type, id, name, project_id
FROM events ev
WHERE NOT EXISTS (
SELECT 1 FROM experiments e
WHERE EXISTS (SELECT 1 FROM json_each(e.data_json, '$.metrics')
WHERE JSON_EXTRACT(value, '$.event_id') = ev.id)
) AND ev.archived = 0
`,
jsonataExpression: `
(
audiences[experiments.$count() = 0].{"type": "audience", "name": name, "id": id} ~>
$append(events[experiments.$count() = 0].{"type": "event", "name": name, "id": id})
)
`
});
}
validateTemplateParams(template, params) {
for (const param of template.parameters) {
const value = params[param.name];
// Check required
if (param.required && (value === undefined || value === null)) {
throw new Error(`Required parameter missing: ${param.name}`);
}
// Check type
if (value !== undefined && value !== null) {
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (actualType !== param.type) {
throw new Error(`Parameter ${param.name} must be of type ${param.type}`);
}
// Check validation rules
if (param.validation) {
if (param.validation.enum && !param.validation.enum.includes(value)) {
throw new Error(`Parameter ${param.name} must be one of: ${param.validation.enum.join(', ')}`);
}
if (param.validation.min !== undefined && value < param.validation.min) {
throw new Error(`Parameter ${param.name} must be at least ${param.validation.min}`);
}
if (param.validation.max !== undefined && value > param.validation.max) {
throw new Error(`Parameter ${param.name} must be at most ${param.validation.max}`);
}
if (param.validation.pattern) {
const regex = new RegExp(param.validation.pattern);
if (!regex.test(value)) {
throw new Error(`Parameter ${param.name} does not match required pattern`);
}
}
}
}
}
}
buildIntentFromTemplate(template, params) {
// Apply defaults
const finalParams = {};
for (const param of template.parameters) {
finalParams[param.name] = params[param.name] ?? param.default;
}
// Substitute parameters in queries
const substitutedQuery = this.substituteParams(template.baseQuery, finalParams);
const substitutedJsonata = template.jsonataExpression ?
this.substituteParams(template.jsonataExpression, finalParams) : undefined;
// Build enhanced intent
const intent = {
action: 'analyze',
primaryEntity: this.inferEntityFromQuery(template.baseQuery),
filters: [],
aggregations: this.inferAggregations(template.baseQuery),
requiresJsonProcessing: !!template.jsonataExpression,
jsonataExpression: substitutedJsonata,
// Apply default limit from environment variable
limit: parseInt(process.env.ANALYTICS_DEFAULT_PAGE_SIZE || '10')
};
// Add filters from parameters
if (finalParams.entity_type) {
intent.filters.push({
field: 'entity_type',
operator: 'eq',
value: finalParams.entity_type
});
}
if (finalParams.project_id) {
intent.filters.push({
field: 'project_id',
operator: 'eq',
value: finalParams.project_id
});
}
return intent;
}
substituteParams(query, params) {
let result = query;
for (const [key, value] of Object.entries(params)) {
// Handle different parameter types
if (typeof value === 'object' && value.value && value.unit) {
// Time range parameters
result = result
.replace(new RegExp(`{${key}}`, 'g'), `${value.value} ${value.unit}`)
.replace(new RegExp(`{time_value}`, 'g'), value.value)
.replace(new RegExp(`{time_unit}`, 'g'), value.unit);
}
else {
// Simple substitution
result = result.replace(new RegExp(`{${key}}`, 'g'), String(value));
}
}
return result;
}
inferEntityFromQuery(query) {
const upperQuery = query.toUpperCase();
if (upperQuery.includes('FROM FLAGS'))
return 'flags';
if (upperQuery.includes('FROM EXPERIMENTS'))
return 'experiments';
if (upperQuery.includes('FROM AUDIENCES'))
return 'audiences';
if (upperQuery.includes('FROM VARIATIONS'))
return 'variations';
if (upperQuery.includes('FROM RULES'))
return 'rules';
if (upperQuery.includes('FROM EVENTS'))
return 'events';
if (upperQuery.includes('FROM ATTRIBUTES'))
return 'attributes';
if (upperQuery.includes('FROM CHANGE_HISTORY'))
return 'changes';
return 'flags'; // default
}
inferAggregations(query) {
const aggregations = [];
const upperQuery = query.toUpperCase();
if (upperQuery.includes('COUNT('))
aggregations.push('count');
if (upperQuery.includes('SUM('))
aggregations.push('sum');
if (upperQuery.includes('AVG(') || upperQuery.includes('AVERAGE('))
aggregations.push('avg');
if (upperQuery.includes('MIN('))
aggregations.push('min');
if (upperQuery.includes('MAX('))
aggregations.push('max');
if (upperQuery.includes('DISTINCT'))
aggregations.push('distinct');
return aggregations;
}
}
//# sourceMappingURL=AnalyticalFunctions.js.map