Browse Source

feat(workflow): Calculate adoption % by selected period (#28165)

Scott Cooper 3 years ago
parent
commit
7d5422c095

+ 75 - 4
static/app/views/releases/utils/releaseHealthRequest.tsx

@@ -106,6 +106,8 @@ type State = {
   totalCountByReleaseIn24h: SessionApiResponse | null;
   totalCountByProjectIn24h: SessionApiResponse | null;
   statusCountByProjectInPeriod: SessionApiResponse | null;
+  totalCountByReleaseInPeriod: SessionApiResponse | null;
+  totalCountByProjectInPeriod: SessionApiResponse | null;
 };
 
 class ReleaseHealthRequest extends React.Component<Props, State> {
@@ -116,6 +118,8 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
     totalCountByReleaseIn24h: null,
     totalCountByProjectIn24h: null,
     statusCountByProjectInPeriod: null,
+    totalCountByReleaseInPeriod: null,
+    totalCountByProjectInPeriod: null,
   };
 
   componentDidMount() {
@@ -184,6 +188,8 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
 
     if (healthStatsPeriod === HealthStatsPeriodOption.AUTO) {
       promises.push(this.fetchStatusCountByProjectInPeriod());
+      promises.push(this.fetchTotalCountByReleaseInPeriod());
+      promises.push(this.fetchTotalCountByProjectInPeriod());
     }
 
     try {
@@ -192,6 +198,8 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
         totalCountByReleaseIn24h,
         totalCountByProjectIn24h,
         statusCountByProjectInPeriod,
+        totalCountByReleaseInPeriod,
+        totalCountByProjectInPeriod,
       ] = await Promise.all(promises);
 
       this.setState({
@@ -200,6 +208,8 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
         totalCountByReleaseIn24h,
         totalCountByProjectIn24h,
         statusCountByProjectInPeriod,
+        totalCountByReleaseInPeriod,
+        totalCountByProjectInPeriod,
       });
     } catch (error) {
       addErrorMessage(error.responseJSON?.detail ?? t('Error loading health data'));
@@ -268,6 +278,20 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
     return response;
   }
 
+  async fetchTotalCountByReleaseInPeriod() {
+    const {api, display} = this.props;
+
+    const response: SessionApiResponse = await api.requestPromise(this.path, {
+      query: {
+        ...this.baseQueryParams,
+        field: display.map(d => sessionDisplayToField(d)),
+        groupBy: ['project', 'release'],
+      },
+    });
+
+    return response;
+  }
+
   /**
    * Used to calculate adoption, and count histogram (Total Project series)
    */
@@ -288,6 +312,21 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
     return response;
   }
 
+  async fetchTotalCountByProjectInPeriod() {
+    const {api, display} = this.props;
+
+    const response: SessionApiResponse = await api.requestPromise(this.path, {
+      query: {
+        ...this.baseQueryParams,
+        query: undefined,
+        field: display.map(d => sessionDisplayToField(d)),
+        groupBy: ['project'],
+      },
+    });
+
+    return response;
+  }
+
   getHealthData = () => {
     // TODO(sessions): investigate if this needs to be optimized to lower O(n) complexity
     return {
@@ -336,6 +375,19 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
       ?.reduce((acc, group) => acc + group.totals[field], 0);
   };
 
+  getPeriodCountByRelease = (
+    version: string,
+    project: number,
+    display: DisplayOption
+  ) => {
+    const {totalCountByReleaseInPeriod} = this.state;
+    const field = sessionDisplayToField(display);
+
+    return totalCountByReleaseInPeriod?.groups
+      .filter(({by}) => by.release === version && by.project === project)
+      ?.reduce((acc, group) => acc + group.totals[field], 0);
+  };
+
   get24hCountByProject = (project: number, display: DisplayOption) => {
     const {totalCountByProjectIn24h} = this.state;
     const field = sessionDisplayToField(display);
@@ -345,6 +397,15 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
       ?.reduce((acc, group) => acc + group.totals[field], 0);
   };
 
+  getPeriodCountByProject = (project: number, display: DisplayOption) => {
+    const {totalCountByProjectInPeriod} = this.state;
+    const field = sessionDisplayToField(display);
+
+    return totalCountByProjectInPeriod?.groups
+      .filter(({by}) => by.project === project)
+      ?.reduce((acc, group) => acc + group.totals[field], 0);
+  };
+
   getTimeSeries = (version: string, project: number, display: DisplayOption) => {
     const {healthStatsPeriod} = this.props;
     if (healthStatsPeriod === HealthStatsPeriodOption.AUTO) {
@@ -421,11 +482,21 @@ class ReleaseHealthRequest extends React.Component<Props, State> {
   };
 
   getAdoption = (version: string, project: number, display: DisplayOption) => {
-    const get24hCountByRelease = this.get24hCountByRelease(version, project, display);
-    const get24hCountByProject = this.get24hCountByProject(project, display);
+    const {healthStatsPeriod} = this.props;
 
-    return defined(get24hCountByRelease) && defined(get24hCountByProject)
-      ? percent(get24hCountByRelease, get24hCountByProject)
+    const countByRelease = (
+      healthStatsPeriod === HealthStatsPeriodOption.AUTO
+        ? this.getPeriodCountByRelease
+        : this.get24hCountByRelease
+    )(version, project, display);
+    const countByProject = (
+      healthStatsPeriod === HealthStatsPeriodOption.AUTO
+        ? this.getPeriodCountByProject
+        : this.get24hCountByProject
+    )(project, display);
+
+    return defined(countByRelease) && defined(countByProject)
+      ? percent(countByRelease, countByProject)
       : undefined;
   };
 

+ 52 - 0
tests/js/spec/views/releases/utils/releaseHealthRequest.spec.tsx

@@ -64,6 +64,49 @@ describe('ReleaseHealthRequest', function () {
     }
   );
 
+  // @ts-expect-error
+  const requestForAutoTotalCountByProjectInPeriod = MockApiClient.addMockResponse(
+    {
+      url: `/organizations/org-slug/sessions/`,
+      // @ts-expect-error
+      body: TestStubs.SessionTotalCountByProjectIn24h(),
+    },
+    {
+      predicate(_url, {query}) {
+        return isEqual(query, {
+          query: undefined,
+          interval: '1d',
+          statsPeriod: '14d',
+          project: [123],
+          field: ['sum(session)'],
+          groupBy: ['project'],
+        });
+      },
+    }
+  );
+
+  // @ts-expect-error
+  const requestForAutoTotalCountByReleaseInPeriod = MockApiClient.addMockResponse(
+    {
+      url: `/organizations/org-slug/sessions/`,
+      // @ts-expect-error
+      body: TestStubs.SesssionTotalCountByReleaseIn24h(),
+    },
+    {
+      predicate(_url, {query}) {
+        return isEqual(query, {
+          query:
+            'release:7a82c130be9143361f20bc77252df783cf91e4fc OR release:e102abb2c46e7fe8686441091005c12aed90da99',
+          interval: '1d',
+          statsPeriod: '14d',
+          project: [123],
+          field: ['sum(session)'],
+          groupBy: ['project', 'release'],
+        });
+      },
+    }
+  );
+
   // @ts-expect-error
   MockApiClient.addMockResponse(
     {
@@ -709,7 +752,16 @@ describe('ReleaseHealthRequest', function () {
         z: 0,
       },
     ]);
+    expect(
+      healthData.getAdoption(
+        '7a82c130be9143361f20bc77252df783cf91e4fc',
+        projectId,
+        DisplayOption.SESSIONS
+      )
+    ).toBe(26.29607698886915);
 
     expect(requestForAutoHealthStatsPeriodSessionHistogram).toHaveBeenCalledTimes(1);
+    expect(requestForAutoTotalCountByProjectInPeriod).toHaveBeenCalledTimes(1);
+    expect(requestForAutoTotalCountByReleaseInPeriod).toHaveBeenCalledTimes(1);
   });
 });