Skip to content

Commit

Permalink
feat(reports): ✨ display totals for fuzzy filtered reports (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNZL committed Jun 28, 2023
1 parent 6425713 commit 7568f2a
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 115 deletions.
14 changes: 9 additions & 5 deletions src/Structures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ object ICloneable.Clone()

if (group?.SubGroups is not null)
{
group.SubGroups.Add(group.GetSubGroupKey(newSubGroup.id, newSubGroup.title), new SummaryReportSubGroup(newSubGroup));
group.SubGroups.Add(group.GetSubGroupKey(newSubGroup.id, newSubGroup.title), new SummaryReportSubGroup(newSubGroup, group));
return clonedSummary;
}

Expand All @@ -382,7 +382,7 @@ object ICloneable.Clone()
group.SubGroups = new Dictionary<string, SummaryReportSubGroup>
{
{
group.GetSubGroupKey(newSubGroup.id, newSubGroup.title), new SummaryReportSubGroup(newSubGroup)
group.GetSubGroupKey(newSubGroup.id, newSubGroup.title), new SummaryReportSubGroup(newSubGroup, group)
},
};
return clonedSummary;
Expand Down Expand Up @@ -447,7 +447,7 @@ public SummaryReportGroup(SummaryReportGroupResponse response, Me me)
this._me = me;

this.Id = response.id;
this.SubGroups = response.sub_groups?.ToDictionary(keySelector: subGroupResponse => this.GetSubGroupKey(subGroupResponse.id, subGroupResponse.title), elementSelector: subGroupResponse => subGroupResponse.ToSummaryReportSubGroup());
this.SubGroups = response.sub_groups?.ToDictionary(keySelector: subGroupResponse => this.GetSubGroupKey(subGroupResponse.id, subGroupResponse.title), elementSelector: subGroupResponse => subGroupResponse.ToSummaryReportSubGroup(this));

this.Project = me.GetProject(this.Id);
this.Client = me.GetClient(this.Id);
Expand Down Expand Up @@ -522,22 +522,26 @@ public class SummaryReportSubGroup : ICloneable
{
private string? _rawTitle;

public readonly SummaryReportGroup Group;
public long? Id;
public long Seconds;
public List<long>? Ids;

public SummaryReportSubGroup(SummaryReportSubGroupResponse response)
public SummaryReportSubGroup(SummaryReportSubGroupResponse response, SummaryReportGroup group)
{
this._rawTitle = response.title;

this.Group = group;
this.Id = response.id;
this.Seconds = response.seconds;
this.Ids = response.ids;
}
public SummaryReportSubGroup(SummaryReportSubGroup subGroup)
{
this.Id = subGroup.Id;
this._rawTitle = subGroup._rawTitle;

this.Group = subGroup.Group;
this.Id = subGroup.Id;
this.Seconds = subGroup.Seconds;
this.Ids = (subGroup.Ids is not null)
? new List<long>(subGroup.Ids)
Expand Down
4 changes: 2 additions & 2 deletions src/Toggl/Responses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,9 @@ public class SummaryReportSubGroupResponse
public long seconds { get; set; }
public List<long>? ids { get; set; }

public SummaryReportSubGroup ToSummaryReportSubGroup()
public SummaryReportSubGroup ToSummaryReportSubGroup(SummaryReportGroup group)
{
return new SummaryReportSubGroup(this);
return new SummaryReportSubGroup(this, group);
}
}

Expand Down
229 changes: 121 additions & 108 deletions src/TogglTrack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2291,6 +2291,7 @@ internal async ValueTask<List<Result>> RequestViewReports(CancellationToken toke
var project = me.GetProject(selectedProjectGroup.Id);

IEnumerable<Result> subResults = Enumerable.Empty<Result>();
var total = TimeSpan.Zero;

string subGroupQuery = Main.ExtractQueryAfter(query, ArgumentIndices.SubGroupingName);

Expand Down Expand Up @@ -2331,31 +2332,33 @@ internal async ValueTask<List<Result>> RequestViewReports(CancellationToken toke
}
}

subResults = subResults.Concat(report.SelectMany(timeEntryGroup =>
var filteredTimeEntries = report.SelectMany(timeEntryGroup =>
{
var filteredTimeEntries = (string.IsNullOrEmpty(subGroupQuery))
return (string.IsNullOrEmpty(subGroupQuery))
? timeEntryGroup.TimeEntries
: timeEntryGroup.TimeEntries.FindAll(timeEntry => this._context.API.FuzzySearch(subGroupQuery, timeEntry.GetDescription()).Score > 0);
});

return filteredTimeEntries.ConvertAll(timeEntry =>
{
DateTimeOffset startDate = timeEntry.StartDate.ToLocalTime();
total = filteredTimeEntries.Aggregate(TimeSpan.Zero, (subTotal, timeEntry) => subTotal + timeEntry.Elapsed);

return new Result
subResults = subResults.Concat(filteredTimeEntries.Select(timeEntry =>
{
DateTimeOffset startDate = timeEntry.StartDate.ToLocalTime();
return new Result
{
Title = timeEntry.GetDescription(),
SubTitle = $"{timeEntry.DetailedElapsed} ({timeEntry.HumanisedStart} at {startDate.ToString("t")} {startDate.ToString("ddd")} {startDate.ToString("m")})",
IcoPath = this._colourIconProvider.GetColourIcon(project?.Colour, "reports.png"),
AutoCompleteText = $"{query.ActionKeyword} {Settings.ReportsCommand} {spanArgument} {groupingArgument} {project?.KebabName ?? "no-project"} {timeEntry.GetDescription(escapePotentialFlags: true)}",
Score = timeEntry.GetScoreByStart(),
Action = c =>
{
Title = timeEntry.GetDescription(),
SubTitle = $"{timeEntry.DetailedElapsed} ({timeEntry.HumanisedStart} at {startDate.ToString("t")} {startDate.ToString("ddd")} {startDate.ToString("m")})",
IcoPath = this._colourIconProvider.GetColourIcon(project?.Colour, "reports.png"),
AutoCompleteText = $"{query.ActionKeyword} {Settings.ReportsCommand} {spanArgument} {groupingArgument} {project?.KebabName ?? "no-project"} {timeEntry.GetDescription(escapePotentialFlags: true)}",
Score = timeEntry.GetScoreByStart(),
Action = c =>
{
this._state.SelectedIds.Project = project?.Id;
this._context.API.ChangeQuery($"{query.ActionKeyword} {Settings.StartCommand} {project?.KebabName ?? "no-project"} {timeEntry.GetRawDescription(withTrailingSpace: true, escapePotentialFlags: true)}");
return false;
},
};
});
this._state.SelectedIds.Project = project?.Id;
this._context.API.ChangeQuery($"{query.ActionKeyword} {Settings.StartCommand} {project?.KebabName ?? "no-project"} {timeEntry.GetRawDescription(withTrailingSpace: true, escapePotentialFlags: true)}");
return false;
},
};
}));
}
else
Expand All @@ -2364,7 +2367,9 @@ internal async ValueTask<List<Result>> RequestViewReports(CancellationToken toke
? selectedProjectGroup.SubGroups.Values
: selectedProjectGroup.SubGroups.Values.Where(subGroup => this._context.API.FuzzySearch(subGroupQuery, subGroup.GetTitle()).Score > 0);

subResults = filteredSubGroups.Select(subGroup => new Result
total = filteredSubGroups.Aggregate(TimeSpan.Zero, (subTotal, subGroup) => subTotal + subGroup.Elapsed);

subResults = subResults.Concat(filteredSubGroups.Select(subGroup => new Result
{
Title = subGroup.GetTitle(),
SubTitle = $"{subGroup.HumanisedElapsed} ({subGroup.DetailedElapsed})",
Expand All @@ -2377,34 +2382,34 @@ internal async ValueTask<List<Result>> RequestViewReports(CancellationToken toke
this._context.API.ChangeQuery($"{query.ActionKeyword} {Settings.StartCommand} {project?.KebabName ?? "no-project"} {subGroup.GetRawTitle(withTrailingSpace: true, escapePotentialFlags: true)}");
return false;
},
});
}));
}

if (string.IsNullOrEmpty(subGroupQuery))
subResults = subResults.Append(new Result
{
subResults = subResults.Append(new Result
// ! see #79... also Flow-Launcher/Flow.Launcher#2201 and Flow-Launcher/Flow.Launcher#2202
Title = $"Display {((this._state.ReportsShowDetailed) ? "summary" : "detailed")} report{new string('\u200B', subGroupQuery.Length)}",
IcoPath = "reports.png",
AutoCompleteText = $"{query.ActionKeyword} {query.Search} ",
Score = int.MaxValue - 1000,
Action = c =>
{
Title = $"Display {((this._state.ReportsShowDetailed) ? "summary" : "detailed")} report",
IcoPath = "reports.png",
AutoCompleteText = $"{query.ActionKeyword} {query.Search} ",
Score = int.MaxValue - 1000,
Action = c =>
{
this._state.ReportsShowDetailed = !this._state.ReportsShowDetailed;
this._context.API.ChangeQuery($"{query.ActionKeyword} {query.Search} ", true);
return false;
},
});
this._state.ReportsShowDetailed = !this._state.ReportsShowDetailed;
this._context.API.ChangeQuery($"{query.ActionKeyword} {query.Search} ", true);
return false;
},
});

subResults = subResults.Append(new Result
{
Title = $"{selectedProjectGroup.HumanisedElapsed} tracked {spanConfiguration.Interpolation(spanArgumentOffset)} ({selectedProjectGroup.DetailedElapsed})",
SubTitle = project?.WithClientName ?? Settings.NoProjectName,
IcoPath = "reports.png",
AutoCompleteText = $"{query.ActionKeyword} {query.Search} ",
Score = int.MaxValue - 100000,
});
}
subResults = subResults.Append(new Result
{
Title = $"{total.Humanize(minUnit: Humanizer.Localisation.TimeUnit.Second, maxUnit: Humanizer.Localisation.TimeUnit.Hour)} tracked {spanConfiguration.Interpolation(spanArgumentOffset)} ({(int)total.TotalHours}:{total.ToString(@"mm\:ss")})",
SubTitle = (!string.IsNullOrEmpty(subGroupQuery))
? $"{project?.WithClientName ?? Settings.NoProjectName} | {subGroupQuery}"
: project?.WithClientName ?? Settings.NoProjectName,
IcoPath = "reports.png",
AutoCompleteText = $"{query.ActionKeyword} {query.Search} ",
Score = int.MaxValue - 100000,
});

return subResults.ToList();
}
Expand Down Expand Up @@ -2502,6 +2507,10 @@ internal async ValueTask<List<Result>> RequestViewReports(CancellationToken toke
}
case (Settings.ReportsGroupingKey.Entries):
{

IEnumerable<Result> subResults = Enumerable.Empty<Result>();
var total = TimeSpan.Zero;

if (this._state.ReportsShowDetailed)
{
var report = (await this._GetDetailedReport(
Expand Down Expand Up @@ -2539,85 +2548,89 @@ internal async ValueTask<List<Result>> RequestViewReports(CancellationToken toke
}
}

results.AddRange(
report.SelectMany(timeEntryGroup =>
{
var filteredTimeEntries = (string.IsNullOrEmpty(groupQuery))
? timeEntryGroup.TimeEntries
: timeEntryGroup.TimeEntries.FindAll(timeEntry => this._context.API.FuzzySearch(groupQuery, timeEntry.GetDescription()).Score > 0);
var filteredTimeEntries = report.SelectMany(timeEntryGroup =>
{
return (string.IsNullOrEmpty(groupQuery))
? timeEntryGroup.TimeEntries
: timeEntryGroup.TimeEntries.FindAll(timeEntry => this._context.API.FuzzySearch(groupQuery, timeEntry.GetDescription()).Score > 0);
});

return filteredTimeEntries.ConvertAll(timeEntry =>
{
DateTimeOffset startDate = timeEntry.StartDate.ToLocalTime();
total = filteredTimeEntries.Aggregate(TimeSpan.Zero, (subTotal, timeEntry) => subTotal + timeEntry.Elapsed);

return new Result
{
Title = timeEntry.GetDescription(),
SubTitle = $"{timeEntry.DetailedElapsed} ({timeEntry.HumanisedStart} at {startDate.ToString("t")} {startDate.ToString("ddd")} {startDate.ToString("m")})",
IcoPath = this._colourIconProvider.GetColourIcon(timeEntry.Project?.Colour, "reports.png"),
AutoCompleteText = $"{query.ActionKeyword} {Settings.ReportsCommand} {spanArgument} {groupingArgument} {timeEntry.GetDescription(escapePotentialFlags: true)}",
Score = timeEntry.GetScoreByStart(),
Action = c =>
{
this._state.SelectedIds.Project = timeEntry.Project?.Id;
this._context.API.ChangeQuery($"{query.ActionKeyword} {Settings.StartCommand} {timeEntry.Project?.KebabName ?? "no-project"} {timeEntry.GetRawDescription(withTrailingSpace: true, escapePotentialFlags: true)}");
return false;
},
};
});
})
);
subResults = subResults.Concat(filteredTimeEntries.Select(timeEntry =>
{
DateTimeOffset startDate = timeEntry.StartDate.ToLocalTime();
return new Result
{
Title = timeEntry.GetDescription(),
SubTitle = $"{timeEntry.DetailedElapsed} ({timeEntry.HumanisedStart} at {startDate.ToString("t")} {startDate.ToString("ddd")} {startDate.ToString("m")})",
IcoPath = this._colourIconProvider.GetColourIcon(timeEntry.Project?.Colour, "reports.png"),
AutoCompleteText = $"{query.ActionKeyword} {Settings.ReportsCommand} {spanArgument} {groupingArgument} {timeEntry.GetDescription(escapePotentialFlags: true)}",
Score = timeEntry.GetScoreByStart(),
Action = c =>
{
this._state.SelectedIds.Project = timeEntry.Project?.Id;
this._context.API.ChangeQuery($"{query.ActionKeyword} {Settings.StartCommand} {timeEntry.Project?.KebabName ?? "no-project"} {timeEntry.GetRawDescription(withTrailingSpace: true, escapePotentialFlags: true)}");
return false;
},
};
}));
}
else
{
results.AddRange(
summary.Groups.Values.SelectMany(group =>
{
if (group.SubGroups is null)
{
return Enumerable.Empty<Result>();
}
var filteredSubGroups = summary.Groups.Values.SelectMany(group =>
{
var filteredSubGroups = (string.IsNullOrEmpty(groupQuery))
? group.SubGroups?.Values
: group.SubGroups?.Values.Where(subGroup => this._context.API.FuzzySearch(groupQuery, subGroup.GetTitle()).Score > 0);
var filteredSubGroups = (string.IsNullOrEmpty(groupQuery))
? group.SubGroups.Values
: group.SubGroups.Values.Where(subGroup => this._context.API.FuzzySearch(groupQuery, subGroup.GetTitle()).Score > 0);
return filteredSubGroups ?? Enumerable.Empty<SummaryReportSubGroup>();
});

return filteredSubGroups.Select(subGroup => new Result
{
Title = subGroup.GetTitle(),
SubTitle = $"{group.Project?.WithClientName ?? Settings.NoProjectName} | {subGroup.HumanisedElapsed} ({subGroup.DetailedElapsed})",
IcoPath = this._colourIconProvider.GetColourIcon(group.Project?.Colour, "reports.png"),
AutoCompleteText = $"{query.ActionKeyword} {Settings.ReportsCommand} {spanArgument} {groupingArgument} {subGroup.GetTitle(escapePotentialFlags: true)}",
Score = subGroup.GetScoreByDuration(),
Action = c =>
{
this._state.SelectedIds.Project = group.Project?.Id;
this._context.API.ChangeQuery($"{query.ActionKeyword} {Settings.StartCommand} {group.Project?.KebabName ?? "no-project"} {subGroup.GetRawTitle(withTrailingSpace: true, escapePotentialFlags: true)}");
return false;
},
});
})
);
}
total = filteredSubGroups.Aggregate(TimeSpan.Zero, (subTotal, subGroup) => subTotal + subGroup.Elapsed);

if (string.IsNullOrEmpty(groupQuery))
{
results.Add(new Result
subResults = subResults.Concat(filteredSubGroups.Select(subGroup => new Result
{
Title = $"Display {((this._state.ReportsShowDetailed) ? "summary" : "detailed")} report",
IcoPath = "reports.png",
AutoCompleteText = $"{query.ActionKeyword} {query.Search} ",
Score = int.MaxValue - 1000,
Title = subGroup.GetTitle(),
SubTitle = $"{subGroup.Group.Project?.WithClientName ?? Settings.NoProjectName} | {subGroup.HumanisedElapsed} ({subGroup.DetailedElapsed})",
IcoPath = this._colourIconProvider.GetColourIcon(subGroup.Group.Project?.Colour, "reports.png"),
AutoCompleteText = $"{query.ActionKeyword} {Settings.ReportsCommand} {spanArgument} {groupingArgument} {subGroup.GetTitle(escapePotentialFlags: true)}",
Score = subGroup.GetScoreByDuration(),
Action = c =>
{
this._state.ReportsShowDetailed = !this._state.ReportsShowDetailed;
this._context.API.ChangeQuery($"{query.ActionKeyword} {query.Search} ", true);
this._state.SelectedIds.Project = subGroup.Group.Project?.Id;
this._context.API.ChangeQuery($"{query.ActionKeyword} {Settings.StartCommand} {subGroup.Group.Project?.KebabName ?? "no-project"} {subGroup.GetRawTitle(withTrailingSpace: true, escapePotentialFlags: true)}");
return false;
}
});
},
}));
}

break;
subResults = subResults.Append(new Result
{
// ! see #79... also Flow-Launcher/Flow.Launcher#2201 and Flow-Launcher/Flow.Launcher#2202
Title = $"Display {((this._state.ReportsShowDetailed) ? "summary" : "detailed")} report{new string('\u200B', groupQuery.Length)}",
IcoPath = "reports.png",
AutoCompleteText = $"{query.ActionKeyword} {query.Search} ",
Score = int.MaxValue - 1000,
Action = c =>
{
this._state.ReportsShowDetailed = !this._state.ReportsShowDetailed;
this._context.API.ChangeQuery($"{query.ActionKeyword} {query.Search} ", true);
return false;
},
});

subResults = subResults.Append(new Result
{
Title = $"{total.Humanize(minUnit: Humanizer.Localisation.TimeUnit.Second, maxUnit: Humanizer.Localisation.TimeUnit.Hour)} tracked {spanConfiguration.Interpolation(spanArgumentOffset)} ({(int)total.TotalHours}:{total.ToString(@"mm\:ss")})",
SubTitle = groupQuery,
IcoPath = "reports.png",
AutoCompleteText = $"{query.ActionKeyword} {query.Search} ",
Score = int.MaxValue - 100000,
});

return subResults.ToList();
}
}

Expand Down

0 comments on commit 7568f2a

Please sign in to comment.