Skip to content

Commit

Permalink
api: query for date range already on server
Browse files Browse the repository at this point in the history
This brings a significant performance boost as not all data has to be
fetched always.
  • Loading branch information
phip1611 committed Aug 26, 2024
1 parent f217ae5 commit 8a78e82
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ indent_size = 4
trim_trailing_whitespace = true
max_line_length = 80

[{*.nix,*.toml,*.yml}]
[{*.graphql,*.nix,*.toml,*.yml}]
indent_size = 2
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Unreleased (Yet)

- time span filtering already happens on the server-side, which accelerates
requests by a notable amount.

# v0.2.2 (2024-07-04)

- improve handling of default xdg config dir (unix only)
Expand Down
2 changes: 1 addition & 1 deletion src/gitlab-query.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
timelogs(username: "%USERNAME%", last: 500, before: "%BEFORE%") {
timelogs(username: "%USERNAME%", last: 500, before: "%BEFORE%", startDate: "%START_DATE%", endDate: "%END_DATE%") {
nodes {
spentAt
timeSpent
Expand Down
72 changes: 65 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ SOFTWARE.

use crate::cli::CfgFile;
use crate::gitlab_api::types::{Response, ResponseNode};
use chrono::{DateTime, Datelike, Local, NaiveDate, Weekday};
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveTime, Weekday};
use clap::Parser;
use cli::CliArgs;
use nu_ansi_term::{Color, Style};
Expand All @@ -62,12 +62,47 @@ mod gitlab_api;

const GRAPHQL_TEMPLATE: &str = include_str!("./gitlab-query.graphql");

/// Transforms a [`NaiveDate`] to a `DateTime<Local>`.
fn naive_date_to_local_datetime(date: NaiveDate) -> DateTime<Local> {
date.and_time(NaiveTime::MIN)
.and_local_timezone(Local)
.unwrap()
}

/// Performs a single request against the GitLab API, getting exactly one page
/// of the paged data source.
fn fetch_result(username: &str, host: &str, token: &str, before: Option<&str>) -> Response {
/// of the paged data source. The data is filtered for the date span to make the
/// request smaller/quicker.
///
/// # Parameters
/// - `username`: The exact GitLab username of the user.
/// - `host`: Host name of the GitLab instance without `https://`
/// - `token`: GitLab token to access the GitLab instance. Must have at least
/// READ access.
/// - `before`: Identifier from previous request to get the next page of the
/// paginated result.
/// - `start_date`: Inclusive begin date.
/// - `end_date`: Inclusive end date.
fn fetch_result(
username: &str,
host: &str,
token: &str,
before: Option<&str>,
start_date: NaiveDate,
end_date: NaiveDate,
) -> Response {
let graphql_query = GRAPHQL_TEMPLATE
.replace("%USERNAME%", username)
.replace("%BEFORE%", before.unwrap_or_default());
.replace("%BEFORE%", before.unwrap_or_default())
.replace(
"%START_DATE%",
naive_date_to_local_datetime(start_date)
.to_string()
.as_str(),
)
.replace(
"%END_DATE%",
naive_date_to_local_datetime(end_date).to_string().as_str(),
);
let payload = json!({ "query": graphql_query });

let authorization = format!("Bearer {token}", token = token);
Expand All @@ -85,8 +120,22 @@ fn fetch_result(username: &str, host: &str, token: &str, before: Option<&str>) -
}

/// Fetches all results from the API with pagination in mind.
fn fetch_all_results(username: &str, host: &str, token: &str) -> Response {
let base = fetch_result(username, host, token, None);
///
/// # Parameters
/// - `username`: The exact GitLab username of the user.
/// - `host`: Host name of the GitLab instance without `https://`
/// - `token`: GitLab token to access the GitLab instance. Must have at least
/// READ access.
/// - `start_date`: Inclusive begin date.
/// - `end_date`: Inclusive end date.
fn fetch_all_results(
username: &str,
host: &str,
token: &str,
start_date: NaiveDate,
end_date: NaiveDate,
) -> Response {
let base = fetch_result(username, host, token, None, start_date, end_date);

let mut aggregated = base;
while aggregated.data.timelogs.pageInfo.hasPreviousPage {
Expand All @@ -102,6 +151,8 @@ fn fetch_all_results(username: &str, host: &str, token: &str) -> Response {
.startCursor
.expect("Should be valid string at this point"),
),
start_date,
end_date,
);

// Ordering here is not that important, happens later anyway.
Expand Down Expand Up @@ -185,9 +236,16 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("Username : {}", cfg.username());
println!("Time Span: {} - {}", cfg.after(), cfg.before());

let res = fetch_all_results(cfg.username(), cfg.host(), cfg.token());
let res = fetch_all_results(
cfg.username(),
cfg.host(),
cfg.token(),
cfg.after(),
cfg.before(),
);

// All dates with timelogs.
// TODO this is now obsolete. We need to refactor the "data filter layer"
let all_dates = find_dates(&res, &cfg.before(), &cfg.after());
let week_to_logs_map = aggregate_dates_by_week(&all_dates);

Expand Down

0 comments on commit 8a78e82

Please sign in to comment.