import {Fragment, useMemo, useRef} from 'react'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; import EmptyMessage from 'sentry/components/emptyMessage'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Pagination from 'sentry/components/pagination'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import PanelHeader from 'sentry/components/panels/panelHeader'; import PanelItem from 'sentry/components/panels/panelItem'; import Placeholder from 'sentry/components/placeholder'; import SearchBar from 'sentry/components/searchBar'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Project} from 'sentry/types/project'; import {browserHistory} from 'sentry/utils/browserHistory'; import {sortProjects} from 'sentry/utils/project/sortProjects'; import {useApiQuery} from 'sentry/utils/queryClient'; import {decodeScalar} from 'sentry/utils/queryString'; import routeTitleGen from 'sentry/utils/routeTitle'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import ProjectListItem from 'sentry/views/settings/components/settingsProjectItem'; import CreateProjectButton from 'sentry/views/settings/organizationProjects/createProjectButton'; import ProjectStatsGraph from './projectStatsGraph'; const ITEMS_PER_PAGE = 50; type ProjectStats = Record>; function OrganizationProjects() { const organization = useOrganization(); const location = useLocation(); const query = decodeScalar(location.query.query, ''); const time = useRef(new Date().getTime()); const { data: projectList, getResponseHeader, isPending, isError, } = useApiQuery( [ `/organizations/${organization.slug}/projects/`, { query: { ...location.query, query, per_page: ITEMS_PER_PAGE, }, }, ], {staleTime: 0} ); const {data: projectStats, isPending: isLoadingStats} = useApiQuery( [ `/organizations/${organization.slug}/stats/`, { query: { projectID: projectList?.map(p => p.id), since: time.current / 1000 - 3600 * 24, stat: 'generated', group: 'project', }, }, ], { staleTime: 60_000, enabled: !!projectList, } ); const projectListPageLinks = getResponseHeader?.('Link'); const action = ; const debouncedSearch = useMemo( () => debounce( (searchQuery: string) => browserHistory.replace({ pathname: location.pathname, query: {...location.query, query: searchQuery, cursor: undefined}, }), DEFAULT_DEBOUNCE_DURATION ), [location.pathname, location.query] ); return ( {t('Projects')} {isPending && } {isError && } {projectList && sortProjects(projectList).map(project => ( {isLoadingStats && } {projectStats && ( )} ))} {projectList && projectList.length === 0 && ( {t('No projects found.')} )} {projectListPageLinks && } ); } export default OrganizationProjects; const SearchWrapper = styled('div')` margin-bottom: ${space(2)}; `; const GridPanelItem = styled(PanelItem)` display: flex; align-items: center; padding: 0; `; const ProjectListItemWrapper = styled('div')` padding: ${space(2)}; flex: 1; `; const ProjectStatsGraphWrapper = styled('div')` padding: ${space(2)}; width: 25%; margin-left: ${space(2)}; `;