projectIssues.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import React, {useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Location} from 'history';
  4. import pick from 'lodash/pick';
  5. import {Client} from 'app/api';
  6. import Button from 'app/components/button';
  7. import ButtonBar from 'app/components/buttonBar';
  8. import {SectionHeading} from 'app/components/charts/styles';
  9. import GroupList from 'app/components/issues/groupList';
  10. import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
  11. import Pagination from 'app/components/pagination';
  12. import {Panel, PanelBody} from 'app/components/panels';
  13. import {DEFAULT_RELATIVE_PERIODS, DEFAULT_STATS_PERIOD} from 'app/constants';
  14. import {URL_PARAM} from 'app/constants/globalSelectionHeader';
  15. import {t, tct} from 'app/locale';
  16. import space from 'app/styles/space';
  17. import {Organization} from 'app/types';
  18. import {trackAnalyticsEvent} from 'app/utils/analytics';
  19. import {decodeScalar} from 'app/utils/queryString';
  20. import NoGroupsHandler from '../issueList/noGroupsHandler';
  21. type Props = {
  22. organization: Organization;
  23. location: Location;
  24. projectId: number;
  25. api: Client;
  26. };
  27. function ProjectIssues({organization, location, projectId, api}: Props) {
  28. const [pageLinks, setPageLinks] = useState<string | undefined>();
  29. const [onCursor, setOnCursor] = useState<(() => void) | undefined>();
  30. function handleOpenClick() {
  31. trackAnalyticsEvent({
  32. eventKey: 'project_detail.open_issues',
  33. eventName: 'Project Detail: Open issues from project detail',
  34. organization_id: parseInt(organization.id, 10),
  35. });
  36. }
  37. function handleFetchSuccess(groupListState, cursorHandler) {
  38. setPageLinks(groupListState.pageLinks);
  39. setOnCursor(() => cursorHandler);
  40. }
  41. const endpointPath = `/organizations/${organization.slug}/issues/`;
  42. const issueQuery = 'is:unresolved error.unhandled:true';
  43. const queryParams = {
  44. limit: 5,
  45. ...getParams(pick(location.query, [...Object.values(URL_PARAM), 'cursor'])),
  46. query: issueQuery,
  47. sort: 'freq',
  48. };
  49. const issueSearch = {
  50. pathname: endpointPath,
  51. query: queryParams,
  52. };
  53. function renderEmptyMessage() {
  54. const selectedTimePeriod = location.query.start
  55. ? null
  56. : DEFAULT_RELATIVE_PERIODS[
  57. decodeScalar(location.query.statsPeriod, DEFAULT_STATS_PERIOD)
  58. ];
  59. const displayedPeriod = selectedTimePeriod
  60. ? selectedTimePeriod.toLowerCase()
  61. : t('given timeframe');
  62. return (
  63. <Panel>
  64. <PanelBody>
  65. <NoGroupsHandler
  66. api={api}
  67. organization={organization}
  68. query={issueQuery}
  69. selectedProjectIds={[projectId]}
  70. groupIds={[]}
  71. emptyMessage={tct('No unhandled issues for the [timePeriod].', {
  72. timePeriod: displayedPeriod,
  73. })}
  74. />
  75. </PanelBody>
  76. </Panel>
  77. );
  78. }
  79. return (
  80. <React.Fragment>
  81. <ControlsWrapper>
  82. <SectionHeading>{t('Frequent Unhandled Issues')}</SectionHeading>
  83. <ButtonBar gap={1}>
  84. <div>
  85. <Button
  86. data-test-id="issues-open"
  87. size="small"
  88. to={issueSearch}
  89. onClick={handleOpenClick}
  90. >
  91. {t('Open in Issues')}
  92. </Button>
  93. </div>
  94. <StyledPagination pageLinks={pageLinks} onCursor={onCursor} />
  95. </ButtonBar>
  96. </ControlsWrapper>
  97. <GroupList
  98. orgId={organization.slug}
  99. endpointPath={endpointPath}
  100. queryParams={queryParams}
  101. query=""
  102. canSelectGroups={false}
  103. renderEmptyMessage={renderEmptyMessage}
  104. withChart={false}
  105. withPagination={false}
  106. onFetchSuccess={handleFetchSuccess}
  107. />
  108. </React.Fragment>
  109. );
  110. }
  111. const ControlsWrapper = styled('div')`
  112. display: flex;
  113. align-items: center;
  114. justify-content: space-between;
  115. margin-bottom: ${space(1)};
  116. @media (max-width: ${p => p.theme.breakpoints[0]}) {
  117. display: block;
  118. }
  119. `;
  120. const StyledPagination = styled(Pagination)`
  121. margin: 0;
  122. `;
  123. export default ProjectIssues;