Skip to content

Commit e5f39b6

Browse files
committed
Add security stats to issues
1 parent 7d4b9ae commit e5f39b6

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

lib/Brass.pm

+12
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,18 @@ any ['get', 'post'] => '/calendar/:id/' => require_any_role [qw(config)] => sub
636636
};
637637
};
638638

639+
any ['get', 'post'] => '/issue/stats/' => require_any_role [qw(issue_read issue_read_all)] => sub {
640+
641+
my $schema = schema;
642+
643+
my $statistics = $schema->resultset('Issue')->statistics;
644+
645+
template 'issue_statistics' => {
646+
statistics => $statistics,
647+
page => 'statistics',
648+
};
649+
};
650+
639651
any ['get', 'post'] => '/issue/?:id?' => require_any_role [qw(issue_read issue_read_project issue_read_all)] => sub {
640652

641653
my $id = param 'id';

lib/Brass/Schema/ResultSet/Issue.pm

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package Brass::Schema::ResultSet::Issue;
2+
3+
use strict;
4+
use warnings;
5+
6+
use DateTime;
7+
use DBIx::Class::Helper::ResultSet::CorrelateRelationship 2.034000;
8+
use Log::Report;
9+
10+
use base qw(DBIx::Class::ResultSet);
11+
12+
__PACKAGE__->load_components(qw(Helper::ResultSet::DateMethods1 Helper::ResultSet::CorrelateRelationship));
13+
14+
sub statistics
15+
{ my $self = shift;
16+
my $schema = $self->result_source->schema;
17+
18+
# Work out dates of last quarter
19+
my $quarter_start =
20+
DateTime
21+
->now(time_zone => 'local')
22+
->set_time_zone('floating')
23+
->truncate(to => 'quarter')
24+
->subtract(months => 3);
25+
26+
my $quarter_end =
27+
$quarter_start
28+
->clone
29+
->add(months => 3)
30+
->subtract(days => 1);
31+
32+
my $formatter = $schema->storage->datetime_parser;
33+
34+
# Number of new security incidents during quarter
35+
my @all = $self->search({
36+
'type.is_breach' => 1,
37+
},{
38+
join => [qw/type/],
39+
'select' => ['me.id', 'me.title',
40+
{
41+
# Earliest status of new or open
42+
"" => $schema->resultset('Issue')
43+
->correlate('issue_statuses')
44+
->search({ status => [1, 2]})
45+
->get_column('datetime')
46+
->min_rs->as_query,
47+
-as => 'opened',
48+
},
49+
],
50+
having => {
51+
opened => {
52+
'>=' => $formatter->format_datetime($quarter_start),
53+
'<=' => $formatter->format_datetime($quarter_end),
54+
},
55+
},
56+
})->all;
57+
58+
my @opened = map {
59+
+{
60+
title => $_->get_column('title'),
61+
opened => $formatter->parse_datetime($_->get_column('opened')),
62+
}
63+
} @all;
64+
65+
# Number of currently open security-related issues
66+
@all = $self->search([
67+
'type.is_vulnerability' => 1,
68+
'type.is_breach' => 1,
69+
'type.is_audit' => 1,
70+
'type.is_other_security' => 1,
71+
],{
72+
join => [qw/type/],
73+
'select' => ['me.id', 'me.title',
74+
{
75+
"" => $schema->resultset('IssueStatus')->search({
76+
'iss_stat.id' => {'=' => $schema->resultset('Issue')
77+
->correlate('issue_statuses')
78+
->search({
79+
datetime => {
80+
'<=' => $formatter->format_datetime($quarter_start),
81+
}
82+
})
83+
->get_column('id')
84+
->max_rs->as_query}
85+
},{
86+
alias => 'iss_stat', # Prevent conflict with other "me" table
87+
})->get_column('status')->as_query,
88+
-as => 'historical_status',
89+
},
90+
{
91+
# Earliest status of new or open
92+
"" => $schema->resultset('Issue')
93+
->correlate('issue_statuses')
94+
->search({ status => [1, 2]})
95+
->search({
96+
datetime => {
97+
'>=' => $formatter->format_datetime($quarter_start),
98+
'<=' => $formatter->format_datetime($quarter_end),
99+
}
100+
})
101+
->get_column('datetime')
102+
->min_rs->as_query,
103+
-as => 'open_in_quarter',
104+
},
105+
{
106+
# Current priority
107+
"" => $schema->resultset('IssuePriority')->search({
108+
'iss_prio.id' => {'=' => $schema->resultset('Issue')
109+
->correlate('issue_priorities')
110+
->get_column('id')
111+
->max_rs->as_query}
112+
},{
113+
alias => 'iss_prio', # Prevent conflict with other "me" table
114+
})->get_column('priority')->as_query,
115+
-as => 'current_priority',
116+
},
117+
],
118+
having => [
119+
historical_status => [1,2],
120+
open_in_quarter => { '!=' => undef },
121+
],
122+
})->all;
123+
124+
my %prio_mapping = map {
125+
$_->id => $_->name,
126+
} $schema->resultset('Priority')->all;
127+
128+
my %existing = map { $_ => 0 } values %prio_mapping;
129+
$existing{$prio_mapping{$_->get_column('current_priority')}}++
130+
foreach @all;
131+
132+
{
133+
from => $quarter_start,
134+
to => $quarter_end,
135+
new_incidents => \@opened,
136+
existing => \%existing,
137+
}
138+
139+
}
140+
141+
1;

views/issue.tt

+1
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@
234234
<a href="/issue/0" class="btn btn-default">New issue</a>
235235
[% END %]
236236
<a href="" data-toggle="modal" data-target="#modal_filter" class="btn btn-default">Filtering...</a>
237+
<a href="/issue/stats/" class="btn btn-default">Statistics</a>
237238
</p>
238239
<table class="table table-striped">
239240
<tr>

views/issue_statistics.tt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<h1>Issue statistics</h1>
2+
3+
<p class="lead">Issue statistics for last quarter from [% statistics.from.ymd | html %] to [% statistics.to.ymd | html %]</p>
4+
5+
<h4>New opened security-related issues</h4>
6+
7+
<ul>
8+
[% FOREACH issue IN statistics.new_incidents %]
9+
<li>[% issue.title | html %] (opened [% issue.opened.ymd %])</li>
10+
[% END %]
11+
</ul>
12+
13+
<h4>Total number of open security-related issues</h4>
14+
15+
<ul>
16+
[% FOREACH k IN statistics.existing.keys %]
17+
<li>[% k | html %]: [% statistics.existing.$k | html %]</li>
18+
[% END %]
19+
</ul>

0 commit comments

Comments
 (0)