Skip to content

Commit cd75210

Browse files
authored
fix: Add LDOptions.application name and versionName. (#358)
Adds support for name and versionName in addition to id and version. I have also renamed the client sdk config option from `application` to `applicationInfo`. I have kept the `ApplicationTags` type though, because that's a shared type with the server sdks and it's a much bigger change to rename that.
1 parent 8d80259 commit cd75210

File tree

10 files changed

+164
-92
lines changed

10 files changed

+164
-92
lines changed
Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,90 @@
1-
import ApplicationTags from '../../src/options/ApplicationTags';
1+
import { logger } from '@launchdarkly/private-js-mocks';
22

3-
function makeLogger() {
4-
return {
5-
error: jest.fn(),
6-
warn: jest.fn(),
7-
info: jest.fn(),
8-
debug: jest.fn(),
9-
};
10-
}
3+
import ApplicationTags from '../../src/options/ApplicationTags';
114

125
describe.each([
136
[
14-
{ application: { id: 'is-valid', version: 'also-valid' }, logger: makeLogger() },
7+
{ application: { id: 'is-valid', version: 'also-valid' }, logger },
158
'application-id/is-valid application-version/also-valid',
169
[],
1710
],
18-
[{ application: { id: 'is-valid' }, logger: makeLogger() }, 'application-id/is-valid', []],
1911
[
20-
{ application: { version: 'also-valid' }, logger: makeLogger() },
21-
'application-version/also-valid',
12+
{
13+
application: {
14+
id: 'is-valid',
15+
version: 'also-valid',
16+
name: 'test-app-1',
17+
versionName: 'test-version-1',
18+
},
19+
logger,
20+
},
21+
'application-id/is-valid application-name/test-app-1 application-version/also-valid application-version-name/test-version-1',
2222
[],
2323
],
24-
[{ application: {}, logger: makeLogger() }, undefined, []],
25-
[{ logger: makeLogger() }, undefined, []],
26-
[undefined, undefined, undefined],
24+
[{ application: { id: 'is-valid' }, logger }, 'application-id/is-valid', []],
25+
[{ application: { version: 'also-valid' }, logger }, 'application-version/also-valid', []],
26+
[{ application: {}, logger }, undefined, []],
27+
[{ logger }, undefined, []],
28+
[undefined, undefined, []],
2729

2830
// Above ones are 'valid' cases. Below are invalid.
31+
[{ application: { id: 'bad tag' }, logger }, undefined, [/Config option "application.id" must/]],
2932
[
30-
{ application: { id: 'bad tag' }, logger: makeLogger() },
31-
undefined,
32-
[{ level: 'warn', matches: /Config option "application.id" must/ }],
33+
{ application: { id: 'bad tag', version: 'good-tag' }, logger },
34+
'application-version/good-tag',
35+
[/Config option "application.id" must/],
3336
],
3437
[
35-
{ application: { id: 'bad tag', version: 'good-tag' }, logger: makeLogger() },
36-
'application-version/good-tag',
37-
[{ level: 'warn', matches: /Config option "application.id" must/ }],
38+
{
39+
application: { id: 'bad tag', version: 'good-tag', name: '', versionName: 'test-version-1' },
40+
logger,
41+
},
42+
'application-version/good-tag application-version-name/test-version-1',
43+
[/Config option "application.id" must/, /Config option "application.name" must/],
3844
],
3945
[
40-
{ application: { id: 'bad tag', version: 'also bad' }, logger: makeLogger() },
46+
{
47+
application: {
48+
id: 'bad tag',
49+
version: 'also bad',
50+
name: 'invalid name',
51+
versionName: 'invalid version name',
52+
},
53+
logger,
54+
},
4155
undefined,
4256
[
43-
{ level: 'warn', matches: /Config option "application.id" must/ },
44-
{ level: 'warn', matches: /Config option "application.version" must/ },
57+
/Config option "application.id" must/,
58+
/Config option "application.version" must/,
59+
/Config option "application.name" must/,
60+
/Config option "application.versionName" must/,
4561
],
4662
],
4763
// Bad tags and no logger.
48-
[
49-
{ application: { id: 'bad tag', version: 'also bad' }, logger: undefined },
50-
undefined,
51-
undefined,
52-
],
53-
])('given application tags configurations %p', (config, result, logs) => {
64+
[{ application: { id: 'bad tag', version: 'also bad' }, logger: undefined }, undefined, []],
65+
])('given application tags configurations %j', (config, result, warnings: RegExp[]) => {
5466
describe('when getting tag values', () => {
55-
// @ts-ignore
56-
const tags = new ApplicationTags(config);
67+
let tags: ApplicationTags;
68+
69+
beforeEach(() => {
70+
// @ts-ignore
71+
tags = new ApplicationTags(config);
72+
});
73+
74+
afterEach(() => {
75+
jest.resetAllMocks();
76+
});
5777

5878
it('produces the correct tag values', () => {
5979
expect(tags.value).toEqual(result);
6080
});
6181

62-
it('logs issues it encounters', () => {
63-
expect(config?.logger?.warn.mock.calls.length).toEqual(logs?.length);
82+
it(`logs issues it encounters for ${JSON.stringify(config)}`, () => {
83+
expect(logger.warn).toHaveBeenCalledTimes(warnings.length);
6484

65-
if (logs) {
66-
const expected = [...logs];
67-
config!.logger!.warn.mock.calls.forEach((call) => {
68-
const index = expected.findIndex((expectedLog) => call[0].match(expectedLog.matches));
69-
if (index < 0) {
70-
throw new Error(`Did not find expectation for ${call[0]}`);
71-
}
72-
expected.splice(index, 1);
73-
});
74-
if (expected.length) {
75-
throw new Error(
76-
`Did not find expected messages: ${expected.map((item) => item.matches.toString())}`,
77-
);
78-
}
79-
}
85+
warnings.forEach((regExp) => {
86+
expect(logger.warn).toHaveBeenCalledWith(expect.stringMatching(regExp));
87+
});
8088
});
8189
});
8290
});

packages/shared/common/src/options/ApplicationTags.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,28 @@ const tagValidator = {
2727
export default class ApplicationTags {
2828
public readonly value?: string;
2929

30-
constructor(options: { application?: { id?: string; version?: string }; logger?: LDLogger }) {
30+
constructor(options: {
31+
application?: { id?: string; version?: string; name?: string; versionName?: string };
32+
logger?: LDLogger;
33+
}) {
3134
const tags: Record<string, string[]> = {};
3235
const application = options?.application;
33-
34-
if (application?.id !== null && application?.id !== undefined) {
35-
const { valid, message } = tagValidator.is(application.id, 'application.id');
36-
37-
if (!valid) {
38-
options.logger?.warn(message);
39-
} else {
40-
tags['application-id'] = [application.id];
41-
}
42-
}
43-
44-
if (application?.version !== null && application?.version !== undefined) {
45-
const { valid, message } = tagValidator.is(application.version, 'application.version');
46-
if (!valid) {
47-
options.logger?.warn(message);
48-
} else {
49-
tags['application-version'] = [application.version];
50-
}
36+
const logger = options?.logger;
37+
38+
if (application) {
39+
Object.entries(application).forEach(([key, value]) => {
40+
if (value !== null && value !== undefined) {
41+
const { valid, message } = tagValidator.is(value, `application.${key}`);
42+
43+
if (!valid) {
44+
logger?.warn(message);
45+
} else if (key === 'versionName') {
46+
tags[`application-version-name`] = [value];
47+
} else {
48+
tags[`application-${key}`] = [value];
49+
}
50+
}
51+
});
5152
}
5253

5354
const tagKeys = Object.keys(tags);

packages/shared/sdk-client/src/api/LDOptions.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface LDOptions {
1414
/**
1515
* Information about the application where the LaunchDarkly SDK is running.
1616
*/
17-
application?: {
17+
applicationInfo?: {
1818
/**
1919
* A unique identifier representing the application where the LaunchDarkly SDK is running.
2020
*
@@ -33,6 +33,22 @@ export interface LDOptions {
3333
* Example: `1.0.0` (standard version string) or `abcdef` (sha prefix)
3434
*/
3535
version?: string;
36+
37+
/**
38+
* A human-friendly application name representing the application where the LaunchDarkly SDK is running.
39+
*
40+
* This can be specified as any string value as long as it only uses the following characters: ASCII letters,
41+
* ASCII digits, period, hyphen, underscore. A string containing any other characters will be ignored.
42+
*/
43+
name?: string;
44+
45+
/**
46+
* A human-friendly name representing the version of the application where the LaunchDarkly SDK is running.
47+
*
48+
* This can be specified as any string value as long as it only uses the following characters: ASCII letters,
49+
* ASCII digits, period, hyphen, underscore. A string containing any other characters will be ignored.
50+
*/
51+
versionName?: string;
3652
};
3753

3854
/**

packages/shared/sdk-client/src/configuration/Configuration.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ export default class Configuration {
3939
public readonly privateAttributes: string[] = [];
4040

4141
public readonly tags: ApplicationTags;
42-
public readonly application?: { id?: string; version?: string };
42+
public readonly applicationInfo?: {
43+
id?: string;
44+
version?: string;
45+
name?: string;
46+
versionName?: string;
47+
};
4348
public readonly bootstrap?: 'localStorage' | LDFlagSet;
4449

4550
// TODO: implement requestHeaderTransform
@@ -66,7 +71,7 @@ export default class Configuration {
6671
internalOptions.diagnosticEventPath,
6772
internalOptions.includeAuthorizationHeader,
6873
);
69-
this.tags = new ApplicationTags({ application: this.application, logger: this.logger });
74+
this.tags = new ApplicationTags({ application: this.applicationInfo, logger: this.logger });
7075
}
7176

7277
validateTypesAndNames(pristineOptions: LDOptions): string[] {

packages/shared/sdk-client/src/configuration/validators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const validators: Record<keyof LDOptions, TypeValidator> = {
3737
}),
3838
privateAttributes: TypeValidators.StringArray,
3939

40-
application: TypeValidators.Object,
40+
applicationInfo: TypeValidators.Object,
4141
bootstrap: new BootStrapValidator(),
4242
wrapperName: TypeValidators.String,
4343
wrapperVersion: TypeValidators.String,

packages/shared/sdk-client/src/utils/addAutoEnv.test.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,18 +318,24 @@ describe('automatic environment attributes', () => {
318318
});
319319

320320
describe('addApplicationInfo', () => {
321-
test('add application tags id, version', () => {
321+
test('add id, version, name, versionName', () => {
322322
config = new Configuration({
323-
application: { id: 'com.from-config.ld', version: '2.2.2' },
323+
applicationInfo: {
324+
id: 'com.from-config.ld',
325+
version: '2.2.2',
326+
name: 'test-ld-app-name',
327+
versionName: 'test-ld-version-name',
328+
},
324329
});
325330
const ldApplication = addApplicationInfo(basicPlatform, config);
326331

327332
expect(ldApplication).toEqual({
328333
envAttributesVersion: '1.0',
329334
id: 'com.from-config.ld',
330335
key: '1234567890123456',
331-
name: 'LDApplication.TestApp',
336+
name: 'test-ld-app-name',
332337
version: '2.2.2',
338+
versionName: 'test-ld-version-name',
333339
});
334340
});
335341

@@ -368,15 +374,15 @@ describe('automatic environment attributes', () => {
368374
});
369375
});
370376

371-
test('omit if both tags and auto generated data are unavailable', () => {
377+
test('omit if customer and auto env data are unavailable', () => {
372378
info.platformData = jest.fn().mockReturnValueOnce({});
373379

374380
const ldApplication = addApplicationInfo(basicPlatform, config);
375381

376382
expect(ldApplication).toBeUndefined();
377383
});
378384

379-
test('omit if tags unavailable and auto generated data are falsy', () => {
385+
test('omit if customer unavailable and auto env data are falsy', () => {
380386
const mockData = info.platformData();
381387
info.platformData = jest.fn().mockReturnValueOnce({
382388
ld_application: {
@@ -392,7 +398,7 @@ describe('automatic environment attributes', () => {
392398
expect(ldApplication).toBeUndefined();
393399
});
394400

395-
test('omit if tags unavailable and auto generated data only contains key and attributesVersion', () => {
401+
test('omit if customer data is unavailable and auto env data only contains key and attributesVersion', () => {
396402
info.platformData = jest.fn().mockReturnValueOnce({
397403
ld_application: { key: 'key-from-sdk', envAttributesVersion: '0.0.1' },
398404
});
@@ -406,7 +412,7 @@ describe('automatic environment attributes', () => {
406412
info.platformData = jest
407413
.fn()
408414
.mockReturnValueOnce({ ld_application: { version: null, locale: '' } });
409-
config = new Configuration({ application: { version: '1.2.3' } });
415+
config = new Configuration({ applicationInfo: { version: '1.2.3' } });
410416
const ldApplication = addApplicationInfo(basicPlatform, config);
411417

412418
expect(ldApplication).toBeUndefined();

packages/shared/sdk-client/src/utils/addAutoEnv.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,34 @@ export const toMulti = (c: LDSingleKindContext) => {
3131
*
3232
* @param crypto
3333
* @param info
34-
* @param applicationTags
34+
* @param applicationInfo
3535
* @param config
3636
* @return An LDApplication object with populated key, envAttributesVersion, id and version.
3737
*/
3838
export const addApplicationInfo = (
3939
{ crypto, info }: Platform,
40-
{ application: applicationTags }: Configuration,
40+
{ applicationInfo }: Configuration,
4141
): LDApplication | undefined => {
4242
const { ld_application } = info.platformData();
43-
const app = deepCompact<LDApplication>(ld_application) ?? ({} as LDApplication);
44-
const id = applicationTags?.id || app?.id;
43+
let app = deepCompact<LDApplication>(ld_application) ?? ({} as LDApplication);
44+
const id = applicationInfo?.id || app?.id;
4545

4646
if (id) {
47-
app.id = id;
47+
const version = applicationInfo?.version || app?.version;
48+
const name = applicationInfo?.name || app?.name;
49+
const versionName = applicationInfo?.versionName || app?.versionName;
4850

49-
const version = applicationTags?.version || app?.version;
50-
if (version) {
51-
app.version = version;
52-
}
51+
app = {
52+
...app,
53+
id,
54+
// only add props if they are defined
55+
...(version ? { version } : {}),
56+
...(name ? { name } : {}),
57+
...(versionName ? { versionName } : {}),
58+
};
5359

5460
const hasher = crypto.createHash('sha256');
55-
hasher.update(app.id);
61+
hasher.update(id);
5662
app.key = hasher.digest('base64');
5763
app.envAttributesVersion = app.envAttributesVersion || defaultAutoEnvSchemaVersion;
5864

packages/shared/sdk-server/__tests__/options/Configuration.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,19 @@ describe('when setting different options', () => {
347347
// This is more thoroughly tested in the application tags test.
348348
it.each([
349349
[{ application: { id: 'valid-id', version: 'valid-version' } }, 0],
350+
[
351+
{
352+
application: {
353+
id: 'valid-id',
354+
version: 'valid-version',
355+
name: 'valid-name',
356+
versionName: 'valid-versionName',
357+
},
358+
},
359+
0,
360+
],
350361
[{ application: 'tomato' }, 1],
351-
])('handles application tag settings', (values, warnings) => {
362+
])('handles application tag settings %j', (values, warnings) => {
352363
// @ts-ignore
353364
const config = new Configuration(withLogger({ ...values }));
354365
expect(logger(config).getCount()).toEqual(warnings);

0 commit comments

Comments
 (0)