-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
userIntegrations.ts
131 lines (117 loc) · 5.16 KB
/
userIntegrations.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { Integration } from '@sentry/types';
export type UserIntegrationsFunction = (integrations: Integration[]) => Integration[];
export type UserIntegrations = Integration[] | UserIntegrationsFunction;
type ForcedIntegrationOptions = {
[keyPath: string]: unknown;
};
/**
* Recursively traverses an object to update an existing nested key.
* Note: The provided key path must include existing properties,
* the function will not create objects while traversing.
*
* @param obj An object to update
* @param value The value to update the nested key with
* @param keyPath The path to the key to update ex. fizz.buzz.foo
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setNestedKey(obj: Record<string, any>, keyPath: string, value: unknown): void {
// Ex. foo.bar.zoop will extract foo and bar.zoop
const match = keyPath.match(/([a-z_]+)\.(.*)/i);
// The match will be null when there's no more recursing to do, i.e., when we've reached the right level of the object
if (match === null) {
obj[keyPath] = value;
} else {
// `match[1]` is the initial segment of the path, and `match[2]` is the remainder of the path
const innerObj = obj[match[1]];
setNestedKey(innerObj, match[2], value);
}
}
/**
* Enforces inclusion of a given integration with specified options in an integration array originally determined by the
* user, by either including the given default instance or by patching an existing user instance with the given options.
*
* Ideally this would happen when integrations are set up, but there isn't currently a mechanism there for merging
* options from a default integration instance with those from a user-provided instance of the same integration, only
* for allowing the user to override a default instance entirely. (TODO: Fix that.)
*
* @param defaultIntegrationInstance An instance of the integration with the correct options already set
* @param userIntegrations Integrations defined by the user.
* @param forcedOptions Options with which to patch an existing user-derived instance on the integration.
* @returns A final integrations array.
*/
export function addOrUpdateIntegration(
defaultIntegrationInstance: Integration,
userIntegrations: UserIntegrations,
forcedOptions: ForcedIntegrationOptions = {},
): UserIntegrations {
return Array.isArray(userIntegrations)
? addOrUpdateIntegrationInArray(defaultIntegrationInstance, userIntegrations, forcedOptions)
: addOrUpdateIntegrationInFunction(defaultIntegrationInstance, userIntegrations, forcedOptions);
}
function addOrUpdateIntegrationInArray(
defaultIntegrationInstance: Integration,
userIntegrations: Integration[],
forcedOptions: ForcedIntegrationOptions,
): Integration[] {
const userInstance = userIntegrations.find(integration => integration.name === defaultIntegrationInstance.name);
if (userInstance) {
for (const [keyPath, value] of Object.entries(forcedOptions)) {
setNestedKey(userInstance, keyPath, value);
}
return userIntegrations;
}
return [...userIntegrations, defaultIntegrationInstance];
}
function addOrUpdateIntegrationInFunction(
defaultIntegrationInstance: Integration,
userIntegrationsFunc: UserIntegrationsFunction,
forcedOptions: ForcedIntegrationOptions,
): UserIntegrationsFunction {
const wrapper: UserIntegrationsFunction = defaultIntegrations => {
const userFinalIntegrations = userIntegrationsFunc(defaultIntegrations);
return addOrUpdateIntegrationInArray(defaultIntegrationInstance, userFinalIntegrations, forcedOptions);
};
return wrapper;
}
/**
* Updates the internal fields of a given integration in an integration array or integration function.
*
* @param defaultIntegrationInstance The `name` field of the integration to patch.
* @param userIntegrations Integrations defined by the user.
* @param forcedOptions Options with which to patch an existing integration instance.
* @returns A final integrations array.
*/
export function updateIntegration(
integrationName: string,
userIntegrations: UserIntegrations,
forcedOptions: ForcedIntegrationOptions,
): UserIntegrations {
return Array.isArray(userIntegrations)
? updateIntegrationInArray(integrationName, userIntegrations, forcedOptions)
: updateIntegrationInFunction(integrationName, userIntegrations, forcedOptions);
}
function updateIntegrationInFunction(
integrationName: string,
userIntegrationsFunc: UserIntegrationsFunction,
forcedOptions: ForcedIntegrationOptions,
): UserIntegrationsFunction {
const wrapper: UserIntegrationsFunction = defaultIntegrations => {
const userFinalIntegrations = userIntegrationsFunc(defaultIntegrations);
updateIntegrationInArray(integrationName, userFinalIntegrations, forcedOptions);
return userFinalIntegrations;
};
return wrapper;
}
function updateIntegrationInArray(
integrationName: string,
userIntegrations: Integration[],
forcedOptions: ForcedIntegrationOptions,
): Integration[] {
const userInstance = userIntegrations.find(integration => integration.name === integrationName);
if (userInstance) {
for (const [keyPath, value] of Object.entries(forcedOptions)) {
setNestedKey(userInstance, keyPath, value);
}
}
return userIntegrations;
}