Skip to content

Conversation

@ChristineWanjau
Copy link
Contributor

@ChristineWanjau ChristineWanjau commented Oct 30, 2025

This change removes the concept of dry-run from the JSImporter and introduces a new approach for exposing configuration changes. Instead of handling the printing of diffs internally, the importer now simply returns the configuration diff, leaving it to the caller to decide how to use it (e.g., print to console or process further).

The previous design tightly coupled the importer with output behavior (printing diffs), which limited flexibility. By returning the diff directly, portal, pipeline tasks or other external callers can decide on how to use the diff.

@ChristineWanjau ChristineWanjau marked this pull request as draft October 30, 2025 13:30
@ChristineWanjau ChristineWanjau marked this pull request as ready for review November 3, 2025 06:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the import logic to distinguish between added, modified, and deleted configuration settings, and introduces a new ConfigurationDiff interface that is returned when the dryRun parameter is enabled.

Key changes:

  • Introduces ConfigurationDiff interface with Added, Modified, and Deleted arrays
  • Refactors import logic into analyzeConfigurationChanges method to separate analysis from execution
  • Modified settings are now tracked separately from additions

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
libraries/azure-app-configuration-importer/src/models.ts Adds ConfigurationDiff interface and imports required types from @azure/app-configuration
libraries/azure-app-configuration-importer/src/index.ts Exports the new ConfigurationDiff interface
libraries/azure-app-configuration-importer/src/appConfigurationImporter.ts Refactors import logic to distinguish between added/modified/deleted settings and returns ConfigurationDiff when dryRun=true

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 120 to 127
if (!settingsAreEqual) {
configurationSettingToModify.push(incoming);
// Remove from add list since it's a modification, not an addition
const addIndex: number = configurationSettingToAdd.findIndex(addSetting =>
addSetting.key === incoming.key && addSetting.label === incoming.label);
if (addIndex !== -1) {
configurationSettingToAdd.splice(addIndex, 1);
}
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient array operations: for each existing setting that differs, the code performs a findIndex followed by splice on the configurationSettingToAdd array. With N incoming settings and M existing settings that differ, this results in O(N*M) complexity. Consider using a Set or Map data structure to track which settings should be added vs modified, or remove from the add list during the initial loop rather than searching for it later.

Copilot uses AI. Check for mistakes.
Comment on lines 129 to 135
else if (importMode == ImportMode.IgnoreMatch) {
// Remove unchanged settings from add list
const addIndex = configurationSettingToAdd.findIndex(addSetting =>
addSetting.key === incoming.key && addSetting.label === incoming.label);
if (addIndex !== -1) {
configurationSettingToAdd.splice(addIndex, 1);
}
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar performance issue: this block also performs findIndex and splice operations within a loop. This should be optimized along with the modification logic in lines 123-127.

Copilot uses AI. Check for mistakes.
@ChristineWanjau ChristineWanjau changed the title Return configuration diff when dry-run is true Return configuration changes to caller Nov 7, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* @param customHeadersOption - Custom headers for the operation.
* @returns ConfigurationChanges object containing Added, Modified, and Deleted settings
*/
public async getConfigurationChanges(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the first letter be upper case given this is a public function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated thank you

configSettingsSource: ConfigurationSettingsSource,
strict = false,
importMode?: ImportMode,
customHeadersOption?: OperationOptions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used in this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The customHeadersOptions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. It does look like needed for this function.

Copy link
Contributor Author

@ChristineWanjau ChristineWanjau Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use customHeadersOption to pass the correlationRequestId to link requests involved in the Import process. When the Import API calls the GetConfigurationChanges API, we pass the same correlationRequestId to associate both the read and write operations.
Additionally, we could use this to link the call to request the diff and Import on the portal for better traceability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline. Updated to generate correlationrequestid if not passed to GetConfigurationChanges

}
}

export interface ConfigurationChanges {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a config hasn't change, do we also return it as part of the ConfigurationChanges

Copy link
Contributor Author

@ChristineWanjau ChristineWanjau Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only when import mode is equal to ALL; if a config hasn't changed, then it will be part of the changes

timeout: number,
strict = false,
progressCallback?: (progress: ImportProgress) => unknown,
importMode?: ImportMode
Copy link
Contributor

@MaryanneNjeri MaryanneNjeri Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can set a defaultValue for importMode

 public async Import(
    configSettingsSource: ConfigurationSettingsSource,
    timeout: number,
    strict = false,
    progressCallback?: (progress: ImportProgress) => unknown,
    importMode = ImportMode.IgnoreMatch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much cleaner - Updated

"author": "Microsoft Corporation",
"description": "A client library for importing/exporting key-values between configuration file sources and Azure App Configuration service",
"version": "1.1.3-preview",
"version": "2.1.3-preview",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be 2.0.0-preview

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

}

/**
* Get configuration changes between source settings and Azure App Configuration service without any applying changes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean "without any applying changes" ? What do you think of "Get configuration changes between source settings and existing settings in Azure App Configuration service...`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant - "without applying any changes". Don't you think its beneficial to mention this so that users can understand they can call it safely just to preview changes?

Copy link
Contributor Author

@ChristineWanjau ChristineWanjau Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggesting -"Get configuration changes between source settings and existing settings in Azure App Configuration service without applying any changes"

importMode = ImportMode.IgnoreMatch;
}

this.validateImportMode(importMode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we will validateImportMode twice in Import function and in function to GetConfigurationChanges

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should still validateImportMode in GetConfigurationChanges since it might be called independently

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But since we call GetConfigurationChanges in Import, and in GetConfigurationChanges we validateImportMode, do we need to validateImportMode again in Import ?


this.validateImportMode(importMode);

// Generate correlation ID for operations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Generate correlationRequestId for operations in the same activity

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

if (!settingsAreEqual) {
configurationSettingToModify.push(incoming);
// Remove from add list since it's a modification, not an addition
const addIndex = configurationSettingToAdd.indexOf(incoming);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can simplify this to configurationSettingsToAdd.splice(configurationSettingToAdd.indexOf(incoming), 1).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines 55 to 57
Deleted: ConfigurationSetting<string>[];
Modified: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[];
Added: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about rename these fields to ToDelete ToModify, ToAdd? The current names seem to imply that the change has been made.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me. Updated

configurationSettingToDelete.push(existing);
}

const incoming = configSettings.find(configSetting => configSetting.key == existing.key &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to suggest instead of adding settings then removing them, maybe we shouldn't add them in the first place, maybe we can have something like this

if (importMode == ImportMode.IgnoreMatch) { 
    const incoming = configSettings.find(configSetting => configSetting.key == existing.key && 
            configSetting.label == existing.label);
    if (!incoming) { 
       configSettingToAdd.push(existing);
  } else if (incoming && !isConfigSettingEqual(incoming, existing)) { 
   configSettingToModify.push(existing);
 } 
}

Doesn't have to be exactly like this just an idea

}
};

const configurationChanges: ConfigurationChanges = await this.GetConfigurationChanges(configSettingsSource, strict, importMode, customHeadersOption);
Copy link
Member

@RichardChen820 RichardChen820 Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to imagine, how the GetConfigurationChanges() and Import() will be used at client side. One possible situation is, client calls GetConfigurationChanges to preview what are the settings to be changed, then it may ask user 's promotion to apply the change or not, if user confirm to apply the change by calling Import(), In this case, I think Import() should no longer need to call the GetConfigurationChanges() again, just apply the chanages directly.

So seems we can't just blindly call GetConfigurationChanges in Import, Import should have another overload that can accept ConfigurationChanges as an argument.

Thoughts? @MaryanneNjeri @ChristineWanjau

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RichardChen820 I agree, and Import would no longer need to take parameters like strict or import mode ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I think of the current design, the caller would call GetConfigurationChanges twice, once when they call GetConfigurationChanges and second time when they want to Import.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point. This is the most likely flow in the client side especially portal. We are planning to preview the changes to the caller then they can click on apply immediately after.

Just one concern I have is consistency issues, what if the pre-calculated changes may no longer be accurate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it completely does not make sense, and also have risk that GetConfigurationChanges may return different values in these two attempts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we provide a preview, only performing the changes exactly as shown in the preview can be considered consistent.

Copy link
Contributor Author

@ChristineWanjau ChristineWanjau Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leads to a result where we delete an item that the customer was never told would be deleted. This is the real inconsistency from the customer’s point of view

I see, this makes sense.
Considering we won't delete anything added into the store before import happens, to some extent there is not much risk involved.

Copy link
Contributor Author

@ChristineWanjau ChristineWanjau Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirm so we have a second overload of the Import() function that takes in ConfigurationChanges.

public async Import(
  configSettingsSource: ConfigurationSettingsSource,
  timeout: number,
  strict?: boolean,
  progressCallback?: (progress: ImportProgress) => unknown,
  importMode?: ImportMode
): Promise<void>;

/**
 * Import pre-calculated configuration changes into the Azure App Configuration service
 * 
 * 
 * @param configurationChanges - Pre-calculated changes from GetConfigurationChanges()
 * @param timeout
 * @param progressCallback
 * @returns Promise<void>
 */
public async Import(
  configurationChanges: ConfigurationChanges,
  timeout: number,
  progressCallback?: (progress: ImportProgress) => unknown
): Promise<void>;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I have updated the function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants