@hanzo/insights-react 1.8.1 → 1.9.1

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.
Files changed (42) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +40 -40
  3. package/dist/esm/index.js +48 -48
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/surveys/index.js +12 -12
  6. package/dist/esm/surveys/index.js.map +1 -1
  7. package/dist/types/index.d.ts +29 -29
  8. package/dist/umd/index.js +55 -55
  9. package/dist/umd/index.js.map +1 -1
  10. package/dist/umd/surveys/index.js +16 -16
  11. package/dist/umd/surveys/index.js.map +1 -1
  12. package/package.json +38 -32
  13. package/src/components/{PostHogCaptureOnViewed.tsx → InsightsCaptureOnViewed.tsx} +12 -37
  14. package/src/components/{PostHogErrorBoundary.tsx → InsightsErrorBoundary.tsx} +13 -13
  15. package/src/components/{PostHogFeature.tsx → InsightsFeature.tsx} +14 -14
  16. package/src/components/__tests__/{PostHogCaptureOnViewed.test.tsx → InsightsCaptureOnViewed.test.tsx} +33 -33
  17. package/src/components/__tests__/{PostHogErrorBoundary.test.tsx → InsightsErrorBoundary.test.tsx} +17 -17
  18. package/src/components/__tests__/{PostHogFeature.test.tsx → InsightsFeature.test.tsx} +73 -73
  19. package/src/components/index.ts +6 -6
  20. package/src/context/InsightsContext.ts +9 -0
  21. package/src/context/InsightsProvider.tsx +108 -0
  22. package/src/context/__tests__/{PostHogContext.test.tsx → InsightsContext.test.tsx} +9 -9
  23. package/src/context/__tests__/{PostHogProvider.test.tsx → InsightsProvider.test.tsx} +33 -33
  24. package/src/context/index.ts +2 -2
  25. package/src/helpers/error-helpers.ts +2 -2
  26. package/src/hooks/__tests__/featureFlags.test.tsx +15 -15
  27. package/src/hooks/__tests__/useInsights.test.tsx +19 -0
  28. package/src/hooks/__tests__/useThumbSurvey.test.tsx +7 -7
  29. package/src/hooks/index.ts +1 -1
  30. package/src/hooks/useActiveFeatureFlags.ts +2 -2
  31. package/src/hooks/useFeatureFlagEnabled.ts +2 -2
  32. package/src/hooks/useFeatureFlagPayload.ts +2 -2
  33. package/src/hooks/useFeatureFlagResult.ts +2 -2
  34. package/src/hooks/useFeatureFlagVariantKey.ts +2 -2
  35. package/src/hooks/useInsights.ts +7 -0
  36. package/src/hooks/useThumbSurvey.ts +13 -13
  37. package/src/utils/type-utils.ts +2 -2
  38. package/surveys/package.json +1 -1
  39. package/src/context/PostHogContext.ts +0 -9
  40. package/src/context/PostHogProvider.tsx +0 -136
  41. package/src/hooks/__tests__/usePostHog.test.tsx +0 -19
  42. package/src/hooks/usePostHog.ts +0 -7
@@ -2,20 +2,20 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import { render } from '@testing-library/react'
5
- import { __POSTHOG_ERROR_MESSAGES, PostHogErrorBoundary } from '../PostHogErrorBoundary'
6
- import posthog from '@hanzo/insights'
5
+ import { __INSIGHTS_ERROR_MESSAGES, InsightsErrorBoundary } from '../InsightsErrorBoundary'
6
+ import insights from '@hanzo/insights'
7
7
 
8
- describe('PostHogErrorBoundary component', () => {
8
+ describe('InsightsErrorBoundary component', () => {
9
9
  mockFunction(console, 'error')
10
10
  mockFunction(console, 'warn')
11
- mockFunction(posthog, 'captureException')
11
+ mockFunction(insights, 'captureException')
12
12
 
13
13
  const renderWithError = (props: any) => render(<RenderWithError {...props} />)
14
14
  const renderWithoutError = (props?: any) => render(<RenderWithoutError {...props} />)
15
15
 
16
16
  it('should call captureException with error message', () => {
17
17
  const { container } = renderWithError({ message: 'Test error', fallback: <div></div> })
18
- expect(posthog.captureException).toHaveBeenCalledWith(new Error('Test error'), undefined)
18
+ expect(insights.captureException).toHaveBeenCalledWith(new Error('Test error'), undefined)
19
19
  expect(container.innerHTML).toBe('<div></div>')
20
20
  expect(console.error).toHaveBeenCalledTimes(1)
21
21
  expect((console.error as any).mock.calls[0][1].message).toEqual('Test error')
@@ -23,22 +23,22 @@ describe('PostHogErrorBoundary component', () => {
23
23
 
24
24
  it('should warn user when fallback is null', () => {
25
25
  const { container } = renderWithError({ fallback: null })
26
- expect(posthog.captureException).toHaveBeenCalledWith(new Error('Error'), undefined)
26
+ expect(insights.captureException).toHaveBeenCalledWith(new Error('Error'), undefined)
27
27
  expect(container.innerHTML).toBe('')
28
- expect(console.warn).toHaveBeenCalledWith(__POSTHOG_ERROR_MESSAGES.INVALID_FALLBACK)
28
+ expect(console.warn).toHaveBeenCalledWith(__INSIGHTS_ERROR_MESSAGES.INVALID_FALLBACK)
29
29
  })
30
30
 
31
31
  it('should warn user when fallback is a string', () => {
32
32
  const { container } = renderWithError({ fallback: 'hello' })
33
- expect(posthog.captureException).toHaveBeenCalledWith(new Error('Error'), undefined)
33
+ expect(insights.captureException).toHaveBeenCalledWith(new Error('Error'), undefined)
34
34
  expect(container.innerHTML).toBe('')
35
- expect(console.warn).toHaveBeenCalledWith(__POSTHOG_ERROR_MESSAGES.INVALID_FALLBACK)
35
+ expect(console.warn).toHaveBeenCalledWith(__INSIGHTS_ERROR_MESSAGES.INVALID_FALLBACK)
36
36
  })
37
37
 
38
38
  it('should add additional properties before sending event (as object)', () => {
39
39
  const props = { team_id: '1234' }
40
40
  renderWithError({ message: 'Kaboom', additionalProperties: props })
41
- expect(posthog.captureException).toHaveBeenCalledWith(new Error('Kaboom'), props)
41
+ expect(insights.captureException).toHaveBeenCalledWith(new Error('Kaboom'), props)
42
42
  })
43
43
 
44
44
  it('should add additional properties before sending event (as function)', () => {
@@ -50,7 +50,7 @@ describe('PostHogErrorBoundary component', () => {
50
50
  return props
51
51
  },
52
52
  })
53
- expect(posthog.captureException).toHaveBeenCalledWith(new Error('Kaboom'), props)
53
+ expect(insights.captureException).toHaveBeenCalledWith(new Error('Kaboom'), props)
54
54
  })
55
55
 
56
56
  it('should render children without errors', () => {
@@ -62,13 +62,13 @@ describe('PostHogErrorBoundary component', () => {
62
62
  describe('captureException processing', () => {
63
63
  mockFunction(console, 'error')
64
64
  mockFunction(console, 'warn')
65
- mockFunction(posthog, 'capture')
65
+ mockFunction(insights, 'capture')
66
66
 
67
67
  const renderWithError = (props: any) => render(<RenderWithError {...props} />)
68
68
 
69
69
  it('should call capture with a stacktrace', () => {
70
70
  renderWithError({ message: 'Kaboom', fallback: <div></div>, additionalProperties: {} })
71
- const captureCalls = (posthog.capture as jest.Mock).mock.calls
71
+ const captureCalls = (insights.capture as jest.Mock).mock.calls
72
72
  expect(captureCalls.length).toBe(1)
73
73
  const exceptionList = captureCalls[0][1].$exception_list
74
74
  expect(exceptionList.length).toBe(1)
@@ -95,16 +95,16 @@ function ComponentWithError({ message }: { message: string }): React.ReactElemen
95
95
 
96
96
  function RenderWithError({ message = 'Error', fallback, additionalProperties }: any) {
97
97
  return (
98
- <PostHogErrorBoundary fallback={fallback} additionalProperties={additionalProperties}>
98
+ <InsightsErrorBoundary fallback={fallback} additionalProperties={additionalProperties}>
99
99
  <ComponentWithError message={message} />
100
- </PostHogErrorBoundary>
100
+ </InsightsErrorBoundary>
101
101
  )
102
102
  }
103
103
 
104
104
  function RenderWithoutError({ additionalProperties }: any) {
105
105
  return (
106
- <PostHogErrorBoundary fallback={<div></div>} additionalProperties={additionalProperties}>
106
+ <InsightsErrorBoundary fallback={<div></div>} additionalProperties={additionalProperties}>
107
107
  <div>Amazing content</div>
108
- </PostHogErrorBoundary>
108
+ </InsightsErrorBoundary>
109
109
  )
110
110
  }
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react'
2
2
  import { useState } from 'react'
3
3
  import { render, screen, fireEvent } from '@testing-library/react'
4
- import { PostHogProvider, PostHog } from '../../context'
5
- import { PostHogFeature } from '../'
4
+ import { InsightsProvider, Insights } from '../../context'
5
+ import { InsightsFeature } from '../'
6
6
  import '@testing-library/jest-dom'
7
7
 
8
8
  const FEATURE_FLAG_STATUS: Record<string, string | boolean> = {
@@ -20,16 +20,16 @@ const FEATURE_FLAG_PAYLOADS: Record<string, any> = {
20
20
  },
21
21
  }
22
22
 
23
- describe('PostHogFeature component', () => {
24
- let posthog: PostHog
23
+ describe('InsightsFeature component', () => {
24
+ let insights: Insights
25
25
 
26
- const renderWith = (instance: PostHog, flag = 'test', matchValue: string | boolean | undefined = true) =>
26
+ const renderWith = (instance: Insights, flag = 'test', matchValue: string | boolean | undefined = true) =>
27
27
  render(
28
- <PostHogProvider client={instance}>
29
- <PostHogFeature flag={flag} match={matchValue}>
28
+ <InsightsProvider client={instance}>
29
+ <InsightsFeature flag={flag} match={matchValue}>
30
30
  <div data-testid="helloDiv">Hello</div>
31
- </PostHogFeature>
32
- </PostHogProvider>
31
+ </InsightsFeature>
32
+ </InsightsProvider>
33
33
  )
34
34
 
35
35
  beforeEach(() => {
@@ -44,7 +44,7 @@ describe('PostHogFeature component', () => {
44
44
  // eslint-disable-next-line compat/compat
45
45
  window.IntersectionObserver = mockIntersectionObserver
46
46
 
47
- posthog = {
47
+ insights = {
48
48
  isFeatureEnabled: (flag: string) => !!FEATURE_FLAG_STATUS[flag],
49
49
  getFeatureFlag: (flag: string) => FEATURE_FLAG_STATUS[flag],
50
50
  getFeatureFlagPayload: (flag: string) => FEATURE_FLAG_PAYLOADS[flag],
@@ -62,44 +62,44 @@ describe('PostHogFeature component', () => {
62
62
  featureFlags: {
63
63
  hasLoadedFlags: true,
64
64
  },
65
- } as unknown as PostHog
65
+ } as unknown as Insights
66
66
  })
67
67
 
68
68
  it('should track interactions with the feature component', () => {
69
- renderWith(posthog)
69
+ renderWith(insights)
70
70
 
71
71
  fireEvent.click(screen.getByTestId('helloDiv'))
72
- expect(posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
72
+ expect(insights.capture).toHaveBeenCalledWith('$feature_interaction', {
73
73
  feature_flag: 'test',
74
74
  $set: { '$feature_interaction/test': true },
75
75
  })
76
- expect(posthog.capture).toHaveBeenCalledTimes(1)
76
+ expect(insights.capture).toHaveBeenCalledTimes(1)
77
77
  })
78
78
 
79
79
  it('should not fire for every interaction with the feature component', () => {
80
- renderWith(posthog)
80
+ renderWith(insights)
81
81
 
82
82
  fireEvent.click(screen.getByTestId('helloDiv'))
83
- expect(posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
83
+ expect(insights.capture).toHaveBeenCalledWith('$feature_interaction', {
84
84
  feature_flag: 'test',
85
85
  $set: { '$feature_interaction/test': true },
86
86
  })
87
- expect(posthog.capture).toHaveBeenCalledTimes(1)
87
+ expect(insights.capture).toHaveBeenCalledTimes(1)
88
88
 
89
89
  fireEvent.click(screen.getByTestId('helloDiv'))
90
90
  fireEvent.click(screen.getByTestId('helloDiv'))
91
91
  fireEvent.click(screen.getByTestId('helloDiv'))
92
- expect(posthog.capture).toHaveBeenCalledTimes(1)
92
+ expect(insights.capture).toHaveBeenCalledTimes(1)
93
93
  })
94
94
 
95
95
  it('should track an interaction with each child node of the feature component', () => {
96
96
  render(
97
- <PostHogProvider client={posthog}>
98
- <PostHogFeature flag={'test'} match={true}>
97
+ <InsightsProvider client={insights}>
98
+ <InsightsFeature flag={'test'} match={true}>
99
99
  <div data-testid="helloDiv">Hello</div>
100
100
  <div data-testid="worldDiv">World!</div>
101
- </PostHogFeature>
102
- </PostHogProvider>
101
+ </InsightsFeature>
102
+ </InsightsProvider>
103
103
  )
104
104
 
105
105
  fireEvent.click(screen.getByTestId('helloDiv'))
@@ -107,29 +107,29 @@ describe('PostHogFeature component', () => {
107
107
  fireEvent.click(screen.getByTestId('worldDiv'))
108
108
  fireEvent.click(screen.getByTestId('worldDiv'))
109
109
  fireEvent.click(screen.getByTestId('worldDiv'))
110
- expect(posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
110
+ expect(insights.capture).toHaveBeenCalledWith('$feature_interaction', {
111
111
  feature_flag: 'test',
112
112
  $set: { '$feature_interaction/test': true },
113
113
  })
114
- expect(posthog.capture).toHaveBeenCalledTimes(1)
114
+ expect(insights.capture).toHaveBeenCalledTimes(1)
115
115
  })
116
116
 
117
117
  it('should not fire events when interaction is disabled', () => {
118
118
  render(
119
- <PostHogProvider client={posthog}>
120
- <PostHogFeature flag={'test'} match={true} trackInteraction={false}>
119
+ <InsightsProvider client={insights}>
120
+ <InsightsFeature flag={'test'} match={true} trackInteraction={false}>
121
121
  <div data-testid="helloDiv">Hello</div>
122
- </PostHogFeature>
123
- </PostHogProvider>
122
+ </InsightsFeature>
123
+ </InsightsProvider>
124
124
  )
125
125
 
126
126
  fireEvent.click(screen.getByTestId('helloDiv'))
127
- expect(posthog.capture).not.toHaveBeenCalled()
127
+ expect(insights.capture).not.toHaveBeenCalled()
128
128
 
129
129
  fireEvent.click(screen.getByTestId('helloDiv'))
130
130
  fireEvent.click(screen.getByTestId('helloDiv'))
131
131
  fireEvent.click(screen.getByTestId('helloDiv'))
132
- expect(posthog.capture).not.toHaveBeenCalled()
132
+ expect(insights.capture).not.toHaveBeenCalled()
133
133
  })
134
134
 
135
135
  it('should fire events when interaction is disabled but re-enabled after', () => {
@@ -146,37 +146,37 @@ describe('PostHogFeature component', () => {
146
146
  >
147
147
  Click me
148
148
  </div>
149
- <PostHogFeature flag={'test'} match={true} trackInteraction={trackInteraction}>
149
+ <InsightsFeature flag={'test'} match={true} trackInteraction={trackInteraction}>
150
150
  <div data-testid="helloDiv">Hello</div>
151
- </PostHogFeature>
151
+ </InsightsFeature>
152
152
  </>
153
153
  )
154
154
  }
155
155
 
156
156
  render(
157
- <PostHogProvider client={posthog}>
157
+ <InsightsProvider client={insights}>
158
158
  <DynamicUpdateComponent />
159
- </PostHogProvider>
159
+ </InsightsProvider>
160
160
  )
161
161
 
162
162
  fireEvent.click(screen.getByTestId('helloDiv'))
163
- expect(posthog.capture).not.toHaveBeenCalled()
163
+ expect(insights.capture).not.toHaveBeenCalled()
164
164
 
165
165
  fireEvent.click(screen.getByTestId('clicker'))
166
166
  fireEvent.click(screen.getByTestId('helloDiv'))
167
167
  fireEvent.click(screen.getByTestId('helloDiv'))
168
- expect(posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
168
+ expect(insights.capture).toHaveBeenCalledWith('$feature_interaction', {
169
169
  feature_flag: 'test',
170
170
  $set: { '$feature_interaction/test': true },
171
171
  })
172
- expect(posthog.capture).toHaveBeenCalledTimes(1)
172
+ expect(insights.capture).toHaveBeenCalledTimes(1)
173
173
  })
174
174
 
175
175
  it('should not show the feature component if the flag is not enabled', () => {
176
- renderWith(posthog, 'test_value')
176
+ renderWith(insights, 'test_value')
177
177
 
178
178
  expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
179
- expect(posthog.capture).not.toHaveBeenCalled()
179
+ expect(insights.capture).not.toHaveBeenCalled()
180
180
 
181
181
  // check if any elements are found
182
182
  const allTags = screen.queryAllByText(/.*/)
@@ -189,101 +189,101 @@ describe('PostHogFeature component', () => {
189
189
 
190
190
  it('should fallback when provided', () => {
191
191
  render(
192
- <PostHogProvider client={posthog}>
193
- <PostHogFeature flag={'test_false'} match={true} fallback={<div data-testid="nope">Nope</div>}>
192
+ <InsightsProvider client={insights}>
193
+ <InsightsFeature flag={'test_false'} match={true} fallback={<div data-testid="nope">Nope</div>}>
194
194
  <div data-testid="helloDiv">Hello</div>
195
- </PostHogFeature>
196
- </PostHogProvider>
195
+ </InsightsFeature>
196
+ </InsightsProvider>
197
197
  )
198
198
 
199
199
  expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
200
- expect(posthog.capture).not.toHaveBeenCalled()
200
+ expect(insights.capture).not.toHaveBeenCalled()
201
201
 
202
202
  fireEvent.click(screen.getByTestId('nope'))
203
- expect(posthog.capture).not.toHaveBeenCalled()
203
+ expect(insights.capture).not.toHaveBeenCalled()
204
204
  })
205
205
 
206
206
  it('should handle showing multivariate flags with bool match', () => {
207
- renderWith(posthog, 'multivariate_feature')
207
+ renderWith(insights, 'multivariate_feature')
208
208
 
209
209
  expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
210
- expect(posthog.capture).not.toHaveBeenCalled()
210
+ expect(insights.capture).not.toHaveBeenCalled()
211
211
  })
212
212
 
213
213
  it('should handle showing multivariate flags with incorrect match', () => {
214
- renderWith(posthog, 'multivariate_feature', 'string-valueCXCC')
214
+ renderWith(insights, 'multivariate_feature', 'string-valueCXCC')
215
215
 
216
216
  expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
217
- expect(posthog.capture).not.toHaveBeenCalled()
217
+ expect(insights.capture).not.toHaveBeenCalled()
218
218
  })
219
219
 
220
220
  it('should handle showing multivariate flags', () => {
221
- renderWith(posthog, 'multivariate_feature', 'string-value')
221
+ renderWith(insights, 'multivariate_feature', 'string-value')
222
222
 
223
223
  expect(screen.queryByTestId('helloDiv')).toBeInTheDocument()
224
- expect(posthog.capture).not.toHaveBeenCalled()
224
+ expect(insights.capture).not.toHaveBeenCalled()
225
225
 
226
226
  fireEvent.click(screen.getByTestId('helloDiv'))
227
- expect(posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
227
+ expect(insights.capture).toHaveBeenCalledWith('$feature_interaction', {
228
228
  feature_flag: 'multivariate_feature',
229
229
  feature_flag_variant: 'string-value',
230
230
  $set: { '$feature_interaction/multivariate_feature': 'string-value' },
231
231
  })
232
- expect(posthog.capture).toHaveBeenCalledTimes(1)
232
+ expect(insights.capture).toHaveBeenCalledTimes(1)
233
233
  })
234
234
 
235
235
  it('should handle payload flags', () => {
236
236
  render(
237
- <PostHogProvider client={posthog}>
238
- <PostHogFeature flag={'example_feature_payload'} match={'test'}>
237
+ <InsightsProvider client={insights}>
238
+ <InsightsFeature flag={'example_feature_payload'} match={'test'}>
239
239
  {(payload: any) => {
240
240
  return <div data-testid={`hi_${payload.name}`}>Hullo</div>
241
241
  }}
242
- </PostHogFeature>
243
- </PostHogProvider>
242
+ </InsightsFeature>
243
+ </InsightsProvider>
244
244
  )
245
245
 
246
246
  expect(screen.queryByTestId('hi_example_feature_1_payload')).toBeInTheDocument()
247
- expect(posthog.capture).not.toHaveBeenCalled()
247
+ expect(insights.capture).not.toHaveBeenCalled()
248
248
 
249
249
  fireEvent.click(screen.getByTestId('hi_example_feature_1_payload'))
250
- expect(posthog.capture).toHaveBeenCalledTimes(1)
250
+ expect(insights.capture).toHaveBeenCalledTimes(1)
251
251
  })
252
252
 
253
253
  it('should not render when flag does not exist and no match is specified', () => {
254
254
  render(
255
- <PostHogProvider client={posthog}>
256
- <PostHogFeature flag={'nonexistent_flag'}>
255
+ <InsightsProvider client={insights}>
256
+ <InsightsFeature flag={'nonexistent_flag'}>
257
257
  <div data-testid="helloDiv">Hello</div>
258
- </PostHogFeature>
259
- </PostHogProvider>
258
+ </InsightsFeature>
259
+ </InsightsProvider>
260
260
  )
261
261
 
262
262
  expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
263
- expect(posthog.capture).not.toHaveBeenCalled()
263
+ expect(insights.capture).not.toHaveBeenCalled()
264
264
  })
265
265
 
266
266
  it('should render fallback when flag does not exist (like new-cta example)', () => {
267
267
  render(
268
- <PostHogProvider client={posthog}>
269
- <PostHogFeature flag={'new-cta'} fallback={<div data-testid="oldButton">Old Button</div>}>
268
+ <InsightsProvider client={insights}>
269
+ <InsightsFeature flag={'new-cta'} fallback={<div data-testid="oldButton">Old Button</div>}>
270
270
  <div data-testid="newButton">New Button</div>
271
- </PostHogFeature>
272
- </PostHogProvider>
271
+ </InsightsFeature>
272
+ </InsightsProvider>
273
273
  )
274
274
 
275
275
  expect(screen.queryByTestId('newButton')).not.toBeInTheDocument()
276
276
  expect(screen.queryByTestId('oldButton')).toBeInTheDocument()
277
- expect(posthog.capture).not.toHaveBeenCalled()
277
+ expect(insights.capture).not.toHaveBeenCalled()
278
278
  })
279
279
 
280
280
  it('should render content when match=false and flag variant is false', () => {
281
281
  render(
282
- <PostHogProvider client={posthog}>
283
- <PostHogFeature flag={'test_false'} match={false}>
282
+ <InsightsProvider client={insights}>
283
+ <InsightsFeature flag={'test_false'} match={false}>
284
284
  <div data-testid="disabledUI">Show when disabled</div>
285
- </PostHogFeature>
286
- </PostHogProvider>
285
+ </InsightsFeature>
286
+ </InsightsProvider>
287
287
  )
288
288
 
289
289
  expect(screen.queryByTestId('disabledUI')).toBeInTheDocument()
@@ -1,7 +1,7 @@
1
- export * from './PostHogFeature'
2
- export * from './PostHogCaptureOnViewed'
1
+ export * from './InsightsFeature'
2
+ export * from './InsightsCaptureOnViewed'
3
3
  export {
4
- PostHogErrorBoundary,
5
- PostHogErrorBoundaryProps,
6
- PostHogErrorBoundaryFallbackProps,
7
- } from './PostHogErrorBoundary'
4
+ InsightsErrorBoundary,
5
+ InsightsErrorBoundaryProps,
6
+ InsightsErrorBoundaryFallbackProps,
7
+ } from './InsightsErrorBoundary'
@@ -0,0 +1,9 @@
1
+ import insightsJs, { BootstrapConfig } from '@hanzo/insights'
2
+ import { createContext } from 'react'
3
+
4
+ export type Insights = typeof insightsJs
5
+
6
+ export const InsightsContext = createContext<{ client: Insights; bootstrap?: BootstrapConfig }>({
7
+ client: insightsJs,
8
+ bootstrap: undefined,
9
+ })
@@ -0,0 +1,108 @@
1
+ /* eslint-disable no-console */
2
+ import insightsJs, { InsightsConfig as InsightsConfig } from '@hanzo/insights'
3
+
4
+ import React, { useEffect, useMemo, useRef } from 'react'
5
+ import { Insights, InsightsContext } from './InsightsContext'
6
+ import { isDeepEqual } from '../utils/object-utils'
7
+
8
+ interface PreviousInitialization {
9
+ apiKey: string
10
+ options: Partial<InsightsConfig>
11
+ }
12
+
13
+ type WithOptionalChildren<T> = T & { children?: React.ReactNode | undefined }
14
+
15
+ /**
16
+ * Props for the InsightsProvider component.
17
+ * This is a discriminated union type that ensures mutually exclusive props:
18
+ *
19
+ * - If `client` is provided, `apiKey` and `options` must not be provided
20
+ * - If `apiKey` is provided, `client` must not be provided, and `options` is optional
21
+ */
22
+ type InsightsProviderProps =
23
+ | { client: Insights; apiKey?: never; options?: never }
24
+ | { apiKey: string; options?: Partial<InsightsConfig>; client?: never }
25
+
26
+ /**
27
+ * InsightsProvider is a React context provider for Hanzo Insights analytics.
28
+ * It can be initialized in two mutually exclusive ways:
29
+ *
30
+ * 1. By providing an existing Insights `client` instance
31
+ * 2. By providing an `apiKey` (and optionally `options`) to create a new client
32
+ *
33
+ * These initialization methods are mutually exclusive - you must use one or the other,
34
+ * but not both simultaneously.
35
+ */
36
+ export function InsightsProvider({ children, client, apiKey, options }: WithOptionalChildren<InsightsProviderProps>) {
37
+ const previousInitializationRef = useRef<PreviousInitialization | null>(null)
38
+
39
+ const insights = useMemo(() => {
40
+ if (client) {
41
+ if (apiKey) {
42
+ console.warn(
43
+ '[Insights] You have provided both `client` and `apiKey` to `InsightsProvider`. `apiKey` will be ignored in favour of `client`.'
44
+ )
45
+ }
46
+ if (options) {
47
+ console.warn(
48
+ '[Insights] You have provided both `client` and `options` to `InsightsProvider`. `options` will be ignored in favour of `client`.'
49
+ )
50
+ }
51
+ return client
52
+ }
53
+
54
+ if (apiKey) {
55
+ return insightsJs
56
+ }
57
+
58
+ console.warn(
59
+ '[Insights] No `apiKey` or `client` were provided to `InsightsProvider`. Using default global instance. You must initialize it manually. This is not recommended behavior.'
60
+ )
61
+ return insightsJs
62
+ // eslint-disable-next-line react-hooks/exhaustive-deps
63
+ }, [client, apiKey, JSON.stringify(options)])
64
+
65
+ useEffect(() => {
66
+ if (client) {
67
+ return
68
+ }
69
+ const previousInitialization = previousInitializationRef.current
70
+
71
+ if (!previousInitialization) {
72
+ if (insightsJs.__loaded) {
73
+ console.warn('[Insights] `insights` was already loaded elsewhere. This may cause issues.')
74
+ }
75
+
76
+ insightsJs.init(apiKey, options)
77
+
78
+ previousInitializationRef.current = {
79
+ apiKey: apiKey,
80
+ options: options ?? {},
81
+ }
82
+ } else {
83
+ if (apiKey !== previousInitialization.apiKey) {
84
+ console.warn(
85
+ "[Insights] You have provided a different `apiKey` to `InsightsProvider` than the one that was already initialized. This is not supported by our provider and we'll keep using the previous key. If you need to toggle between API Keys you need to control the `client` yourself and pass it in as a prop rather than an `apiKey` prop."
86
+ )
87
+ }
88
+
89
+ if (options && !isDeepEqual(options, previousInitialization.options)) {
90
+ insightsJs.set_config(options)
91
+ }
92
+
93
+ previousInitializationRef.current = {
94
+ apiKey: apiKey,
95
+ options: options ?? {},
96
+ }
97
+ }
98
+ // eslint-disable-next-line react-hooks/exhaustive-deps
99
+ }, [client, apiKey, JSON.stringify(options)])
100
+
101
+ return (
102
+ <InsightsContext.Provider
103
+ value={{ client: insights, bootstrap: options?.bootstrap ?? client?.config?.bootstrap }}
104
+ >
105
+ {children}
106
+ </InsightsContext.Provider>
107
+ )
108
+ }
@@ -1,15 +1,15 @@
1
1
  import * as React from 'react'
2
2
  import { render } from '@testing-library/react'
3
- import { PostHogProvider, PostHog } from '..'
3
+ import { InsightsProvider, Insights } from '..'
4
4
 
5
- describe('PostHogContext component', () => {
6
- const posthog = {} as unknown as PostHog
5
+ describe('InsightsContext component', () => {
6
+ const insights = {} as unknown as Insights
7
7
 
8
8
  it('should return a client instance from the context if available', () => {
9
9
  render(
10
- <PostHogProvider client={posthog}>
10
+ <InsightsProvider client={insights}>
11
11
  <div>Hello</div>
12
- </PostHogProvider>
12
+ </InsightsProvider>
13
13
  )
14
14
  })
15
15
 
@@ -20,16 +20,16 @@ describe('PostHogContext component', () => {
20
20
  expect(() => {
21
21
  render(
22
22
  // we have to cast `as any` so that we can test for when
23
- // posthog might not exist - in SSR for example
24
- <PostHogProvider client={undefined as any}>
23
+ // insights might not exist - in SSR for example
24
+ <InsightsProvider client={undefined as any}>
25
25
  <div>Hello</div>
26
- </PostHogProvider>
26
+ </InsightsProvider>
27
27
  )
28
28
  }).not.toThrow()
29
29
 
30
30
  // eslint-disable-next-line no-console
31
31
  expect(console.warn).toHaveBeenCalledWith(
32
- '[PostHog.js] No `apiKey` or `client` were provided to `PostHogProvider`. Using default global `window.posthog` instance. You must initialize it manually. This is not recommended behavior.'
32
+ '[Insights] No `apiKey` or `client` were provided to `InsightsProvider`. Using default global instance. You must initialize it manually. This is not recommended behavior.'
33
33
  )
34
34
  })
35
35
  })