groupEvents.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import {Component} from 'react';
  2. import {browserHistory, RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import pick from 'lodash/pick';
  5. import {Client} from 'sentry/api';
  6. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  7. import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
  8. import EventsTable from 'sentry/components/eventsTable/eventsTable';
  9. import * as Layout from 'sentry/components/layouts/thirds';
  10. import LoadingError from 'sentry/components/loadingError';
  11. import LoadingIndicator from 'sentry/components/loadingIndicator';
  12. import Pagination from 'sentry/components/pagination';
  13. import {Panel, PanelBody} from 'sentry/components/panels';
  14. import SearchBar from 'sentry/components/searchBar';
  15. import {t} from 'sentry/locale';
  16. import space from 'sentry/styles/space';
  17. import {Group, Organization} from 'sentry/types';
  18. import {Event} from 'sentry/types/event';
  19. import parseApiError from 'sentry/utils/parseApiError';
  20. import withApi from 'sentry/utils/withApi';
  21. import withOrganization from 'sentry/utils/withOrganization';
  22. type Props = {
  23. api: Client;
  24. group: Group;
  25. organization: Organization;
  26. } & RouteComponentProps<{groupId: string; orgId: string}, {}>;
  27. type State = {
  28. error: string | false;
  29. eventList: Event[];
  30. loading: boolean;
  31. pageLinks: string;
  32. query: string;
  33. };
  34. class GroupEvents extends Component<Props, State> {
  35. constructor(props: Props) {
  36. super(props);
  37. const queryParams = this.props.location.query;
  38. this.state = {
  39. eventList: [],
  40. loading: true,
  41. error: false,
  42. pageLinks: '',
  43. query: queryParams.query || '',
  44. };
  45. }
  46. UNSAFE_componentWillMount() {
  47. this.fetchData();
  48. }
  49. UNSAFE_componentWillReceiveProps(nextProps: Props) {
  50. if (this.props.location.search !== nextProps.location.search) {
  51. const queryParams = nextProps.location.query;
  52. this.setState(
  53. {
  54. query: queryParams.query,
  55. },
  56. this.fetchData
  57. );
  58. }
  59. }
  60. handleSearch = (query: string) => {
  61. const targetQueryParams = {...this.props.location.query};
  62. targetQueryParams.query = query;
  63. const {groupId, orgId} = this.props.params;
  64. browserHistory.push({
  65. pathname: `/organizations/${orgId}/issues/${groupId}/events/`,
  66. query: targetQueryParams,
  67. });
  68. };
  69. fetchData = () => {
  70. this.setState({
  71. loading: true,
  72. error: false,
  73. });
  74. const query = {
  75. ...pick(this.props.location.query, ['cursor', 'environment']),
  76. limit: 50,
  77. query: this.state.query,
  78. };
  79. this.props.api.request(`/issues/${this.props.params.groupId}/events/`, {
  80. query,
  81. method: 'GET',
  82. success: (data, _, resp) => {
  83. this.setState({
  84. eventList: data,
  85. error: false,
  86. loading: false,
  87. pageLinks: resp?.getResponseHeader('Link') ?? '',
  88. });
  89. },
  90. error: err => {
  91. this.setState({
  92. error: parseApiError(err),
  93. loading: false,
  94. });
  95. },
  96. });
  97. };
  98. renderNoQueryResults() {
  99. return (
  100. <EmptyStateWarning>
  101. <p>{t('Sorry, no events match your search query.')}</p>
  102. </EmptyStateWarning>
  103. );
  104. }
  105. renderEmpty() {
  106. return (
  107. <EmptyStateWarning>
  108. <p>{t("There don't seem to be any events yet.")}</p>
  109. </EmptyStateWarning>
  110. );
  111. }
  112. renderResults() {
  113. const {group, params} = this.props;
  114. const tagList = group.tags.filter(tag => tag.key !== 'user') || [];
  115. return (
  116. <EventsTable
  117. tagList={tagList}
  118. events={this.state.eventList}
  119. orgId={params.orgId}
  120. projectId={group.project.slug}
  121. groupId={params.groupId}
  122. />
  123. );
  124. }
  125. renderBody() {
  126. let body: React.ReactNode;
  127. if (this.state.loading) {
  128. body = <LoadingIndicator />;
  129. } else if (this.state.error) {
  130. body = <LoadingError message={this.state.error} onRetry={this.fetchData} />;
  131. } else if (this.state.eventList.length > 0) {
  132. body = this.renderResults();
  133. } else if (this.state.query && this.state.query !== '') {
  134. body = this.renderNoQueryResults();
  135. } else {
  136. body = this.renderEmpty();
  137. }
  138. return body;
  139. }
  140. render() {
  141. return (
  142. <Layout.Body>
  143. <Layout.Main fullWidth>
  144. <Wrapper>
  145. <FilterSection>
  146. <EnvironmentPageFilter />
  147. <SearchBar
  148. defaultQuery=""
  149. placeholder={t('Search events by id, message, or tags')}
  150. query={this.state.query}
  151. onSearch={this.handleSearch}
  152. />
  153. </FilterSection>
  154. <Panel className="event-list">
  155. <PanelBody>{this.renderBody()}</PanelBody>
  156. </Panel>
  157. <Pagination pageLinks={this.state.pageLinks} />
  158. </Wrapper>
  159. </Layout.Main>
  160. </Layout.Body>
  161. );
  162. }
  163. }
  164. const FilterSection = styled('div')`
  165. display: grid;
  166. gap: ${space(1)};
  167. grid-template-columns: max-content 1fr;
  168. `;
  169. const Wrapper = styled('div')`
  170. display: grid;
  171. gap: ${space(2)};
  172. `;
  173. export {GroupEvents};
  174. export default withOrganization(withApi(GroupEvents));