import {Fragment} from 'react'; import {RouteComponentProps} from 'react-router'; import styled from '@emotion/styled'; import {Location} from 'history'; import Button from 'sentry/components/button'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Pagination from 'sentry/components/pagination'; import {Panel, PanelBody, PanelHeader, PanelItem} from 'sentry/components/panels'; import Placeholder from 'sentry/components/placeholder'; import {IconAdd} from 'sentry/icons'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import {Organization, Project} from 'sentry/types'; import {sortProjects} from 'sentry/utils'; import {decodeScalar} from 'sentry/utils/queryString'; import routeTitleGen from 'sentry/utils/routeTitle'; import withOrganization from 'sentry/utils/withOrganization'; import AsyncView from 'sentry/views/asyncView'; import EmptyMessage from 'sentry/views/settings/components/emptyMessage'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import ProjectListItem from 'sentry/views/settings/components/settingsProjectItem'; import ProjectStatsGraph from './projectStatsGraph'; const ITEMS_PER_PAGE = 50; type Props = { location: Location; organization: Organization; } & RouteComponentProps<{orgId: string}, {}>; type ProjectStats = Record>; type State = AsyncView['state'] & { projectList: Project[] | null; projectListPageLinks: string | null; projectStats: ProjectStats | null; }; class OrganizationProjects extends AsyncView { getEndpoints(): ReturnType { const {orgId} = this.props.params; const {location} = this.props; const query = decodeScalar(location.query.query); return [ [ 'projectList', `/organizations/${orgId}/projects/`, { query: { query, per_page: ITEMS_PER_PAGE, }, }, ], [ 'projectStats', `/organizations/${orgId}/stats/`, { query: { since: new Date().getTime() / 1000 - 3600 * 24, stat: 'generated', group: 'project', per_page: ITEMS_PER_PAGE, }, }, ], ]; } getTitle(): string { const {organization} = this.props; return routeTitleGen(t('Projects'), organization.slug, false); } renderLoading(): React.ReactNode { return this.renderBody(); } renderBody(): React.ReactNode { const {projectList, projectListPageLinks, projectStats} = this.state; const {organization} = this.props; const canCreateProjects = new Set(organization.access).has('project:admin'); const action = ( ); return ( {this.renderSearchInput({ updateRoute: true, placeholder: t('Search Projects'), className: 'search', })} {t('Projects')} {projectList ? ( sortProjects(projectList).map(project => ( {projectStats ? ( ) : ( )} )) ) : ( )} {projectList && projectList.length === 0 && ( {t('No projects found.')} )} {projectListPageLinks && ( )} ); } } export default withOrganization(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)}; `;