@servicetitan/docs-uikit 28.1.1 → 28.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,336 @@
1
+ ---
2
+ title: LaunchDarkly Service
3
+ ---
4
+
5
+ #### [CHANGELOG (@servicetitan/launchdarkly-service)](https://github.com/servicetitan/uikit/blob/master/packages/launchdarkly-service/CHANGELOG.md)
6
+
7
+ ServiceTitan's LaunchDarkly platform has multiple "projects" (e.g., Dispatch Center, Contact Center, FleetPro),
8
+ each with its own set of feature flags and client-side IDs that authorize connections.
9
+
10
+ LaunchDarkly's SDKs require applications to create a separate client connection to the LaunchDarkly backend for each project.
11
+ However, if applications and MFEs create multiple, independent connections to the same LaunchDarkly project, it can degrade performance
12
+ and cause the UI to behave inconsistently. It also harms performance to teardown and recreate connections each time an
13
+ MFE is unloaded and reloaded.
14
+
15
+ To prevent these issues **@servicetitan/launchdarkly-service** implements a coordinated approach for creating
16
+ and reusing client connections for LaunchDarkly projects.
17
+ Instead of using **LDProvider**, **withLDProvider** or **withAsyncLDProvider** from the LaunchDarkly React SDK, use **LDProvider** from **@servicetitan/launchdarkly-service**.
18
+
19
+ ## Usage
20
+
21
+ **@servicetitan/launchdarkly-service** allows host applications and MFEs to share and reuse connections to LaunchDarkly. It ensures that only one connection is created for each LaunchDarkly project. To use:
22
+
23
+ 1. Wrap the MFE application (or code within the host) with [`LDProvider`](#ldprovider) from `@servicetitan/launchdarkly-service`.
24
+ 2. Wrap the host application with [`LDServiceProvider`](#ldserviceprovider) (if applicable).
25
+ 3. Use the standard [hooks](https://docs.launchdarkly.com/sdk/client-side/react/react-web#hooks) from the [launchdarkly-react-client-sdk](https://docs.launchdarkly.com/sdk/client-side/react/react-web) to access feature flags and the client object.
26
+
27
+ ## API
28
+
29
+ ### LDProvider
30
+
31
+ `LDProvider` provides LaunchDarkly feature flags within hosts and MFEs.
32
+ It ensures that hosts and MFEs share one client connection to each LaunchDarkly project (as identified by the client-side ID)
33
+ and automatically [provides a store](#use-flags-in-mobx-store) that allows flag values to be used in MobX stores.
34
+
35
+ #### Props
36
+
37
+ | Name | Type | Description |
38
+ | :---------------------- | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | `clientSideID` | `string` | The client-side ID that authorizes the client to connect to a particular LaunchDarkly project. The [getValueForEnvironment](./web-components#getvalueforenvironment) helper provides a convenient way to map Monolith environments to the corresponding client-side ID. |
40
+ | `projectName` | `string` | The name of the LaunchDarkly project, for logs and error messages. |
41
+ | `context` | `object` | (Optional) Context for targeting rules. Defaults to anonymous user. Use LaunchDarkly's [`identify`](https://docs.launchdarkly.com/sdk/features/identify) method to change the context after initialization. |
42
+ | `waitForInitialization` | `boolean` | (Optional) Whether or not to wait until flags values are available before rendering children. Defaults to `true` |
43
+
44
+ :::caution
45
+ When a `context` is provided, only the initial value takes affect. Subsequent calls with the same client-side ID reuse the client that was created with the initial context.
46
+
47
+ This is typically only a problem when different code paths call `LDProvider` with different contexts. To avoid this, create a standard `LDProvider` wrapper for each project (see example below).
48
+ If that isn't practical, you can use [`getContext`](https://launchdarkly.github.io/js-client-sdk/interfaces/LDClient.html#getContext) and [`identify`](https://launchdarkly.github.io/js-client-sdk/interfaces/LDClient.html#identify) to fetch and amend the active context.
49
+ :::
50
+
51
+ :::note
52
+ When `waitForInitialization` is set to false, `LDProvider` renders children immediately, possibly with no flag values,
53
+ then automatically re-renders when the flags become available.
54
+ :::
55
+
56
+ #### Examples
57
+
58
+ For example, to provide feature flags for the TitanAdvisor project, create a `TitanAdvisorLDProvider` component that wraps its children with the appropriate `LDProvider`:
59
+
60
+ ```tsx title="titan-advisor-ld-provider.tsx"
61
+ import { LDProvider } from '@servicetitan/launchdarkly-service';
62
+ import { getValueForEnvironment } from '@servicetitan/web-components'
63
+ import { FC, PropsWithChildren, useMemo } from 'react';
64
+
65
+ export const TitanAdvisorLDProvider: FC<PropsWithChildren> = ({ children }) => {
66
+ const clientSideID = useMemo(() => {
67
+ return getValueForEnvironment({
68
+ dev: '669fe6e5cc97eb103be7a620',
69
+ go: '669fe5bff00a0c0fa60b25e4',
70
+ next: '669fe5bff00a0c0fa60b25e4' // using 'go' here, but could also be 'qa'
71
+ qa: '669fe6d6f00a0c0fa60b2721',
72
+ stage: '669fe65979abf310b860236e',
73
+ });
74
+ }, []);
75
+
76
+ if (!clientSideID) {
77
+ // This only happens in the "test" environment, because "test" was omitted above.
78
+ return children;
79
+ }
80
+
81
+ return (
82
+ <LDProvider clientSideID={clientSideID} projectName="TitanAdvisor">
83
+ {children}
84
+ </LDProvider>
85
+ );
86
+ };
87
+ ```
88
+
89
+ Then, to provide those flags globally to the entire TitanAdvisor MFE, wrap the MFE's `App`:
90
+
91
+ ```tsx title="app.tsx"
92
+ import { FC, StrictMode } from 'react';
93
+
94
+ import { TitanAdvisorLDProvider } from './titan-advisor-ld-provider';
95
+
96
+ export const App: FC = () => {
97
+ return (
98
+ <StrictMode>
99
+ <TitanAdvisorLDProvider>
100
+ {...}
101
+ </TitanAdvisorLDProvider>
102
+ </StrictMode>
103
+ );
104
+ };
105
+ ```
106
+
107
+ Or, to provide TitanAdvisor flags to a component in the Monolith, wrap the component:
108
+
109
+ ```tsx
110
+ // Rename original Component to ComponentUnwrapped
111
+ const ComponentUnwrapped: FC<ComponentProps> = props => {};
112
+
113
+ // Export wrapped Component
114
+ export const Component: FC<ComponentProps> = props => {
115
+ return (
116
+ <TitanAdvisorLDProvider>
117
+ <ComponentUnwrapped {...props} />
118
+ </TitanAdvisorLDProvider>
119
+ );
120
+ };
121
+ ```
122
+
123
+ ### LDServiceProvider
124
+
125
+ `LDServiceProvider` enables sharing and reusing LaunchDarkly clients outside the Monolith.
126
+ Simply wrap the host with `LDServiceProvider` to ensure that it and embedded MFEs use a single connection for each LaunchDarkly project.
127
+
128
+ ```tsx
129
+ import { FC, StrictMode } from 'react';
130
+ import { LogService } from '@servicetitan/launchdarkly-service';
131
+
132
+ export const HostApp: FC = () => {
133
+ return(
134
+ <StrictMode>
135
+ <LDServiceProvider>
136
+ {...}
137
+ </LDServiceProvider>
138
+ </StrictMode>
139
+ );
140
+ };
141
+ ```
142
+
143
+ ### FeatureFlagStore
144
+
145
+ `LDProvider` automatically provides a `FeatureFlagStore` that allows you to access
146
+ feature flags in MobX stores.
147
+ To use [@inject](./react-ioc#inject) where needed.
148
+
149
+ #### Props
150
+
151
+ | Name | Type | Description |
152
+ | :------ | :---------- | :------------------------------ |
153
+ | `flags` | `LDFlagSet` | The LaunchDarkly feature flags. |
154
+
155
+ #### Example
156
+
157
+ ```tsx
158
+ import { FeatureFlagStore } from '@servicetitan/launchdarkly-service';
159
+
160
+ class ConsumerStore {
161
+ constructor(@inject(FeatureFlagStore) private readonly featureFlagStore: FeatureFlagStore) {
162
+ // this.featureFlagStore.flags contains feature flags
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### WithFeatureFlag
168
+
169
+ `WithFeatureFlag` conditionally renders content depending on the value of a feature flag.
170
+
171
+ #### Props
172
+
173
+ | Name | Type | Description |
174
+ | :-------- | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------- |
175
+ | `flagKey` | `string` | The feature flag's key. To type check `flagKey`, pass a type argument that defines the flag set (see [below](#type-check-flag-key)) |
176
+ | `on` | `React.ComponentType` | (Optional) Component to render when the flag is turned on. Defaults to `children` prop. |
177
+ | `off` | `React.ComponentType` | (Optional) Component to render when the flag is turned off. Defaults to `null`. |
178
+ | `value` | `LDFlagValue` | (Optional) Flag value that indicates the flag is turned on. Defaults to treating any truthy value as "on". |
179
+
180
+ #### Examples
181
+
182
+ Render children when flag is turned on:
183
+
184
+ ```tsx
185
+ <WithFeatureFlag flagKey={flagKey}>
186
+ <Text>Flag is ON</Text>
187
+ </WithFeatureFlag>
188
+ ```
189
+
190
+ Render children when flag has specific value:
191
+
192
+ ```tsx
193
+ <WithFeatureFlag flagKey={flagKey} value={false}>
194
+ <Text>Flag is false</Text>
195
+ </WithFeatureFlag>
196
+ ```
197
+
198
+ Render children when flag is on, and render other component when flag is turned off:
199
+
200
+ ```tsx
201
+ <WithFeatureFlag flagKey={flagKey} off={ComponentV1}>
202
+ <ComponentV2 />
203
+ </WithFeatureFlag>
204
+ ```
205
+
206
+ Render component when flag is on, and render other component when flag is turned off:
207
+
208
+ ```tsx
209
+ <WithFeatureFlag flagKey={flagKey} on={ComponentV2} off={ComponentV1} />
210
+
211
+ <WithFeatureFlag flagKey={flagKey} on={() => 'Flag is ON'} off={() => 'Flag is OFF'} />
212
+ ```
213
+
214
+ ## Examples
215
+
216
+ ### Using flag values in React component
217
+
218
+ Use the `useFlags` hook from the LaunchDarkly React SDK to access flags in React components. E.g.,
219
+
220
+ ```tsx
221
+ import { useFlags } from 'launchdarkly-react-client-sdk';
222
+
223
+ export const Component: FC = () => {
224
+ const { enableRedesign } = useFlags();
225
+ return enableRedesign ? <NewDesign /> : <OldDesign />;
226
+ };
227
+ ```
228
+
229
+ Or, use this package's [WithFeatureFlag](#withfeatureflag) helper:
230
+
231
+ ```tsx
232
+ import { WithFeatureFlag } from '@servicetitan/launchdarkly-service';
233
+
234
+ export const Component: FC = () => (
235
+ <WithFeatureFlag flagKey="enableRedesign" on={NewDesign} off={OldDesign} />
236
+ );
237
+ ```
238
+
239
+ ### Using flag values in MobX store {#use-flags-in-mobx-store}
240
+
241
+ To use flag values in MobX stores, inject the [FeatureFlagStore](#featureflagstore) provided by `LDProvider`.
242
+ For example,
243
+
244
+ ```tsx
245
+ import { FeatureFlagStore } from '@servicetitan/launchdarkly-service';
246
+
247
+ @injectable()
248
+ export class DataStore extends Store {
249
+ constructor(@inject(FeatureFlagStore) private readonly featureFlagStore: FeatureFlagStore) {
250
+ // Use this.featureFlagStore.flags to access feature flags
251
+ if (this.featureFlagStore.flags.enableDataV2) {
252
+ this.initializeDataV2();
253
+ } else {
254
+ this.initializeData();
255
+ }
256
+ }
257
+ }
258
+ ```
259
+
260
+ ### Type checking WithFeatureFlag key {#type-check-flag-key}
261
+
262
+ To type check the `flagKey` passed to `WithFeatureFlag`, pass a type argument that defines the flag set.
263
+ For example,
264
+
265
+ ```tsx title='fleet-flag-set.ts'
266
+ export interface FleetFlagSet {
267
+ enablePermissions: boolean;
268
+ enableBreadcrumbPage: boolean;
269
+ enableAlertsRedesign: boolean;
270
+ }
271
+ ```
272
+
273
+ ```tsx
274
+ <WithFeatureFlag<FleetFlagSet> flagKey={flagKey} /> // Enforces that flagKey is in FleetFlagSet
275
+ ```
276
+
277
+ Or, create a custom wrapper that does this automatically. For example,
278
+
279
+ ```tsx title='with-fleet-feature-flag.tsx'
280
+ import { WithFeatureFlag, WithFeatureFlagProps } from '@servicetitan/launchdarkly-service';
281
+ import { FleetFlagSet } from './fleet-flag-set';
282
+
283
+ export const WithFleetFeatureFlag = <T extends keyof FleetFlagSet>(
284
+ props: WithFeatureFlagProps<FleetFlagSet, T>
285
+ ) => <WithFeatureFlag<FleetFlagSet, T> {...props} />;
286
+ ```
287
+
288
+ ```tsx
289
+ <WithFleetFeatureFlag flagKey={flagKey} /> // Enforces that flagKey is in FleetFlagSet
290
+ ```
291
+
292
+ ### Type checking WithFeatureFlag value
293
+
294
+ To type check the `value` passed to `WithFeatureFlag`, use the `createFlagKeys` helper.
295
+ It returns an object with keys that enable type checking against the passed `value`. For example,
296
+
297
+ ```tsx title='fleet-flag-set.ts'
298
+ import { createFlagKeys } from '@servicetitan/launchdarkly-service';
299
+
300
+ export interface FleetFlagSet {
301
+ enablePermissions: boolean;
302
+ enableBreadcrumbPage: boolean;
303
+ enableAlertsRedesign: boolean;
304
+ triStateFlag: 0 | 1 | 2; // flag has non-boolean value
305
+ }
306
+
307
+ export const FleetFlagKeys = createFlagKeys<FleetFlagSet>( // generates type-aware keys
308
+ 'enablePermissions',
309
+ 'enableBreadcrumbPage',
310
+ 'enableAlertsRedesign',
311
+ 'triStateFlag'
312
+ );
313
+ ```
314
+
315
+ ```tsx
316
+ // Use generated key to enforce that value is correct type, in this case, 0, 1 or 2
317
+ <WithFeatureFlag flagKey={FleetFlagKeys.triStateFlag} value={2} />
318
+
319
+ // Use generated key to type check value passed to custom wrapper
320
+ <WithFleetFeatureFlag flagKey={FleetFlagKeys.triStateFlag} value={2} />
321
+ ```
322
+
323
+ ### Type checking FeatureFlagStore
324
+
325
+ To type check `FeatureFlagStore` keys, pass a type argument that defines the flag set.
326
+ For example,
327
+
328
+ ```tsx title='consumer-store.ts'
329
+ class ConsumerStore {
330
+ constructor(
331
+ @inject(FeatureFlagStore)
332
+ // Establishes that type of this.featureFlagStore.flags is FleetFlagSet
333
+ private readonly featureFlagStore: FeatureFlagStore<FleetFlagSet>
334
+ ) {}
335
+ }
336
+ ```
@@ -90,7 +90,7 @@ export const App: FC = () => {
90
90
  <StrictMode>
91
91
  <LogService>
92
92
  {...}
93
- <LogService>
93
+ </LogService>
94
94
  </StrictMode>
95
95
  );
96
96
  };
@@ -153,6 +153,59 @@ The following metadata about an MFE is available from `useMFEMetadataContext` ho
153
153
  | `shadowRoot` | ShadowRoot | ShadowRoot root node of which the MFE is rendered within |
154
154
  | `portalShadowRoot` | ShadowRoot | ShadowRoot root node of the "portal" tied to the MFE, which is used to render elements in a div directly under the body element |
155
155
 
156
+ ### getValueForEnvironment
157
+
158
+ `getValueForEnvironment` detects the Monolith environment and returns a corresponding value.
159
+
160
+ :::caution
161
+ When no value is provided for the detected environment, `getValueForEnvironment` returns `undefined`.
162
+ :::
163
+
164
+ #### Props
165
+
166
+ | Name | Type | Description |
167
+ | :------------------- | :----------------------------- | :----------------------------------------------------------------------------------------- |
168
+ | `values` | `Record<Environment, string>` | Object that maps each environment to a value (see below). |
169
+ | `defaultEnvironment` | `Environment` | The environment to use when the current environment is not recognized. Defaults to `"qa"`. |
170
+ | `hostname` | `string` | The hostname of the current environment. Defaults to `window.location.hostname` |
171
+
172
+ The recognized environments are:
173
+
174
+ | Environment | Description |
175
+ | :---------- | :-------------------------------------------------------------- |
176
+ | **dev** | Development environment (e.g., `localhost`) |
177
+ | **go** | Production environment |
178
+ | **qa** | QA environment |
179
+ | **next** | Next environment |
180
+ | **stage** | Staging environment |
181
+ | **test** | Unit test environment (i.e., `process.env.NODE_ENV === 'test'`) |
182
+
183
+ #### Examples
184
+
185
+ Determine LaunchDarkly client-side ID for TitanAdvisor project.
186
+
187
+ ```tsx
188
+ function useTitanAdvisorClientSideID() {
189
+ return useMemo(() => {
190
+ return getValueForEnvironment({
191
+ dev: '669fe6e5cc97eb103be7a620',
192
+ go: '669fe5bff00a0c0fa60b25e4',
193
+ next: '669fe5bff00a0c0fa60b25e4', // same a 'go' but could also be 'qa'
194
+ qa: '669fe6d6f00a0c0fa60b2721',
195
+ stage: '669fe65979abf310b860236e',
196
+ });
197
+ }, []);
198
+ }
199
+ ```
200
+
201
+ :::info
202
+ Note that because the environment is fixed for the life the application, it is safe to memoize the return value.
203
+ :::
204
+
205
+ :::caution
206
+ Do not call `getValueForEnvironment` globally, outside a React component or hook. That usage assumes that all the information needed to determine the environment is available immediately when the Javascript runtime loads and before the application is initialized. While that might work it might also change in the future.
207
+ :::
208
+
156
209
  ### EVENT_BUS_TOKEN - Emitting Events to MFEs
157
210
 
158
211
  Sometimes you may need to send events from the host to the MFE or the other way around, the EventBus ([mitt](https://github.com/developit/mitt) is used under the hood) should cover these needs. Starting <code>v22.1.0</code> you can also send payload.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/docs-uikit",
3
- "version": "28.1.1",
3
+ "version": "28.3.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,5 +16,5 @@
16
16
  "cli": {
17
17
  "webpack": false
18
18
  },
19
- "gitHead": "35c714f4eaf0ba60dd6fa3fc583f0283edfb4a44"
19
+ "gitHead": "63ce3ca18966fc5522a3b61df5e4c55f82b4272e"
20
20
  }