Skip to content

Commit b8114fb

Browse files
committed
create a new resolved attribute for incidents
1 parent d63e394 commit b8114fb

File tree

10 files changed

+101
-11
lines changed

10 files changed

+101
-11
lines changed

db/dummy_data.sql

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ UNLOCK TABLES;
6868

6969
LOCK TABLES `incident` WRITE;
7070
INSERT INTO `incident` VALUES
71-
(1,40,'2017-01-25 23:22:55','2017-01-25 23:24:55','{\"console_url\": \"\", \"fabric\": \"DC1\", \"notes\": \"This is a note\", \"filename\": \"dashboard\", \"zones\": [\"zone1\", \"zone2\"], \"metanodes\": [[\"execution_time.metanode1\", \"threshold: 72 is greater than the max (65)\"]], \"nodes\": [[\"execution_time.server1.example.com\", \"threshold: 72 is greater than the max (65)\"]], \"graph_image_url\": \"http://url.example.com/foo\", \"name\":\"API Alert\"}',1,8,1,0),
72-
(2,40,'2017-01-25 23:22:55','2017-01-25 23:24:55','{\"console_url\": \"\", \"fabric\": \"DC1\", \"notes\": \"This is a note\", \"filename\": \"dashboard\", \"zones\": [\"zone1\", \"zone2\"], \"metanodes\": [[\"execution_time.metanode1\", \"threshold: 72 is greater than the max (65)\"]], \"nodes\": [[\"execution_time.server1.example.com\", \"threshold: 72 is greater than the max (65)\"]], \"graph_image_url\": \"http://url.example.com/foo\", \"name\":\"API Alert\"}',1,8,1,0),
73-
(3,40,'2017-01-25 23:22:55','2017-01-25 23:24:55','{\"console_url\": \"\", \"fabric\": \"DC1\", \"notes\": \"This is a note\", \"filename\": \"dashboard\", \"zones\": [\"zone1\", \"zone2\"], \"metanodes\": [[\"execution_time.metanode1\", \"threshold: 72 is greater than the max (65)\"]], \"nodes\": [[\"execution_time.server1.example.com\", \"threshold: 72 is greater than the max (65)\"]], \"graph_image_url\": \"http://url.example.com/foo\", \"name\":\"API Alert\"}',1,8,1,0);
71+
(1,40,'2017-01-25 23:22:55','2017-01-25 23:24:55','{\"console_url\": \"\", \"fabric\": \"DC1\", \"notes\": \"This is a note\", \"filename\": \"dashboard\", \"zones\": [\"zone1\", \"zone2\"], \"metanodes\": [[\"execution_time.metanode1\", \"threshold: 72 is greater than the max (65)\"]], \"nodes\": [[\"execution_time.server1.example.com\", \"threshold: 72 is greater than the max (65)\"]], \"graph_image_url\": \"http://url.example.com/foo\", \"name\":\"API Alert\"}',1,8,1,0,0),
72+
(2,40,'2017-01-25 23:22:55','2017-01-25 23:24:55','{\"console_url\": \"\", \"fabric\": \"DC1\", \"notes\": \"This is a note\", \"filename\": \"dashboard\", \"zones\": [\"zone1\", \"zone2\"], \"metanodes\": [[\"execution_time.metanode1\", \"threshold: 72 is greater than the max (65)\"]], \"nodes\": [[\"execution_time.server1.example.com\", \"threshold: 72 is greater than the max (65)\"]], \"graph_image_url\": \"http://url.example.com/foo\", \"name\":\"API Alert\"}',1,8,1,0,0),
73+
(3,40,'2017-01-25 23:22:55','2017-01-25 23:24:55','{\"console_url\": \"\", \"fabric\": \"DC1\", \"notes\": \"This is a note\", \"filename\": \"dashboard\", \"zones\": [\"zone1\", \"zone2\"], \"metanodes\": [[\"execution_time.metanode1\", \"threshold: 72 is greater than the max (65)\"]], \"nodes\": [[\"execution_time.server1.example.com\", \"threshold: 72 is greater than the max (65)\"]], \"graph_image_url\": \"http://url.example.com/foo\", \"name\":\"API Alert\"}',1,8,1,0,0);
7474
UNLOCK TABLES;
7575

7676
LOCK TABLES `incident_emails` WRITE;

db/schema_0.sql

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ CREATE TABLE `incident` (
5959
`application_id` int(11) NOT NULL,
6060
`current_step` int(11) NOT NULL,
6161
`active` tinyint(1) NOT NULL,
62+
`resolved` tinyint(1) unsigned NOT NULL DEFAULT '0',
6263
PRIMARY KEY (`id`),
6364
KEY `ix_incident_plan_id` (`plan_id`),
6465
KEY `ix_incident_updated` (`updated`),

src/iris/api.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ def ts_to_sql_datetime(ts):
161161
'created': 'UNIX_TIMESTAMP(`incident`.`created`) as `created`',
162162
'owner': '`target`.`name` as `owner`',
163163
'current_step': '`incident`.`current_step` as `current_step`',
164-
'title_variable_name': '`template_variable`.`name` as `title_variable_name`'
164+
'title_variable_name': '`template_variable`.`name` as `title_variable_name`',
165+
'resolved': '`incident`.`resolved` as `resolved`'
165166
}
166167

167168
incident_filters = {
@@ -175,6 +176,7 @@ def ts_to_sql_datetime(ts):
175176
'created': '`incident`.`created`',
176177
'owner': '`target`.`name`',
177178
'current_step': '`incident`.`current_step`',
179+
'resolved': '`incident`.`resolved` as `resolved`'
178180
}
179181

180182
incident_filter_types = {
@@ -200,7 +202,8 @@ def ts_to_sql_datetime(ts):
200202
`target`.`name` as `owner`,
201203
`application`.`name` as `application`,
202204
`incident`.`current_step` as `current_step`,
203-
`incident`.`active` as `active`
205+
`incident`.`active` as `active`,
206+
`incident`.`resolved` as `resolved`
204207
FROM `incident`
205208
JOIN `plan` ON `incident`.`plan_id` = `plan`.`id`
206209
LEFT OUTER JOIN `target` ON `incident`.`owner_id` = `target`.`id`
@@ -1873,6 +1876,24 @@ def on_post(self, req, resp, incident_id):
18731876
'active': is_active})
18741877

18751878

1879+
class Resolved(object):
1880+
allow_read_no_auth = False
1881+
1882+
def on_post(self, req, resp, incident_id):
1883+
1884+
incident_params = ujson.loads(req.context['body'])
1885+
if 'resolved' not in incident_params:
1886+
raise HTTPBadRequest('resolved field required')
1887+
resolved = incident_params.get('resolved')
1888+
try:
1889+
utils.resolve_incident(incident_id, resolved)
1890+
except Exception as e:
1891+
raise HTTPInternalServerError(description=e)
1892+
resp.status = HTTP_200
1893+
resp.body = ujson.dumps({'incident_id': incident_id,
1894+
'resolved': resolved})
1895+
1896+
18761897
class ClaimIncidents(object):
18771898
allow_read_no_auth = False
18781899

@@ -5263,6 +5284,7 @@ def construct_falcon_api(debug, healthcheck_path, allowed_origins, iris_sender_a
52635284
api.add_route('/v0/incidents/{incident_id}', Incident())
52645285
api.add_route('/v0/incidents', Incidents())
52655286
api.add_route('/v0/incidents/claim', ClaimIncidents())
5287+
api.add_route('/v0/incidents/{incident_id}/resolve', Resolved())
52665288
api.add_route('/v0/incidents/{incident_id}/comments', Comments())
52675289

52685290
api.add_route('/v0/messages/{message_id}', Message())

src/iris/ui/static/js/iris.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ iris = {
12601260
initialized: false,
12611261
data: {
12621262
url: '/v0/incidents/',
1263-
fields: ['id', 'owner', 'application', 'plan', 'plan_id', 'created', 'updated', 'active', 'current_step'],
1263+
fields: ['id', 'owner', 'application', 'plan', 'plan_id', 'created', 'updated', 'active', 'current_step', 'resolved'],
12641264
$page: $('.main'),
12651265
$table: $('#incidents-table'),
12661266
$filterApp: $('#filter-application'),
@@ -1284,6 +1284,7 @@ iris = {
12841284
{ width: '10%' },
12851285
{ width: '10%' },
12861286
null,
1287+
null,
12871288
null
12881289
]
12891290
}
@@ -1501,6 +1502,7 @@ iris = {
15011502
reescalateBtn: '#re-escalate-btn',
15021503
reescalatePlanContainer: '.re-escalate-plan-container',
15031504
claimIncidentBtn: '#claim-incident',
1505+
resolveIncidentBtn: '#resolve-incident',
15041506
showAddCommentBtn: '#show-comment',
15051507
hideAddCommentBtn: '#cancel-comment',
15061508
addCommentBtn: '#comment-incident',
@@ -1537,6 +1539,7 @@ iris = {
15371539
data.$page.on('click', data.showReescalateBtn, this.reescalateModal.bind(this));
15381540
data.$page.on('click', data.selectPlanBtn, this.selectPlan.bind(this));
15391541
data.$page.on('click', data.reescalateBtn, this.reescalateIncident.bind(this));
1542+
data.$page.on('click', data.resolveIncidentBtn, this.resolveIncident.bind(this));
15401543
},
15411544
showComment: function() {
15421545
$(this.data.addCommentContainer).show();
@@ -1618,6 +1621,27 @@ iris = {
16181621
$this.prop('disabled', false);
16191622
});
16201623
},
1624+
resolveIncident: function(e){
1625+
var $this = $(e.target),
1626+
resolved = $this.attr('data-action') === 'resolve' ? true : false,
1627+
self = this,
1628+
incidentId = $this.attr('data-id');
1629+
$.ajax({
1630+
url: self.data.url + incidentId + '/resolve',
1631+
data: JSON.stringify({
1632+
resolved: resolved
1633+
}),
1634+
method: 'POST',
1635+
contentType: 'application/json'
1636+
}).done(function(){
1637+
self.getIncident(incidentId).done(function(){
1638+
var message = resolved ? 'Incident ' + incidentId + ' marked as resolved.' : 'Incident ' + incidentId + ' marked as unresolved.';
1639+
iris.createAlert(message, 'success');
1640+
});
1641+
}).fail(function(){
1642+
iris.createAlert('Failed to modify incident', 'danger');
1643+
});
1644+
},
16211645
reescalateModal: function() {
16221646
var $modal = $(this.data.reescalateModal),
16231647
self = this;

src/iris/ui/templates/incident.html

+13-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ <h3>
1313
<span class="light"> Owner:</span> {{#if owner}} {{owner}} {{else}} Unclaimed {{/if}}
1414
</span>
1515
{{#isUser owner}}
16-
<button type="button" id="claim-incident" class="pull-right btn btn-default blue btn-sm" data-id="{{id}}" data-action="unclaim">Unclaim Incident</button>
16+
<button type="button" id="claim-incident" class="pull-right btn btn-default blue btn-sm" data-id="{{id}}" data-action="unclaim" data-toggle="tooltip" data-placement="right" title="This will cause the incident to continue escalating where it left off at the time it was claimed">Unclaim Incident</button>
1717
{{else}}
1818
{{#if active}}
19-
<button type="button" id="claim-incident" class="pull-right btn btn-default blue btn-sm" data-id="{{id}}" data-action="claim">Claim Incident</button>
19+
<button type="button" id="claim-incident" class="pull-right btn btn-default blue btn-sm" data-id="{{id}}" data-action="claim" data-toggle="tooltip" data-placement="right" title="This stops any further escalation and marks you as the owner of the incident">Claim Incident</button>
2020
{{else}}
2121
{{#if owner}}
2222
<button type="button" id="claim-incident" class="pull-right btn btn-default btn-sm disabled" disabled data-id="{{id}}" data-action="claim">Claimed</button>
@@ -25,8 +25,18 @@ <h3>
2525
{{/if}}
2626
{{/if}}
2727
{{/isUser}}
28-
<button type="button" id="re-escalate-modal-btn" class="pull-right btn btn-default blue btn-sm">Re-escalate</button>
28+
<button type="button" id="re-escalate-modal-btn" class="pull-right btn btn-default blue btn-sm" data-toggle="tooltip" data-placement="left" title="Re-raise this incident using a different plan">Re-escalate</button>
2929
</h3>
30+
<h4>
31+
{{#if owner}}
32+
{{#if resolved}} <i>Resolved</i> {{else}} Unresolved{{/if}}
33+
{{#if resolved}}
34+
<button type="button" id="resolve-incident" class="pull-right btn btn-default blue btn-sm" data-id="{{id}}" data-action="unresolve" data-toggle="tooltip" data-placement="right" title="This removes a semantic tag that indicates the underlying issue is mitigated">Mark Unresolved</button>
35+
{{else}}
36+
<button type="button" id="resolve-incident" class="pull-right btn btn-default blue btn-sm" data-id="{{id}}" data-action="resolve" data-toggle="tooltip" data-placement="right" title="This applies a semantic tag that indicates the underlying issue is mitigated">Mark Resolved</button>
37+
{{/if}}
38+
{{/if}}
39+
</h4>
3040
<span class="light italic"><small>created at {{convertToLocal created}}</small></span>
3141
{{#if updated}}
3242
<span class="light">|</span>

src/iris/ui/templates/incidents.html

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ <h4 class="modal-title">Claim all active incidents</h4>
142142
<td class="light created">Created On <i class="glyphicon glyphicon-sort"></i></td>
143143
<td class="light active">Active <i class="glyphicon glyphicon-sort"></i></td>
144144
<td class="light claim center">Claim <i class="glyphicon glyphicon-sort"></i></td>
145+
<td class="light resolved">Resolved <i class="glyphicon glyphicon-sort"></i></td>
145146
</tr>
146147
</thead>
147148
<tbody>
@@ -181,6 +182,7 @@ <h4 class="modal-title">Claim all active incidents</h4>
181182
{{/if}}
182183
{{/isUser}}
183184
</td>
185+
<td> {{#if resolved}} Resolved {{else}} Unresolved {{/if}}</td>
184186
</tr>
185187
{{/each}}
186188
</tbody>

src/iris/ui/templates/plan.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ <h5>Target {{this}}</h5>
9494
<div class="module">
9595
<h3>
9696
{{name}} <i class="badge" data-active="{{active}}"> </i>
97-
<button type="button" class="btn btn-default blue btn-sm pull-right" id="clone-plan">Clone Plan</button>
97+
<button type="button" class="btn btn-default blue btn-sm pull-right" id="clone-plan" data-toggle="tooltip" data-placement="right" title="Make changes to the plan or fork it off into a new plan">Edit Plan</button>
9898
<button type="button" class="btn btn-default blue btn-sm pull-right" id="test-plan-modal-btn">Test Plan</button>
9999
</h3>
100100
<div class="form-inline version-container">

src/iris/ui/templates/template.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ <h4 class="modal-title">Related Plans</h4>
5959
{{#if viewMode}}
6060
<h3>
6161
{{name}} <i class="badge" data-active="{{active}}"> </i>
62-
<button type="button" class="btn btn-default blue btn-sm pull-right" id="clone-template">Clone template</button>
62+
<button type="button" class="btn btn-default blue btn-sm pull-right" id="clone-template" data-toggle="tooltip" data-placement="right" title="Make changes to the template or fork it off into a new template">Edit template</button>
6363
</h3>
6464
<div><small><a class="view-related" data-toggle="modal" data-target="#view-related-modal" data-name="{{name}}" href=""> View all plans using this template. </a></small></div>
6565
<div class="form-inline pull-left version-container">

src/iris/utils.py

+24
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,30 @@ def claim_incidents_from_batch_id(batch_id, owner):
312312
connection.close()
313313

314314

315+
def resolve_incident(incident_id, resolved_state):
316+
317+
now = datetime.datetime.utcnow()
318+
resolved = 1
319+
if not resolved_state:
320+
resolved = 0
321+
322+
connection = db.engine.raw_connection()
323+
cursor = connection.cursor()
324+
try:
325+
cursor.execute('''UPDATE `incident`
326+
SET `incident`.`updated` = %(updated)s,
327+
`incident`.`resolved` = %(resolved)s
328+
WHERE `incident`.`id` = %(incident_id)s''',
329+
{'incident_id': incident_id, 'resolved': resolved, 'updated': now})
330+
331+
connection.commit()
332+
except Exception:
333+
logger.exception('failed updating resolved state for incident %s', incident_id)
334+
finally:
335+
cursor.close()
336+
connection.close()
337+
338+
315339
def msgpack_unpack_msg_from_socket(socket):
316340
unpacker = msgpack.Unpacker()
317341
while True:

test/e2etest.py

+7
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,13 @@ def test_post_incident(sample_user, sample_team, sample_application_name, sample
12971297
assert re.status_code == 400
12981298
assert re.json()['title'] == 'Invalid claim: no matching owner'
12991299

1300+
# Test resolvling
1301+
re = requests.post(base_url + 'incidents/%s/resolve' % incident_id, headers=username_header(sample_user), json={
1302+
'resolved': True
1303+
})
1304+
assert re.status_code == 200
1305+
assert re.json() == {'incident_id': str(incident_id), 'resolved': True}
1306+
13001307

13011308
def test_post_dynamic_incident(sample_user, sample_team, sample_application_name, sample_template_name):
13021309
data = {

0 commit comments

Comments
 (0)