Browse Source

fix(stats): Reload per-min stat and fix regression (#45946)

- Forgot a consideration for
https://github.com/getsentry/sentry/pull/45895 in which providing new
props (via project selection) doesn't automatically reload the endpoint
query.
- Also fixes a regression on individual project stats where 'all
projects' table is hidden without the appropriate feature-flags

<img width="1176" alt="image"
src="https://user-images.githubusercontent.com/35509934/225726960-844ab9e2-de31-4568-bfa5-0d34ccd2e749.png">
Leander Rodrigues 2 years ago
parent
commit
5de2a21de0

+ 2 - 3
static/app/views/organizationStats/index.spec.tsx

@@ -186,7 +186,6 @@ describe('OrganizationStats', function () {
         query: {transform: ChartDataTransform.CUMULATIVE},
       })
     );
-
     const inputQuery = 'proj-1';
     userEvent.type(
       screen.getByPlaceholderText('Filter your projects'),
@@ -337,8 +336,8 @@ describe('OrganizationStats', function () {
 
     expect(screen.queryByText('My Projects')).not.toBeInTheDocument();
     expect(screen.getByTestId('usage-stats-chart')).toBeInTheDocument();
-    // Doesn't render for single project view
-    expect(screen.queryByTestId('usage-stats-table')).not.toBeInTheDocument();
+    expect(screen.getByTestId('usage-stats-table')).toBeInTheDocument();
+    expect(screen.getByText('All Projects')).toBeInTheDocument();
 
     expect(mockRequest).toHaveBeenCalledWith(
       endpoint,

+ 15 - 16
static/app/views/organizationStats/index.tsx

@@ -394,22 +394,21 @@ export class OrganizationStats extends Component<Props> {
                   />
                 </ErrorBoundary>
               </div>
-              {!isSingleProject && (
-                <ErrorBoundary mini>
-                  <UsageStatsProjects
-                    organization={organization}
-                    dataCategory={this.dataCategory}
-                    dataCategoryName={this.dataCategoryName}
-                    projectIds={this.projectIds}
-                    dataDatetime={this.dataDatetime}
-                    tableSort={this.tableSort}
-                    tableQuery={this.tableQuery}
-                    tableCursor={this.tableCursor}
-                    handleChangeState={this.setStateOnUrl}
-                    getNextLocations={this.getNextLocations}
-                  />
-                </ErrorBoundary>
-              )}
+              <ErrorBoundary mini>
+                <UsageStatsProjects
+                  organization={organization}
+                  dataCategory={this.dataCategory}
+                  dataCategoryName={this.dataCategoryName}
+                  isSingleProject={isSingleProject}
+                  projectIds={this.projectIds}
+                  dataDatetime={this.dataDatetime}
+                  tableSort={this.tableSort}
+                  tableQuery={this.tableQuery}
+                  tableCursor={this.tableCursor}
+                  handleChangeState={this.setStateOnUrl}
+                  getNextLocations={this.getNextLocations}
+                />
+              </ErrorBoundary>
             </Layout.Main>
           </Body>
         </PageFiltersContainer>

+ 7 - 0
static/app/views/organizationStats/usageStatsPerMin.tsx

@@ -28,6 +28,13 @@ type State = {
  * as small as possible, this call is quite fast.
  */
 class UsageStatsPerMin extends AsyncComponent<Props, State> {
+  componentDidUpdate(prevProps: Props) {
+    const {projectIds} = this.props;
+    if (prevProps.projectIds !== projectIds) {
+      this.reloadData();
+    }
+  }
+
   getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
     return [['orgStats', this.endpointPath, {query: this.endpointQuery}]];
   }

+ 38 - 14
static/app/views/organizationStats/usageStatsProjects.tsx

@@ -10,6 +10,7 @@ import SortLink, {Alignments, Directions} from 'sentry/components/gridEditable/s
 import Pagination from 'sentry/components/pagination';
 import SearchBar from 'sentry/components/searchBar';
 import {DATA_CATEGORY_INFO, DEFAULT_STATS_PERIOD} from 'sentry/constants';
+import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {DataCategoryInfo, Organization, Outcome, Project} from 'sentry/types';
@@ -22,7 +23,6 @@ type Props = {
   dataCategory: DataCategoryInfo['plural'];
   dataCategoryName: string;
   dataDatetime: DateTimeObject;
-
   getNextLocations: (project: Project) => Record<string, LocationDescriptorObject>;
   handleChangeState: (
     nextState: {
@@ -32,6 +32,7 @@ type Props = {
     },
     options?: {willUpdateRouter?: boolean}
   ) => LocationDescriptorObject;
+  isSingleProject: boolean;
   loadingProjects: boolean;
   organization: Organization;
   projectIds: number[];
@@ -92,7 +93,7 @@ class UsageStatsProjects extends AsyncComponent<Props, State> {
   }
 
   get endpointQuery() {
-    const {dataDatetime, dataCategory, projectIds} = this.props;
+    const {dataDatetime, dataCategory, projectIds, isSingleProject} = this.props;
 
     const queryDatetime =
       dataDatetime.start && dataDatetime.end
@@ -111,7 +112,8 @@ class UsageStatsProjects extends AsyncComponent<Props, State> {
       interval: getSeriesApiInterval(dataDatetime),
       groupBy: ['outcome', 'project'],
       field: ['sum(quantity)'],
-      project: projectIds,
+      // If only one project is in selected, display the entire project list
+      project: isSingleProject ? [ALL_ACCESS_PROJECTS] : projectIds,
       category: dataCategory.slice(0, -1), // backend is singular
     };
   }
@@ -184,11 +186,11 @@ class UsageStatsProjects extends AsyncComponent<Props, State> {
   }
 
   get projectSelectionFilter(): (p: Project) => boolean {
-    const {projectIds} = this.props;
+    const {projectIds, isSingleProject} = this.props;
     const selectedProjects = new Set(projectIds.map(id => `${id}`));
 
     // If 'My Projects' or 'All Projects' are selected
-    return selectedProjects.size === 0 || selectedProjects.has('-1')
+    return selectedProjects.size === 0 || selectedProjects.has('-1') || isSingleProject
       ? _p => true
       : p => selectedProjects.has(p.id);
   }
@@ -419,19 +421,26 @@ class UsageStatsProjects extends AsyncComponent<Props, State> {
 
   renderComponent() {
     const {error, errors, loading} = this.state;
-    const {dataCategory, loadingProjects, tableQuery} = this.props;
+    const {dataCategory, loadingProjects, tableQuery, isSingleProject} = this.props;
     const {headers, tableStats} = this.tableData;
 
     return (
       <Fragment>
-        <Container>
-          <SearchBar
-            defaultQuery=""
-            query={tableQuery}
-            placeholder={t('Filter your projects')}
-            onSearch={this.handleSearch}
-          />
-        </Container>
+        {isSingleProject && (
+          <PanelHeading>
+            <Title>{t('All Projects')}</Title>
+          </PanelHeading>
+        )}
+        {!isSingleProject && (
+          <Container>
+            <SearchBar
+              defaultQuery=""
+              query={tableQuery}
+              placeholder={t('Filter your projects')}
+              onSearch={this.handleSearch}
+            />
+          </Container>
+        )}
         <Container data-test-id="usage-stats-table">
           <UsageTable
             isLoading={loading || loadingProjects}
@@ -454,3 +463,18 @@ export default withProjects(UsageStatsProjects);
 const Container = styled('div')`
   margin-bottom: ${space(2)};
 `;
+
+const Title = styled('div')`
+  font-weight: bold;
+  font-size: ${p => p.theme.fontSizeLarge};
+  color: ${p => p.theme.gray400};
+  display: flex;
+  flex: 1;
+  align-items: center;
+`;
+
+const PanelHeading = styled('div')`
+  display: flex;
+  margin-bottom: ${space(2)};
+  align-items: center;
+`;