Skip to content

Commit 12e1e8c

Browse files
committed
wip
1 parent cedbe91 commit 12e1e8c

File tree

6 files changed

+182
-124
lines changed

6 files changed

+182
-124
lines changed

app/src/components/stats/StatsPage.js

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,109 @@ import ToggleButton from '@mui/material/ToggleButton';
77
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
88
import { useState } from "react";
99

10+
function MonthlyDates(length) {
11+
const today = new Date();
12+
const dates = Array.from({ length }, (_, i) => {
13+
const date = new Date(today);
14+
date.setMonth(date.getMonth() - i);
15+
return date;
16+
});
17+
return dates;
18+
}
19+
20+
function DailyDates(length) {
21+
const today = new Date();
22+
const dates = Array.from({ length }, (_, i) => {
23+
const date = new Date(today);
24+
date.setDate(date.getDate() - i);
25+
return date;
26+
});
27+
return dates;
28+
}
29+
30+
function QuarterlyDates(length) {
31+
const today = new Date();
32+
const dates = Array.from({ length }, (_, i) => {
33+
const date = new Date(today);
34+
date.setMonth(date.getMonth() - i * 3);
35+
return date;
36+
});
37+
return dates;
38+
}
39+
1040
export function StatsPage() {
11-
const [platform, setPlatform] = useState('web');
41+
const [interval, setInterval] = useState('month');
1242

1343
const handleChange = (
1444
event,
1545
newAlignment,
1646
) => {
17-
setPlatform(newAlignment);
47+
setInterval(newAlignment);
1848
};
1949

50+
51+
let dateAxis = { data: DailyDates(31), scaleType: 'time', label: 'Date' };
52+
if (interval === 'year') {
53+
dateAxis = { data: MonthlyDates(12), scaleType: 'time', label: 'Date' };
54+
} else if (interval === 'all time') {
55+
dateAxis = {
56+
data: QuarterlyDates(12), scaleType: 'time', label: 'Date', valueFormatter: (date, context) =>
57+
`${date.getMonth()} ${date.getYear()}`, // TODO: Use a better formatter, set different on tick/label
58+
};
59+
}
60+
2061
return (
2162
<CenteredPage>
2263
<Grid size={12}>
2364
<Typography variant="h5">Stats</Typography>
24-
<Typography>Stats about the application</Typography>
65+
<br />
66+
<Typography>Metics for your threat modeling process.</Typography>
67+
<br />
68+
2569
<ToggleButtonGroup
26-
color="primary"
27-
value={platform}
28-
exclusive
29-
onChange={handleChange}
30-
aria-label="Platform"
70+
color="primary"
71+
value={interval}
72+
exclusive
73+
onChange={handleChange}
74+
aria-label="Time interval"
75+
size="small"
3176
>
32-
<ToggleButton value="web">month</ToggleButton>
33-
<ToggleButton value="android">year</ToggleButton>
34-
<ToggleButton value="ios">all time</ToggleButton>
77+
<ToggleButton value="month">month</ToggleButton>
78+
<ToggleButton value="year">year</ToggleButton>
79+
<ToggleButton value="all time">all time</ToggleButton>
3580
</ToggleButtonGroup>
3681
</Grid>
3782
<Grid size={6}>
3883
<Paper sx={{ padding: "20px" }}>
3984
<Typography variant="h6">Systems with an approved threat model (%)</Typography>
4085
<LineChart
41-
xAxis={[{ data: [1, 2, 3, 5, 8, 10] }]}
86+
xAxis={[dateAxis]}
87+
yAxis={[{ max: 100, label: 'Percentage' }]}
4288
series={[
4389
{
44-
data: [2, 5.5, 2, 8.5, 1.5, 5],
90+
data: [2, null, null, 5.5, 2, 8.5, 1.5, 5],
4591
},
92+
4693
]}
4794
width={700}
4895
height={300}
4996
grid={{ vertical: true, horizontal: true }}
97+
loading={false}
98+
title="Systems with an approved threat model (%)"
5099
/>
51100
</Paper>
52101
</Grid>
53102
<Grid size={6}>
54103
<Paper sx={{ padding: "20px" }}>
55104
<Typography variant="h6">Systems with an approved threat model (Total)</Typography>
56105
<LineChart
57-
xAxis={[{ data: [1, 2, 3, 5, 8, 10] }]}
106+
xAxis={[dateAxis]}
58107
series={[
59108
{
60-
data: [2, 5.5, 2, 8.5, 1.5, 5],
109+
data: [2, 5.5, 2, 8, 1.5, 5],
110+
},
111+
{
112+
data: [8, 9, 8, 8, 10, 10], label: 'Total Systems',
61113
},
62114
]}
63115
width={700}
@@ -74,7 +126,8 @@ export function StatsPage() {
74126
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
75127
width={500}
76128
height={300}
77-
/>
129+
grid={{ horizontal: true }}
130+
/>
78131
</Paper>
79132
</Grid>
80133
<Grid size={6}>

core/src/data/migrations/30_stats.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TABLE if not exists stats (
2+
key varchar(255) NOT NULL,
3+
date TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp,
4+
value decimal NOT NULL,
5+
interval varchar(255) NOT NULL,
6+
PRIMARY KEY (key, date, interval)
7+
);
8+

core/src/data/reports/ReportDataService.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import pg from "pg";
21
import log4js from "log4js";
32
import { linkToModel } from "../../util/links.js";
43
import { DataAccessLayer } from "../dal.js";
5-
import { SystemPropertyValue } from "../system-property/types.js";
6-
import { RequestContext } from "../providers/RequestContext.js";
74
import { GramConnectionPool } from "../postgres.js";
5+
import { RequestContext } from "../providers/RequestContext.js";
6+
import { SystemPropertyValue } from "../system-property/types.js";
87

98
interface SystemCompliance {
109
SystemID: string;
@@ -67,13 +66,11 @@ export class ReportDataService {
6766
) pending_models on pending_models.system_id = m.system_id
6867
WHERE m.system_id IS NOT NULL
6968
ORDER BY m.system_id DESC
70-
${
71-
pagesize && pagesize > 0
72-
? `LIMIT ${pagesize} ${
73-
page && page > 0 ? `OFFSET ${(page - 1) * pagesize}` : ""
74-
}`
75-
: ""
76-
};
69+
${pagesize && pagesize > 0
70+
? `LIMIT ${pagesize} ${page && page > 0 ? `OFFSET ${(page - 1) * pagesize}` : ""
71+
}`
72+
: ""
73+
};
7774
`;
7875

7976
const res = await this.pool.query(query);

core/src/data/reports/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import log4js from "log4js";
2+
import { DataAccessLayer } from "../dal.js";
3+
import { GramConnectionPool } from "../postgres.js";
4+
5+
export type StatInterval = "day" | "month" | "year";
6+
7+
export class StatsDataService {
8+
constructor(private dal: DataAccessLayer) {
9+
this.pool = dal.pool;
10+
}
11+
12+
private pool: GramConnectionPool;
13+
14+
log = log4js.getLogger("StatsDataService");
15+
16+
async upsertStat(
17+
key: string,
18+
interval: StatInterval,
19+
date: Date,
20+
value: number
21+
): Promise<void> {
22+
const query = `
23+
INSERT INTO stats (key, interval, date, value)
24+
VALUES ($1, $2, $3, $4)
25+
ON CONFLICT (key, interval, date) DO UPDATE
26+
SET value = $3
27+
`;
28+
29+
await this.pool.query(query, [key, interval, date, value]);
30+
}
31+
32+
async listStats(
33+
key: string,
34+
interval: StatInterval,
35+
start: Date,
36+
end: Date
37+
): Promise<{ date: Date; value: number }[]> {
38+
const query = `
39+
SELECT date, value
40+
FROM stats
41+
WHERE key = $1
42+
AND interval = $2
43+
AND date >= $3
44+
AND date <= $4
45+
ORDER BY date
46+
`;
47+
48+
const res = await this.pool.query(query, [key, interval, start, end]);
49+
50+
return res.rows;
51+
}
52+
}

0 commit comments

Comments
 (0)