Просмотр исходного кода

feat(performance-detector-thresholds): Added scroll to setting and uncollapse option functionality. (#55692)

For project: [Detector Threshold
Configuration](https://www.notion.so/sentry/Detector-Threshold-Configuration-f8cb07e7cceb42388cedb09ea05fc116)

- Added scroll to setting + uncollapsing option functionality when
navigating to project settings from issue details.


https://github.com/getsentry/sentry/assets/60121741/2d7a2a47-90de-4028-b7b1-f17151739a1d

---------

Co-authored-by: Abdullah Khan <abdullahkhan@PG9Y57YDXQ.local>
Abdkhan14 1 год назад
Родитель
Сommit
afa70e4128

+ 6 - 3
static/app/components/events/interfaces/performance/spanEvidence.spec.tsx

@@ -6,8 +6,8 @@ import {
 } from 'sentry-test/performance/utils';
 } from 'sentry-test/performance/utils';
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 
-import {EntryType} from 'sentry/types';
-import {projectDetectorSettingsId} from 'sentry/views/settings/projectPerformance/projectPerformance';
+import {EntryType, IssueTitle, IssueType} from 'sentry/types';
+import {sanitizeQuerySelector} from 'sentry/utils/sanitizeQuerySelector';
 
 
 import {SpanEvidenceSection} from './spanEvidence';
 import {SpanEvidenceSection} from './spanEvidence';
 
 
@@ -91,6 +91,7 @@ describe('spanEvidence', () => {
     const event = TestStubs.Event({
     const event = TestStubs.Event({
       occurrence: {
       occurrence: {
         type: 1001,
         type: 1001,
+        issueTitle: IssueTitle.PERFORMANCE_SLOW_DB_QUERY,
       },
       },
       entries: [
       entries: [
         {
         {
@@ -116,7 +117,9 @@ describe('spanEvidence', () => {
     expect(settingsBtn).toBeInTheDocument();
     expect(settingsBtn).toBeInTheDocument();
     expect(settingsBtn).toHaveAttribute(
     expect(settingsBtn).toHaveAttribute(
       'href',
       'href',
-      `/settings/projects/project-slug/performance/#${projectDetectorSettingsId}`
+      `/settings/projects/project-slug/performance/?issueType=${
+        IssueType.PERFORMANCE_SLOW_DB_QUERY
+      }#${sanitizeQuerySelector(IssueTitle.PERFORMANCE_SLOW_DB_QUERY)}`
     );
     );
   });
   });
 
 

+ 5 - 9
static/app/components/events/interfaces/performance/spanEvidence.tsx

@@ -9,12 +9,11 @@ import {space} from 'sentry/styles/space';
 import {
 import {
   EventTransaction,
   EventTransaction,
   getIssueTypeFromOccurenceType,
   getIssueTypeFromOccurenceType,
-  IssueType,
   Organization,
   Organization,
 } from 'sentry/types';
 } from 'sentry/types';
+import {sanitizeQuerySelector} from 'sentry/utils/sanitizeQuerySelector';
 import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider';
 import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider';
 import {ProfileContext, ProfilesProvider} from 'sentry/views/profiling/profilesProvider';
 import {ProfileContext, ProfilesProvider} from 'sentry/views/profiling/profilesProvider';
-import {projectDetectorSettingsId} from 'sentry/views/settings/projectPerformance/projectPerformance';
 
 
 import TraceView from '../spans/traceView';
 import TraceView from '../spans/traceView';
 import {TraceContextType} from '../spans/types';
 import {TraceContextType} from '../spans/types';
@@ -44,13 +43,10 @@ export function SpanEvidenceSection({event, organization, projectSlug}: Props) {
   const hasProfilingFeature = organization.features.includes('profiling');
   const hasProfilingFeature = organization.features.includes('profiling');
 
 
   const issueType = getIssueTypeFromOccurenceType(event.occurrence?.type);
   const issueType = getIssueTypeFromOccurenceType(event.occurrence?.type);
+  const issueTitle = event.occurrence?.issueTitle;
+  const sanitizedIssueTitle = issueTitle && sanitizeQuerySelector(issueTitle);
   const hasConfigurableThresholds =
   const hasConfigurableThresholds =
-    organization.features.includes('project-performance-settings-admin') &&
-    issueType &&
-    ![
-      IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS, // TODO Abdullah Khan: Remove check when thresholds for these two issues are configurable.
-      IssueType.PERFORMANCE_CONSECUTIVE_HTTP,
-    ].includes(issueType);
+    organization.features.includes('project-performance-settings-admin') && issueType;
 
 
   return (
   return (
     <EventDataSection
     <EventDataSection
@@ -63,7 +59,7 @@ export function SpanEvidenceSection({event, organization, projectSlug}: Props) {
         hasConfigurableThresholds && (
         hasConfigurableThresholds && (
           <LinkButton
           <LinkButton
             data-test-id="span-evidence-settings-btn"
             data-test-id="span-evidence-settings-btn"
-            to={`/settings/projects/${projectSlug}/performance/#${projectDetectorSettingsId}`}
+            to={`/settings/projects/${projectSlug}/performance/?issueType=${issueType}#${sanitizedIssueTitle}`}
             size="xs"
             size="xs"
           >
           >
             <StyledSettingsIcon size="xs" />
             <StyledSettingsIcon size="xs" />

+ 89 - 0
static/app/components/forms/jsonForm.spec.tsx

@@ -4,6 +4,8 @@ import JsonForm from 'sentry/components/forms/jsonForm';
 import accountDetailsFields from 'sentry/data/forms/accountDetails';
 import accountDetailsFields from 'sentry/data/forms/accountDetails';
 import {fields} from 'sentry/data/forms/projectGeneralSettings';
 import {fields} from 'sentry/data/forms/projectGeneralSettings';
 
 
+import {JsonFormObject} from './types';
+
 const user = TestStubs.User();
 const user = TestStubs.User();
 
 
 describe('JsonForm', function () {
 describe('JsonForm', function () {
@@ -15,6 +17,93 @@ describe('JsonForm', function () {
       expect(container).toSnapshot();
       expect(container).toSnapshot();
     });
     });
 
 
+    it('initiallyCollapsed json form prop collapses forms', function () {
+      const forms: JsonFormObject[] = [
+        {
+          title: 'Form1 title',
+          fields: [
+            {
+              name: 'name',
+              type: 'string',
+              required: true,
+              label: 'Field Label 1 ',
+              placeholder: 'e.g. John Doe',
+            },
+          ],
+        },
+        {
+          title: 'Form2 title',
+          fields: [
+            {
+              name: 'name',
+              type: 'string',
+              required: true,
+              label: 'Field Label 2',
+              placeholder: 'e.g. Abdullah Khan',
+            },
+          ],
+        },
+      ];
+      render(
+        <JsonForm
+          forms={forms}
+          additionalFieldProps={{user}}
+          collapsible
+          initiallyCollapsed
+        />
+      );
+
+      expect(screen.getByText('Form1 title')).toBeInTheDocument();
+      expect(screen.getByText('Form2 title')).toBeInTheDocument();
+
+      expect(screen.queryByText('Field Label 1')).not.toBeVisible();
+      expect(screen.queryByText('Field Label 2')).not.toBeVisible();
+    });
+
+    it('initiallyCollapsed prop from children form groups override json form initiallyCollapsed prop', function () {
+      const forms: JsonFormObject[] = [
+        {
+          title: 'Form1 title',
+          fields: [
+            {
+              name: 'name',
+              type: 'string',
+              required: true,
+              label: 'Field Label 1 ',
+              placeholder: 'e.g. John Doe',
+            },
+          ],
+        },
+        {
+          title: 'Form2 title',
+          fields: [
+            {
+              name: 'name',
+              type: 'string',
+              required: true,
+              label: 'Field Label 2',
+              placeholder: 'e.g. Abdullah Khan',
+            },
+          ],
+          initiallyCollapsed: false, // Prevents this form group from being collapsed
+        },
+      ];
+      render(
+        <JsonForm
+          forms={forms}
+          additionalFieldProps={{user}}
+          collapsible
+          initiallyCollapsed
+        />
+      );
+
+      expect(screen.getByText('Form1 title')).toBeInTheDocument();
+      expect(screen.getByText('Form2 title')).toBeInTheDocument();
+
+      expect(screen.queryByText('Field Label 1')).not.toBeVisible();
+      expect(screen.queryByText('Field Label 2')).toBeVisible();
+    });
+
     it('missing additionalFieldProps required in "valid" prop', function () {
     it('missing additionalFieldProps required in "valid" prop', function () {
       // eslint-disable-next-line no-console
       // eslint-disable-next-line no-console
       jest.spyOn(console, 'error').mockImplementation(jest.fn());
       jest.spyOn(console, 'error').mockImplementation(jest.fn());

+ 12 - 2
static/app/components/forms/jsonForm.tsx

@@ -99,6 +99,7 @@ class JsonForm extends Component<Props, State> {
     fields,
     fields,
     formPanelProps,
     formPanelProps,
     title,
     title,
+    initiallyCollapsed,
   }: {
   }: {
     fields: FieldObject[];
     fields: FieldObject[];
     formPanelProps: Pick<
     formPanelProps: Pick<
@@ -109,8 +110,10 @@ class JsonForm extends Component<Props, State> {
       | 'additionalFieldProps'
       | 'additionalFieldProps'
       | 'renderFooter'
       | 'renderFooter'
       | 'renderHeader'
       | 'renderHeader'
+      | 'initiallyCollapsed'
     > &
     > &
       Pick<State, 'highlighted'>;
       Pick<State, 'highlighted'>;
+    initiallyCollapsed?: boolean;
     title?: React.ReactNode;
     title?: React.ReactNode;
   }) {
   }) {
     const shouldDisplayForm = this.shouldDisplayForm(fields);
     const shouldDisplayForm = this.shouldDisplayForm(fields);
@@ -123,14 +126,21 @@ class JsonForm extends Component<Props, State> {
       return null;
       return null;
     }
     }
 
 
-    return <FormPanel title={title} fields={fields} {...formPanelProps} />;
+    return (
+      <FormPanel
+        title={title}
+        fields={fields}
+        {...formPanelProps}
+        initiallyCollapsed={initiallyCollapsed ?? formPanelProps.initiallyCollapsed}
+      />
+    );
   }
   }
 
 
   render() {
   render() {
     const {
     const {
       access,
       access,
       collapsible,
       collapsible,
-      initiallyCollapsed,
+      initiallyCollapsed = false,
       fields,
       fields,
       title,
       title,
       forms,
       forms,

+ 1 - 0
static/app/components/forms/types.tsx

@@ -216,6 +216,7 @@ export type FieldObject = Field | Function;
 
 
 export type JsonFormObject = {
 export type JsonFormObject = {
   fields: FieldObject[];
   fields: FieldObject[];
+  initiallyCollapsed?: boolean;
   title?: React.ReactNode;
   title?: React.ReactNode;
 };
 };
 
 

+ 15 - 0
static/app/types/group.tsx

@@ -81,6 +81,21 @@ export enum IssueType {
   PROFILE_REGEX_MAIN_THREAD = 'profile_regex_main_thread',
   PROFILE_REGEX_MAIN_THREAD = 'profile_regex_main_thread',
 }
 }
 
 
+export enum IssueTitle {
+  PERFORMANCE_CONSECUTIVE_DB_QUERIES = 'Consecutive DB Queries',
+  PERFORMANCE_CONSECUTIVE_HTTP = 'Consecutive HTTP',
+  PERFORMANCE_FILE_IO_MAIN_THREAD = 'File IO on Main Thread',
+  PERFORMANCE_DB_MAIN_THREAD = 'DB on Main Thread',
+  PERFORMANCE_N_PLUS_ONE_API_CALLS = 'N+1 API Call',
+  PERFORMANCE_N_PLUS_ONE_DB_QUERIES = 'N+1 Query',
+  PERFORMANCE_SLOW_DB_QUERY = 'Slow DB Query',
+  PERFORMANCE_RENDER_BLOCKING_ASSET = 'Large Render Blocking Asset',
+  PERFORMANCE_UNCOMPRESSED_ASSET = 'Uncompressed Asset',
+  PERFORMANCE_LARGE_HTTP_PAYLOAD = 'Large HTTP payload',
+  PERFORMANCE_HTTP_OVERHEAD = 'HTTP/1.1 Overhead',
+  PERFORMANCE_DURATION_REGRESSION = 'Duration Regression',
+}
+
 export const getIssueTypeFromOccurenceType = (
 export const getIssueTypeFromOccurenceType = (
   typeId: number | undefined
   typeId: number | undefined
 ): IssueType | null => {
 ): IssueType | null => {

+ 1 - 1
static/app/utils/sanitizeQuerySelector.tsx

@@ -8,5 +8,5 @@
  * @return Returns a sanitized string (replace
  * @return Returns a sanitized string (replace
  */
  */
 export function sanitizeQuerySelector(str: string) {
 export function sanitizeQuerySelector(str: string) {
-  return typeof str === 'string' ? str.replace(/[ :]+/g, '-') : '';
+  return typeof str === 'string' ? str.replace(/[ :+/.]+/g, '-') : '';
 }
 }

+ 12 - 11
static/app/views/settings/projectPerformance/projectPerformance.spec.tsx

@@ -5,6 +5,7 @@ import {
   userEvent,
   userEvent,
 } from 'sentry-test/reactTestingLibrary';
 } from 'sentry-test/reactTestingLibrary';
 
 
+import {IssueTitle} from 'sentry/types';
 import * as utils from 'sentry/utils/isActiveSuperuser';
 import * as utils from 'sentry/utils/isActiveSuperuser';
 import ProjectPerformance, {
 import ProjectPerformance, {
   allowedDurationValues,
   allowedDurationValues,
@@ -192,7 +193,7 @@ describe('projectPerformance', function () {
 
 
   it.each([
   it.each([
     {
     {
-      title: 'N+1 DB Queries',
+      title: IssueTitle.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
       threshold: DetectorConfigCustomer.N_PLUS_DB_DURATION,
       threshold: DetectorConfigCustomer.N_PLUS_DB_DURATION,
       allowedValues: allowedDurationValues,
       allowedValues: allowedDurationValues,
       defaultValue: 100,
       defaultValue: 100,
@@ -200,7 +201,7 @@ describe('projectPerformance', function () {
       sliderIndex: 1,
       sliderIndex: 1,
     },
     },
     {
     {
-      title: 'Slow DB Queries',
+      title: IssueTitle.PERFORMANCE_SLOW_DB_QUERY,
       threshold: DetectorConfigCustomer.SLOW_DB_DURATION,
       threshold: DetectorConfigCustomer.SLOW_DB_DURATION,
       allowedValues: allowedDurationValues.slice(5),
       allowedValues: allowedDurationValues.slice(5),
       defaultValue: 1000,
       defaultValue: 1000,
@@ -208,7 +209,7 @@ describe('projectPerformance', function () {
       sliderIndex: 2,
       sliderIndex: 2,
     },
     },
     {
     {
-      title: 'N+1 API Calls',
+      title: IssueTitle.PERFORMANCE_N_PLUS_ONE_API_CALLS,
       threshold: DetectorConfigCustomer.N_PLUS_API_CALLS_DURATION,
       threshold: DetectorConfigCustomer.N_PLUS_API_CALLS_DURATION,
       allowedValues: allowedDurationValues.slice(5),
       allowedValues: allowedDurationValues.slice(5),
       defaultValue: 300,
       defaultValue: 300,
@@ -216,7 +217,7 @@ describe('projectPerformance', function () {
       sliderIndex: 3,
       sliderIndex: 3,
     },
     },
     {
     {
-      title: 'Large Render Blocking Asset',
+      title: IssueTitle.PERFORMANCE_RENDER_BLOCKING_ASSET,
       threshold: DetectorConfigCustomer.RENDER_BLOCKING_ASSET_RATIO,
       threshold: DetectorConfigCustomer.RENDER_BLOCKING_ASSET_RATIO,
       allowedValues: allowedPercentageValues,
       allowedValues: allowedPercentageValues,
       defaultValue: 0.33,
       defaultValue: 0.33,
@@ -224,7 +225,7 @@ describe('projectPerformance', function () {
       sliderIndex: 4,
       sliderIndex: 4,
     },
     },
     {
     {
-      title: 'Large HTTP Payload',
+      title: IssueTitle.PERFORMANCE_LARGE_HTTP_PAYLOAD,
       threshold: DetectorConfigCustomer.LARGE_HTT_PAYLOAD_SIZE,
       threshold: DetectorConfigCustomer.LARGE_HTT_PAYLOAD_SIZE,
       allowedValues: allowedSizeValues.slice(1),
       allowedValues: allowedSizeValues.slice(1),
       defaultValue: 1000000,
       defaultValue: 1000000,
@@ -232,7 +233,7 @@ describe('projectPerformance', function () {
       sliderIndex: 5,
       sliderIndex: 5,
     },
     },
     {
     {
-      title: 'DB on Main Thread',
+      title: IssueTitle.PERFORMANCE_DB_MAIN_THREAD,
       threshold: DetectorConfigCustomer.DB_ON_MAIN_THREAD_DURATION,
       threshold: DetectorConfigCustomer.DB_ON_MAIN_THREAD_DURATION,
       allowedValues: [10, 16, 33, 50],
       allowedValues: [10, 16, 33, 50],
       defaultValue: 16,
       defaultValue: 16,
@@ -240,7 +241,7 @@ describe('projectPerformance', function () {
       sliderIndex: 6,
       sliderIndex: 6,
     },
     },
     {
     {
-      title: 'File I/O on Main Thread',
+      title: IssueTitle.PERFORMANCE_FILE_IO_MAIN_THREAD,
       threshold: DetectorConfigCustomer.FILE_IO_MAIN_THREAD_DURATION,
       threshold: DetectorConfigCustomer.FILE_IO_MAIN_THREAD_DURATION,
       allowedValues: [10, 16, 33, 50],
       allowedValues: [10, 16, 33, 50],
       defaultValue: 16,
       defaultValue: 16,
@@ -248,7 +249,7 @@ describe('projectPerformance', function () {
       sliderIndex: 7,
       sliderIndex: 7,
     },
     },
     {
     {
-      title: 'Consecutive DB Queries',
+      title: IssueTitle.PERFORMANCE_CONSECUTIVE_DB_QUERIES,
       threshold: DetectorConfigCustomer.CONSECUTIVE_DB_MIN_TIME_SAVED,
       threshold: DetectorConfigCustomer.CONSECUTIVE_DB_MIN_TIME_SAVED,
       allowedValues: allowedDurationValues.slice(0, 23),
       allowedValues: allowedDurationValues.slice(0, 23),
       defaultValue: 100,
       defaultValue: 100,
@@ -256,7 +257,7 @@ describe('projectPerformance', function () {
       sliderIndex: 8,
       sliderIndex: 8,
     },
     },
     {
     {
-      title: 'Uncompressed Asset',
+      title: IssueTitle.PERFORMANCE_UNCOMPRESSED_ASSET,
       threshold: DetectorConfigCustomer.UNCOMPRESSED_ASSET_SIZE,
       threshold: DetectorConfigCustomer.UNCOMPRESSED_ASSET_SIZE,
       allowedValues: allowedSizeValues.slice(1),
       allowedValues: allowedSizeValues.slice(1),
       defaultValue: 512000,
       defaultValue: 512000,
@@ -264,7 +265,7 @@ describe('projectPerformance', function () {
       sliderIndex: 9,
       sliderIndex: 9,
     },
     },
     {
     {
-      title: 'Uncompressed Asset',
+      title: IssueTitle.PERFORMANCE_UNCOMPRESSED_ASSET,
       threshold: DetectorConfigCustomer.UNCOMPRESSED_ASSET_DURATION,
       threshold: DetectorConfigCustomer.UNCOMPRESSED_ASSET_DURATION,
       allowedValues: allowedDurationValues.slice(5),
       allowedValues: allowedDurationValues.slice(5),
       defaultValue: 500,
       defaultValue: 500,
@@ -272,7 +273,7 @@ describe('projectPerformance', function () {
       sliderIndex: 10,
       sliderIndex: 10,
     },
     },
     {
     {
-      title: 'Consecutive HTTP',
+      title: IssueTitle.PERFORMANCE_CONSECUTIVE_HTTP,
       threshold: DetectorConfigCustomer.CONSECUTIVE_HTTP_MIN_TIME_SAVED,
       threshold: DetectorConfigCustomer.CONSECUTIVE_HTTP_MIN_TIME_SAVED,
       allowedValues: allowedDurationValues.slice(14),
       allowedValues: allowedDurationValues.slice(14),
       defaultValue: 2000,
       defaultValue: 2000,

+ 37 - 24
static/app/views/settings/projectPerformance/projectPerformance.tsx

@@ -21,10 +21,11 @@ import {t, tct} from 'sentry/locale';
 import ConfigStore from 'sentry/stores/configStore';
 import ConfigStore from 'sentry/stores/configStore';
 import ProjectsStore from 'sentry/stores/projectsStore';
 import ProjectsStore from 'sentry/stores/projectsStore';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
-import {Organization, Project, Scope} from 'sentry/types';
+import {IssueTitle, IssueType, Organization, Project, Scope} from 'sentry/types';
 import {DynamicSamplingBiasType} from 'sentry/types/sampling';
 import {DynamicSamplingBiasType} from 'sentry/types/sampling';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {formatPercentage} from 'sentry/utils/formatters';
 import {formatPercentage} from 'sentry/utils/formatters';
+import {safeGetQsParam} from 'sentry/utils/integrationUtil';
 import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser';
 import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser';
 import routeTitleGen from 'sentry/utils/routeTitle';
 import routeTitleGen from 'sentry/utils/routeTitle';
 import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
 import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
@@ -471,9 +472,11 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
       return fps ? `${Math.floor(fps / 5) * 5}fps` : '';
       return fps ? `${Math.floor(fps / 5) * 5}fps` : '';
     };
     };
 
 
+    const issueType = safeGetQsParam('issueType');
+
     return [
     return [
       {
       {
-        title: t('N+1 DB Queries'),
+        title: IssueTitle.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.N_PLUS_DB_DURATION,
             name: DetectorConfigCustomer.N_PLUS_DB_DURATION,
@@ -481,7 +484,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Total Duration'),
             label: t('Minimum Total Duration'),
             defaultValue: 100, // ms
             defaultValue: 100, // ms
             help: t(
             help: t(
-              'Setting the value to 100ms, means that an eligible event will be stored as a N+1 DB Query Issue only if the total duration of the involved spans exceeds 100ms'
+              'Setting the value to 100ms, means that an eligible event will be detected as a N+1 DB Query Issue only if the total duration of the involved spans exceeds 100ms'
             ),
             ),
             allowedValues: allowedDurationValues,
             allowedValues: allowedDurationValues,
             disabled: !(
             disabled: !(
@@ -494,9 +497,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
       },
       },
       {
       {
-        title: t('Slow DB Queries'),
+        title: IssueTitle.PERFORMANCE_SLOW_DB_QUERY,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.SLOW_DB_DURATION,
             name: DetectorConfigCustomer.SLOW_DB_DURATION,
@@ -504,7 +508,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Duration'),
             label: t('Minimum Duration'),
             defaultValue: 1000, // ms
             defaultValue: 1000, // ms
             help: t(
             help: t(
-              'Setting the value to 1s, means that an eligible event will be stored as a Slow DB Query Issue only if the duration of the involved db span exceeds 1s.'
+              'Setting the value to 1s, means that an eligible event will be detected as a Slow DB Query Issue only if the duration of the involved db span exceeds 1s.'
             ),
             ),
             tickValues: [0, allowedDurationValues.slice(5).length - 1],
             tickValues: [0, allowedDurationValues.slice(5).length - 1],
             showTickLabels: true,
             showTickLabels: true,
@@ -516,9 +520,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_SLOW_DB_QUERY,
       },
       },
       {
       {
-        title: t('N+1 API Calls'),
+        title: IssueTitle.PERFORMANCE_N_PLUS_ONE_API_CALLS,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.N_PLUS_API_CALLS_DURATION,
             name: DetectorConfigCustomer.N_PLUS_API_CALLS_DURATION,
@@ -526,7 +531,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Total Duration'),
             label: t('Minimum Total Duration'),
             defaultValue: 300, // ms
             defaultValue: 300, // ms
             help: t(
             help: t(
-              'Setting the value to 300ms, means that an eligible event will be stored as a N+1 API Calls Issue only if the total duration of the involved spans exceeds 300ms'
+              'Setting the value to 300ms, means that an eligible event will be detected as a N+1 API Calls Issue only if the total duration of the involved spans exceeds 300ms'
             ),
             ),
             allowedValues: allowedDurationValues.slice(5),
             allowedValues: allowedDurationValues.slice(5),
             disabled: !(
             disabled: !(
@@ -540,9 +545,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS,
       },
       },
       {
       {
-        title: t('Large Render Blocking Asset'),
+        title: IssueTitle.PERFORMANCE_RENDER_BLOCKING_ASSET,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.RENDER_BLOCKING_ASSET_RATIO,
             name: DetectorConfigCustomer.RENDER_BLOCKING_ASSET_RATIO,
@@ -550,7 +556,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum FCP Ratio'),
             label: t('Minimum FCP Ratio'),
             defaultValue: 0.33,
             defaultValue: 0.33,
             help: t(
             help: t(
-              'Setting the value to 33%, means that an eligible event will be stored as a Large Render Blocking Asset Issue only if the duration of the involved span is at least 33% of First Contentful Paint (FCP).'
+              'Setting the value to 33%, means that an eligible event will be detected as a Large Render Blocking Asset Issue only if the duration of the involved span is at least 33% of First Contentful Paint (FCP).'
             ),
             ),
             allowedValues: allowedPercentageValues,
             allowedValues: allowedPercentageValues,
             tickValues: [0, allowedPercentageValues.length - 1],
             tickValues: [0, allowedPercentageValues.length - 1],
@@ -563,9 +569,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_RENDER_BLOCKING_ASSET,
       },
       },
       {
       {
-        title: t('Large HTTP Payload'),
+        title: IssueTitle.PERFORMANCE_LARGE_HTTP_PAYLOAD,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.LARGE_HTT_PAYLOAD_SIZE,
             name: DetectorConfigCustomer.LARGE_HTT_PAYLOAD_SIZE,
@@ -573,7 +580,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Size'),
             label: t('Minimum Size'),
             defaultValue: 1000000, // 1MB in bytes
             defaultValue: 1000000, // 1MB in bytes
             help: t(
             help: t(
-              'Setting the value to 1MB, means that an eligible event will be stored as a Large HTTP Payload Issue only if the involved HTTP span has a payload size that exceeds 1MB.'
+              'Setting the value to 1MB, means that an eligible event will be detected as a Large HTTP Payload Issue only if the involved HTTP span has a payload size that exceeds 1MB.'
             ),
             ),
             tickValues: [0, allowedSizeValues.slice(1).length - 1],
             tickValues: [0, allowedSizeValues.slice(1).length - 1],
             showTickLabels: true,
             showTickLabels: true,
@@ -586,9 +593,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_LARGE_HTTP_PAYLOAD,
       },
       },
       {
       {
-        title: t('DB on Main Thread'),
+        title: IssueTitle.PERFORMANCE_DB_MAIN_THREAD,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.DB_ON_MAIN_THREAD_DURATION,
             name: DetectorConfigCustomer.DB_ON_MAIN_THREAD_DURATION,
@@ -596,7 +604,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Frame Rate Drop'),
             label: t('Frame Rate Drop'),
             defaultValue: 16, // ms
             defaultValue: 16, // ms
             help: t(
             help: t(
-              'Setting the value to 60fps, means that an eligible event will be stored as a DB on Main Thread Issue only if database spans on the main thread cause frame rate to drop below 60fps.'
+              'Setting the value to 60fps, means that an eligible event will be detected as a DB on Main Thread Issue only if database spans on the main thread cause frame rate to drop below 60fps.'
             ),
             ),
             tickValues: [0, 3],
             tickValues: [0, 3],
             showTickLabels: true,
             showTickLabels: true,
@@ -608,9 +616,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_DB_MAIN_THREAD,
       },
       },
       {
       {
-        title: t('File I/O on Main Thread'),
+        title: IssueTitle.PERFORMANCE_FILE_IO_MAIN_THREAD,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.FILE_IO_MAIN_THREAD_DURATION,
             name: DetectorConfigCustomer.FILE_IO_MAIN_THREAD_DURATION,
@@ -618,7 +627,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Frame Rate Drop'),
             label: t('Frame Rate Drop'),
             defaultValue: 16, // ms
             defaultValue: 16, // ms
             help: t(
             help: t(
-              'Setting the value to 60fps, means that an eligible event will be stored as a File I/O on Main Thread Issue only if File I/O spans on the main thread cause frame rate to drop below 60fps.'
+              'Setting the value to 60fps, means that an eligible event will be detected as a File I/O on Main Thread Issue only if File I/O spans on the main thread cause frame rate to drop below 60fps.'
             ),
             ),
             tickValues: [0, 3],
             tickValues: [0, 3],
             showTickLabels: true,
             showTickLabels: true,
@@ -630,9 +639,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_FILE_IO_MAIN_THREAD,
       },
       },
       {
       {
-        title: t('Consecutive DB Queries'),
+        title: IssueTitle.PERFORMANCE_CONSECUTIVE_DB_QUERIES,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.CONSECUTIVE_DB_MIN_TIME_SAVED,
             name: DetectorConfigCustomer.CONSECUTIVE_DB_MIN_TIME_SAVED,
@@ -640,7 +650,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Time Saved'),
             label: t('Minimum Time Saved'),
             defaultValue: 100, // ms
             defaultValue: 100, // ms
             help: t(
             help: t(
-              'Setting the value to 100ms, means that an eligible event will be stored as a Consecutive DB Queries Issue only if the time saved by parallelizing the queries exceeds 100ms.'
+              'Setting the value to 100ms, means that an eligible event will be detected as a Consecutive DB Queries Issue only if the time saved by parallelizing the queries exceeds 100ms.'
             ),
             ),
             tickValues: [0, allowedDurationValues.slice(0, 23).length - 1],
             tickValues: [0, allowedDurationValues.slice(0, 23).length - 1],
             showTickLabels: true,
             showTickLabels: true,
@@ -652,9 +662,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_CONSECUTIVE_DB_QUERIES,
       },
       },
       {
       {
-        title: t('Uncompressed Asset'),
+        title: IssueTitle.PERFORMANCE_UNCOMPRESSED_ASSET,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.UNCOMPRESSED_ASSET_SIZE,
             name: DetectorConfigCustomer.UNCOMPRESSED_ASSET_SIZE,
@@ -662,7 +673,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Size'),
             label: t('Minimum Size'),
             defaultValue: 512000, // in kilobytes
             defaultValue: 512000, // in kilobytes
             help: t(
             help: t(
-              'Setting the value to 512KB, means that an eligible event will be stored as an Uncompressed Asset Issue only if the size of the uncompressed asset being transferred exceeds 512KB.'
+              'Setting the value to 512KB, means that an eligible event will be detected as an Uncompressed Asset Issue only if the size of the uncompressed asset being transferred exceeds 512KB.'
             ),
             ),
             tickValues: [0, allowedSizeValues.slice(1).length - 1],
             tickValues: [0, allowedSizeValues.slice(1).length - 1],
             showTickLabels: true,
             showTickLabels: true,
@@ -680,7 +691,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Duration'),
             label: t('Minimum Duration'),
             defaultValue: 500, // in ms
             defaultValue: 500, // in ms
             help: t(
             help: t(
-              'Setting the value to 500ms, means that an eligible event will be stored as an Uncompressed Asset Issue only if the duration of the span responsible for transferring the uncompressed asset exceeds 500ms.'
+              'Setting the value to 500ms, means that an eligible event will be detected as an Uncompressed Asset Issue only if the duration of the span responsible for transferring the uncompressed asset exceeds 500ms.'
             ),
             ),
             tickValues: [0, allowedDurationValues.slice(5).length - 1],
             tickValues: [0, allowedDurationValues.slice(5).length - 1],
             showTickLabels: true,
             showTickLabels: true,
@@ -693,9 +704,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_UNCOMPRESSED_ASSET,
       },
       },
       {
       {
-        title: t('Consecutive HTTP'),
+        title: IssueTitle.PERFORMANCE_CONSECUTIVE_HTTP,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.CONSECUTIVE_HTTP_MIN_TIME_SAVED,
             name: DetectorConfigCustomer.CONSECUTIVE_HTTP_MIN_TIME_SAVED,
@@ -703,7 +715,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             label: t('Minimum Time Saved'),
             label: t('Minimum Time Saved'),
             defaultValue: 2000, // in ms
             defaultValue: 2000, // in ms
             help: t(
             help: t(
-              'Setting the value to 2s, means that an eligible event will be stored as a Consecutive HTTP Issue only if the time saved by parallelizing the http spans exceeds 2s.'
+              'Setting the value to 2s, means that an eligible event will be detected as a Consecutive HTTP Issue only if the time saved by parallelizing the http spans exceeds 2s.'
             ),
             ),
             tickValues: [0, allowedDurationValues.slice(14).length - 1],
             tickValues: [0, allowedDurationValues.slice(14).length - 1],
             showTickLabels: true,
             showTickLabels: true,
@@ -716,9 +728,10 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_CONSECUTIVE_HTTP,
       },
       },
       {
       {
-        title: t('HTTP Overhead'),
+        title: IssueTitle.PERFORMANCE_HTTP_OVERHEAD,
         fields: [
         fields: [
           {
           {
             name: DetectorConfigCustomer.HTTP_OVERHEAD_REQUEST_DELAY,
             name: DetectorConfigCustomer.HTTP_OVERHEAD_REQUEST_DELAY,
@@ -738,6 +751,7 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
             disabledReason,
             disabledReason,
           },
           },
         ],
         ],
+        initiallyCollapsed: issueType !== IssueType.PERFORMANCE_HTTP_OVERHEAD,
       },
       },
     ];
     ];
   };
   };
@@ -966,7 +980,6 @@ class ProjectPerformance extends DeprecatedAsyncView<Props, State> {
                     <StyledJsonForm
                     <StyledJsonForm
                       forms={this.project_owner_detector_settings(hasAccess)}
                       forms={this.project_owner_detector_settings(hasAccess)}
                       collapsible
                       collapsible
-                      initiallyCollapsed
                     />
                     />
                     <StyledPanelFooter>
                     <StyledPanelFooter>
                       <Actions>
                       <Actions>