featureObserver.spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {ProjectFixture} from 'sentry-fixture/project';
  3. import type {Organization} from 'sentry/types/organization';
  4. import type {Project} from 'sentry/types/project';
  5. import FeatureObserver from 'sentry/utils/featureObserver';
  6. describe('FeatureObserver', () => {
  7. let organization: Organization;
  8. let project: Project;
  9. beforeEach(() => {
  10. organization = OrganizationFixture({
  11. features: ['enable-issues', 'enable-profiling', 'enable-replay'],
  12. });
  13. project = ProjectFixture({
  14. features: ['enable-proj-flag', 'enable-performance'],
  15. });
  16. });
  17. describe('observeOrganizationFlags', () => {
  18. it('should add recently evaluated org flags to the flag queue', () => {
  19. const inst = new FeatureObserver({bufferSize: 3});
  20. expect(organization.features).toEqual([
  21. 'enable-issues',
  22. 'enable-profiling',
  23. 'enable-replay',
  24. ]);
  25. inst.observeOrganizationFlags({organization});
  26. expect(inst.getFeatureFlags().values).toEqual([]);
  27. organization.features.includes('enable-issues');
  28. organization.features.includes('replay-mobile-ui');
  29. expect(inst.getFeatureFlags().values).toEqual([
  30. {flag: 'feature.organizations:enable-issues', result: true},
  31. {flag: 'feature.organizations:replay-mobile-ui', result: false},
  32. ]);
  33. // do more evaluations to fill up and overflow the buffer
  34. organization.features.includes('enable-replay');
  35. organization.features.includes('autofix-ui');
  36. organization.features.includes('new-issue-details');
  37. expect(inst.getFeatureFlags().values).toEqual([
  38. {flag: 'feature.organizations:enable-replay', result: true},
  39. {flag: 'feature.organizations:autofix-ui', result: false},
  40. {flag: 'feature.organizations:new-issue-details', result: false},
  41. ]);
  42. });
  43. it('should remove duplicate flags with a full queue', () => {
  44. const inst = new FeatureObserver({bufferSize: 3});
  45. inst.observeOrganizationFlags({organization});
  46. expect(inst.getFeatureFlags().values).toEqual([]);
  47. organization.features.includes('enable-issues');
  48. organization.features.includes('replay-mobile-ui');
  49. organization.features.includes('enable-discover');
  50. expect(inst.getFeatureFlags().values).toEqual([
  51. {flag: 'feature.organizations:enable-issues', result: true},
  52. {flag: 'feature.organizations:replay-mobile-ui', result: false},
  53. {flag: 'feature.organizations:enable-discover', result: false},
  54. ]);
  55. // this is already in the queue; it should be removed and
  56. // added back to the end of the queue
  57. organization.features.includes('enable-issues');
  58. expect(inst.getFeatureFlags().values).toEqual([
  59. {flag: 'feature.organizations:replay-mobile-ui', result: false},
  60. {flag: 'feature.organizations:enable-discover', result: false},
  61. {flag: 'feature.organizations:enable-issues', result: true},
  62. ]);
  63. organization.features.includes('spam-ingest');
  64. expect(inst.getFeatureFlags().values).toEqual([
  65. {flag: 'feature.organizations:enable-discover', result: false},
  66. {flag: 'feature.organizations:enable-issues', result: true},
  67. {flag: 'feature.organizations:spam-ingest', result: false},
  68. ]);
  69. // this is already in the queue but in the back
  70. // the queue should not change
  71. organization.features.includes('spam-ingest');
  72. expect(inst.getFeatureFlags().values).toEqual([
  73. {flag: 'feature.organizations:enable-discover', result: false},
  74. {flag: 'feature.organizations:enable-issues', result: true},
  75. {flag: 'feature.organizations:spam-ingest', result: false},
  76. ]);
  77. });
  78. it('should remove duplicate flags with an unfilled queue', () => {
  79. const inst = new FeatureObserver({bufferSize: 3});
  80. inst.observeOrganizationFlags({organization});
  81. expect(inst.getFeatureFlags().values).toEqual([]);
  82. organization.features.includes('enable-issues');
  83. organization.features.includes('replay-mobile-ui');
  84. expect(inst.getFeatureFlags().values).toEqual([
  85. {flag: 'feature.organizations:enable-issues', result: true},
  86. {flag: 'feature.organizations:replay-mobile-ui', result: false},
  87. ]);
  88. // this is already in the queue; it should be removed and
  89. // added back to the end of the queue
  90. organization.features.includes('enable-issues');
  91. expect(inst.getFeatureFlags().values).toEqual([
  92. {flag: 'feature.organizations:replay-mobile-ui', result: false},
  93. {flag: 'feature.organizations:enable-issues', result: true},
  94. ]);
  95. // this is already in the queue but in the back
  96. // the queue should not change
  97. organization.features.includes('enable-issues');
  98. expect(inst.getFeatureFlags().values).toEqual([
  99. {flag: 'feature.organizations:replay-mobile-ui', result: false},
  100. {flag: 'feature.organizations:enable-issues', result: true},
  101. ]);
  102. });
  103. it('should not change the functionality of `includes`', () => {
  104. const inst = new FeatureObserver({bufferSize: 3});
  105. inst.observeOrganizationFlags({organization});
  106. expect(inst.getFeatureFlags().values).toEqual([]);
  107. organization.features.includes('enable-issues');
  108. organization.features.includes('replay-mobile-ui');
  109. expect(inst.getFeatureFlags().values).toEqual([
  110. {flag: 'feature.organizations:enable-issues', result: true},
  111. {flag: 'feature.organizations:replay-mobile-ui', result: false},
  112. ]);
  113. expect(organization.features.includes('enable-issues')).toBe(true);
  114. expect(organization.features.includes('replay-mobile-ui')).toBe(false);
  115. });
  116. });
  117. describe('observeProjectFlags', () => {
  118. it('should add recently evaluated proj flags to the flag queue', () => {
  119. const inst = new FeatureObserver({bufferSize: 3});
  120. expect(project.features).toEqual(['enable-proj-flag', 'enable-performance']);
  121. inst.observeProjectFlags({project});
  122. expect(inst.getFeatureFlags().values).toEqual([]);
  123. project.features.includes('enable-proj-flag');
  124. project.features.includes('replay-mobile-ui');
  125. expect(inst.getFeatureFlags().values).toEqual([
  126. {flag: 'feature.projects:enable-proj-flag', result: true},
  127. {flag: 'feature.projects:replay-mobile-ui', result: false},
  128. ]);
  129. // do more evaluations to fill up and overflow the buffer
  130. project.features.includes('enable-performance');
  131. project.features.includes('autofix-ui');
  132. project.features.includes('new-issue-details');
  133. expect(inst.getFeatureFlags().values).toEqual([
  134. {flag: 'feature.projects:enable-performance', result: true},
  135. {flag: 'feature.projects:autofix-ui', result: false},
  136. {flag: 'feature.projects:new-issue-details', result: false},
  137. ]);
  138. });
  139. it('should not change the functionality of `includes`', () => {
  140. const inst = new FeatureObserver({bufferSize: 3});
  141. inst.observeProjectFlags({project});
  142. expect(inst.getFeatureFlags().values).toEqual([]);
  143. project.features.includes('enable-proj-flag');
  144. project.features.includes('replay-mobile-ui');
  145. expect(inst.getFeatureFlags().values).toEqual([
  146. {flag: 'feature.projects:enable-proj-flag', result: true},
  147. {flag: 'feature.projects:replay-mobile-ui', result: false},
  148. ]);
  149. expect(project.features.includes('enable-proj-flag')).toBe(true);
  150. expect(project.features.includes('replay-mobile-ui')).toBe(false);
  151. });
  152. });
  153. describe('observeProjectFlags and observeOrganizationFlags', () => {
  154. it('should add recently evaluated org and proj flags to the flag queue', () => {
  155. const inst = new FeatureObserver({bufferSize: 3});
  156. expect(project.features).toEqual(['enable-proj-flag', 'enable-performance']);
  157. expect(organization.features).toEqual([
  158. 'enable-issues',
  159. 'enable-profiling',
  160. 'enable-replay',
  161. ]);
  162. inst.observeProjectFlags({project});
  163. inst.observeOrganizationFlags({organization});
  164. expect(inst.getFeatureFlags().values).toEqual([]);
  165. project.features.includes('enable-proj-flag');
  166. project.features.includes('enable-replay');
  167. organization.features.includes('enable-issues');
  168. expect(inst.getFeatureFlags().values).toEqual([
  169. {flag: 'feature.projects:enable-proj-flag', result: true},
  170. {flag: 'feature.projects:enable-replay', result: false},
  171. {flag: 'feature.organizations:enable-issues', result: true},
  172. ]);
  173. organization.features.includes('enable-replay');
  174. expect(inst.getFeatureFlags().values).toEqual([
  175. {flag: 'feature.projects:enable-replay', result: false},
  176. {flag: 'feature.organizations:enable-issues', result: true},
  177. {flag: 'feature.organizations:enable-replay', result: true},
  178. ]);
  179. });
  180. it('should keep the same queue if the project changes', () => {
  181. const inst = new FeatureObserver({bufferSize: 3});
  182. expect(project.features).toEqual(['enable-proj-flag', 'enable-performance']);
  183. expect(organization.features).toEqual([
  184. 'enable-issues',
  185. 'enable-profiling',
  186. 'enable-replay',
  187. ]);
  188. inst.observeProjectFlags({project});
  189. inst.observeOrganizationFlags({organization});
  190. expect(inst.getFeatureFlags().values).toEqual([]);
  191. project.features.includes('enable-proj-flag');
  192. project.features.includes('enable-replay');
  193. organization.features.includes('enable-issues');
  194. expect(inst.getFeatureFlags().values).toEqual([
  195. {flag: 'feature.projects:enable-proj-flag', result: true},
  196. {flag: 'feature.projects:enable-replay', result: false},
  197. {flag: 'feature.organizations:enable-issues', result: true},
  198. ]);
  199. project = ProjectFixture({
  200. features: ['enable-new-flag'],
  201. });
  202. inst.observeProjectFlags({project});
  203. expect(inst.getFeatureFlags().values).toEqual([
  204. {flag: 'feature.projects:enable-proj-flag', result: true},
  205. {flag: 'feature.projects:enable-replay', result: false},
  206. {flag: 'feature.organizations:enable-issues', result: true},
  207. ]);
  208. project.features.includes('enable-new-flag');
  209. expect(inst.getFeatureFlags().values).toEqual([
  210. {flag: 'feature.projects:enable-replay', result: false},
  211. {flag: 'feature.organizations:enable-issues', result: true},
  212. {flag: 'feature.projects:enable-new-flag', result: true},
  213. ]);
  214. });
  215. });
  216. });