-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resolve threshold index rebase (#58116)
Separate flagged threshold routing has now been completed in [this PR](#58202) Most recent updates resolve rebase discrepancies and re-implement the thresholds list view with appropriate page filters and redirects https://github.com/getsentry/sentry/assets/6186377/0cd52029-ed07-45a6-a9ce-cf6b5e6b0924
- Loading branch information
Showing
4 changed files
with
250 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,180 @@ | ||
import {useEffect} from 'react'; | ||
import {RouteComponentProps} from 'react-router'; | ||
import {useEffect, useMemo} from 'react'; | ||
import styled from '@emotion/styled'; | ||
|
||
// import {Organization, PageFilters, Project} from 'sentry/types'; | ||
import GuideAnchor from 'sentry/components/assistant/guideAnchor'; | ||
import EnvironmentPageFilter from 'sentry/components/environmentPageFilter'; | ||
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 PageFilterBar from 'sentry/components/organizations/pageFilterBar'; | ||
import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; | ||
import PanelTable from 'sentry/components/panels/panelTable'; | ||
import ProjectPageFilter from 'sentry/components/projectPageFilter'; | ||
// import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters'; | ||
import {t} from 'sentry/locale'; | ||
// import ConfigStore from 'sentry/stores/configStore'; | ||
import {space} from 'sentry/styles/space'; | ||
// import {Project} from 'sentry/types'; | ||
import useOrganization from 'sentry/utils/useOrganization'; | ||
|
||
// import usePageFilters from 'sentry/utils/usePageFilters'; | ||
import usePageFilters from 'sentry/utils/usePageFilters'; | ||
// import useProjects from 'sentry/utils/useProjects'; | ||
// import useRouter from 'sentry/utils/useRouter'; | ||
import useRouter from 'sentry/utils/useRouter'; | ||
|
||
import Header from '../components/header'; | ||
import {Threshold} from '../utils/types'; | ||
import useFetchThresholdsListData from '../utils/useFetchThresholdsListData'; | ||
|
||
type RouteParams = { | ||
orgId: string; | ||
}; | ||
import {ThresholdGroupRow} from './thresholdGroupRow'; | ||
|
||
type Props = RouteComponentProps<RouteParams, {}> & {}; | ||
type Props = {}; | ||
|
||
function ReleaseThresholdList({router}: Props) { | ||
function ReleaseThresholdList({}: Props) { | ||
const router = useRouter(); | ||
const organization = useOrganization(); | ||
useEffect(() => { | ||
const hasV2ReleaseUIEnabled = organization.features.includes('release-ui-v2'); | ||
if (!hasV2ReleaseUIEnabled) { | ||
router.replace('/releases/'); | ||
} | ||
}, [router, organization]); | ||
// const {projects, initiallyLoaded: projectsLoaded} = useProjects(); | ||
// const {selection, isReady, desyncedFilters} = usePageFilters(); | ||
// const {projects} = useProjects(); | ||
const {selection} = usePageFilters(); | ||
const { | ||
data: thresholds = [], | ||
error, | ||
isLoading, | ||
isError, | ||
refetch, | ||
} = useFetchThresholdsListData({ | ||
selectedProjectIds: selection.projects, | ||
}); | ||
|
||
// const _getAllSelectedProjects = (): Project[] => { | ||
// return projects.filter(project => | ||
// selection.projects.some(id => String(id) === project.id || id === -1) | ||
// ); | ||
// }; | ||
|
||
// const _getAllEnvironments = (): string[] => { | ||
// const selectedProjects = selection.projects.map(id => String(id)); | ||
// const {user} = ConfigStore.getState(); | ||
// const allEnvSet = new Set(projects.flatMap(project => project.environments)); | ||
// // NOTE: mostly taken from environmentSelector.tsx | ||
// const unSortedEnvs = new Set( | ||
// projects.flatMap(project => { | ||
// /** | ||
// * Include environments from: | ||
// * all projects if the user is a superuser | ||
// * the requested projects | ||
// * all member projects if 'my projects' (empty list) is selected. | ||
// * all projects if -1 is the only selected project. | ||
// */ | ||
// if ( | ||
// (selectedProjects.length === 1 && | ||
// selectedProjects[0] === ALL_ACCESS_PROJECTS && | ||
// project.hasAccess) || | ||
// (selectedProjects.length === 0 && (project.isMember || user.isSuperuser)) || | ||
// selectedProjects.includes(project.id) | ||
// ) { | ||
// return project.environments; | ||
// } | ||
|
||
// return []; | ||
// }) | ||
// ); | ||
// const envDiff = new Set([...allEnvSet].filter(x => !unSortedEnvs.has(x))); | ||
|
||
// return Array.from(unSortedEnvs) | ||
// .sort() | ||
// .concat([...envDiff].sort()); | ||
// }; | ||
|
||
// NOTE: currently no way to filter for 'None' environments | ||
const filteredThresholds = selection.environments.length | ||
? thresholds.filter( | ||
threshold => selection.environments.indexOf(threshold.environment.name) > -1 | ||
) | ||
: thresholds; | ||
|
||
const thresholdGroups: {[key: string]: {[key: string]: Threshold[]}} = useMemo(() => { | ||
const byProj = {}; | ||
filteredThresholds.forEach(threshold => { | ||
const projId = threshold.project.id; | ||
if (!byProj[projId]) { | ||
byProj[projId] = {}; | ||
} | ||
const env = threshold.environment.name; | ||
if (!byProj[projId][env]) { | ||
byProj[projId][env] = []; | ||
} | ||
byProj[projId][env].push(threshold); | ||
}); | ||
return byProj; | ||
}, [filteredThresholds]); | ||
|
||
if (isError) { | ||
return <LoadingError onRetry={refetch} message={error.message} />; | ||
} | ||
if (isLoading) { | ||
return <LoadingIndicator />; | ||
} | ||
|
||
return ( | ||
<div> | ||
<Header router={router} hasV2ReleaseUIEnabled /> | ||
</div> | ||
<PageFiltersContainer> | ||
<NoProjectMessage organization={organization}> | ||
<Header router={router} hasV2ReleaseUIEnabled /> | ||
<Layout.Body> | ||
<Layout.Main fullWidth> | ||
<ReleaseThresholdsPageFilterBar condensed> | ||
<GuideAnchor target="release_projects"> | ||
<ProjectPageFilter /> | ||
</GuideAnchor> | ||
<EnvironmentPageFilter /> | ||
</ReleaseThresholdsPageFilterBar> | ||
<StyledPanelTable | ||
isLoading={isLoading} | ||
isEmpty={filteredThresholds.length === 0 && !isError} | ||
emptyMessage={t('No thresholds found.')} | ||
headers={[ | ||
t('Project Name'), | ||
t('Environment'), | ||
t('Window'), | ||
t('Conditions'), | ||
t('Actions'), | ||
]} | ||
> | ||
{thresholdGroups && | ||
Object.entries(thresholdGroups).map(([projId, byEnv]) => { | ||
return Object.entries(byEnv).map(([envName, thresholdGroup]) => ( | ||
<ThresholdGroupRow | ||
key={`${projId}-${envName}`} | ||
thresholds={thresholdGroup} | ||
/> | ||
)); | ||
})} | ||
</StyledPanelTable> | ||
</Layout.Main> | ||
</Layout.Body> | ||
</NoProjectMessage> | ||
</PageFiltersContainer> | ||
); | ||
} | ||
|
||
export default ReleaseThresholdList; | ||
|
||
const StyledPanelTable = styled(PanelTable)` | ||
@media (min-width: ${p => p.theme.breakpoints.small}) { | ||
overflow: initial; | ||
} | ||
grid-template-columns: | ||
minmax(150px, 1fr) minmax(150px, 1fr) minmax(150px, 1fr) minmax(250px, 4fr) | ||
auto; | ||
white-space: nowrap; | ||
font-size: ${p => p.theme.fontSizeMedium}; | ||
`; | ||
|
||
const ReleaseThresholdsPageFilterBar = styled(PageFilterBar)` | ||
margin-bottom: ${space(2)}; | ||
`; |
80 changes: 80 additions & 0 deletions
80
static/app/views/releases/thresholdsList/thresholdGroupRow.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import {Fragment} from 'react'; | ||
import styled from '@emotion/styled'; | ||
|
||
import {DropdownMenu, MenuItemProps} from 'sentry/components/dropdownMenu'; | ||
import ProjectBadge from 'sentry/components/idBadge/projectBadge'; | ||
import {IconEllipsis} from 'sentry/icons'; | ||
import {t} from 'sentry/locale'; | ||
import {space} from 'sentry/styles/space'; | ||
import {getExactDuration} from 'sentry/utils/formatters'; | ||
|
||
import {Threshold} from '../utils/types'; | ||
|
||
type Props = { | ||
thresholds: {[key: string]: any}; | ||
}; | ||
|
||
export function ThresholdGroupRow({thresholds}: Props) { | ||
const actions: MenuItemProps[] = [ | ||
{ | ||
key: 'edit', | ||
label: t('Edit'), | ||
onAction: () => {}, | ||
}, | ||
{ | ||
key: 'delete', | ||
label: t('Delete'), | ||
priority: 'danger', | ||
onAction: () => { | ||
// console.log('oops'); | ||
}, | ||
}, | ||
]; | ||
|
||
return thresholds.map((threshold: Threshold, idx: number) => ( | ||
<Fragment key={idx}> | ||
<FlexCenter> | ||
{idx === 0 ? ( | ||
<ProjectBadge | ||
project={threshold.project} | ||
avatarSize={16} | ||
hideOverflow | ||
disableLink | ||
/> | ||
) : ( | ||
'' | ||
)} | ||
</FlexCenter> | ||
<FlexCenter>{idx === 0 ? threshold.environment.name || 'None' : ''}</FlexCenter> | ||
<FlexCenter>{getExactDuration(threshold.window_in_seconds)}</FlexCenter> | ||
<FlexCenter> | ||
{threshold.trigger_type === 'over' ? '>' : '<'} {threshold.threshold_type} | ||
</FlexCenter> | ||
<ActionsColumn> | ||
<DropdownMenu | ||
items={actions} | ||
position="bottom-end" | ||
triggerProps={{ | ||
'aria-label': t('Actions'), | ||
size: 'xs', | ||
icon: <IconEllipsis size="xs" />, | ||
showChevron: false, | ||
}} | ||
// disabledKeys={hasAccess && canEdit ? [] : ['delete']} | ||
/> | ||
</ActionsColumn> | ||
</Fragment> | ||
)); | ||
} | ||
|
||
const FlexCenter = styled('div')` | ||
display: flex; | ||
align-items: center; | ||
`; | ||
|
||
const ActionsColumn = styled('div')` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
padding: ${space(1)}; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters