UI tweaks for match columns step + auto hide empty columns
This commit is contained in:
@@ -1,206 +0,0 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { TimeManager } from '../utils/time.utils.js';
|
||||
import { RedisService } from './redis.service.js';
|
||||
|
||||
export class CampaignsService {
|
||||
constructor(apiKey, apiRevision) {
|
||||
this.apiKey = apiKey;
|
||||
this.apiRevision = apiRevision;
|
||||
this.baseUrl = 'https://a.klaviyo.com/api';
|
||||
this.timeManager = new TimeManager();
|
||||
this.redisService = new RedisService();
|
||||
}
|
||||
|
||||
async getCampaigns(params = {}) {
|
||||
try {
|
||||
// Add request debouncing
|
||||
const requestKey = JSON.stringify(params);
|
||||
if (this._pendingRequests && this._pendingRequests[requestKey]) {
|
||||
return this._pendingRequests[requestKey];
|
||||
}
|
||||
|
||||
// Try to get from cache first
|
||||
const cacheKey = this.redisService._getCacheKey('campaigns', params);
|
||||
let cachedData = null;
|
||||
try {
|
||||
cachedData = await this.redisService.get(`${cacheKey}:raw`);
|
||||
if (cachedData) {
|
||||
return cachedData;
|
||||
}
|
||||
} catch (cacheError) {
|
||||
console.warn('[CampaignsService] Cache error:', cacheError);
|
||||
}
|
||||
|
||||
this._pendingRequests = this._pendingRequests || {};
|
||||
this._pendingRequests[requestKey] = (async () => {
|
||||
let allCampaigns = [];
|
||||
let nextCursor = params.pageCursor;
|
||||
let pageCount = 0;
|
||||
|
||||
const filter = params.filter || this._buildFilter(params);
|
||||
|
||||
do {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (filter) {
|
||||
queryParams.append('filter', filter);
|
||||
}
|
||||
queryParams.append('sort', params.sort || '-send_time');
|
||||
|
||||
if (nextCursor) {
|
||||
queryParams.append('page[cursor]', nextCursor);
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}/campaigns?${queryParams.toString()}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Klaviyo-API-Key ${this.apiKey}`,
|
||||
'revision': this.apiRevision
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error('[CampaignsService] API Error:', errorData);
|
||||
throw new Error(`Klaviyo API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
allCampaigns = allCampaigns.concat(responseData.data || []);
|
||||
pageCount++;
|
||||
|
||||
nextCursor = responseData.links?.next ?
|
||||
new URL(responseData.links.next).searchParams.get('page[cursor]') : null;
|
||||
|
||||
if (nextCursor) {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
} catch (fetchError) {
|
||||
console.error('[CampaignsService] Fetch error:', fetchError);
|
||||
throw fetchError;
|
||||
}
|
||||
|
||||
} while (nextCursor);
|
||||
|
||||
const transformedCampaigns = this._transformCampaigns(allCampaigns);
|
||||
|
||||
const result = {
|
||||
data: transformedCampaigns,
|
||||
meta: {
|
||||
total_count: transformedCampaigns.length,
|
||||
page_count: pageCount
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const ttl = this.redisService._getTTL(params.timeRange);
|
||||
await this.redisService.set(`${cacheKey}:raw`, result, ttl);
|
||||
} catch (cacheError) {
|
||||
console.warn('[CampaignsService] Cache set error:', cacheError);
|
||||
}
|
||||
|
||||
delete this._pendingRequests[requestKey];
|
||||
return result;
|
||||
})();
|
||||
|
||||
return await this._pendingRequests[requestKey];
|
||||
} catch (error) {
|
||||
console.error('[CampaignsService] Error fetching campaigns:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
_buildFilter(params) {
|
||||
const filters = [];
|
||||
|
||||
if (params.startDate && params.endDate) {
|
||||
const startUtc = this.timeManager.formatForAPI(params.startDate);
|
||||
const endUtc = this.timeManager.formatForAPI(params.endDate);
|
||||
|
||||
filters.push(`greater-or-equal(send_time,${startUtc})`);
|
||||
filters.push(`less-than(send_time,${endUtc})`);
|
||||
}
|
||||
|
||||
if (params.status) {
|
||||
filters.push(`equals(status,"${params.status}")`);
|
||||
}
|
||||
|
||||
if (params.customFilters) {
|
||||
filters.push(...params.customFilters);
|
||||
}
|
||||
|
||||
return filters.length > 0 ? (filters.length > 1 ? `and(${filters.join(',')})` : filters[0]) : null;
|
||||
}
|
||||
|
||||
async getCampaignsByTimeRange(timeRange, options = {}) {
|
||||
const range = this.timeManager.getDateRange(timeRange);
|
||||
if (!range) {
|
||||
throw new Error('Invalid time range specified');
|
||||
}
|
||||
|
||||
const params = {
|
||||
timeRange,
|
||||
startDate: range.start.toISO(),
|
||||
endDate: range.end.toISO(),
|
||||
...options
|
||||
};
|
||||
|
||||
// Try to get from cache first
|
||||
const cacheKey = this.redisService._getCacheKey('campaigns', params);
|
||||
let cachedData = null;
|
||||
try {
|
||||
cachedData = await this.redisService.get(`${cacheKey}:raw`);
|
||||
if (cachedData) {
|
||||
return cachedData;
|
||||
}
|
||||
} catch (cacheError) {
|
||||
console.warn('[CampaignsService] Cache error:', cacheError);
|
||||
}
|
||||
|
||||
return this.getCampaigns(params);
|
||||
}
|
||||
|
||||
_transformCampaigns(campaigns) {
|
||||
if (!Array.isArray(campaigns)) {
|
||||
console.warn('[CampaignsService] Campaigns is not an array:', campaigns);
|
||||
return [];
|
||||
}
|
||||
|
||||
return campaigns.map(campaign => {
|
||||
try {
|
||||
const stats = campaign.attributes?.campaign_message?.stats || {};
|
||||
|
||||
return {
|
||||
id: campaign.id,
|
||||
name: campaign.attributes?.name || "Unnamed Campaign",
|
||||
subject: campaign.attributes?.campaign_message?.subject || "",
|
||||
send_time: campaign.attributes?.send_time,
|
||||
stats: {
|
||||
delivery_rate: stats.delivery_rate || 0,
|
||||
delivered: stats.delivered || 0,
|
||||
recipients: stats.recipients || 0,
|
||||
open_rate: stats.open_rate || 0,
|
||||
opens_unique: stats.opens_unique || 0,
|
||||
opens: stats.opens || 0,
|
||||
clicks_unique: stats.clicks_unique || 0,
|
||||
click_rate: stats.click_rate || 0,
|
||||
click_to_open_rate: stats.click_to_open_rate || 0,
|
||||
conversion_value: stats.conversion_value || 0,
|
||||
conversion_uniques: stats.conversion_uniques || 0
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[CampaignsService] Error transforming campaign:', error, campaign);
|
||||
return {
|
||||
id: campaign.id || 'unknown',
|
||||
name: 'Error Processing Campaign',
|
||||
stats: {}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user