optivise
Version:
Optivise - The Ultimate Optimizely Development Assistant with AI-powered features, zero-config setup, and comprehensive development support
395 lines (372 loc) • 13.7 kB
JavaScript
/**
* Documentation Service
* Fetches live Optimizely documentation with intelligent caching
*/
export class DocumentationService {
logger;
cache = new Map();
isInitialized = false;
cacheTTL = 24 * 60 * 60 * 1000; // 24 hours
// Optimizely documentation endpoints
endpoints = [
{
name: 'Configured Commerce Documentation',
url: 'https://docs.optimizely.com/configured-commerce/',
type: 'guide',
products: ['configured-commerce']
},
{
name: 'CMS Documentation',
url: 'https://docs.optimizely.com/content-management-system/',
type: 'guide',
products: ['cms-paas', 'cms-saas']
},
{
name: 'Experimentation Documentation',
url: 'https://docs.optimizely.com/experimentation/',
type: 'guide',
products: ['web-experimentation', 'feature-experimentation']
},
{
name: 'DXP Documentation',
url: 'https://docs.optimizely.com/digital-experience-platform/',
type: 'guide',
products: ['dxp']
},
{
name: 'Data Platform Documentation',
url: 'https://docs.optimizely.com/data-platform/',
type: 'reference',
products: ['data-platform']
}
];
constructor(logger) {
this.logger = logger;
}
async initialize() {
if (this.isInitialized) {
return;
}
this.logger.debug('Initializing Documentation Service');
// Load cached documentation if available
await this.loadCache();
this.isInitialized = true;
this.logger.info('Documentation Service initialized', {
endpoints: this.endpoints.length,
cachedItems: this.cache.size
});
}
async fetchDocumentation(products) {
const startTime = Date.now();
try {
this.logger.debug('Fetching documentation', { products });
const results = [];
const relevantEndpoints = this.getRelevantEndpoints(products);
for (const endpoint of relevantEndpoints.slice(0, 3)) { // Limit for performance
try {
const content = await this.fetchEndpointContent(endpoint);
if (content) {
results.push(content);
}
}
catch (error) {
this.logger.warn(`Failed to fetch from ${endpoint.name}`, {
url: endpoint.url,
error: error instanceof Error ? error.message : 'Unknown error'
});
}
}
this.logger.info('Documentation fetch completed', {
requestedProducts: products,
endpointsChecked: relevantEndpoints.length,
contentRetrieved: results.length,
processingTime: Date.now() - startTime
});
return results;
}
catch (error) {
this.logger.error('Documentation fetch failed', error, {
products,
processingTime: Date.now() - startTime
});
return [];
}
}
async searchDocumentation(query, products) {
const startTime = Date.now();
try {
this.logger.debug('Searching documentation', { query, products });
const results = [];
const documentation = await this.fetchDocumentation(products);
for (const doc of documentation) {
const matches = this.searchInContent(query, doc);
results.push(...matches);
}
// Sort by relevance
results.sort((a, b) => b.relevance - a.relevance);
this.logger.info('Documentation search completed', {
query,
products,
totalResults: results.length,
processingTime: Date.now() - startTime
});
return results.slice(0, 10); // Return top 10 results
}
catch (error) {
this.logger.error('Documentation search failed', error, {
query,
products,
processingTime: Date.now() - startTime
});
return [];
}
}
getRelevantEndpoints(products) {
return this.endpoints.filter(endpoint => endpoint.products.some(product => products.includes(product)));
}
async fetchEndpointContent(endpoint) {
const cacheKey = `doc_${endpoint.name}`;
// Check cache first
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached)) {
this.logger.debug('Using cached documentation', { endpoint: endpoint.name });
return cached.data;
}
try {
// In a real implementation, you would fetch from the actual URL
// For Phase 2, we'll simulate with structured content
const simulatedContent = this.generateSimulatedContent(endpoint);
// Cache the content
this.cache.set(cacheKey, {
key: cacheKey,
data: simulatedContent,
timestamp: new Date(),
ttl: this.cacheTTL,
accessCount: 1,
lastAccessed: new Date()
});
return simulatedContent;
}
catch (error) {
this.logger.warn(`Failed to fetch ${endpoint.name}`, {
error: error instanceof Error ? error.message : 'Unknown error'
});
// Return cached content even if expired
if (cached) {
this.logger.debug('Using expired cached content as fallback');
return cached.data;
}
return null;
}
}
generateSimulatedContent(endpoint) {
// This simulates documentation content for Phase 2
// In Phase 3, this would be replaced with actual web scraping/API calls
const productName = endpoint.products[0];
if (!productName) {
throw new Error('No product specified for endpoint');
}
const baseContent = this.getProductSpecificContent(productName);
return {
source: 'optimizely-docs',
content: baseContent,
title: endpoint.name,
url: endpoint.url,
lastUpdated: new Date(),
relevanceScore: 0.9,
products: endpoint.products,
cacheKey: `doc_${endpoint.name}`,
ttl: this.cacheTTL
};
}
getProductSpecificContent(product) {
const contentMap = {
'configured-commerce': `
# Configured Commerce Development Guide
## Handler Chain Patterns
Configured Commerce uses a handler chain pattern for extending functionality:
\`\`\`csharp
public class CustomHandler : HandlerBase<CustomParameter, CustomResult>
{
public override int Order => 100;
public override CustomResult Execute(IUnitOfWork unitOfWork, CustomParameter parameter, CustomResult result)
{
// Implementation logic
return NextHandler.Execute(unitOfWork, parameter, result);
}
}
\`\`\`
## Best Practices
- Always call NextHandler.Execute() to maintain the chain
- Use dependency injection for services
- Implement proper error handling and logging
- Follow naming conventions for handlers
## Common Extension Points
- Product pricing calculations
- Cart manipulation
- Order processing workflows
- User authentication flows
`,
'cms-paas': `
# CMS Development Guide
## Content Types
Create custom content types with proper inheritance:
\`\`\`csharp
[ContentType(GUID = "12345678-1234-1234-1234-123456789012")]
public class CustomBlock : BlockData
{
[Display(GroupName = SystemTabNames.Content, Order = 10)]
public virtual string Title { get; set; }
[Display(GroupName = SystemTabNames.Content, Order = 20)]
public virtual XhtmlString Content { get; set; }
}
\`\`\`
## MVC Implementation
Follow proper MVC patterns for controllers and views.
## Caching Strategies
Implement appropriate caching for performance optimization.
`,
'web-experimentation': `
# Web Experimentation SDK Guide
## SDK Initialization
Initialize the Optimizely SDK in your application:
\`\`\`javascript
import optimizely from '@optimizely/optimizely-sdk';
const optimizelyClient = optimizely.createInstance({
datafile: datafile,
logger: optimizely.logging.createLogger()
});
\`\`\`
## Running Experiments
Execute experiments and track events:
\`\`\`javascript
const userId = 'user123';
const variation = optimizelyClient.activate('experiment_key', userId);
if (variation === 'treatment') {
// Show treatment experience
}
// Track conversion events
optimizelyClient.track('conversion_event', userId);
\`\`\`
## Best Practices
- Always check if variation is null
- Implement proper error handling
- Use meaningful user IDs
- Track relevant conversion events
`,
'cms-saas': `
# CMS SaaS Development Guide
## Content Delivery API
Access content through the Content Delivery API:
\`\`\`javascript
import { ContentDeliveryAPI } from '@optimizely/cms-api';
const api = new ContentDeliveryAPI({
baseUrl: 'https://your-app.optimizely.com',
accessToken: 'your-access-token'
});
const content = await api.getContent('/page-url');
\`\`\`
## GraphQL Integration
Leverage Optimizely Graph for flexible content queries.
`,
// Default content for other products
'commerce-connect': 'Commerce Connect integration patterns and best practices.',
'cmp': 'Content Marketing Platform workflows and automation.',
'dxp': 'Digital Experience Platform personalization and visitor groups.',
'feature-experimentation': 'Feature flag management and rollout strategies.',
'data-platform': 'Data collection, analysis, and reporting capabilities.',
'connect-platform': 'Integration patterns and webhook management.',
'recommendations': 'Product recommendation algorithms and implementation.'
};
return contentMap[product] || `Documentation for ${product} development.`;
}
extractSections(content) {
const sections = [];
const lines = content.split('\n');
for (const line of lines) {
if (line.startsWith('##')) {
sections.push(line.replace('##', '').trim());
}
}
return sections;
}
searchInContent(query, doc) {
const results = [];
const lowerQuery = query.toLowerCase();
const content = doc.content.toLowerCase();
// Simple text search with relevance scoring
if (content.includes(lowerQuery)) {
const relevance = this.calculateRelevance(query, doc.content);
results.push({
title: doc.title,
content: this.extractSnippet(doc.content, query),
url: doc.url,
relevance,
product: doc.products[0] || 'configured-commerce',
type: 'documentation',
lastModified: doc.lastUpdated
});
}
return results;
}
calculateRelevance(query, content) {
const lowerQuery = query.toLowerCase();
const lowerContent = content.toLowerCase();
// Count matches
const matches = (lowerContent.match(new RegExp(lowerQuery, 'g')) || []).length;
const words = content.split(/\s+/).length;
// Calculate relevance score
return Math.min(matches / words * 100, 1.0);
}
extractSnippet(content, query) {
const lowerContent = content.toLowerCase();
const lowerQuery = query.toLowerCase();
const index = lowerContent.indexOf(lowerQuery);
if (index === -1)
return content.substring(0, 200) + '...';
const start = Math.max(0, index - 100);
const end = Math.min(content.length, index + query.length + 100);
return content.substring(start, end) + '...';
}
async loadCache() {
// In a real implementation, this would load from persistent storage
// For now, we'll start with an empty cache
this.logger.debug('Cache loading completed', { items: this.cache.size });
}
isCacheExpired(cacheEntry) {
const now = new Date().getTime();
const entryTime = cacheEntry.timestamp.getTime();
return (now - entryTime) > cacheEntry.ttl;
}
isEnabled() {
return this.isInitialized;
}
getCacheStats() {
const now = new Date().getTime();
let activeEntries = 0;
let expiredEntries = 0;
for (const entry of this.cache.values()) {
if (this.isCacheExpired(entry)) {
expiredEntries++;
}
else {
activeEntries++;
}
}
return {
totalEntries: this.cache.size,
activeEntries,
expiredEntries,
cacheTTL: this.cacheTTL
};
}
/**
* Cleanup cached data and mark service uninitialized
*/
destroy() {
this.cache.clear();
this.isInitialized = false;
this.logger.info('Documentation Service destroyed and cache cleared');
}
}
//# sourceMappingURL=documentation-service.js.map