Skip to content

Commit

Permalink
add endpoint for graph
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukas-Heiligenbrunner committed Jan 12, 2025
1 parent bd5e262 commit 7cd1fe1
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 63 deletions.
1 change: 1 addition & 0 deletions backend/src/api/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub fn build_api() -> Vec<Route> {
delete_build,
list_builds,
stats,
dashboard_graph_data,
get_build,
get_package,
rery_build,
Expand Down
52 changes: 50 additions & 2 deletions backend/src/api/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ use rocket::serde::json::Json;
use rocket::{get, State};

use crate::api::types::authenticated::Authenticated;
use crate::api::types::input::ListStats;
use crate::api::types::input::{GraphDataPoint, ListStats, SimplePackageModel};
use crate::builder::types::BuildStates;
use crate::db::helpers::dbtype::{database_type, DbType};
use rocket_okapi::openapi;
use sea_orm::prelude::BigDecimal;
use sea_orm::{ColumnTrait, QueryFilter};
use sea_orm::{ColumnTrait, QueryFilter, TryGetableMany};
use sea_orm::{DatabaseConnection, EntityTrait};
use sea_orm::{DbBackend, FromQueryResult, PaginatorTrait, Statement};

Expand All @@ -32,6 +33,53 @@ pub async fn stats(
.map(Json)
}

/// get graph data for dashboard
#[openapi(tag = "stats")]
#[get("/graph")]
pub async fn dashboard_graph_data(
db: &State<DatabaseConnection>,
_a: Authenticated,
) -> Result<Json<Vec<GraphDataPoint>>, NotFound<String>> {
let db = db as &DatabaseConnection;

get_graph_datapoints(db)
.await
.map_err(|e| NotFound(e.to_string()))
.map(Json)
}

async fn get_graph_datapoints(db: &DatabaseConnection) -> anyhow::Result<Vec<GraphDataPoint>> {
match database_type() {
DbType::Sqlite => {
let query = "SELECT
CAST(strftime('%Y', datetime(start_time, 'unixepoch')) AS INTEGER) AS year,
CAST(strftime('%m', datetime(start_time, 'unixepoch')) AS INTEGER) AS month,
COUNT(*) AS count
FROM
builds
WHERE
start_time >= strftime('%s', 'now', '-12 months')
GROUP BY
year, month
ORDER BY
year DESC, month DESC;";

let result = GraphDataPoint::find_by_statement(Statement::from_sql_and_values(
DbBackend::Sqlite,
query,
vec![],
))
.all(db)
.await?;

return Ok(result);
}
DbType::Postgres => {}
}

Ok(vec![])
}

async fn get_stats(db: &DatabaseConnection) -> anyhow::Result<ListStats> {
// Count total builds
let total_builds: u32 = Builds::find().count(db).await?.try_into()?;
Expand Down
8 changes: 8 additions & 0 deletions backend/src/api/types/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,11 @@ pub struct ListStats {
pub avg_build_time_trend: f32,
pub build_success_trend: f32,
}

#[derive(FromQueryResult, Deserialize, JsonSchema, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct GraphDataPoint {
pub month: u8,
pub year: u16,
pub count: u32,
}
11 changes: 11 additions & 0 deletions frontend/lib/api/statistics.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '../models/graph_datapoint.dart';
import '../models/stats.dart';
import 'api_client.dart';

Expand All @@ -6,4 +7,14 @@ extension StatsAPI on ApiClient {
final resp = await getRawClient().get("/stats");
return Stats.fromJson(resp.data);
}

Future<List<GraphDataPoint>> getGraphData() async {
final resp = await getRawClient().get("/graph");

final responseObject = resp.data as List;
final List<GraphDataPoint> packages = responseObject
.map((e) => GraphDataPoint.fromJson(e))
.toList(growable: false);
return packages;
}
}
175 changes: 114 additions & 61 deletions frontend/lib/components/build_line_chart.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import 'package:aurcache/api/API.dart';
import 'package:aurcache/api/statistics.dart';
import 'package:aurcache/models/graph_datapoint.dart';
import 'package:aurcache/utils/num_formatter.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';

Expand All @@ -9,11 +13,48 @@ class BuildLineChart extends StatefulWidget {
}

class _BuildLineChartState extends State<BuildLineChart> {
final months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];

List<FlSpot> data = [
FlSpot(0, 0),
FlSpot(1, 0),
FlSpot(2, 0),
FlSpot(3, 0),
FlSpot(4, 0),
FlSpot(5, 0),
FlSpot(6, 0),
FlSpot(7, 0),
FlSpot(8, 0),
FlSpot(9, 0),
FlSpot(10, 0),
FlSpot(11, 0),
];
int maxValue = 10;

List<Color> gradientColors = [
Colors.purple,
Colors.deepPurpleAccent,
];

@override
void initState() {
super.initState();
getGraphData();
}

@override
Widget build(BuildContext context) {
return AspectRatio(
Expand All @@ -29,58 +70,68 @@ class _BuildLineChartState extends State<BuildLineChart> {
fontWeight: FontWeight.bold,
fontSize: 12,
);
Widget text;
switch (value) {
case 0:
text = const Text('Jul', style: style);
break;
case 2:
text = const Text('Aug', style: style);
break;
case 4:
text = const Text('Sep', style: style);
break;
case 6:
text = const Text('Oct', style: style);
break;
case 8:
text = const Text('Nov', style: style);
break;
case 10:
text = const Text('Dec', style: style);
break;
default:
text = const Text('', style: style);
break;
}
final now = DateTime.now();
final ivalue = value.toInt();

return SideTitleWidget(
axisSide: meta.axisSide,
child: text,
);
if (ivalue % 2 == 0 || value % 1 != 0) {
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(""),
);
} else {
final text = Text(
months[(now.month + ivalue) % 12],
style: style,
);

return SideTitleWidget(
axisSide: meta.axisSide,
child: text,
);
}
}

Widget leftTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
);
String text;
switch (value.toInt()) {
case 0:
text = '10K';
break;
case 3:
text = '30k';
break;
case 5:
text = '50k';
break;
default:
return Container();
final ivalue = value.toInt();
if (ivalue % (maxValue ~/ 4) == 0) {
return Text(value.toInt().shortForm(),
style: style, textAlign: TextAlign.left);
} else {
return Text("", style: style, textAlign: TextAlign.left);
}
}

getGraphData() async {
final graphdata = await API.getGraphData();

final spotData = data;
final currentMonth = DateTime.now().month;
final currentYear = DateTime.now().year;

int newMaxValue = maxValue;

return Text(text, style: style, textAlign: TextAlign.left);
for (final p in graphdata) {
if (p.year == currentYear) {
final idx = 12 - currentMonth + p.month - 1;
spotData[idx] = spotData[idx].copyWith(y: p.count.toDouble());
} else {
final idx = p.month - currentMonth - 1;
spotData[idx] = spotData[idx].copyWith(y: p.count.toDouble());
}

if (newMaxValue < p.count) {
newMaxValue = p.count;
}
}

setState(() {
data = spotData;
maxValue = newMaxValue;
});
}

LineChartData mainData() {
Expand All @@ -91,10 +142,24 @@ class _BuildLineChartState extends State<BuildLineChart> {
horizontalInterval: 1,
verticalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.8),
strokeWidth: 1,
dashArray: [5, 5]);
final ivalue = value.toInt();
if (ivalue % (maxValue ~/ 4) == 0) {
return FlLine(
color: Colors.grey.withOpacity(0.8),
strokeWidth: 1,
dashArray: [5, 5]);
} else {
return FlLine(strokeWidth: 0);
}

if (value % 4 == 0) {
return FlLine(
color: Colors.grey.withOpacity(0.8),
strokeWidth: 1,
dashArray: [5, 5]);
} else {
return FlLine(strokeWidth: 0);
}
},
),
titlesData: FlTitlesData(
Expand Down Expand Up @@ -126,24 +191,12 @@ class _BuildLineChartState extends State<BuildLineChart> {
show: false,
),
minX: -0.25,
maxX: 10.25,
maxX: 11.25,
minY: 0,
maxY: 6,
maxY: maxValue.toDouble() * 1.1,
lineBarsData: [
LineChartBarData(
spots: const [
FlSpot(0, 3),
FlSpot(1, 1),
FlSpot(2, 2),
FlSpot(3, 1),
FlSpot(4, 4),
FlSpot(5, 2.5),
FlSpot(6, 1.7),
FlSpot(7, 2.1),
FlSpot(8, 3),
FlSpot(9, 4),
FlSpot(10, 3),
],
spots: data,
isCurved: true,
gradient: LinearGradient(
colors: gradientColors,
Expand Down
17 changes: 17 additions & 0 deletions frontend/lib/models/graph_datapoint.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class GraphDataPoint {
final int month, year, count;

factory GraphDataPoint.fromJson(Map<String, dynamic> json) {
return GraphDataPoint(
month: json["month"] as int,
year: json["year"] as int,
count: json["count"] as int,
);
}

GraphDataPoint({
required this.month,
required this.year,
required this.count,
});
}
11 changes: 11 additions & 0 deletions frontend/lib/utils/num_formatter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extension ShortNrFormatter on int {
String shortForm() {
if (this < 1000) {
return toString();
} else if (this < 1000000) {
return "${(this / 1000).floor()}k";
} else {
return "${(this / 1000000).floor()}m";
}
}
}

0 comments on commit 7cd1fe1

Please sign in to comment.