Skip to content

Commit 5cca799

Browse files
committed
fix add vs replace on if flags already have custom property and its being updated
1 parent 6af236d commit 5cca799

File tree

2 files changed

+90
-108
lines changed

2 files changed

+90
-108
lines changed

dist/index.js

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -332,15 +332,18 @@ async function getFeatureFlag(apiKey, projectKey, flagKey) {
332332
/**
333333
* Set a custom property on a feature flag
334334
*/
335-
async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, propertyValue) {
335+
async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, propertyValue, hasExistingProperty = false) {
336336
const url = `https://app.launchdarkly.com/api/v2/flags/${projectKey}/${flagKey}`;
337337

338+
// Use 'replace' if property exists, 'add' if it doesn't
339+
const operation = hasExistingProperty ? 'replace' : 'add';
340+
338341
// Prepare the JSON patch operation to set the custom property
339342
// Using standard JSON patch format with correct "values" field
340343
const patchData = {
341344
patch: [
342345
{
343-
op: 'add',
346+
op: operation,
344347
path: `/customProperties/${propertyName}`,
345348
value: {
346349
name: propertyName,
@@ -350,7 +353,7 @@ async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, prop
350353
]
351354
};
352355

353-
core.debug(`Setting custom property ${propertyName} = ${propertyValue} on flag: ${flagKey}`);
356+
core.debug(`Setting custom property ${propertyName} = ${propertyValue} on flag: ${flagKey} (operation: ${operation})`);
354357
core.debug(`Request URL: ${url}`);
355358
core.debug(`Request body: ${JSON.stringify(patchData)}`);
356359

@@ -484,8 +487,14 @@ async function processSingleFlag(flag, apiKey, projectKey, customPropertyName, d
484487
const expiryDateString = calculateExpiryFromCreation(flag, daysFromCreation, dateFormat);
485488
const creationDate = new Date(flag.creationDate);
486489

487-
// Set the custom property
488-
await setCustomProperty(apiKey, projectKey, flag.key, customPropertyName, expiryDateString);
490+
// Check if the custom property already exists
491+
const hasExistingProperty = flag.customProperties &&
492+
flag.customProperties[customPropertyName] &&
493+
flag.customProperties[customPropertyName].value &&
494+
flag.customProperties[customPropertyName].value.length > 0;
495+
496+
// Set the custom property (with appropriate operation)
497+
await setCustomProperty(apiKey, projectKey, flag.key, customPropertyName, expiryDateString, hasExistingProperty);
489498

490499
return {
491500
key: flag.key,
@@ -512,7 +521,7 @@ async function processSingleFlag(flag, apiKey, projectKey, customPropertyName, d
512521
}
513522

514523
/**
515-
* Process flags in batches to avoid overwhelming the API
524+
* Process flags sequentially to avoid rate limiting
516525
*/
517526
async function processFlagsInBatches(flagsToProcess, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat) {
518527
const results = {
@@ -526,60 +535,42 @@ async function processFlagsInBatches(flagsToProcess, apiKey, projectKey, customP
526535
return results;
527536
}
528537

529-
// Split flags into batches
530-
const batches = [];
531-
for (let i = 0; i < flagsToProcess.length; i += BATCH_SIZE) {
532-
batches.push(flagsToProcess.slice(i, i + BATCH_SIZE));
533-
}
538+
core.info(`🚀 Processing ${flagsToProcess.length} flags sequentially`);
534539

535-
core.info(`🚀 Processing ${flagsToProcess.length} flags in ${batches.length} batches of ${BATCH_SIZE}`);
536-
537-
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
538-
const batch = batches[batchIndex];
539-
540-
core.info(`📦 Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} flags)`);
540+
for (let i = 0; i < flagsToProcess.length; i++) {
541+
const flag = flagsToProcess[i];
542+
results.totalProcessed++;
541543

542-
// Process batch in parallel
543-
const batchPromises = batch.map(flag =>
544-
processSingleFlag(flag, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat)
545-
);
544+
core.info(`📦 Processing flag ${i + 1}/${flagsToProcess.length}: ${flag.key}`);
546545

547-
const batchResults = await Promise.allSettled(batchPromises);
548-
549-
// Collect results
550-
batchResults.forEach((result, index) => {
551-
const flag = batch[index];
552-
results.totalProcessed++;
553-
554-
if (result.status === 'fulfilled') {
555-
results.updatedFlags.push(result.value);
556-
core.info(` ✅ ${flag.key}: ${result.value.calculatedExpiryDate}`);
546+
try {
547+
const result = await processSingleFlag(flag, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat);
548+
results.updatedFlags.push(result);
549+
core.info(` ✅ ${flag.key}: ${result.calculatedExpiryDate}`);
550+
} catch (error) {
551+
// Better error handling - extract message safely
552+
let errorMessage;
553+
if (error instanceof Error) {
554+
errorMessage = error.message;
555+
} else if (typeof error === 'string') {
556+
errorMessage = error;
557+
} else if (error && error.message) {
558+
errorMessage = error.message;
557559
} else {
558-
// Better error handling - extract message safely
559-
let errorMessage;
560-
if (result.reason instanceof Error) {
561-
errorMessage = result.reason.message;
562-
} else if (typeof result.reason === 'string') {
563-
errorMessage = result.reason;
564-
} else if (result.reason && result.reason.message) {
565-
errorMessage = result.reason.message;
566-
} else {
567-
errorMessage = 'Unknown error occurred';
568-
}
569-
570-
results.failedFlags.push({
571-
key: flag.key,
572-
name: flag.name,
573-
error: errorMessage
574-
});
575-
core.error(` ❌ ${flag.key}: ${errorMessage}`);
560+
errorMessage = 'Unknown error occurred';
576561
}
577-
});
562+
563+
results.failedFlags.push({
564+
key: flag.key,
565+
name: flag.name,
566+
error: errorMessage
567+
});
568+
core.error(` ❌ ${flag.key}: ${errorMessage}`);
569+
}
578570

579-
// Delay between batches (except last) to be respectful of rate limits
580-
if (batchIndex < batches.length - 1) {
581-
core.debug(`Waiting ${BATCH_DELAY}ms before next batch...`);
582-
await sleep(BATCH_DELAY);
571+
// Add delay between each flag to respect rate limits
572+
if (i < flagsToProcess.length - 1) {
573+
await sleep(RATE_LIMIT_DELAY);
583574
}
584575
}
585576

index.js

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -326,15 +326,18 @@ async function getFeatureFlag(apiKey, projectKey, flagKey) {
326326
/**
327327
* Set a custom property on a feature flag
328328
*/
329-
async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, propertyValue) {
329+
async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, propertyValue, hasExistingProperty = false) {
330330
const url = `https://app.launchdarkly.com/api/v2/flags/${projectKey}/${flagKey}`;
331331

332+
// Use 'replace' if property exists, 'add' if it doesn't
333+
const operation = hasExistingProperty ? 'replace' : 'add';
334+
332335
// Prepare the JSON patch operation to set the custom property
333336
// Using standard JSON patch format with correct "values" field
334337
const patchData = {
335338
patch: [
336339
{
337-
op: 'add',
340+
op: operation,
338341
path: `/customProperties/${propertyName}`,
339342
value: {
340343
name: propertyName,
@@ -344,7 +347,7 @@ async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, prop
344347
]
345348
};
346349

347-
core.debug(`Setting custom property ${propertyName} = ${propertyValue} on flag: ${flagKey}`);
350+
core.debug(`Setting custom property ${propertyName} = ${propertyValue} on flag: ${flagKey} (operation: ${operation})`);
348351
core.debug(`Request URL: ${url}`);
349352
core.debug(`Request body: ${JSON.stringify(patchData)}`);
350353

@@ -478,8 +481,14 @@ async function processSingleFlag(flag, apiKey, projectKey, customPropertyName, d
478481
const expiryDateString = calculateExpiryFromCreation(flag, daysFromCreation, dateFormat);
479482
const creationDate = new Date(flag.creationDate);
480483

481-
// Set the custom property
482-
await setCustomProperty(apiKey, projectKey, flag.key, customPropertyName, expiryDateString);
484+
// Check if the custom property already exists
485+
const hasExistingProperty = flag.customProperties &&
486+
flag.customProperties[customPropertyName] &&
487+
flag.customProperties[customPropertyName].value &&
488+
flag.customProperties[customPropertyName].value.length > 0;
489+
490+
// Set the custom property (with appropriate operation)
491+
await setCustomProperty(apiKey, projectKey, flag.key, customPropertyName, expiryDateString, hasExistingProperty);
483492

484493
return {
485494
key: flag.key,
@@ -506,7 +515,7 @@ async function processSingleFlag(flag, apiKey, projectKey, customPropertyName, d
506515
}
507516

508517
/**
509-
* Process flags in batches to avoid overwhelming the API
518+
* Process flags sequentially to avoid rate limiting
510519
*/
511520
async function processFlagsInBatches(flagsToProcess, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat) {
512521
const results = {
@@ -520,60 +529,42 @@ async function processFlagsInBatches(flagsToProcess, apiKey, projectKey, customP
520529
return results;
521530
}
522531

523-
// Split flags into batches
524-
const batches = [];
525-
for (let i = 0; i < flagsToProcess.length; i += BATCH_SIZE) {
526-
batches.push(flagsToProcess.slice(i, i + BATCH_SIZE));
527-
}
532+
core.info(`🚀 Processing ${flagsToProcess.length} flags sequentially`);
528533

529-
core.info(`🚀 Processing ${flagsToProcess.length} flags in ${batches.length} batches of ${BATCH_SIZE}`);
530-
531-
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
532-
const batch = batches[batchIndex];
533-
534-
core.info(`📦 Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} flags)`);
534+
for (let i = 0; i < flagsToProcess.length; i++) {
535+
const flag = flagsToProcess[i];
536+
results.totalProcessed++;
535537

536-
// Process batch in parallel
537-
const batchPromises = batch.map(flag =>
538-
processSingleFlag(flag, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat)
539-
);
538+
core.info(`📦 Processing flag ${i + 1}/${flagsToProcess.length}: ${flag.key}`);
540539

541-
const batchResults = await Promise.allSettled(batchPromises);
542-
543-
// Collect results
544-
batchResults.forEach((result, index) => {
545-
const flag = batch[index];
546-
results.totalProcessed++;
547-
548-
if (result.status === 'fulfilled') {
549-
results.updatedFlags.push(result.value);
550-
core.info(` ✅ ${flag.key}: ${result.value.calculatedExpiryDate}`);
540+
try {
541+
const result = await processSingleFlag(flag, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat);
542+
results.updatedFlags.push(result);
543+
core.info(` ✅ ${flag.key}: ${result.calculatedExpiryDate}`);
544+
} catch (error) {
545+
// Better error handling - extract message safely
546+
let errorMessage;
547+
if (error instanceof Error) {
548+
errorMessage = error.message;
549+
} else if (typeof error === 'string') {
550+
errorMessage = error;
551+
} else if (error && error.message) {
552+
errorMessage = error.message;
551553
} else {
552-
// Better error handling - extract message safely
553-
let errorMessage;
554-
if (result.reason instanceof Error) {
555-
errorMessage = result.reason.message;
556-
} else if (typeof result.reason === 'string') {
557-
errorMessage = result.reason;
558-
} else if (result.reason && result.reason.message) {
559-
errorMessage = result.reason.message;
560-
} else {
561-
errorMessage = 'Unknown error occurred';
562-
}
563-
564-
results.failedFlags.push({
565-
key: flag.key,
566-
name: flag.name,
567-
error: errorMessage
568-
});
569-
core.error(` ❌ ${flag.key}: ${errorMessage}`);
554+
errorMessage = 'Unknown error occurred';
570555
}
571-
});
556+
557+
results.failedFlags.push({
558+
key: flag.key,
559+
name: flag.name,
560+
error: errorMessage
561+
});
562+
core.error(` ❌ ${flag.key}: ${errorMessage}`);
563+
}
572564

573-
// Delay between batches (except last) to be respectful of rate limits
574-
if (batchIndex < batches.length - 1) {
575-
core.debug(`Waiting ${BATCH_DELAY}ms before next batch...`);
576-
await sleep(BATCH_DELAY);
565+
// Add delay between each flag to respect rate limits
566+
if (i < flagsToProcess.length - 1) {
567+
await sleep(RATE_LIMIT_DELAY);
577568
}
578569
}
579570

0 commit comments

Comments
 (0)