Skip to content

load feature flags from new endpoint - #341

Open
linglingye001 wants to merge 7 commits into
previewfrom
linglingye/load-new-flag
Open

load feature flags from new endpoint#341
linglingye001 wants to merge 7 commits into
previewfrom
linglingye/load-new-flag

Conversation

@linglingye001

Copy link
Copy Markdown
Member

Summary
Adds support for loading feature flags from new endpoint (via FeatureFlagClient.listFeatureFlags), in addition to the classic key-value path. When feature flags are enabled, the provider now loads flags from both sources and merges them, with flags from the new endpoint taking precedence over classic ones of the same name. Reference Azure/AppConfiguration-DotnetProvider#738

Key changes

  • Added appConfigClient.ts: an IAppConfigurationClient interface plus AppConfigClient implementation that wraps both AppConfigurationClient (classic KV) and FeatureFlagClient (new FF endpoint). Each method applies request tracing before delegating to the underlying SDK client.

  • Added featureFlagConverter.ts with convertToMicrosoftSchema(), which maps the SDK's typed FeatureFlag (camelCase) into the Microsoft Feature Management schema consumed by feature_management.feature_flags.

  • Load and merge on init and on refresh: #loadClassicFeatureFlags() + #loadFeatureFlags() → #setFeatureFlags() dedups by name (new endpoint supersedes classic).

  • Change detection covers both paths

@zhiyuanliang-ms Zhiyuan Liang (zhiyuanliang-ms) Aug 12, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FeatureFlagConditions, FeatureFilter, .... all of these types/interfaces/enum are exported by the Azure SDK. We should use them instead of abusing any type.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

FeatureFlag related types exported in SDK are different with those defined by MS schema, that's why we need the converter. If imported them from SDK we still need to define the corresponding structs with snake_case field names. What concerns do you have about using the any type?

/** The conditions that must be met for the feature flag to be enabled. */
export interface FeatureFlagConditions {
  /** The requirement type for the conditions. */
  requirementType?: RequirementType;
  /** The filters that will conditionally enable or disable the flag. */
  filters?: FeatureFilter[];
}
/** A filter that conditionally enables or disables a feature flag. */
export interface FeatureFilter {
  /** The name of the filter. */
  name: string;
  /** The parameters used by the filter. */
  parameters?: { [propertyName: string]: string };
}

Comment thread src/requestTracing/utils.ts
Comment thread test/requestTracing.test.ts
Comment thread src/appConfigClient.ts Outdated
Comment thread src/configurationClientManager.ts Outdated
Comment thread src/appConfigClient.ts Outdated
Comment thread test/utils/testHelper.ts Outdated
Comment thread test/utils/testHelper.ts Outdated
Comment thread test/utils/testHelper.ts Outdated
Comment thread test/utils/testHelper.ts
Comment thread test/afd.test.ts
Comment thread src/featureManagement/featureFlagConverter.ts Outdated
Comment thread src/featureManagement/featureFlagConverter.ts Outdated
Comment thread src/featureManagement/featureFlagConverter.ts Outdated
Comment thread src/appConfigurationImpl.ts
Comment thread src/appConfigurationImpl.ts
Comment on lines +1373 to +1380
// Deep clone so the caller's option objects are never mutated.
const clonedSelectors = structuredClone(selectors);
clonedSelectors.forEach(selector => {
if (selector.keyFilter) {
selector.keyFilter = `${featureFlagPrefix}${selector.keyFilter}`;
}
});
return getValidSettingSelectors(selectors);
return getValidSettingSelectors(clonedSelectors);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// Deep clone so the caller's option objects are never mutated.
const clonedSelectors = structuredClone(selectors);
clonedSelectors.forEach(selector => {
if (selector.keyFilter) {
selector.keyFilter = `${featureFlagPrefix}${selector.keyFilter}`;
}
});
return getValidSettingSelectors(selectors);
return getValidSettingSelectors(clonedSelectors);
// Create prefixed copies because the original selectors are also used as unprefixed selectors for enhanced feature flags.
const prefixedSelectors = selectors.map(selector => ({
...selector,
keyFilter: selector.keyFilter
? `${featureFlagPrefix}${selector.keyFilter}`
: selector.keyFilter
}));
return getValidSettingSelectors(prefixedSelectors);

Comment thread src/appConfigurationImpl.ts
Comment thread src/appConfigurationImpl.ts Outdated
const lastServerResponseTime = pageWatchers[i].lastServerResponseTime;
let isResponseFresh = false;
if (lastServerResponseTime !== undefined) {
isResponseFresh = serverResponseTime > lastServerResponseTime;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In .NET, we use ">=". But I guess this behavior difference has existed before this PR. Let's align with .NET even though this difference will not affect anything in reality

}
i++;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In .NET provider, we have an additional check to see whether there is more old pages.

To align with .NET, we should add

if (i < pageWatchers.length) {
    return true;
}

after the for loop here.

But this behavior difference has existed before this PR and it will not affect anything in reality, because we will have an empty page after a full page. That empty page will have different etag.
But add this additional check can make code more robust.

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.

2 participants