Add ENABLE_AZURE_DEVOPS feature flag to disable Azure DevOps UI on cloud

- Add ENABLE_AZURE_DEVOPS constant to enterprise/server/auth/constants.py
- Add AZURE_DEVOPS_APP_CLIENT_ID and AZURE_DEVOPS_APP_CLIENT_SECRET constants
- Update SaaSServerConfig to include enable_azure_devops field
- Add ENABLE_AZURE_DEVOPS to FEATURE_FLAGS and PROVIDERS_CONFIGURED in get_config()
- Update frontend git-settings.tsx to conditionally render Azure DevOps section based on FEATURE_FLAGS.ENABLE_AZURE_DEVOPS
- Import ENABLE_AZURE_DEVOPS in enterprise/saas_server.py
- Add ENABLE_AZURE_DEVOPS to frontend TypeScript types, mocks, and test files

This ensures the 'Connect Azure DevOps Account' interface only appears when Azure DevOps OAuth credentials are configured, similar to existing patterns for Jira and Linear integrations.
This commit is contained in:
openhands 2025-11-25 18:24:14 +00:00
parent b532a5e7fe
commit d25650efb5
12 changed files with 29 additions and 1 deletions

View File

@ -10,6 +10,7 @@ from fastapi.middleware.cors import CORSMiddleware # noqa: E402
from fastapi.responses import JSONResponse # noqa: E402
from server.auth.auth_error import ExpiredError, NoCredentialsError # noqa: E402
from server.auth.constants import ( # noqa: E402
ENABLE_AZURE_DEVOPS,
ENABLE_JIRA,
ENABLE_JIRA_DC,
ENABLE_LINEAR,

View File

@ -17,7 +17,10 @@ GITLAB_APP_CLIENT_ID = os.getenv('GITLAB_APP_CLIENT_ID', '').strip()
GITLAB_APP_CLIENT_SECRET = os.getenv('GITLAB_APP_CLIENT_SECRET', '').strip()
BITBUCKET_APP_CLIENT_ID = os.getenv('BITBUCKET_APP_CLIENT_ID', '').strip()
BITBUCKET_APP_CLIENT_SECRET = os.getenv('BITBUCKET_APP_CLIENT_SECRET', '').strip()
AZURE_DEVOPS_APP_CLIENT_ID = os.getenv('AZURE_DEVOPS_APP_CLIENT_ID', '').strip()
AZURE_DEVOPS_APP_CLIENT_SECRET = os.getenv('AZURE_DEVOPS_APP_CLIENT_SECRET', '').strip()
ENABLE_ENTERPRISE_SSO = os.getenv('ENABLE_ENTERPRISE_SSO', '').strip()
ENABLE_AZURE_DEVOPS = os.environ.get('ENABLE_AZURE_DEVOPS', 'false') == 'true'
ENABLE_JIRA = os.environ.get('ENABLE_JIRA', 'false') == 'true'
ENABLE_JIRA_DC = os.environ.get('ENABLE_JIRA_DC', 'false') == 'true'
ENABLE_LINEAR = os.environ.get('ENABLE_LINEAR', 'false') == 'true'

View File

@ -8,7 +8,9 @@ import jwt
import requests # type: ignore
from fastapi import HTTPException
from server.auth.constants import (
AZURE_DEVOPS_APP_CLIENT_ID,
BITBUCKET_APP_CLIENT_ID,
ENABLE_AZURE_DEVOPS,
ENABLE_ENTERPRISE_SSO,
ENABLE_JIRA,
ENABLE_JIRA_DC,
@ -84,6 +86,7 @@ class SaaSServerConfig(ServerConfig):
maintenance_start_time: str = os.environ.get(
'MAINTENANCE_START_TIME', ''
) # Timestamp in EST e.g 2025-07-29T14:18:01.219616-04:00
enable_azure_devops = ENABLE_AZURE_DEVOPS
enable_jira = ENABLE_JIRA
enable_jira_dc = ENABLE_JIRA_DC
enable_linear = ENABLE_LINEAR
@ -160,6 +163,9 @@ class SaaSServerConfig(ServerConfig):
if BITBUCKET_APP_CLIENT_ID:
providers_configured.append(ProviderType.BITBUCKET)
if AZURE_DEVOPS_APP_CLIENT_ID:
providers_configured.append(ProviderType.AZURE_DEVOPS)
if ENABLE_ENTERPRISE_SSO:
providers_configured.append(ProviderType.ENTERPRISE_SSO)
@ -171,6 +177,7 @@ class SaaSServerConfig(ServerConfig):
'FEATURE_FLAGS': {
'ENABLE_BILLING': self.enable_billing,
'HIDE_LLM_SETTINGS': self.hide_llm_settings,
'ENABLE_AZURE_DEVOPS': self.enable_azure_devops,
'ENABLE_JIRA': self.enable_jira,
'ENABLE_JIRA_DC': self.enable_jira_dc,
'ENABLE_LINEAR': self.enable_linear,

View File

@ -123,6 +123,7 @@ describe("ExpandableMessage", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
});
const RouterStub = createRoutesStub([

View File

@ -38,6 +38,7 @@ describe("PaymentForm", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
});
});

View File

@ -80,6 +80,7 @@ describe("frontend/routes/_oh", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
});
@ -118,6 +119,7 @@ describe("frontend/routes/_oh", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
});
@ -202,6 +204,7 @@ describe("frontend/routes/_oh", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
});

View File

@ -24,6 +24,7 @@ const VALID_OSS_CONFIG: GetConfigResponse = {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
};
@ -37,6 +38,7 @@ const VALID_SAAS_CONFIG: GetConfigResponse = {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
};

View File

@ -404,6 +404,7 @@ describe("Settings 404", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
});
const error = createAxiosNotFoundErrorObject();
@ -429,6 +430,7 @@ describe("Setup Payment modal", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
});
const error = createAxiosNotFoundErrorObject();

View File

@ -73,6 +73,7 @@ describe("Settings Billing", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
},
isLoading: false,
@ -128,6 +129,7 @@ describe("Settings Billing", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
},
isLoading: false,
@ -152,6 +154,7 @@ describe("Settings Billing", () => {
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
},
isLoading: false,

View File

@ -13,6 +13,7 @@ export interface GetConfigResponse {
ENABLE_JIRA: boolean;
ENABLE_JIRA_DC: boolean;
ENABLE_LINEAR: boolean;
ENABLE_AZURE_DEVOPS: boolean;
};
MAINTENANCE?: {
startTime: string;

View File

@ -180,6 +180,7 @@ export const handlers = [
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
ENABLE_AZURE_DEVOPS: false,
},
// Uncomment the following to test the maintenance banner
// MAINTENANCE: {

View File

@ -128,6 +128,9 @@ function GitSettingsScreen() {
!bitbucketHostInputHasValue &&
!azureDevOpsHostInputHasValue;
const shouldRenderExternalConfigureButtons = isSaas && config.APP_SLUG;
const shouldRenderAzureDevOpsSection =
shouldRenderExternalConfigureButtons &&
config?.FEATURE_FLAGS?.ENABLE_AZURE_DEVOPS;
const shouldRenderProjectManagementIntegrations =
config?.FEATURE_FLAGS?.ENABLE_JIRA ||
config?.FEATURE_FLAGS?.ENABLE_JIRA_DC ||
@ -153,7 +156,7 @@ function GitSettingsScreen() {
</>
)}
{shouldRenderExternalConfigureButtons && !isLoading && (
{shouldRenderAzureDevOpsSection && !isLoading && (
<>
<div className="pb-1 mt-6 flex flex-col">
<h3 className="text-xl font-medium text-white">