Skip to content

Commit a9be7f5

Browse files
JonasBapriscilawebdevclaude
authored
ref(layout): use Layout.Page on alerts (#111642)
Wraps non-compliant routes in the alerts area with Layout.Page. Part of the Layout.Page audit remediation. ## Changes - `static/app/views/alerts/list/incidents/index.tsx` — wrap `renderBody()` return and `renderDisabled()` in `Layout.Page` - `static/app/views/alerts/list/rules/alertRulesList.tsx` — wrap `PageFiltersContainer` block in `Layout.Page` - `static/app/views/alerts/create.tsx` — replace outer `Fragment` with `Layout.Page` - `static/app/views/alerts/edit.tsx` — replace outer `Fragment` with `Layout.Page` ## Affected routes | Path | Strategy | | --- | --- | | `/organizations/:orgId/alerts/` | leaf fix | | `/organizations/:orgId/alerts/rules/` | leaf fix | | `/organizations/:orgId/alerts/:projectId/new/` | leaf fix | | `/organizations/:orgId/alerts/crons-rules/:projectId/:monitorSlug/` | leaf fix | ## Verification - [x] Each route above loads in the browser and renders inside a `<main>` element --------- Co-authored-by: Priscila Oliveira <priscila.oliveira@sentry.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e7aa611 commit a9be7f5

5 files changed

Lines changed: 289 additions & 281 deletions

File tree

static/app/views/alerts/create.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export default function Create() {
137137
const title = t('New Alert Rule');
138138

139139
return (
140-
<Fragment>
140+
<Layout.Page>
141141
<SentryDocumentTitle title={title} projectSlug={project.slug} />
142142
<Layout.Header>
143143
<Layout.HeaderContent>
@@ -228,6 +228,6 @@ export default function Create() {
228228
</Fragment>
229229
)}
230230
</Layout.Body>
231-
</Fragment>
231+
</Layout.Page>
232232
);
233233
}

static/app/views/alerts/edit.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default function ProjectAlertsEditor() {
6161
const {teams, isLoading: teamsLoading} = useUserTeams();
6262

6363
return (
64-
<Fragment>
64+
<Layout.Page>
6565
<SentryDocumentTitle
6666
title={title}
6767
orgSlug={organization.slug}
@@ -133,6 +133,6 @@ export default function ProjectAlertsEditor() {
133133
</Fragment>
134134
)}
135135
</Layout.Body>
136-
</Fragment>
136+
</Layout.Page>
137137
);
138138
}

static/app/views/alerts/list/incidents/index.tsx

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -263,28 +263,30 @@ class IncidentsList extends DeprecatedAsyncComponent<
263263

264264
return (
265265
<SentryDocumentTitle title={t('Alerts')} orgSlug={organization.slug}>
266-
<PageFiltersContainer>
267-
<AlertHeader activeTab="stream" />
268-
<Layout.Body>
269-
<Layout.Main width="full">
270-
{!this.tryRenderOnboarding() && (
271-
<Fragment>
272-
<StyledAlert variant="info">
273-
{t('This page only shows metric alerts.')}
274-
</StyledAlert>
275-
<FilterBar
276-
location={location}
277-
onChangeFilter={this.handleChangeFilter}
278-
onChangeSearch={this.handleChangeSearch}
279-
onChangeStatus={this.handleChangeStatus}
280-
hasStatusFilters
281-
/>
282-
</Fragment>
283-
)}
284-
{this.renderList()}
285-
</Layout.Main>
286-
</Layout.Body>
287-
</PageFiltersContainer>
266+
<Layout.Page>
267+
<PageFiltersContainer>
268+
<AlertHeader activeTab="stream" />
269+
<Layout.Body>
270+
<Layout.Main width="full">
271+
{!this.tryRenderOnboarding() && (
272+
<Fragment>
273+
<StyledAlert variant="info">
274+
{t('This page only shows metric alerts.')}
275+
</StyledAlert>
276+
<FilterBar
277+
location={location}
278+
onChangeFilter={this.handleChangeFilter}
279+
onChangeSearch={this.handleChangeSearch}
280+
onChangeStatus={this.handleChangeStatus}
281+
hasStatusFilters
282+
/>
283+
</Fragment>
284+
)}
285+
{this.renderList()}
286+
</Layout.Main>
287+
</Layout.Body>
288+
</PageFiltersContainer>
289+
</Layout.Page>
288290
</SentryDocumentTitle>
289291
);
290292
}
@@ -303,15 +305,17 @@ export default function IncidentsListContainer() {
303305
}, []);
304306

305307
const renderDisabled = () => (
306-
<Layout.Body>
307-
<Layout.Main width="full">
308-
<Alert.Container>
309-
<Alert variant="warning" showIcon={false}>
310-
{t("You don't have access to this feature")}
311-
</Alert>
312-
</Alert.Container>
313-
</Layout.Main>
314-
</Layout.Body>
308+
<Layout.Page>
309+
<Layout.Body>
310+
<Layout.Main width="full">
311+
<Alert.Container>
312+
<Alert variant="warning" showIcon={false}>
313+
{t("You don't have access to this feature")}
314+
</Alert>
315+
</Alert.Container>
316+
</Layout.Main>
317+
</Layout.Body>
318+
</Layout.Page>
315319
);
316320

317321
return (

static/app/views/alerts/list/rules/alertRulesList.tsx

Lines changed: 124 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -210,132 +210,134 @@ export default function AlertRulesList() {
210210
<Fragment>
211211
<SentryDocumentTitle title={t('Alerts')} orgSlug={organization.slug} />
212212

213-
<PageFiltersContainer>
214-
<AlertHeader activeTab="rules" />
215-
<Layout.Body>
216-
<Layout.Main width="full">
217-
<DataConsentBanner source="alerts" />
218-
{!hasMetricAlertsFeature && hasAnyMetricAlerts && (
219-
<Alert.Container>
220-
<Alert variant="danger">
221-
Your metric alerts have been disabled. Upgrade your plan to re-enable
222-
them.
223-
</Alert>
224-
</Alert.Container>
225-
)}
226-
<FilterBar
227-
location={location}
228-
onChangeFilter={handleChangeFilter}
229-
onChangeSearch={handleChangeSearch}
230-
onChangeAlertType={handleChangeType}
231-
hasTypeFilter
232-
/>
233-
<StyledPanelTable
234-
isLoading={isPending}
235-
isEmpty={ruleList.length === 0 && !isError}
236-
emptyMessage={t('No alert rules found for the current query.')}
237-
headers={[
238-
<StyledSortLink
239-
key="name"
240-
role="columnheader"
241-
aria-sort={
242-
sort.field === 'name'
243-
? sort.asc
244-
? 'ascending'
245-
: 'descending'
246-
: 'none'
247-
}
248-
to={{
249-
pathname: location.pathname,
250-
query: {
251-
...currentQuery,
252-
// sort by name should start by ascending on first click
253-
asc: sort.field === 'name' && sort.asc ? undefined : '1',
254-
sort: 'name',
255-
},
256-
}}
257-
>
258-
{t('Alert Rule')} {sort.field === 'name' ? sortArrow : null}
259-
</StyledSortLink>,
260-
<StyledSortLink
261-
key="status"
262-
role="columnheader"
263-
aria-sort={
264-
isAlertRuleSort ? (sort.asc ? 'ascending' : 'descending') : 'none'
265-
}
266-
to={{
267-
pathname: location.pathname,
268-
query: {
269-
...currentQuery,
270-
asc: isAlertRuleSort && !sort.asc ? '1' : undefined,
271-
sort: ['incident_status', 'date_triggered'],
272-
},
273-
}}
274-
>
275-
{t('Status')} {isAlertRuleSort ? sortArrow : null}
276-
</StyledSortLink>,
277-
t('Project'),
278-
t('Team'),
279-
t('Actions'),
280-
]}
281-
>
282-
{isError ? (
283-
<StyledLoadingError
284-
message={t('There was an error loading alerts.')}
285-
onRetry={refetch}
286-
/>
287-
) : null}
288-
<VisuallyCompleteWithData
289-
id="AlertRules-Body"
290-
hasData={ruleList.length > 0}
213+
<Layout.Page>
214+
<PageFiltersContainer>
215+
<AlertHeader activeTab="rules" />
216+
<Layout.Body>
217+
<Layout.Main width="full">
218+
<DataConsentBanner source="alerts" />
219+
{!hasMetricAlertsFeature && hasAnyMetricAlerts && (
220+
<Alert.Container>
221+
<Alert variant="danger">
222+
Your metric alerts have been disabled. Upgrade your plan to re-enable
223+
them.
224+
</Alert>
225+
</Alert.Container>
226+
)}
227+
<FilterBar
228+
location={location}
229+
onChangeFilter={handleChangeFilter}
230+
onChangeSearch={handleChangeSearch}
231+
onChangeAlertType={handleChangeType}
232+
hasTypeFilter
233+
/>
234+
<StyledPanelTable
235+
isLoading={isPending}
236+
isEmpty={ruleList.length === 0 && !isError}
237+
emptyMessage={t('No alert rules found for the current query.')}
238+
headers={[
239+
<StyledSortLink
240+
key="name"
241+
role="columnheader"
242+
aria-sort={
243+
sort.field === 'name'
244+
? sort.asc
245+
? 'ascending'
246+
: 'descending'
247+
: 'none'
248+
}
249+
to={{
250+
pathname: location.pathname,
251+
query: {
252+
...currentQuery,
253+
// sort by name should start by ascending on first click
254+
asc: sort.field === 'name' && sort.asc ? undefined : '1',
255+
sort: 'name',
256+
},
257+
}}
258+
>
259+
{t('Alert Rule')} {sort.field === 'name' ? sortArrow : null}
260+
</StyledSortLink>,
261+
<StyledSortLink
262+
key="status"
263+
role="columnheader"
264+
aria-sort={
265+
isAlertRuleSort ? (sort.asc ? 'ascending' : 'descending') : 'none'
266+
}
267+
to={{
268+
pathname: location.pathname,
269+
query: {
270+
...currentQuery,
271+
asc: isAlertRuleSort && !sort.asc ? '1' : undefined,
272+
sort: ['incident_status', 'date_triggered'],
273+
},
274+
}}
275+
>
276+
{t('Status')} {isAlertRuleSort ? sortArrow : null}
277+
</StyledSortLink>,
278+
t('Project'),
279+
t('Team'),
280+
t('Actions'),
281+
]}
291282
>
292-
<Projects orgId={organization.slug} slugs={projectsFromResults}>
293-
{({initiallyLoaded, projects}) =>
294-
ruleList.map(rule => {
295-
const isIssueAlertInstance = isIssueAlert(rule);
296-
const keyPrefix = isIssueAlertInstance
297-
? AlertRuleType.ISSUE
298-
: rule.type === CombinedAlertType.UPTIME
299-
? AlertRuleType.UPTIME
300-
: AlertRuleType.METRIC;
283+
{isError ? (
284+
<StyledLoadingError
285+
message={t('There was an error loading alerts.')}
286+
onRetry={refetch}
287+
/>
288+
) : null}
289+
<VisuallyCompleteWithData
290+
id="AlertRules-Body"
291+
hasData={ruleList.length > 0}
292+
>
293+
<Projects orgId={organization.slug} slugs={projectsFromResults}>
294+
{({initiallyLoaded, projects}) =>
295+
ruleList.map(rule => {
296+
const isIssueAlertInstance = isIssueAlert(rule);
297+
const keyPrefix = isIssueAlertInstance
298+
? AlertRuleType.ISSUE
299+
: rule.type === CombinedAlertType.UPTIME
300+
? AlertRuleType.UPTIME
301+
: AlertRuleType.METRIC;
301302

302-
return (
303-
<RuleListRow
304-
// Metric and issue alerts can have the same id
305-
key={`${keyPrefix}-${rule.id}`}
306-
projectsLoaded={initiallyLoaded}
307-
projects={projects as Project[]}
308-
rule={rule}
309-
organization={organization}
310-
onOwnerChange={handleOwnerChange}
311-
onDelete={handleDeleteRule}
312-
hasEditAccess={hasEditAccess}
313-
hasMetricAlerts={hasMetricAlertsFeature}
314-
/>
315-
);
316-
})
303+
return (
304+
<RuleListRow
305+
// Metric and issue alerts can have the same id
306+
key={`${keyPrefix}-${rule.id}`}
307+
projectsLoaded={initiallyLoaded}
308+
projects={projects as Project[]}
309+
rule={rule}
310+
organization={organization}
311+
onOwnerChange={handleOwnerChange}
312+
onDelete={handleDeleteRule}
313+
hasEditAccess={hasEditAccess}
314+
hasMetricAlerts={hasMetricAlertsFeature}
315+
/>
316+
);
317+
})
318+
}
319+
</Projects>
320+
</VisuallyCompleteWithData>
321+
</StyledPanelTable>
322+
<Pagination
323+
pageLinks={ruleListPageLinks}
324+
onCursor={(cursor, path, _direction) => {
325+
let team = currentQuery.team;
326+
// Keep team parameter, but empty to remove parameters
327+
if (!team || team.length === 0) {
328+
team = '';
317329
}
318-
</Projects>
319-
</VisuallyCompleteWithData>
320-
</StyledPanelTable>
321-
<Pagination
322-
pageLinks={ruleListPageLinks}
323-
onCursor={(cursor, path, _direction) => {
324-
let team = currentQuery.team;
325-
// Keep team parameter, but empty to remove parameters
326-
if (!team || team.length === 0) {
327-
team = '';
328-
}
329330

330-
navigate({
331-
pathname: path,
332-
query: {...currentQuery, team, cursor},
333-
});
334-
}}
335-
/>
336-
</Layout.Main>
337-
</Layout.Body>
338-
</PageFiltersContainer>
331+
navigate({
332+
pathname: path,
333+
query: {...currentQuery, team, cursor},
334+
});
335+
}}
336+
/>
337+
</Layout.Main>
338+
</Layout.Body>
339+
</PageFiltersContainer>
340+
</Layout.Page>
339341
</Fragment>
340342
);
341343
}

0 commit comments

Comments
 (0)