modal.tsx 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import {Fragment} from 'react';
  2. import uniq from 'lodash/uniq';
  3. import AsyncComponent from 'sentry/components/asyncComponent';
  4. import {t} from 'sentry/locale';
  5. import {Frame, Organization, Project, TagWithTopValues} from 'sentry/types';
  6. import {Entry, EventError} from 'sentry/types/event';
  7. import OwnerInput from 'sentry/views/settings/project/projectOwnership/ownerInput';
  8. type IssueOwnershipResponse = {
  9. autoAssignment: boolean;
  10. dateCreated: string;
  11. fallthrough: boolean;
  12. isActive: boolean;
  13. lastUpdated: string;
  14. raw: string;
  15. };
  16. type Props = AsyncComponent['props'] & {
  17. issueId: string;
  18. onSave: () => void;
  19. organization: Organization;
  20. project: Project;
  21. };
  22. type State = {
  23. eventData: null | EventError;
  24. ownership: null | IssueOwnershipResponse;
  25. urlTagData: null | TagWithTopValues;
  26. } & AsyncComponent['state'];
  27. class ProjectOwnershipModal extends AsyncComponent<Props, State> {
  28. getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
  29. const {organization, project, issueId} = this.props;
  30. return [
  31. ['ownership', `/projects/${organization.slug}/${project.slug}/ownership/`],
  32. [
  33. 'urlTagData',
  34. `/issues/${issueId}/tags/url/`,
  35. {},
  36. {
  37. allowError: error =>
  38. // Allow for 404s
  39. error.status === 404,
  40. },
  41. ],
  42. ['eventData', `/issues/${issueId}/events/latest/`],
  43. ];
  44. }
  45. renderBody() {
  46. const {ownership, urlTagData, eventData} = this.state;
  47. if (!ownership && !urlTagData && !eventData) {
  48. return null;
  49. }
  50. const urls = urlTagData
  51. ? urlTagData.topValues
  52. .sort((a, b) => a.count - b.count)
  53. .map(i => i.value)
  54. .slice(0, 5)
  55. : [];
  56. // pull frame data out of exception or the stacktrace
  57. const entry = (eventData?.entries as Entry[])?.find(({type}) =>
  58. ['exception', 'stacktrace'].includes(type)
  59. );
  60. let frames: Frame[] = [];
  61. if (entry?.type === 'exception') {
  62. frames = entry?.data?.values?.[0]?.stacktrace?.frames ?? [];
  63. }
  64. if (entry?.type === 'stacktrace') {
  65. frames = entry?.data?.frames ?? [];
  66. }
  67. // Only display in-app frames
  68. frames = frames.filter(frame => frame.inApp);
  69. const paths = uniq(frames.map(frame => frame.filename || frame.absPath || ''))
  70. .filter(i => i)
  71. .slice(0, 30);
  72. return (
  73. <Fragment>
  74. <p>{t('Match against Issue Data: (globbing syntax *, ? supported)')}</p>
  75. <OwnerInput
  76. {...this.props}
  77. initialText={ownership?.raw || ''}
  78. urls={urls}
  79. paths={paths}
  80. />
  81. </Fragment>
  82. );
  83. }
  84. }
  85. export default ProjectOwnershipModal;