index.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import {useEffect, useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  4. import * as Layout from 'sentry/components/layouts/thirds';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import NoProjectMessage from 'sentry/components/noProjectMessage';
  8. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  9. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  10. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  11. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  12. import PanelTable from 'sentry/components/panels/panelTable';
  13. import {t} from 'sentry/locale';
  14. import {space} from 'sentry/styles/space';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import usePageFilters from 'sentry/utils/usePageFilters';
  17. import useRouter from 'sentry/utils/useRouter';
  18. import Header from '../components/header';
  19. import {Threshold} from '../utils/types';
  20. import useFetchThresholdsListData from '../utils/useFetchThresholdsListData';
  21. import {ThresholdGroupRows} from './thresholdGroupRows';
  22. type Props = {};
  23. function ReleaseThresholdList({}: Props) {
  24. const [listError, setListError] = useState<string>('');
  25. const router = useRouter();
  26. const organization = useOrganization();
  27. useEffect(() => {
  28. const hasV2ReleaseUIEnabled = organization.features.includes('release-ui-v2');
  29. if (!hasV2ReleaseUIEnabled) {
  30. router.replace('/releases/');
  31. }
  32. }, [router, organization]);
  33. // const {projects} = useProjects();
  34. const {selection} = usePageFilters();
  35. const {
  36. data: thresholds = [],
  37. error: requestError,
  38. isLoading,
  39. isError,
  40. refetch,
  41. } = useFetchThresholdsListData({
  42. selectedProjectIds: selection.projects,
  43. selectedEnvs: selection.environments,
  44. });
  45. // const _getAllSelectedProjects = (): Project[] => {
  46. // return projects.filter(project =>
  47. // selection.projects.some(id => String(id) === project.id || id === -1)
  48. // );
  49. // };
  50. // const _getAllEnvironments = (): string[] => {
  51. // const selectedProjects = selection.projects.map(id => String(id));
  52. // const {user} = ConfigStore.getState();
  53. // const allEnvSet = new Set(projects.flatMap(project => project.environments));
  54. // // NOTE: mostly taken from environmentSelector.tsx
  55. // const unSortedEnvs = new Set(
  56. // projects.flatMap(project => {
  57. // /**
  58. // * Include environments from:
  59. // * all projects if the user is a superuser
  60. // * the requested projects
  61. // * all member projects if 'my projects' (empty list) is selected.
  62. // * all projects if -1 is the only selected project.
  63. // */
  64. // if (
  65. // (selectedProjects.length === 1 &&
  66. // selectedProjects[0] === ALL_ACCESS_PROJECTS &&
  67. // project.hasAccess) ||
  68. // (selectedProjects.length === 0 && (project.isMember || user.isSuperuser)) ||
  69. // selectedProjects.includes(project.id)
  70. // ) {
  71. // return project.environments;
  72. // }
  73. // return [];
  74. // })
  75. // );
  76. // const envDiff = new Set([...allEnvSet].filter(x => !unSortedEnvs.has(x)));
  77. // return Array.from(unSortedEnvs)
  78. // .sort()
  79. // .concat([...envDiff].sort());
  80. // };
  81. // NOTE: currently no way to filter for 'None' environments
  82. const filteredThresholds = selection.environments.length
  83. ? thresholds.filter(
  84. threshold => selection.environments.indexOf(threshold.environment.name) > -1
  85. )
  86. : thresholds;
  87. const thresholdGroups: {[key: string]: {[key: string]: Threshold[]}} = useMemo(() => {
  88. const byProj = {};
  89. filteredThresholds.forEach(threshold => {
  90. const projId = threshold.project.id;
  91. if (!byProj[projId]) {
  92. byProj[projId] = {};
  93. }
  94. const env = threshold.environment.name;
  95. if (!byProj[projId][env]) {
  96. byProj[projId][env] = [];
  97. }
  98. byProj[projId][env].push(threshold);
  99. });
  100. return byProj;
  101. }, [filteredThresholds]);
  102. const tempError = msg => {
  103. setListError(msg);
  104. setTimeout(() => setListError(''), 5000);
  105. };
  106. if (isError) {
  107. return <LoadingError onRetry={refetch} message={requestError.message} />;
  108. }
  109. if (isLoading) {
  110. return <LoadingIndicator />;
  111. }
  112. return (
  113. <PageFiltersContainer>
  114. <NoProjectMessage organization={organization}>
  115. <Header router={router} hasV2ReleaseUIEnabled />
  116. <Layout.Body>
  117. <Layout.Main fullWidth>
  118. <FilterRow>
  119. <ReleaseThresholdsPageFilterBar condensed>
  120. <GuideAnchor target="release_projects">
  121. <ProjectPageFilter />
  122. </GuideAnchor>
  123. <EnvironmentPageFilter />
  124. </ReleaseThresholdsPageFilterBar>
  125. <ListError>{listError}</ListError>
  126. </FilterRow>
  127. <StyledPanelTable
  128. isLoading={isLoading}
  129. isEmpty={filteredThresholds.length === 0 && !isError}
  130. emptyMessage={t('No thresholds found.')}
  131. headers={[
  132. t('Project Name'),
  133. t('Environment'),
  134. t('Window'),
  135. t('Condition'),
  136. t(' '),
  137. ]}
  138. >
  139. {thresholdGroups &&
  140. Object.entries(thresholdGroups).map(([projId, byEnv]) => {
  141. return Object.entries(byEnv).map(([envName, thresholdGroup]) => (
  142. <ThresholdGroupRows
  143. key={`${projId}-${envName}`}
  144. thresholds={thresholdGroup}
  145. refetch={refetch}
  146. columns={5}
  147. orgSlug={organization.slug}
  148. setError={tempError}
  149. />
  150. ));
  151. })}
  152. </StyledPanelTable>
  153. </Layout.Main>
  154. </Layout.Body>
  155. </NoProjectMessage>
  156. </PageFiltersContainer>
  157. );
  158. }
  159. export default ReleaseThresholdList;
  160. const FilterRow = styled('div')`
  161. display: flex;
  162. align-items: center;
  163. `;
  164. const ListError = styled('div')`
  165. color: red;
  166. margin: 0 ${space(2)};
  167. width: 100%;
  168. display: flex;
  169. justify-content: center;
  170. `;
  171. const StyledPanelTable = styled(PanelTable)`
  172. @media (min-width: ${p => p.theme.breakpoints.small}) {
  173. overflow: initial;
  174. }
  175. grid-template-columns:
  176. minmax(100px, 1fr) minmax(100px, 1fr) minmax(250px, 1fr) minmax(200px, 4fr)
  177. minmax(150px, auto);
  178. white-space: nowrap;
  179. font-size: ${p => p.theme.fontSizeMedium};
  180. > * {
  181. border-bottom: inherit;
  182. }
  183. > *:last-child {
  184. > *:last-child {
  185. border-radius: 0 0 ${p => p.theme.borderRadius} 0;
  186. border-bottom: 0;
  187. }
  188. }
  189. `;
  190. const ReleaseThresholdsPageFilterBar = styled(PageFilterBar)`
  191. margin-bottom: ${space(2)};
  192. `;