diff --git a/static/app/components/nav/issueViews/issueViewNavItems.tsx b/static/app/components/nav/issueViews/issueViewNavItems.tsx index 9734b487da03ba..17eaea598b0b48 100644 --- a/static/app/components/nav/issueViews/issueViewNavItems.tsx +++ b/static/app/components/nav/issueViews/issueViewNavItems.tsx @@ -297,6 +297,11 @@ export function IssueViewNavItems({ /> ))} + {organization.features.includes('issue-view-sharing') && ( + + {t('All Views')} + + )} ); } diff --git a/static/app/routes.tsx b/static/app/routes.tsx index 1d3bddadf30bb6..95ccefbb82aef9 100644 --- a/static/app/routes.tsx +++ b/static/app/routes.tsx @@ -2102,6 +2102,12 @@ function buildRoutes() { const issueRoutes = ( + import('sentry/views/issueList/issueViews/issueViewsList/issueViewsList') + )} + /> + + { + navigate({ + pathname: location.pathname, + query: { + ...location.query, + [cursorQueryParam]: newCursor, + }, + }); + }} + /> + + ); +} + +export default function IssueViewsList() { + const organization = useOrganization(); + const navigate = useNavigate(); + const location = useLocation(); + const query = typeof location.query.query === 'string' ? location.query.query : ''; + + if (!organization.features.includes('issue-view-sharing')) { + return ; + } + + return ( + + + {t('All Views')} + + + + { + navigate({ + pathname: location.pathname, + query: {query: newQuery}, + }); + }} + placeholder="" + /> + {t('Owned by Me')} + + {t('Shared with Me')} + + + + + ); +} + +const TableHeading = styled('h2')` + display: flex; + justify-content: space-between; + align-items: center; + font-size: ${p => p.theme.fontSizeExtraLarge}; + margin-top: ${space(3)}; + margin-bottom: ${space(1.5)}; +`; diff --git a/static/app/views/issueList/issueViews/issueViewsList/issueViewsTable.tsx b/static/app/views/issueList/issueViews/issueViewsList/issueViewsTable.tsx new file mode 100644 index 00000000000000..a7985848dca3ad --- /dev/null +++ b/static/app/views/issueList/issueViews/issueViewsList/issueViewsTable.tsx @@ -0,0 +1,229 @@ +import {css} from '@emotion/react'; +import styled from '@emotion/styled'; + +import InteractionStateLayer from 'sentry/components/interactionStateLayer'; +import Link from 'sentry/components/links/link'; +import LoadingError from 'sentry/components/loadingError'; +import {PanelTable} from 'sentry/components/panels/panelTable'; +import {FormattedQuery} from 'sentry/components/searchQueryBuilder/formattedQuery'; +import {getAbsoluteSummary} from 'sentry/components/timeRangeSelector/utils'; +import TimeSince from 'sentry/components/timeSince'; +import {Tooltip} from 'sentry/components/tooltip'; +import {IconLock, IconStar, IconUser} from 'sentry/icons'; +import {t} from 'sentry/locale'; +import {space} from 'sentry/styles/space'; +import useOrganization from 'sentry/utils/useOrganization'; +import useProjects from 'sentry/utils/useProjects'; +import type {GroupSearchView} from 'sentry/views/issueList/types'; +import {getSortLabel} from 'sentry/views/issueList/utils'; +import {ProjectsRenderer} from 'sentry/views/traces/fieldRenderers'; + +type IssueViewsTableProps = { + isError: boolean; + isPending: boolean; + views: GroupSearchView[]; +}; + +function StarCellContent({isStarred}: {isStarred: boolean}) { + return ; +} + +function ProjectsCellContent({projects}: {projects: GroupSearchView['projects']}) { + const {projects: allProjects} = useProjects(); + + const projectSlugs = allProjects + .filter(project => projects.includes(parseInt(project.id, 10))) + .map(project => project.slug); + + if (projects.length === 0) { + return t('My Projects'); + } + if (projects.includes(-1)) { + return t('All Projects'); + } + return ; +} + +function EnvironmentsCellContent({ + environments, +}: { + environments: GroupSearchView['environments']; +}) { + const environmentsLabel = + environments.length === 0 ? t('All') : environments.join(', '); + + return ( + + {environmentsLabel} + + ); +} + +function TimeCellContent({timeFilters}: {timeFilters: GroupSearchView['timeFilters']}) { + if (timeFilters.period) { + return timeFilters.period; + } + + return getAbsoluteSummary(timeFilters.start, timeFilters.end, timeFilters.utc); +} + +function SharingCellContent({visibility}: {visibility: GroupSearchView['visibility']}) { + if (visibility === 'organization') { + return ( + + + + + + ); + } + return ( + + + + + + ); +} + +function LastVisitedCellContent({ + lastVisited, +}: { + lastVisited: GroupSearchView['lastVisited']; +}) { + if (!lastVisited) { + return '-'; + } + return ; +} + +export function IssueViewsTable({views, isPending, isError}: IssueViewsTableProps) { + const organization = useOrganization(); + + return ( + + {isError && } + {views.map((view, index) => ( + + + + {/* TODO: Add isStarred when the API is update to include it */} + + + + + {view.name} + + + + + + + + + + + + + + + {getSortLabel(view.querySort, organization)} + + + + + + + + ))} + + ); +} + +const StyledPanelTable = styled(PanelTable)` + white-space: nowrap; + font-size: ${p => p.theme.fontSizeMedium}; + overflow: auto; + grid-template-columns: 36px auto auto 1fr auto auto 105px 90px 115px; + + @media (min-width: ${p => p.theme.breakpoints.small}) { + overflow: hidden; + } + + & > * { + padding: ${space(1)} ${space(2)}; + } +`; + +const Row = styled('div')<{isFirst: boolean}>` + display: grid; + position: relative; + grid-template-columns: subgrid; + grid-column: 1/-1; + padding: 0; + + ${p => + p.isFirst && + css` + border-top: 1px solid ${p.theme.border}; + `} + + &:not(:last-child) { + border-bottom: 1px solid ${p => p.theme.innerBorder}; + } +`; + +const Cell = styled('div')` + display: flex; + align-items: center; + padding: ${space(1)} ${space(2)}; +`; + +const StarCell = styled(Cell)` + padding: 0 0 0 ${space(2)}; +`; + +const RowHoverStateLayer = styled(InteractionStateLayer)``; + +const RowLink = styled(Link)` + color: ${p => p.theme.textColor}; + + &:hover { + color: ${p => p.theme.textColor}; + text-decoration: underline; + } + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +`; + +const PositionedTimeSince = styled(TimeSince)` + position: relative; +`; + +const PositionedContent = styled('div')` + position: relative; + display: flex; + align-items: center; +`;