posthog-node 2.2.2 → 2.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.
@@ -12,10 +12,12 @@ const mockedFetch = jest.mocked(fetch, true)
12
12
  export const apiImplementation = ({
13
13
  localFlags,
14
14
  decideFlags,
15
+ decideFlagPayloads,
15
16
  decideStatus = 200,
16
17
  }: {
17
18
  localFlags?: any
18
19
  decideFlags?: any
20
+ decideFlagPayloads?: any
19
21
  decideStatus?: number
20
22
  }) => {
21
23
  return (url: any): Promise<any> => {
@@ -29,6 +31,7 @@ export const apiImplementation = ({
29
31
  } else {
30
32
  return Promise.resolve({
31
33
  featureFlags: decideFlags,
34
+ featureFlagPayloads: decideFlagPayloads,
32
35
  })
33
36
  }
34
37
  },
@@ -58,7 +61,7 @@ export const anyLocalEvalCall = [
58
61
  'http://example.com/api/feature_flag/local_evaluation?token=TEST_API_KEY',
59
62
  expect.any(Object),
60
63
  ]
61
- export const anyDecideCall = ['http://example.com/decide/?v=2', expect.any(Object)]
64
+ export const anyDecideCall = ['http://example.com/decide/?v=3', expect.any(Object)]
62
65
 
63
66
  describe('local evaluation', () => {
64
67
  let posthog: PostHog
@@ -325,7 +328,7 @@ describe('local evaluation', () => {
325
328
  })
326
329
  ).toEqual('decide-fallback-value')
327
330
  expect(mockedFetch).toHaveBeenCalledWith(
328
- 'http://example.com/decide/?v=2',
331
+ 'http://example.com/decide/?v=3',
329
332
  expect.objectContaining({
330
333
  body: JSON.stringify({
331
334
  token: 'TEST_API_KEY',
@@ -343,7 +346,7 @@ describe('local evaluation', () => {
343
346
  await posthog.getFeatureFlag('complex-flag', 'some-distinct-id', { personProperties: { doesnt_matter: '1' } })
344
347
  ).toEqual('decide-fallback-value')
345
348
  expect(mockedFetch).toHaveBeenCalledWith(
346
- 'http://example.com/decide/?v=2',
349
+ 'http://example.com/decide/?v=3',
347
350
  expect.objectContaining({
348
351
  body: JSON.stringify({
349
352
  token: 'TEST_API_KEY',
@@ -687,6 +690,88 @@ describe('local evaluation', () => {
687
690
  mockedFetch.mockClear()
688
691
  })
689
692
 
693
+ it('get all payloads with fallback', async () => {
694
+ const flags = {
695
+ flags: [
696
+ {
697
+ id: 1,
698
+ name: 'Beta Feature',
699
+ key: 'beta-feature',
700
+ is_simple_flag: false,
701
+ active: true,
702
+ rollout_percentage: 100,
703
+ filters: {
704
+ groups: [
705
+ {
706
+ properties: [],
707
+ rollout_percentage: 100,
708
+ },
709
+ ],
710
+ payloads: {
711
+ true: 'some-payload',
712
+ },
713
+ },
714
+ },
715
+ {
716
+ id: 2,
717
+ name: 'Beta Feature',
718
+ key: 'disabled-feature',
719
+ is_simple_flag: false,
720
+ active: true,
721
+ filters: {
722
+ groups: [
723
+ {
724
+ properties: [],
725
+ rollout_percentage: 0,
726
+ },
727
+ ],
728
+ payloads: {
729
+ true: 'another-payload',
730
+ },
731
+ },
732
+ },
733
+ {
734
+ id: 3,
735
+ name: 'Beta Feature',
736
+ key: 'beta-feature2',
737
+ is_simple_flag: false,
738
+ active: true,
739
+ filters: {
740
+ groups: [
741
+ {
742
+ properties: [{ key: 'country', value: 'US' }],
743
+ rollout_percentage: 0,
744
+ },
745
+ ],
746
+ payloads: {
747
+ true: 'payload-3',
748
+ },
749
+ },
750
+ },
751
+ ],
752
+ }
753
+ mockedFetch.mockImplementation(
754
+ apiImplementation({
755
+ localFlags: flags,
756
+ decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
757
+ decideFlagPayloads: { 'beta-feature': 100, 'beta-feature2': 300 },
758
+ })
759
+ )
760
+
761
+ posthog = new PostHog('TEST_API_KEY', {
762
+ host: 'http://example.com',
763
+ personalApiKey: 'TEST_PERSONAL_API_KEY',
764
+ })
765
+
766
+ // # beta-feature value overridden by /decide
767
+ expect((await posthog.getAllFlagsAndPayloads('distinct-id')).featureFlagPayloads).toEqual({
768
+ 'beta-feature': 100,
769
+ 'beta-feature2': 300,
770
+ })
771
+ expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
772
+ mockedFetch.mockClear()
773
+ })
774
+
690
775
  it('get all flags with fallback but only_locally_evaluated set', async () => {
691
776
  const flags = {
692
777
  flags: [
@@ -758,6 +843,87 @@ describe('local evaluation', () => {
758
843
  expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
759
844
  })
760
845
 
846
+ it('get all payloads with fallback but only_evaluate_locally set', async () => {
847
+ const flags = {
848
+ flags: [
849
+ {
850
+ id: 1,
851
+ name: 'Beta Feature',
852
+ key: 'beta-feature',
853
+ is_simple_flag: false,
854
+ active: true,
855
+ rollout_percentage: 100,
856
+ filters: {
857
+ groups: [
858
+ {
859
+ properties: [],
860
+ rollout_percentage: 100,
861
+ },
862
+ ],
863
+ payloads: {
864
+ true: 'some-payload',
865
+ },
866
+ },
867
+ },
868
+ {
869
+ id: 2,
870
+ name: 'Beta Feature',
871
+ key: 'disabled-feature',
872
+ is_simple_flag: false,
873
+ active: true,
874
+ filters: {
875
+ groups: [
876
+ {
877
+ properties: [],
878
+ rollout_percentage: 0,
879
+ },
880
+ ],
881
+ payloads: {
882
+ true: 'another-payload',
883
+ },
884
+ },
885
+ },
886
+ {
887
+ id: 3,
888
+ name: 'Beta Feature',
889
+ key: 'beta-feature2',
890
+ is_simple_flag: false,
891
+ active: true,
892
+ filters: {
893
+ groups: [
894
+ {
895
+ properties: [{ key: 'country', value: 'US' }],
896
+ rollout_percentage: 0,
897
+ },
898
+ ],
899
+ payloads: {
900
+ true: 'payload-3',
901
+ },
902
+ },
903
+ },
904
+ ],
905
+ }
906
+ mockedFetch.mockImplementation(
907
+ apiImplementation({
908
+ localFlags: flags,
909
+ decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
910
+ decideFlagPayloads: { 'beta-feature': 100, 'beta-feature2': 300 },
911
+ })
912
+ )
913
+
914
+ posthog = new PostHog('TEST_API_KEY', {
915
+ host: 'http://example.com',
916
+ personalApiKey: 'TEST_PERSONAL_API_KEY',
917
+ })
918
+
919
+ expect(
920
+ (await posthog.getAllFlagsAndPayloads('distinct-id', { onlyEvaluateLocally: true })).featureFlagPayloads
921
+ ).toEqual({
922
+ 'beta-feature': 'some-payload',
923
+ })
924
+ expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
925
+ })
926
+
761
927
  it('get all flags with fallback, with no local flags', async () => {
762
928
  const flags = {
763
929
  flags: [],
@@ -782,6 +948,31 @@ describe('local evaluation', () => {
782
948
  mockedFetch.mockClear()
783
949
  })
784
950
 
951
+ it('get all payloads with fallback, with no local payloads', async () => {
952
+ const flags = {
953
+ flags: [],
954
+ }
955
+ mockedFetch.mockImplementation(
956
+ apiImplementation({
957
+ localFlags: flags,
958
+ decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
959
+ decideFlagPayloads: { 'beta-feature': 100, 'beta-feature2': 300 },
960
+ })
961
+ )
962
+
963
+ posthog = new PostHog('TEST_API_KEY', {
964
+ host: 'http://example.com',
965
+ personalApiKey: 'TEST_PERSONAL_API_KEY',
966
+ })
967
+
968
+ expect((await posthog.getAllFlagsAndPayloads('distinct-id')).featureFlagPayloads).toEqual({
969
+ 'beta-feature': 100,
970
+ 'beta-feature2': 300,
971
+ })
972
+ expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
973
+ mockedFetch.mockClear()
974
+ })
975
+
785
976
  it('get all flags with no fallback', async () => {
786
977
  const flags = {
787
978
  flags: [
@@ -834,6 +1025,64 @@ describe('local evaluation', () => {
834
1025
  expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
835
1026
  })
836
1027
 
1028
+ it('get all payloads with no fallback', async () => {
1029
+ const flags = {
1030
+ flags: [
1031
+ {
1032
+ id: 1,
1033
+ name: 'Beta Feature',
1034
+ key: 'beta-feature',
1035
+ is_simple_flag: false,
1036
+ active: true,
1037
+ rollout_percentage: 100,
1038
+ filters: {
1039
+ groups: [
1040
+ {
1041
+ properties: [],
1042
+ rollout_percentage: 100,
1043
+ },
1044
+ ],
1045
+ payloads: {
1046
+ true: 'new',
1047
+ },
1048
+ },
1049
+ },
1050
+ {
1051
+ id: 2,
1052
+ name: 'Beta Feature',
1053
+ key: 'disabled-feature',
1054
+ is_simple_flag: false,
1055
+ active: true,
1056
+ filters: {
1057
+ groups: [
1058
+ {
1059
+ properties: [],
1060
+ rollout_percentage: 0,
1061
+ },
1062
+ ],
1063
+ payloads: {
1064
+ true: 'some-payload',
1065
+ },
1066
+ },
1067
+ },
1068
+ ],
1069
+ }
1070
+ mockedFetch.mockImplementation(
1071
+ apiImplementation({
1072
+ localFlags: flags,
1073
+ decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
1074
+ })
1075
+ )
1076
+
1077
+ posthog = new PostHog('TEST_API_KEY', {
1078
+ host: 'http://example.com',
1079
+ personalApiKey: 'TEST_PERSONAL_API_KEY',
1080
+ })
1081
+
1082
+ expect((await posthog.getAllFlagsAndPayloads('distinct-id')).featureFlagPayloads).toEqual({ 'beta-feature': 'new' })
1083
+ expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1084
+ })
1085
+
837
1086
  it('computes inactive flags locally as well', async () => {
838
1087
  const flags = {
839
1088
  flags: [
@@ -1222,6 +1471,136 @@ describe('local evaluation', () => {
1222
1471
  // decide not called
1223
1472
  expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1224
1473
  })
1474
+
1475
+ it('get feature flag payload based on boolean flag', async () => {
1476
+ const flags = {
1477
+ flags: [
1478
+ {
1479
+ id: 1,
1480
+ name: 'Beta Feature',
1481
+ key: 'person-flag',
1482
+ is_simple_flag: true,
1483
+ active: true,
1484
+ filters: {
1485
+ groups: [
1486
+ {
1487
+ properties: [
1488
+ {
1489
+ key: 'region',
1490
+ operator: 'exact',
1491
+ value: ['USA'],
1492
+ type: 'person',
1493
+ },
1494
+ ],
1495
+ rollout_percentage: null,
1496
+ },
1497
+ ],
1498
+ payloads: {
1499
+ true: {
1500
+ log: 'all',
1501
+ },
1502
+ },
1503
+ },
1504
+ },
1505
+ ],
1506
+ }
1507
+ mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1508
+
1509
+ posthog = new PostHog('TEST_API_KEY', {
1510
+ host: 'http://example.com',
1511
+ personalApiKey: 'TEST_PERSONAL_API_KEY',
1512
+ })
1513
+
1514
+ expect(
1515
+ await posthog.getFeatureFlagPayload('person-flag', 'some-distinct-id', true, {
1516
+ personProperties: { region: 'USA' },
1517
+ })
1518
+ ).toEqual({
1519
+ log: 'all',
1520
+ })
1521
+ expect(
1522
+ await posthog.getFeatureFlagPayload('person-flag', 'some-distinct-id', undefined, {
1523
+ personProperties: { region: 'USA' },
1524
+ })
1525
+ ).toEqual({
1526
+ log: 'all',
1527
+ })
1528
+ expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1529
+ // decide not called
1530
+ expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1531
+ })
1532
+
1533
+ it('get feature flag payload on a multivariate', async () => {
1534
+ const flags = {
1535
+ flags: [
1536
+ {
1537
+ id: 1,
1538
+ name: 'Beta Feature',
1539
+ key: 'beta-feature',
1540
+ is_simple_flag: true,
1541
+ active: true,
1542
+ filters: {
1543
+ groups: [
1544
+ {
1545
+ properties: [
1546
+ {
1547
+ key: 'email',
1548
+ operator: 'exact',
1549
+ value: 'test@posthog.com',
1550
+ type: 'person',
1551
+ },
1552
+ ],
1553
+ rollout_percentage: 100,
1554
+ variant: 'second-variant',
1555
+ },
1556
+ {
1557
+ rollout_percentage: 50,
1558
+ variant: 'first-variant',
1559
+ },
1560
+ ],
1561
+ multivariate: {
1562
+ variants: [
1563
+ {
1564
+ key: 'first-variant',
1565
+ name: 'First Variant',
1566
+ rollout_percentage: 50,
1567
+ },
1568
+ {
1569
+ key: 'second-variant',
1570
+ name: 'Second Variant',
1571
+ rollout_percentage: 25,
1572
+ },
1573
+ {
1574
+ key: 'third-variant',
1575
+ name: 'Third Variant',
1576
+ rollout_percentage: 25,
1577
+ },
1578
+ ],
1579
+ },
1580
+ payloads: {
1581
+ 'second-variant': 2500,
1582
+ },
1583
+ },
1584
+ },
1585
+ ],
1586
+ }
1587
+ mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1588
+
1589
+ posthog = new PostHog('TEST_API_KEY', {
1590
+ host: 'http://example.com',
1591
+ personalApiKey: 'TEST_PERSONAL_API_KEY',
1592
+ })
1593
+
1594
+ expect(
1595
+ await posthog.getFeatureFlagPayload('beta-feature', 'test_id', 'second-variant', {
1596
+ personProperties: { email: 'test@posthog.com' },
1597
+ })
1598
+ ).toEqual(2500)
1599
+
1600
+ expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1601
+ // decide not called
1602
+ expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1603
+ })
1225
1604
  })
1226
1605
 
1227
1606
  describe('match properties', () => {
@@ -138,6 +138,27 @@ describe('PostHog Node.js', () => {
138
138
  })
139
139
  })
140
140
 
141
+ describe('groupIdentify', () => {
142
+ it('should identify group with unique id', () => {
143
+ posthog.groupIdentify({ groupType: 'posthog', groupKey: 'team-1', properties: { analytics: true } })
144
+ jest.runOnlyPendingTimers()
145
+ const batchEvents = getLastBatchEvents()
146
+ console.log(batchEvents)
147
+ expect(batchEvents).toMatchObject([
148
+ {
149
+ distinct_id: '$posthog_team-1',
150
+ event: '$groupidentify',
151
+ properties: {
152
+ $group_type: 'posthog',
153
+ $group_key: 'team-1',
154
+ $group_set: { analytics: true },
155
+ $lib: 'posthog-node',
156
+ },
157
+ },
158
+ ])
159
+ })
160
+ })
161
+
141
162
  describe('feature flags', () => {
142
163
  beforeEach(() => {
143
164
  const mockFeatureFlags = {
@@ -146,7 +167,14 @@ describe('PostHog Node.js', () => {
146
167
  'feature-variant': 'variant',
147
168
  }
148
169
 
149
- mockedFetch.mockImplementation(apiImplementation({ decideFlags: mockFeatureFlags }))
170
+ const mockFeatureFlagPayloads = {
171
+ 'feature-1': { color: 'blue' },
172
+ 'feature-variant': 2,
173
+ }
174
+
175
+ mockedFetch.mockImplementation(
176
+ apiImplementation({ decideFlags: mockFeatureFlags, decideFlagPayloads: mockFeatureFlagPayloads })
177
+ )
150
178
 
151
179
  posthog = new PostHog('TEST_API_KEY', {
152
180
  host: 'http://example.com',
@@ -186,7 +214,7 @@ describe('PostHog Node.js', () => {
186
214
  })
187
215
 
188
216
  expect(mockedFetch).toHaveBeenCalledWith(
189
- 'http://example.com/decide/?v=2',
217
+ 'http://example.com/decide/?v=3',
190
218
  expect.objectContaining({ method: 'POST' })
191
219
  )
192
220
 
@@ -410,5 +438,21 @@ describe('PostHog Node.js', () => {
410
438
  expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
411
439
  expect(mockedFetch).not.toHaveBeenCalledWith('http://example.com/batch/', expect.any(Object))
412
440
  })
441
+
442
+ it('should do getFeatureFlagPayloads', async () => {
443
+ expect(mockedFetch).toHaveBeenCalledTimes(0)
444
+ await expect(
445
+ posthog.getFeatureFlagPayload('feature-variant', '123', 'variant', { groups: { org: '123' } })
446
+ ).resolves.toEqual(2)
447
+ expect(mockedFetch).toHaveBeenCalledTimes(1)
448
+ })
449
+
450
+ it('should do getFeatureFlagPayloads without matchValue', async () => {
451
+ expect(mockedFetch).toHaveBeenCalledTimes(0)
452
+ await expect(
453
+ posthog.getFeatureFlagPayload('feature-variant', '123', undefined, { groups: { org: '123' } })
454
+ ).resolves.toEqual(2)
455
+ expect(mockedFetch).toHaveBeenCalledTimes(1)
456
+ })
413
457
  })
414
458
  })