-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Arc-2308 add backend ffs #183
Changes from all commits
e19f32c
037156a
e564086
602af59
c6a7a3f
2868114
5ae295b
b7d3517
4147c07
3e3c976
ea84b6d
95c9bda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
16.20.2 | ||
18.17.0 | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
16.20.2 | ||
18.17.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const fetch = jest.fn(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export interface EnvVars { | ||
LAUNCHDARKLY_API_KEY: string; | ||
LAUNCHDARKLY_APP_NAME: string; | ||
} | ||
|
||
const envVars: EnvVars = { | ||
LAUNCHDARKLY_API_KEY: process.env.LAUNCHDARKLY_API_KEY || '', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These have been set for the appId using some forge cli commands. For more info see https://developer.atlassian.com/platform/forge/environments-and-versions/ |
||
LAUNCHDARKLY_APP_NAME: process.env.LAUNCHDARKLY_APP_NAME || 'jenkins-for-jira' | ||
}; | ||
|
||
export type Environment = 'test' | 'development' | 'staging' | 'production'; | ||
|
||
export default envVars; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { fetch as forgeFetch } from '@forge/api'; | ||
import { fetchFeatureFlag, LAUNCH_DARKLY_URL, launchDarklyService } from './feature-flags'; | ||
import envVars from './env'; | ||
|
||
jest.mock('@forge/api', () => ({ | ||
fetch: jest.fn(), | ||
})); | ||
|
||
const fetch = forgeFetch as jest.Mock; | ||
|
||
describe('fetchFeatureFlag', () => { | ||
afterEach(() => { | ||
fetch.mockClear(); | ||
}); | ||
|
||
it('should return true when flag is on', async () => { | ||
const mockFeatureFlagData = { | ||
name: 'test-flag', | ||
kind: 'boolean', | ||
environments: { | ||
test: { | ||
on: true, | ||
archived: false, | ||
} | ||
}, | ||
}; | ||
|
||
fetch.mockResolvedValueOnce({ | ||
status: 200, | ||
json: async () => mockFeatureFlagData, | ||
}); | ||
|
||
const result = await fetchFeatureFlag('test-flag', 'test'); | ||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('test-flag'), expect.anything()); | ||
expect(result).toEqual(true); | ||
}); | ||
|
||
it('should return false when flag is off', async () => { | ||
const mockFeatureFlagData = { | ||
name: 'test-flag', | ||
kind: 'boolean', | ||
environments: { | ||
test: { | ||
on: false, | ||
archived: false, | ||
} | ||
}, | ||
}; | ||
|
||
fetch.mockResolvedValueOnce({ | ||
status: 200, | ||
json: async () => mockFeatureFlagData, | ||
}); | ||
|
||
const result = await fetchFeatureFlag('test-flag', 'test'); | ||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('test-flag'), expect.anything()); | ||
expect(result).toEqual(false); | ||
}); | ||
|
||
it('handles fetch errors', async () => { | ||
fetch.mockRejectedValueOnce(new Error('Fetch error')); | ||
await expect(fetchFeatureFlag('test-flag', 'test')).rejects.toThrow('Fetch error'); | ||
}); | ||
}); | ||
|
||
describe('launchDarklyService', () => { | ||
it('returns the correct product app name', () => { | ||
expect(launchDarklyService.getProductAppName()).toEqual(envVars.LAUNCHDARKLY_APP_NAME); | ||
}); | ||
|
||
it('returns the correct product app page URL', () => { | ||
expect(launchDarklyService.getProductAppPageUrl()) | ||
.toEqual(`https://app.launchdarkly.com/${envVars.LAUNCHDARKLY_APP_NAME}`); | ||
}); | ||
|
||
it('returns the correct feature flag page URL for testing environment', () => { | ||
const featureFlagKey = 'yourFeatureFlagKey'; | ||
const environment = 'testing'; | ||
|
||
const expectedUrl = | ||
`${LAUNCH_DARKLY_URL}/${envVars.LAUNCHDARKLY_APP_NAME}/${environment}/features/${featureFlagKey}`; | ||
expect(launchDarklyService.getFeatureFlagPageUrl(featureFlagKey, environment)).toEqual(expectedUrl); | ||
}); | ||
|
||
it('returns the correct feature flag page URL for development environment', () => { | ||
const featureFlagKey = 'yourFeatureFlagKey'; | ||
const environment = 'development'; | ||
|
||
const expectedUrl = | ||
`${LAUNCH_DARKLY_URL}/${envVars.LAUNCHDARKLY_APP_NAME}/${environment}/features/${featureFlagKey}`; | ||
expect(launchDarklyService.getFeatureFlagPageUrl(featureFlagKey, environment)).toEqual(expectedUrl); | ||
}); | ||
|
||
it('returns the correct feature flag page URL for staging environment', () => { | ||
const featureFlagKey = 'yourFeatureFlagKey'; | ||
const environment = 'staging'; | ||
|
||
const expectedUrl = | ||
`${LAUNCH_DARKLY_URL}/${envVars.LAUNCHDARKLY_APP_NAME}/${environment}/features/${featureFlagKey}`; | ||
expect(launchDarklyService.getFeatureFlagPageUrl(featureFlagKey, environment)).toEqual(expectedUrl); | ||
}); | ||
|
||
it('returns the correct feature flag page URL for production environment', () => { | ||
const featureFlagKey = 'yourFeatureFlagKey'; | ||
const environment = 'production'; | ||
|
||
const expectedUrl = | ||
`${LAUNCH_DARKLY_URL}/${envVars.LAUNCHDARKLY_APP_NAME}/${environment}/features/${featureFlagKey}`; | ||
expect(launchDarklyService.getFeatureFlagPageUrl(featureFlagKey, environment)).toEqual(expectedUrl); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { fetch } from '@forge/api'; | ||
import { Logger } from './logger'; | ||
import envVars, { Environment } from './env'; | ||
|
||
interface Variation { | ||
_id: string; | ||
value: boolean; | ||
} | ||
|
||
interface EnvironmentData { | ||
on: boolean; | ||
archived: boolean; | ||
salt: string; | ||
sel: string; | ||
lastModified: number; | ||
version: number; | ||
targets: any[]; | ||
contextTargets: any[]; | ||
rules: any[]; | ||
fallthrough: any; | ||
offVariation: number; | ||
prerequisites: any[]; | ||
_site: any; | ||
_environmentName: string; | ||
trackEvents: boolean; | ||
trackEventsFallthrough: boolean; | ||
_summary: any; | ||
} | ||
|
||
interface FeatureFlag { | ||
name: string; | ||
kind: string; | ||
description: string; | ||
key: string; | ||
_version: number; | ||
creationDate: number; | ||
includeInSnippet: boolean; | ||
clientSideAvailability: { | ||
usingMobileKey: boolean; | ||
usingEnvironmentId: boolean; | ||
}; | ||
variations: Variation[]; | ||
variationJsonSchema: null | any; | ||
temporary: boolean; | ||
tags: string[]; | ||
_links: { | ||
parent: { | ||
href: string; | ||
type: string; | ||
}; | ||
self: { | ||
href: string; | ||
type: string; | ||
}; | ||
}; | ||
maintainerId: string; | ||
_maintainer: { | ||
_links: { | ||
self: any; | ||
}; | ||
_id: string; | ||
firstName: string; | ||
lastName: string; | ||
role: string; | ||
email: string; | ||
}; | ||
goalIds: string[]; | ||
experiments: { | ||
baselineIdx: number; | ||
items: any[]; | ||
}; | ||
customProperties: any; | ||
archived: boolean; | ||
defaults: { | ||
onVariation: number; | ||
offVariation: number; | ||
}; | ||
environments: { | ||
development: EnvironmentData; | ||
production: EnvironmentData; | ||
staging: EnvironmentData; | ||
test: EnvironmentData; | ||
}; | ||
} | ||
|
||
export const LAUNCH_DARKLY_URL = `https://app.launchdarkly.com`; | ||
const BASE_URL = `${LAUNCH_DARKLY_URL}/api/v2/flags/${envVars.LAUNCHDARKLY_APP_NAME}`; | ||
|
||
const baseHeaders = { | ||
headers: { | ||
Authorization: envVars.LAUNCHDARKLY_API_KEY | ||
} | ||
}; | ||
|
||
const logger = Logger.getInstance('featureFlags'); | ||
|
||
async function getFeatureFlag(featureFlagKey: string): Promise<FeatureFlag> { | ||
const eventType = 'retrievingFeatureFlag'; | ||
const errorMsg = 'fetching feature flag unexpected status'; | ||
|
||
try { | ||
const response = await fetch(`${BASE_URL}/${featureFlagKey}`, { ...baseHeaders }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using fetch to call the LD API as we currently can't use the @launchdarkly/node-server-sdk due to Forge's lack of support for the Node.js runtime. https://hello.atlassian.net/wiki/spaces/OTFS/pages/2803689031/Forge+Limitations Work is being done to add this support and we can opt in by being onboarded to a feature flag but doing some removed the ability to run |
||
|
||
if (response.status === 200) { | ||
logger.logInfo({ eventType, data: { message: `Successfully retrieved ${featureFlagKey}` } }); | ||
return await response.json(); | ||
} | ||
|
||
logger.logWarn({ eventType: `${eventType}Error`, errorMsg }); | ||
throw new Error(errorMsg); | ||
} catch (error) { | ||
logger.logWarn({ eventType: `${eventType}Error`, errorMsg, error }); | ||
throw error; | ||
} | ||
} | ||
|
||
export const launchDarklyService = { | ||
getProductAppName: () => envVars.LAUNCHDARKLY_APP_NAME, | ||
getProductAppPageUrl: () => `${LAUNCH_DARKLY_URL}/${envVars.LAUNCHDARKLY_APP_NAME}`, | ||
getFeatureFlag, | ||
getFeatureFlagPageUrl: (featureFlagKey: string, environment: string) => | ||
`${LAUNCH_DARKLY_URL}/${envVars.LAUNCHDARKLY_APP_NAME}/${environment}/features/${featureFlagKey}` | ||
}; | ||
|
||
export const fetchFeatureFlag = | ||
async (featureFlagKey: string, env: Environment): Promise<boolean | null> => { | ||
try { | ||
const environment: Environment = env?.toLowerCase() as Environment; | ||
const featureFlag = await launchDarklyService.getFeatureFlag(featureFlagKey); | ||
const envData = featureFlag.environments[environment]; | ||
return envData?.on || false; | ||
} catch (error) { | ||
logger.logError({ | ||
eventType: 'fetchFeatureFlagError', | ||
errorMsg: 'Error fetching feature flag:', | ||
error | ||
}); | ||
|
||
throw new Error(`Failed to retrieve feature flag: ${error}`); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forge cli was complaining I was using an old version and was having issues running forge commands. Had to bump, which came parcelled with a need to change this. Will need to merge this PR straight after this one.