import {Fragment, useEffect, useMemo, useState} from 'react'; import LazyLoad from 'react-lazyload'; import {RouteComponentProps} from 'react-router'; import styled from '@emotion/styled'; import {withProfiler} from '@sentry/react'; import debounce from 'lodash/debounce'; import flatten from 'lodash/flatten'; import uniqBy from 'lodash/uniqBy'; import {Client} from 'sentry/api'; import Button from 'sentry/components/button'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import NoProjectMessage from 'sentry/components/noProjectMessage'; import PageHeading from 'sentry/components/pageHeading'; import SearchBar from 'sentry/components/searchBar'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants'; import {IconAdd, IconUser} from 'sentry/icons'; import {t} from 'sentry/locale'; import ProjectsStatsStore from 'sentry/stores/projectsStatsStore'; import space from 'sentry/styles/space'; import {Organization, TeamWithProjects} from 'sentry/types'; import {sortProjects} from 'sentry/utils'; import withApi from 'sentry/utils/withApi'; import withOrganization from 'sentry/utils/withOrganization'; import withTeamsForUser from 'sentry/utils/withTeamsForUser'; import TeamFilter from 'sentry/views/alerts/list/rules/teamFilter'; import ProjectCard from './projectCard'; import Resources from './resources'; import {getTeamParams} from './utils'; type Props = { api: Client; error: Error | null; loadingTeams: boolean; organization: Organization; teams: TeamWithProjects[]; } & RouteComponentProps<{orgId: string}, {}>; function Dashboard({teams, organization, loadingTeams, error, router, location}: Props) { useEffect(() => { return function cleanup() { ProjectsStatsStore.reset(); }; }, []); const [projectQuery, setProjectQuery] = useState(''); const debouncedSearchQuery = useMemo( () => debounce(handleSearch, DEFAULT_DEBOUNCE_DURATION), [] ); if (loadingTeams) { return ; } if (error) { return ; } const canCreateProjects = organization.access.includes('project:admin'); const canJoinTeam = organization.access.includes('team:read'); const hasProjectAccess = organization.access.includes('project:read'); const selectedTeams = getTeamParams(location ? location.query.team : ''); const filteredTeams = teams.filter(team => selectedTeams.includes(team.id)); const filteredTeamProjects = uniqBy( flatten((filteredTeams ?? teams).map(team => team.projects)), 'id' ); const projects = uniqBy(flatten(teams.map(teamObj => teamObj.projects)), 'id'); const currentProjects = selectedTeams.length === 0 ? projects : filteredTeamProjects; const filteredProjects = (currentProjects ?? projects).filter(project => project.slug.includes(projectQuery) ); const favorites = projects.filter(project => project.isBookmarked); const showEmptyMessage = projects.length === 0 && favorites.length === 0; const showResources = projects.length === 1 && !projects[0].firstEvent; function handleSearch(searchQuery: string) { setProjectQuery(searchQuery); } function handleChangeFilter(activeFilters: string[]) { const {...currentQuery} = location.query; router.push({ pathname: location.pathname, query: { ...currentQuery, team: activeFilters.length > 0 ? activeFilters : '', }, }); } if (showEmptyMessage) { return ( ); } return ( {projects.length > 0 && ( <PageHeading>{t('Projects')}</PageHeading> {sortProjects(filteredProjects).map(project => ( ))} {showResources && } )} ); } const OrganizationDashboard = (props: Props) => ( ); const ProjectsHeader = styled(Layout.Header)` border-bottom: none; align-items: end; @media (min-width: ${p => p.theme.breakpoints.medium}) { padding: 26px ${space(4)} 0 ${space(4)}; } `; const Title = styled(Layout.HeaderContent)` margin-bottom: 0; `; const ButtonContainer = styled('div')` display: inline-flex; gap: ${space(1)}; `; const SearchAndSelectorWrapper = styled('div')` display: flex; gap: ${space(2)}; justify-content: flex-end; align-items: flex-end; margin-bottom: ${space(2)}; @media (max-width: ${p => p.theme.breakpoints.small}) { display: block; } @media (min-width: ${p => p.theme.breakpoints.xlarge}) { display: flex; } `; const StyledSearchBar = styled(SearchBar)` flex-grow: 1; @media (max-width: ${p => p.theme.breakpoints.small}) { margin-top: ${space(1)}; } `; const Body = styled(Layout.Body)` padding-top: ${space(2)} !important; background-color: ${p => p.theme.surface100}; `; const ProjectCards = styled('div')` display: grid; grid-template-columns: minmax(100px, 1fr); gap: ${space(3)}; @media (min-width: ${p => p.theme.breakpoints.small}) { grid-template-columns: repeat(2, minmax(100px, 1fr)); } @media (min-width: ${p => p.theme.breakpoints.xlarge}) { grid-template-columns: repeat(3, minmax(100px, 1fr)); } `; const OrganizationDashboardWrapper = styled('div')` display: flex; flex: 1; flex-direction: column; `; export {Dashboard}; export default withApi( withOrganization(withTeamsForUser(withProfiler(OrganizationDashboard))) );