Files
2024-02-15 17:01:50 +00:00

150 lines
5.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {Fragment} from 'react';
import styled from '@emotion/styled';
import * as qs from 'query-string';
import {openBulkEditMonitorsModal} from 'sentry/actionCreators/modal';
import {Button} from 'sentry/components/button';
import ButtonBar from 'sentry/components/buttonBar';
import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
import HookOrDefault from 'sentry/components/hookOrDefault';
import * as Layout from 'sentry/components/layouts/thirds';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
import Pagination from 'sentry/components/pagination';
import SearchBar from 'sentry/components/searchBar';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {IconAdd, IconList} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {useApiQuery} from 'sentry/utils/queryClient';
import {decodeScalar} from 'sentry/utils/queryString';
import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
import useOrganization from 'sentry/utils/useOrganization';
import useRouter from 'sentry/utils/useRouter';
import {
CronsLandingPanel,
isValidGuide,
isValidPlatform,
} from './components/cronsLandingPanel';
import {NewMonitorButton} from './components/newMonitorButton';
import {OverviewTimeline} from './components/overviewTimeline';
import type {Monitor} from './types';
import {makeMonitorListQueryKey} from './utils';
const CronsListPageHeader = HookOrDefault({
hookName: 'component:crons-list-page-header',
});
export default function Monitors() {
const organization = useOrganization();
const router = useRouter();
const platform = decodeScalar(router.location.query?.platform) ?? null;
const guide = decodeScalar(router.location.query?.guide);
const queryKey = makeMonitorListQueryKey(organization, router.location.query);
const {
data: monitorList,
getResponseHeader: monitorListHeaders,
isLoading,
refetch,
} = useApiQuery<Monitor[]>(queryKey, {
staleTime: 0,
});
useRouteAnalyticsEventNames('monitors.page_viewed', 'Monitors: Page Viewed');
useRouteAnalyticsParams({empty_state: !monitorList || monitorList.length === 0});
const monitorListPageLinks = monitorListHeaders?.('Link');
const handleSearch = (query: string) => {
const currentQuery = {...(router.location.query ?? {}), cursor: undefined};
router.push({
pathname: location.pathname,
query: normalizeDateTimeParams({...currentQuery, query}),
});
};
const showAddMonitor = !isValidPlatform(platform) || !isValidGuide(guide);
return (
<SentryDocumentTitle title={`Crons — ${organization.slug}`}>
<CronsListPageHeader organization={organization} />
<Layout.Page>
<Layout.Header>
<Layout.HeaderContent>
<Layout.Title>
{t('Cron Monitors')}
<PageHeadingQuestionTooltip
title={t(
'Scheduled monitors that check in on recurring jobs and tell you if they’re running on schedule, failing, or succeeding.'
)}
docsUrl="https://docs.sentry.io/product/crons/"
/>
</Layout.Title>
</Layout.HeaderContent>
<Layout.HeaderActions>
<ButtonBar gap={1}>
<FeedbackWidgetButton />
<Button
icon={<IconList />}
size="sm"
onClick={() =>
openBulkEditMonitorsModal({
onClose: refetch,
})
}
>
{t('Manage Monitors')}
</Button>
{showAddMonitor && (
<NewMonitorButton size="sm" icon={<IconAdd isCircled />}>
{t('Add Monitor')}
</NewMonitorButton>
)}
</ButtonBar>
</Layout.HeaderActions>
</Layout.Header>
<Layout.Body>
<Layout.Main fullWidth>
<Filters>
<PageFilterBar>
<ProjectPageFilter resetParamsOnChange={['cursor']} />
<EnvironmentPageFilter resetParamsOnChange={['cursor']} />
</PageFilterBar>
<SearchBar
query={decodeScalar(qs.parse(location.search)?.query, '')}
placeholder={t('Search by name or slug')}
onSearch={handleSearch}
/>
</Filters>
{isLoading ? (
<LoadingIndicator />
) : monitorList?.length ? (
<Fragment>
<OverviewTimeline monitorList={monitorList} />
{monitorListPageLinks && <Pagination pageLinks={monitorListPageLinks} />}
</Fragment>
) : (
<CronsLandingPanel />
)}
</Layout.Main>
</Layout.Body>
</Layout.Page>
</SentryDocumentTitle>
);
}
const Filters = styled('div')`
display: grid;
grid-template-columns: max-content 1fr;
gap: ${space(1.5)};
margin-bottom: ${space(2)};
`;