mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
262 lines
7.3 KiB
TypeScript
262 lines
7.3 KiB
TypeScript
import type {RouteComponentProps} from 'react-router';
|
|
import {useTheme} from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
import type {LocationDescriptorObject} from 'history';
|
|
import pick from 'lodash/pick';
|
|
import moment from 'moment';
|
|
|
|
import SelectControl from 'sentry/components/forms/controls/selectControl';
|
|
import TeamSelector from 'sentry/components/teamSelector';
|
|
import type {ChangeData} from 'sentry/components/timeRangeSelector';
|
|
import {TimeRangeSelector} from 'sentry/components/timeRangeSelector';
|
|
import {t} from 'sentry/locale';
|
|
import {space} from 'sentry/styles/space';
|
|
import type {DateString, TeamWithProjects} from 'sentry/types';
|
|
import {uniq} from 'sentry/utils/array/uniq';
|
|
import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser';
|
|
import localStorage from 'sentry/utils/localStorage';
|
|
import useOrganization from 'sentry/utils/useOrganization';
|
|
import useProjects from 'sentry/utils/useProjects';
|
|
|
|
import {dataDatetime} from './utils';
|
|
|
|
const INSIGHTS_DEFAULT_STATS_PERIOD = '8w';
|
|
|
|
const relativeOptions = {
|
|
'14d': t('Last 2 weeks'),
|
|
'4w': t('Last 4 weeks'),
|
|
[INSIGHTS_DEFAULT_STATS_PERIOD]: t('Last 8 weeks'),
|
|
'12w': t('Last 12 weeks'),
|
|
};
|
|
|
|
const PAGE_QUERY_PARAMS = [
|
|
'pageStatsPeriod',
|
|
'pageStart',
|
|
'pageEnd',
|
|
'pageUtc',
|
|
'dataCategory',
|
|
'transform',
|
|
'sort',
|
|
'query',
|
|
'cursor',
|
|
'team',
|
|
'environment',
|
|
];
|
|
|
|
type Props = Pick<RouteComponentProps<{}, {}>, 'router' | 'location'> & {
|
|
currentEnvironment?: string;
|
|
currentTeam?: TeamWithProjects;
|
|
showEnvironment?: boolean;
|
|
};
|
|
|
|
function TeamStatsControls({
|
|
location,
|
|
router,
|
|
currentTeam,
|
|
currentEnvironment,
|
|
showEnvironment,
|
|
}: Props) {
|
|
const {projects} = useProjects({
|
|
slugs: currentTeam?.projects?.map(project => project.slug) ?? [],
|
|
});
|
|
const organization = useOrganization();
|
|
const isSuperuser = isActiveSuperuser();
|
|
const theme = useTheme();
|
|
|
|
const query = location?.query ?? {};
|
|
const localStorageKey = `teamInsightsSelectedTeamId:${organization.slug}`;
|
|
|
|
function handleChangeTeam(teamId: string) {
|
|
localStorage.setItem(localStorageKey, teamId);
|
|
// TODO(workflow): Preserve environment if it exists for the new team
|
|
setStateOnUrl({team: teamId, environment: undefined});
|
|
}
|
|
|
|
function handleEnvironmentChange({value}: {label: string; value: string}) {
|
|
if (value === '') {
|
|
setStateOnUrl({environment: undefined});
|
|
} else {
|
|
setStateOnUrl({environment: value});
|
|
}
|
|
}
|
|
|
|
function handleUpdateDatetime(datetime: ChangeData): LocationDescriptorObject {
|
|
const {start, end, relative, utc} = datetime;
|
|
|
|
if (start && end) {
|
|
const parser = utc ? moment.utc : moment;
|
|
|
|
return setStateOnUrl({
|
|
pageStatsPeriod: undefined,
|
|
pageStart: parser(start).format(),
|
|
pageEnd: parser(end).format(),
|
|
pageUtc: utc ?? undefined,
|
|
});
|
|
}
|
|
|
|
return setStateOnUrl({
|
|
pageStatsPeriod: relative || undefined,
|
|
pageStart: undefined,
|
|
pageEnd: undefined,
|
|
pageUtc: undefined,
|
|
});
|
|
}
|
|
|
|
function setStateOnUrl(nextState: {
|
|
environment?: string;
|
|
pageEnd?: DateString;
|
|
pageStart?: DateString;
|
|
pageStatsPeriod?: string | null;
|
|
pageUtc?: boolean | null;
|
|
team?: string;
|
|
}): LocationDescriptorObject {
|
|
const nextQueryParams = pick(nextState, PAGE_QUERY_PARAMS);
|
|
|
|
const nextLocation = {
|
|
...location,
|
|
query: {
|
|
...query,
|
|
...nextQueryParams,
|
|
},
|
|
};
|
|
|
|
router.push(nextLocation);
|
|
|
|
return nextLocation;
|
|
}
|
|
|
|
const {period, start, end, utc} = dataDatetime(query);
|
|
const environmentOptions = uniq(projects.flatMap(project => project.environments)).map(
|
|
env => ({label: env, value: env})
|
|
);
|
|
|
|
// org:admin is a unique scope that only org owners have
|
|
const isOrgOwner = organization.access.includes('org:admin');
|
|
|
|
return (
|
|
<ControlsWrapper showEnvironment={showEnvironment}>
|
|
<StyledTeamSelector
|
|
name="select-team"
|
|
inFieldLabel={t('Team: ')}
|
|
value={currentTeam?.slug}
|
|
onChange={choice => handleChangeTeam(choice.actor.id)}
|
|
teamFilter={
|
|
isSuperuser || isOrgOwner ? undefined : filterTeam => filterTeam.isMember
|
|
}
|
|
styles={{
|
|
singleValue(provided: any) {
|
|
const custom = {
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
fontSize: theme.fontSizeMedium,
|
|
':before': {
|
|
...provided[':before'],
|
|
color: theme.textColor,
|
|
marginRight: space(1.5),
|
|
marginLeft: space(0.5),
|
|
},
|
|
};
|
|
return {...provided, ...custom};
|
|
},
|
|
input: (provided: any, state: any) => ({
|
|
...provided,
|
|
display: 'grid',
|
|
gridTemplateColumns: 'max-content 1fr',
|
|
alignItems: 'center',
|
|
gridGap: space(1),
|
|
':before': {
|
|
backgroundColor: state.theme.backgroundSecondary,
|
|
height: 24,
|
|
width: 38,
|
|
borderRadius: 3,
|
|
content: '""',
|
|
display: 'block',
|
|
},
|
|
}),
|
|
}}
|
|
/>
|
|
{showEnvironment && (
|
|
<SelectControl
|
|
options={[
|
|
{
|
|
value: '',
|
|
label: t('All'),
|
|
},
|
|
...environmentOptions,
|
|
]}
|
|
value={currentEnvironment ?? ''}
|
|
onChange={handleEnvironmentChange}
|
|
styles={{
|
|
input: (provided: any) => ({
|
|
...provided,
|
|
display: 'grid',
|
|
gridTemplateColumns: 'max-content 1fr',
|
|
alignItems: 'center',
|
|
gridGap: space(1),
|
|
':before': {
|
|
height: 24,
|
|
width: 90,
|
|
borderRadius: 3,
|
|
content: '""',
|
|
display: 'block',
|
|
},
|
|
}),
|
|
control: (base: any) => ({
|
|
...base,
|
|
boxShadow: 'none',
|
|
}),
|
|
singleValue: (base: any) => ({
|
|
...base,
|
|
fontSize: theme.fontSizeMedium,
|
|
display: 'flex',
|
|
':before': {
|
|
...base[':before'],
|
|
color: theme.textColor,
|
|
marginRight: space(1.5),
|
|
},
|
|
}),
|
|
}}
|
|
inFieldLabel={t('Environment:')}
|
|
/>
|
|
)}
|
|
<StyledTimeRangeSelector
|
|
relative={period ?? ''}
|
|
start={start ?? null}
|
|
end={end ?? null}
|
|
utc={utc ?? null}
|
|
onChange={handleUpdateDatetime}
|
|
showAbsolute={false}
|
|
relativeOptions={relativeOptions}
|
|
triggerLabel={period && relativeOptions[period]}
|
|
triggerProps={{prefix: t('Date Range')}}
|
|
/>
|
|
</ControlsWrapper>
|
|
);
|
|
}
|
|
|
|
export default TeamStatsControls;
|
|
|
|
const ControlsWrapper = styled('div')<{showEnvironment?: boolean}>`
|
|
display: grid;
|
|
align-items: center;
|
|
gap: ${space(2)};
|
|
margin-bottom: ${space(2)};
|
|
|
|
@media (min-width: ${p => p.theme.breakpoints.small}) {
|
|
grid-template-columns: 246px ${p => (p.showEnvironment ? '246px' : '')} 1fr;
|
|
}
|
|
`;
|
|
|
|
const StyledTeamSelector = styled(TeamSelector)`
|
|
& > div {
|
|
box-shadow: ${p => p.theme.dropShadowMedium};
|
|
}
|
|
`;
|
|
|
|
const StyledTimeRangeSelector = styled(TimeRangeSelector)`
|
|
div {
|
|
min-height: unset;
|
|
}
|
|
`;
|