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;
+`;