Skip to content

Commit 2abaf95

Browse files
billyvgNisanthan Nanthakumar
authored and
Nisanthan Nanthakumar
committed
feat(ui): Require org:write role for Metric Alerts form (#16340)
This disables all form fields when editing a Metric Alert without the `org:write` role.
1 parent 6d79b5c commit 2abaf95

File tree

7 files changed

+117
-67
lines changed

7 files changed

+117
-67
lines changed

src/sentry/static/sentry/app/views/settings/incidentRules/ruleConditionsForm.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import getMetricDisplayName from './utils/getMetricDisplayName';
1212

1313
type Props = {
1414
organization: Organization;
15+
disabled: boolean;
1516
};
1617

1718
type TimeWindowMapType = {[key in TimeWindow]: string};
@@ -30,7 +31,7 @@ const TIME_WINDOW_MAP: TimeWindowMapType = {
3031

3132
class RuleConditionsForm extends React.PureComponent<Props> {
3233
render() {
33-
const {organization} = this.props;
34+
const {organization, disabled} = this.props;
3435

3536
return (
3637
<Panel>
@@ -51,6 +52,7 @@ class RuleConditionsForm extends React.PureComponent<Props> {
5152
],
5253
]}
5354
required
55+
disabled={disabled}
5456
/>
5557
<FormField
5658
name="query"
@@ -64,6 +66,7 @@ class RuleConditionsForm extends React.PureComponent<Props> {
6466
{({onChange, onBlur, onKeyDown}) => {
6567
return (
6668
<SearchBar
69+
disabled={disabled}
6770
useFormWrapper={false}
6871
organization={organization}
6972
onChange={onChange}
@@ -80,6 +83,7 @@ class RuleConditionsForm extends React.PureComponent<Props> {
8083
help={t('The time window to use when evaluating the Metric')}
8184
choices={Object.entries(TIME_WINDOW_MAP)}
8285
required
86+
disabled={disabled}
8387
/>
8488
</PanelBody>
8589
</Panel>

src/sentry/static/sentry/app/views/settings/incidentRules/ruleForm/index.tsx

Lines changed: 68 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import {createDefaultTrigger} from 'app/views/settings/incidentRules/constants';
1515
import {defined} from 'app/utils';
1616
import {t} from 'app/locale';
17+
import Access from 'app/components/acl/access';
1718
import AsyncComponent from 'app/components/asyncComponent';
1819
import Button from 'app/components/button';
1920
import Confirm from 'app/components/confirm';
@@ -274,66 +275,73 @@ class RuleFormContainer extends AsyncComponent<Props, State> {
274275
const {query, aggregation, timeWindow, triggers} = this.state;
275276

276277
return (
277-
<Form
278-
apiMethod={incidentRuleId ? 'PUT' : 'POST'}
279-
apiEndpoint={`/organizations/${organization.slug}/alert-rules/${
280-
incidentRuleId ? `${incidentRuleId}/` : ''
281-
}`}
282-
initialData={{
283-
name: rule.name || '',
284-
aggregation: rule.aggregation,
285-
query: rule.query || '',
286-
timeWindow: rule.timeWindow,
287-
}}
288-
saveOnBlur={false}
289-
onSubmit={this.handleSubmit}
290-
onSubmitSuccess={onSubmitSuccess}
291-
onCancel={this.handleCancel}
292-
onFieldChange={this.handleFieldChange}
293-
extraButton={
294-
!!rule.id ? (
295-
<Confirm
296-
message={t('Are you sure you want to delete this alert rule?')}
297-
header={t('Delete Alert Rule?')}
298-
priority="danger"
299-
confirmText={t('Delete Rule')}
300-
onConfirm={this.handleDeleteRule}
301-
>
302-
<Button type="button" priority="danger">
303-
{t('Delete Rule')}
304-
</Button>
305-
</Confirm>
306-
) : null
307-
}
308-
submitLabel={t('Save Rule')}
309-
>
310-
<TriggersChart
311-
api={api}
312-
config={config}
313-
organization={organization}
314-
projects={this.state.projects}
315-
triggers={triggers}
316-
query={query}
317-
aggregation={aggregation}
318-
timeWindow={timeWindow}
319-
/>
320-
321-
<RuleConditionsForm organization={organization} />
322-
323-
<Triggers
324-
projects={this.state.projects}
325-
errors={this.state.triggerErrors}
326-
triggers={triggers}
327-
currentProject={params.projectId}
328-
organization={organization}
329-
incidentRuleId={incidentRuleId}
330-
availableActions={this.state.availableActions}
331-
onChange={this.handleChangeTriggers}
332-
onAdd={this.handleAddTrigger}
333-
/>
334-
335-
<RuleNameForm />
336-
</Form>
278+
<Access access={['org:write']}>
279+
{({hasAccess}) => (
280+
<Form
281+
apiMethod={incidentRuleId ? 'PUT' : 'POST'}
282+
apiEndpoint={`/organizations/${organization.slug}/alert-rules/${
283+
incidentRuleId ? `${incidentRuleId}/` : ''
284+
}`}
285+
submitDisabled={!hasAccess}
286+
initialData={{
287+
name: rule.name || '',
288+
aggregation: rule.aggregation,
289+
query: rule.query || '',
290+
timeWindow: rule.timeWindow,
291+
}}
292+
saveOnBlur={false}
293+
onSubmit={this.handleSubmit}
294+
onSubmitSuccess={onSubmitSuccess}
295+
onCancel={this.handleCancel}
296+
onFieldChange={this.handleFieldChange}
297+
extraButton={
298+
!!rule.id ? (
299+
<Confirm
300+
disabled={!hasAccess}
301+
message={t('Are you sure you want to delete this alert rule?')}
302+
header={t('Delete Alert Rule?')}
303+
priority="danger"
304+
confirmText={t('Delete Rule')}
305+
onConfirm={this.handleDeleteRule}
306+
>
307+
<Button type="button" priority="danger">
308+
{t('Delete Rule')}
309+
</Button>
310+
</Confirm>
311+
) : null
312+
}
313+
submitLabel={t('Save Rule')}
314+
>
315+
<TriggersChart
316+
api={api}
317+
config={config}
318+
organization={organization}
319+
projects={this.state.projects}
320+
triggers={triggers}
321+
query={query}
322+
aggregation={aggregation}
323+
timeWindow={timeWindow}
324+
/>
325+
326+
<RuleConditionsForm organization={organization} disabled={!hasAccess} />
327+
328+
<Triggers
329+
disabled={!hasAccess}
330+
projects={this.state.projects}
331+
errors={this.state.triggerErrors}
332+
triggers={triggers}
333+
currentProject={params.projectId}
334+
organization={organization}
335+
incidentRuleId={incidentRuleId}
336+
availableActions={this.state.availableActions}
337+
onChange={this.handleChangeTriggers}
338+
onAdd={this.handleAddTrigger}
339+
/>
340+
341+
<RuleNameForm disabled={!hasAccess} />
342+
</Form>
343+
)}
344+
</Access>
337345
);
338346
}
339347
}

src/sentry/static/sentry/app/views/settings/incidentRules/ruleNameForm.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import {Panel, PanelBody, PanelHeader} from 'app/components/panels';
44
import {t} from 'app/locale';
55
import TextField from 'app/views/settings/components/forms/textField';
66

7-
class RuleNameForm extends React.PureComponent {
7+
type Props = {
8+
disabled: boolean;
9+
};
10+
11+
class RuleNameForm extends React.PureComponent<Props> {
812
render() {
13+
const {disabled} = this.props;
14+
915
return (
1016
<Panel>
1117
<PanelHeader>{t('Give your rule a name')}</PanelHeader>
1218
<PanelBody>
1319
<TextField
20+
disabled={disabled}
1421
name="name"
1522
type="text"
1623
label={t('Rule Name')}

src/sentry/static/sentry/app/views/settings/incidentRules/triggers/actionsPanel/index.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type Props = {
3333
currentProject: string;
3434
organization: Organization;
3535
projects: Project[];
36+
disabled: boolean;
3637
loading: boolean;
3738
error: boolean;
3839

@@ -90,6 +91,7 @@ class ActionsPanel extends React.PureComponent<Props> {
9091
actions,
9192
availableActions,
9293
currentProject,
94+
disabled,
9395
loading,
9496
organization,
9597
projects,
@@ -121,7 +123,7 @@ class ActionsPanel extends React.PureComponent<Props> {
121123

122124
{availableAction && availableAction.allowedTargetTypes.length > 1 ? (
123125
<SelectControl
124-
disabled={loading}
126+
disabled={disabled || loading}
125127
value={action.targetType}
126128
options={
127129
availableAction &&
@@ -138,6 +140,7 @@ class ActionsPanel extends React.PureComponent<Props> {
138140

139141
{isUser || isTeam ? (
140142
<SelectMembers
143+
disabled={disabled}
141144
key={isTeam ? 'team' : 'member'}
142145
showTeam={isTeam}
143146
project={projects.find(({slug}) => slug === currentProject)}
@@ -147,21 +150,26 @@ class ActionsPanel extends React.PureComponent<Props> {
147150
/>
148151
) : (
149152
<Input
153+
disabled={disabled}
150154
key={action.type}
151155
value={action.targetIdentifier}
152156
onChange={this.handleChangeSpecificTargetIdentifier.bind(this, i)}
153157
placeholder="Channel or user i.e. #critical"
154158
/>
155159
)}
156-
<DeleteActionButton index={i} onClick={this.handleDeleteAction} />
160+
<DeleteActionButton
161+
index={i}
162+
onClick={this.handleDeleteAction}
163+
disabled={disabled}
164+
/>
157165
</PanelItemGrid>
158166
);
159167
})}
160168
<PanelItem>
161169
<StyledSelectControl
162170
name="add-action"
163171
aria-label={t('Add an Action')}
164-
disabled={loading}
172+
disabled={disabled || loading}
165173
placeholder={t('Add an Action')}
166174
onChange={this.handleAddAction}
167175
options={items}

src/sentry/static/sentry/app/views/settings/incidentRules/triggers/form.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type AlertRuleThresholdKey = {
2222
type Props = {
2323
api: Client;
2424
config: Config;
25+
disabled: boolean;
2526
organization: Organization;
2627

2728
/**
@@ -119,7 +120,7 @@ class TriggerForm extends React.PureComponent<Props> {
119120
};
120121

121122
render() {
122-
const {error, trigger, isCritical} = this.props;
123+
const {disabled, error, trigger, isCritical} = this.props;
123124
const triggerLabel = isCritical
124125
? t('Critical Trigger Threshold')
125126
: t('Warning Trigger Threshold');
@@ -136,6 +137,7 @@ class TriggerForm extends React.PureComponent<Props> {
136137
error={error && error.alertThreshold}
137138
>
138139
<ThresholdControl
140+
disabled={disabled}
139141
type={AlertRuleThreshold.INCIDENT}
140142
thresholdType={trigger.thresholdType}
141143
threshold={trigger.alertThreshold}
@@ -149,6 +151,7 @@ class TriggerForm extends React.PureComponent<Props> {
149151
error={error && error.resolutionThreshold}
150152
>
151153
<ThresholdControl
154+
disabled={disabled}
152155
type={AlertRuleThreshold.RESOLUTION}
153156
thresholdType={trigger.thresholdType}
154157
threshold={trigger.resolveThreshold}
@@ -219,6 +222,7 @@ class TriggerFormContainer extends React.Component<TriggerFormContainerProps> {
219222
availableActions,
220223
config,
221224
currentProject,
225+
disabled,
222226
error,
223227
isCritical,
224228
organization,
@@ -232,6 +236,7 @@ class TriggerFormContainer extends React.Component<TriggerFormContainerProps> {
232236
<TriggerForm
233237
api={api}
234238
config={config}
239+
disabled={disabled}
235240
error={error}
236241
trigger={trigger}
237242
organization={organization}
@@ -241,6 +246,7 @@ class TriggerFormContainer extends React.Component<TriggerFormContainerProps> {
241246
onChange={this.handleChangeTrigger}
242247
/>
243248
<ActionsPanel
249+
disabled={disabled}
244250
loading={availableActions === null}
245251
error={false}
246252
availableActions={availableActions}

src/sentry/static/sentry/app/views/settings/incidentRules/triggers/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import withProjects from 'app/utils/withProjects';
1616

1717
type DeleteButtonProps = {
1818
triggerIndex: number;
19+
disabled: boolean;
1920
onDelete: (triggerIndex: number, e: React.MouseEvent<Element>) => void;
2021
};
2122

@@ -25,13 +26,15 @@ type DeleteButtonProps = {
2526
const DeleteButton: React.FC<DeleteButtonProps> = ({
2627
triggerIndex,
2728
onDelete,
29+
disabled,
2830
}: DeleteButtonProps) => (
2931
<Button
3032
type="button"
3133
icon="icon-trash"
3234
size="xsmall"
3335
aria-label={t('Delete Trigger')}
3436
onClick={(e: React.MouseEvent<Element>) => onDelete(triggerIndex, e)}
37+
disabled={disabled}
3538
>
3639
{t('Delete')}
3740
</Button>
@@ -44,6 +47,7 @@ type Props = {
4447
triggers: Trigger[];
4548
currentProject: string;
4649
availableActions: MetricAction[] | null;
50+
disabled: boolean;
4751

4852
errors: Map<number, {[fieldName: string]: string}>;
4953

@@ -77,6 +81,7 @@ class Triggers extends React.Component<Props> {
7781
organization,
7882
projects,
7983
triggers,
84+
disabled,
8085
onAdd,
8186
} = this.props;
8287

@@ -95,13 +100,15 @@ class Triggers extends React.Component<Props> {
95100
</Title>
96101
{!isCritical && (
97102
<DeleteButton
103+
disabled={disabled}
98104
triggerIndex={index}
99105
onDelete={this.handleDeleteTrigger}
100106
/>
101107
)}
102108
</PanelHeader>
103109
<PanelBody>
104110
<TriggerForm
111+
disabled={disabled}
105112
isCritical={isCritical}
106113
error={errors && errors.get(index)}
107114
availableActions={availableActions}

0 commit comments

Comments
 (0)