Browse Source

feat(query-builder): Organize and sort issue/event filters better (#73476)

- Removes event properties from the "Issue filters" tab and places them
in a new "Event filters" category
- Sort event tags by count within the "Event tags" section so that more
useful ones are at the top
Malachi Willey 8 months ago
parent
commit
ae2388a81e

+ 3 - 3
static/app/stores/tagStore.spec.tsx

@@ -50,7 +50,7 @@ describe('TagStore', function () {
         },
       ]);
 
-      expect(TagStore.getIssueAttributes().has).toEqual({
+      expect(TagStore.getIssueAttributes(OrganizationFixture()).has).toEqual({
         key: 'has',
         name: 'Has Tag',
         values: ['mytag', 'otherkey'],
@@ -66,7 +66,7 @@ describe('TagStore', function () {
         },
       ]);
 
-      const tags = TagStore.getIssueAttributes();
+      const tags = TagStore.getIssueAttributes(OrganizationFixture());
       expect(tags.is).toBeTruthy();
       expect(tags.is.key).toBe('is');
       expect(tags.assigned).toBeTruthy();
@@ -80,7 +80,7 @@ describe('TagStore', function () {
         },
       ]);
 
-      const tags = TagStore.getIssueAttributes();
+      const tags = TagStore.getIssueAttributes(OrganizationFixture());
       expect(tags.is.values).toContain('archived');
     });
   });

+ 71 - 20
static/app/stores/tagStore.tsx

@@ -10,20 +10,45 @@ import {
 } from 'sentry/types/group';
 import type {Organization} from 'sentry/types/organization';
 import {SEMVER_TAGS} from 'sentry/utils/discover/fields';
-import {FieldKey, ISSUE_FIELDS} from 'sentry/utils/fields';
+import {
+  FieldKey,
+  FieldKind,
+  ISSUE_EVENT_PROPERTY_FIELDS,
+  ISSUE_FIELDS,
+  ISSUE_PROPERTY_FIELDS,
+} from 'sentry/utils/fields';
 
 import type {StrictStoreDefinition} from './types';
 
 // This list is only used on issues. Events/discover
 // have their own field list that exists elsewhere.
-// contexts.key and contexts.value omitted on purpose.
 const BUILTIN_TAGS = ISSUE_FIELDS.reduce<TagCollection>((acc, tag) => {
   acc[tag] = {key: tag, name: tag};
   return acc;
 }, {});
 
+// For the new query builder, we need to differentiate between issue and event fields
+const BUILTIN_TAGS_BY_CATEGORY = {
+  ...ISSUE_PROPERTY_FIELDS.reduce<TagCollection>((acc, tag) => {
+    acc[tag] = {key: tag, name: tag, predefined: true, kind: FieldKind.ISSUE_FIELD};
+    return acc;
+  }, {}),
+  ...ISSUE_EVENT_PROPERTY_FIELDS.reduce<TagCollection>((acc, tag) => {
+    acc[tag] = {key: tag, name: tag, predefined: false, kind: FieldKind.EVENT_FIELD};
+    return acc;
+  }, {}),
+};
+
+function getBuiltInTags(organization: Organization) {
+  if (organization.features.includes('issue-stream-search-query-builder')) {
+    return BUILTIN_TAGS_BY_CATEGORY;
+  }
+
+  return BUILTIN_TAGS;
+}
+
 interface TagStoreDefinition extends StrictStoreDefinition<TagCollection> {
-  getIssueAttributes(): TagCollection;
+  getIssueAttributes(organization: Organization): TagCollection;
   getIssueTags(org: Organization): TagCollection;
   loadTagsSuccess(data: Tag[]): void;
   reset(): void;
@@ -41,7 +66,7 @@ const storeConfig: TagStoreDefinition = {
   /**
    * Gets only predefined issue attributes
    */
-  getIssueAttributes() {
+  getIssueAttributes(organization: Organization) {
     // TODO(mitsuhiko): what do we do with translations here?
     const isSuggestions = [
       'resolved',
@@ -58,8 +83,11 @@ const storeConfig: TagStoreDefinition = {
       return a.toLowerCase().localeCompare(b.toLowerCase());
     });
 
+    const builtinTags = getBuiltInTags(organization);
+
     const tagCollection = {
       [FieldKey.IS]: {
+        ...builtinTags[FieldKey.IS],
         key: FieldKey.IS,
         name: 'Status',
         values: isSuggestions,
@@ -67,25 +95,27 @@ const storeConfig: TagStoreDefinition = {
         predefined: true,
       },
       [FieldKey.HAS]: {
+        ...builtinTags[FieldKey.HAS],
         key: FieldKey.HAS,
         name: 'Has Tag',
         values: sortedTagKeys,
         predefined: true,
       },
       [FieldKey.ASSIGNED]: {
+        ...builtinTags[FieldKey.ASSIGNED],
         key: FieldKey.ASSIGNED,
         name: 'Assigned To',
         values: [],
         predefined: true,
       },
       [FieldKey.BOOKMARKS]: {
-        key: FieldKey.BOOKMARKS,
+        ...builtinTags[FieldKey.BOOKMARKS],
         name: 'Bookmarked By',
         values: [],
         predefined: true,
       },
       [FieldKey.ISSUE_CATEGORY]: {
-        key: FieldKey.ISSUE_CATEGORY,
+        ...builtinTags[FieldKey.ISSUE_CATEGORY],
         name: 'Issue Category',
         values: [
           IssueCategory.ERROR,
@@ -96,7 +126,7 @@ const storeConfig: TagStoreDefinition = {
         predefined: true,
       },
       [FieldKey.ISSUE_TYPE]: {
-        key: FieldKey.ISSUE_TYPE,
+        ...builtinTags[FieldKey.ISSUE_TYPE],
         name: 'Issue Type',
         values: [
           IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
@@ -123,31 +153,31 @@ const storeConfig: TagStoreDefinition = {
         predefined: true,
       },
       [FieldKey.LAST_SEEN]: {
-        key: FieldKey.LAST_SEEN,
+        ...builtinTags[FieldKey.LAST_SEEN],
         name: 'Last Seen',
         values: [],
         predefined: false,
       },
       [FieldKey.FIRST_SEEN]: {
-        key: FieldKey.FIRST_SEEN,
+        ...builtinTags[FieldKey.FIRST_SEEN],
         name: 'First Seen',
         values: [],
         predefined: false,
       },
       [FieldKey.FIRST_RELEASE]: {
-        key: FieldKey.FIRST_RELEASE,
+        ...builtinTags[FieldKey.FIRST_RELEASE],
         name: 'First Release',
         values: ['latest'],
         predefined: true,
       },
       [FieldKey.EVENT_TIMESTAMP]: {
-        key: FieldKey.EVENT_TIMESTAMP,
+        ...builtinTags[FieldKey.EVENT_TIMESTAMP],
         name: 'Event Timestamp',
         values: [],
         predefined: true,
       },
       [FieldKey.TIMES_SEEN]: {
-        key: FieldKey.TIMES_SEEN,
+        ...builtinTags[FieldKey.TIMES_SEEN],
         name: 'Times Seen',
         isInput: true,
         // Below values are required or else SearchBar will attempt to get values
@@ -156,14 +186,14 @@ const storeConfig: TagStoreDefinition = {
         predefined: true,
       },
       [FieldKey.ASSIGNED_OR_SUGGESTED]: {
-        key: FieldKey.ASSIGNED_OR_SUGGESTED,
+        ...builtinTags[FieldKey.ASSIGNED_OR_SUGGESTED],
         name: 'Assigned or Suggested',
         isInput: true,
         values: [],
         predefined: true,
       },
       [FieldKey.ISSUE_PRIORITY]: {
-        key: FieldKey.ISSUE_PRIORITY,
+        ...builtinTags[FieldKey.ISSUE_PRIORITY],
         name: 'Issue Priority',
         values: [PriorityLevel.HIGH, PriorityLevel.MEDIUM, PriorityLevel.LOW],
         predefined: true,
@@ -184,13 +214,34 @@ const storeConfig: TagStoreDefinition = {
    * Get all tags including builtin issue tags and issue attributes
    */
   getIssueTags(org: Organization) {
+    const eventTags = Object.values(this.state).reduce<TagCollection>((acc, tag) => {
+      return {
+        ...acc,
+        [tag.key]: {
+          ...tag,
+          kind: FieldKind.TAG,
+        },
+      };
+    }, {});
+
+    const semverFields = Object.values(SEMVER_TAGS).reduce<TagCollection>((acc, tag) => {
+      return {
+        ...acc,
+        [tag.key]: {
+          predefined: false,
+          ...tag,
+          kind: org.features.includes('issue-stream-search-query-builder')
+            ? FieldKind.EVENT_FIELD
+            : FieldKind.FIELD,
+        },
+      };
+    }, {});
+
     const issueTags = {
-      ...BUILTIN_TAGS,
-      ...SEMVER_TAGS,
-      // State tags should overwrite built ins.
-      ...this.state,
-      // We want issue attributes to overwrite any built in and state tags
-      ...this.getIssueAttributes(),
+      ...getBuiltInTags(org),
+      ...semverFields,
+      ...eventTags,
+      ...this.getIssueAttributes(org),
     };
     if (!org.features.includes('device-classification')) {
       delete issueTags[FieldKey.DEVICE_CLASS];

+ 28 - 16
static/app/utils/fields/index.ts

@@ -8,6 +8,8 @@ export enum FieldKind {
   MEASUREMENT = 'measurement',
   BREAKDOWN = 'breakdown',
   FIELD = 'field',
+  ISSUE_FIELD = 'issue_field',
+  EVENT_FIELD = 'event_field',
   FUNCTION = 'function',
   EQUATION = 'equation',
   METRICS = 'metric',
@@ -1044,11 +1046,28 @@ const EVENT_FIELD_DEFINITIONS: Record<AllEventFieldKeys, FieldDefinition> = {
   },
 };
 
-export const ISSUE_FIELDS = [
+//
+export const ISSUE_PROPERTY_FIELDS: FieldKey[] = [
   FieldKey.AGE,
-  FieldKey.ASSIGNED,
   FieldKey.ASSIGNED_OR_SUGGESTED,
+  FieldKey.ASSIGNED,
   FieldKey.BOOKMARKS,
+  FieldKey.FIRST_RELEASE,
+  FieldKey.FIRST_SEEN,
+  FieldKey.HAS,
+  FieldKey.IS,
+  FieldKey.ISSUE_CATEGORY,
+  FieldKey.ISSUE_PRIORITY,
+  FieldKey.ISSUE_TYPE,
+  FieldKey.ISSUE,
+  FieldKey.LAST_SEEN,
+  FieldKey.RELEASE_STAGE,
+  FieldKey.TIMES_SEEN,
+];
+
+// Should match Snuba columns defined in sentry/snuba/events.py
+export const ISSUE_EVENT_PROPERTY_FIELDS: FieldKey[] = [
+  FieldKey.APP_IN_FOREGROUND,
   FieldKey.DEVICE_ARCH,
   FieldKey.DEVICE_BRAND,
   FieldKey.DEVICE_CLASS,
@@ -1060,41 +1079,31 @@ export const ISSUE_FIELDS = [
   FieldKey.DEVICE_UUID,
   FieldKey.DIST,
   FieldKey.ERROR_HANDLED,
+  FieldKey.ERROR_MAIN_THREAD,
   FieldKey.ERROR_MECHANISM,
   FieldKey.ERROR_TYPE,
   FieldKey.ERROR_UNHANDLED,
   FieldKey.ERROR_VALUE,
-  FieldKey.ERROR_MAIN_THREAD,
   FieldKey.EVENT_TIMESTAMP,
   FieldKey.EVENT_TYPE,
-  FieldKey.FIRST_RELEASE,
-  FieldKey.FIRST_SEEN,
   FieldKey.GEO_CITY,
   FieldKey.GEO_COUNTRY_CODE,
   FieldKey.GEO_REGION,
   FieldKey.GEO_SUBDIVISION,
-  FieldKey.HAS,
   FieldKey.HTTP_METHOD,
   FieldKey.HTTP_REFERER,
   FieldKey.HTTP_STATUS_CODE,
   FieldKey.HTTP_URL,
   FieldKey.ID,
-  FieldKey.IS,
-  FieldKey.ISSUE,
-  FieldKey.ISSUE_CATEGORY,
-  FieldKey.ISSUE_PRIORITY,
-  FieldKey.ISSUE_TYPE,
-  FieldKey.LAST_SEEN,
   FieldKey.LOCATION,
   FieldKey.MESSAGE,
   FieldKey.OS_BUILD,
   FieldKey.OS_KERNEL_VERSION,
   FieldKey.PLATFORM,
-  FieldKey.RELEASE,
   FieldKey.RELEASE_BUILD,
   FieldKey.RELEASE_PACKAGE,
-  FieldKey.RELEASE_STAGE,
   FieldKey.RELEASE_VERSION,
+  FieldKey.RELEASE,
   FieldKey.SDK_NAME,
   FieldKey.SDK_VERSION,
   FieldKey.STACK_ABS_PATH,
@@ -1104,7 +1113,6 @@ export const ISSUE_FIELDS = [
   FieldKey.STACK_PACKAGE,
   FieldKey.STACK_STACK_LEVEL,
   FieldKey.TIMESTAMP,
-  FieldKey.TIMES_SEEN,
   FieldKey.TITLE,
   FieldKey.TRACE,
   FieldKey.TRANSACTION,
@@ -1113,7 +1121,11 @@ export const ISSUE_FIELDS = [
   FieldKey.USER_ID,
   FieldKey.USER_IP,
   FieldKey.USER_USERNAME,
-  FieldKey.APP_IN_FOREGROUND,
+];
+
+export const ISSUE_FIELDS: FieldKey[] = [
+  ...ISSUE_PROPERTY_FIELDS,
+  ...ISSUE_EVENT_PROPERTY_FIELDS,
 ];
 
 /**

+ 22 - 6
static/app/views/issueList/searchBar.tsx

@@ -26,7 +26,7 @@ import usePageFilters from 'sentry/utils/usePageFilters';
 import type {WithIssueTagsProps} from 'sentry/utils/withIssueTags';
 import withIssueTags from 'sentry/utils/withIssueTags';
 
-const getSupportedTags = (supportedTags: TagCollection) => {
+const getSupportedTags = (supportedTags: TagCollection): TagCollection => {
   return Object.fromEntries(
     Object.keys(supportedTags).map(key => [
       key,
@@ -41,16 +41,32 @@ const getSupportedTags = (supportedTags: TagCollection) => {
 };
 
 const getFilterKeySections = (tags: TagCollection): FilterKeySection[] => {
-  const allTags: Tag[] = orderBy(Object.values(getSupportedTags(tags)), 'key');
-  const eventTags = allTags.filter(tag => tag.kind === FieldKind.TAG);
-  const issueFields = allTags.filter(tag => tag.kind === FieldKind.FIELD);
+  const allTags: Tag[] = Object.values(tags);
+  const eventTags = orderBy(
+    allTags.filter(tag => tag.kind === FieldKind.TAG),
+    ['totalValues', 'key'],
+    ['desc', 'asc']
+  );
+  const issueFields = orderBy(
+    allTags.filter(tag => tag.kind === FieldKind.ISSUE_FIELD),
+    ['key']
+  );
+  const eventFields = orderBy(
+    allTags.filter(tag => tag.kind === FieldKind.EVENT_FIELD),
+    ['key']
+  );
 
   return [
     {
-      value: FieldKind.FIELD,
-      label: t('Issue Fields'),
+      value: FieldKind.ISSUE_FIELD,
+      label: t('Issue Filters'),
       children: issueFields,
     },
+    {
+      value: FieldKind.EVENT_FIELD,
+      label: t('Event Filters'),
+      children: eventFields,
+    },
     {
       value: FieldKind.TAG,
       label: t('Event Tags'),