This commit is contained in:
@@ -45,7 +45,8 @@ public class AdminAnalyticsController {
|
||||
public ResponseEntity<Map<String, Object>> getStatisticsTable(
|
||||
@RequestParam String mode,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "50") int size) {
|
||||
@RequestParam(defaultValue = "50") int size,
|
||||
@RequestParam(defaultValue = "desc") String sortDir) {
|
||||
|
||||
AdminStatisticsTableService.TableMode tableMode;
|
||||
try {
|
||||
@@ -54,10 +55,16 @@ public class AdminAnalyticsController {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
String dir = sortDir != null ? sortDir.trim().toLowerCase() : "desc";
|
||||
if (!dir.equals("asc") && !dir.equals("desc")) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
boolean sortAscending = dir.equals("asc");
|
||||
|
||||
int cappedSize = Math.min(200, Math.max(1, size));
|
||||
int safePage = Math.max(0, page);
|
||||
|
||||
var dtoPage = adminStatisticsTableService.getStatisticsTable(tableMode, safePage, cappedSize);
|
||||
var dtoPage = adminStatisticsTableService.getStatisticsTable(tableMode, safePage, cappedSize, sortAscending);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("content", dtoPage.getContent());
|
||||
@@ -68,6 +75,7 @@ public class AdminAnalyticsController {
|
||||
response.put("hasNext", dtoPage.hasNext());
|
||||
response.put("hasPrevious", dtoPage.hasPrevious());
|
||||
response.put("mode", tableMode.name());
|
||||
response.put("sortDir", dir);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,15 @@ import java.util.Map;
|
||||
/**
|
||||
* Native aggregate queries for the admin statistics-by-day/month table.
|
||||
* Registration metrics use {@code db_users_d.date_reg} only (no join to {@code db_users_a}).
|
||||
* <p>
|
||||
* Row order (newest vs oldest) is applied in the service when building pages, not via SQL {@code ORDER BY}.
|
||||
* Range filters use columns that should stay indexed for performance:
|
||||
* <ul>
|
||||
* <li>{@code db_users_d.date_reg} — {@code idx_users_d_date_reg} (Flyway V80)</li>
|
||||
* <li>{@code payments(status, completed_at)} — {@code idx_payments_status_completed_at} (V78)</li>
|
||||
* <li>{@code payments(status, ftd, completed_at)} — {@code idx_payments_status_ftd_completed_at} (V77)</li>
|
||||
* <li>{@code db_users_d(master_id, referer_id_1)} — {@code idx_users_d_master_referer1} (V79) for “ours” counts</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Repository
|
||||
public class AdminStatisticsTableRepository {
|
||||
|
||||
@@ -40,14 +40,17 @@ public class AdminStatisticsTableService {
|
||||
MONTH
|
||||
}
|
||||
|
||||
public Page<AdminStatisticsTableRowDto> getStatisticsTable(TableMode mode, int page, int size) {
|
||||
/**
|
||||
* @param sortAscending when {@code false} (default), newest periods first; when {@code true}, oldest first.
|
||||
*/
|
||||
public Page<AdminStatisticsTableRowDto> getStatisticsTable(TableMode mode, int page, int size, boolean sortAscending) {
|
||||
LocalDate todayUtc = LocalDate.now(ZoneOffset.UTC);
|
||||
LocalDate firstDay = resolveFirstProjectDay(todayUtc);
|
||||
|
||||
if (mode == TableMode.DAY) {
|
||||
return buildDayPage(todayUtc, firstDay, page, size);
|
||||
return buildDayPage(todayUtc, firstDay, page, size, sortAscending);
|
||||
}
|
||||
return buildMonthPage(todayUtc, firstDay, page, size);
|
||||
return buildMonthPage(todayUtc, firstDay, page, size, sortAscending);
|
||||
}
|
||||
|
||||
private LocalDate resolveFirstProjectDay(LocalDate todayUtc) {
|
||||
@@ -62,7 +65,8 @@ public class AdminStatisticsTableService {
|
||||
return d;
|
||||
}
|
||||
|
||||
private Page<AdminStatisticsTableRowDto> buildDayPage(LocalDate todayUtc, LocalDate firstDay, int page, int size) {
|
||||
private Page<AdminStatisticsTableRowDto> buildDayPage(
|
||||
LocalDate todayUtc, LocalDate firstDay, int page, int size, boolean sortAscending) {
|
||||
long totalPeriods = ChronoUnit.DAYS.between(firstDay, todayUtc) + 1;
|
||||
if (totalPeriods < 1) {
|
||||
totalPeriods = 1;
|
||||
@@ -74,8 +78,16 @@ public class AdminStatisticsTableService {
|
||||
}
|
||||
|
||||
int pageLen = (int) Math.min(size, totalPeriods - offset);
|
||||
LocalDate newestOnPage = todayUtc.minusDays(offset);
|
||||
LocalDate oldestOnPage = todayUtc.minusDays(offset + pageLen - 1);
|
||||
|
||||
LocalDate oldestOnPage;
|
||||
LocalDate newestOnPage;
|
||||
if (sortAscending) {
|
||||
oldestOnPage = firstDay.plusDays(offset);
|
||||
newestOnPage = firstDay.plusDays(offset + pageLen - 1L);
|
||||
} else {
|
||||
newestOnPage = todayUtc.minusDays(offset);
|
||||
oldestOnPage = todayUtc.minusDays(offset + pageLen - 1L);
|
||||
}
|
||||
|
||||
long startSec = oldestOnPage.atStartOfDay(ZoneOffset.UTC).toEpochSecond();
|
||||
long endSec = newestOnPage.plusDays(1).atStartOfDay(ZoneOffset.UTC).toEpochSecond();
|
||||
@@ -91,15 +103,17 @@ public class AdminStatisticsTableService {
|
||||
|
||||
List<AdminStatisticsTableRowDto> rows = new ArrayList<>(pageLen);
|
||||
for (int i = 0; i < pageLen; i++) {
|
||||
int k = offset + i;
|
||||
LocalDate day = todayUtc.minusDays(k);
|
||||
LocalDate day = sortAscending
|
||||
? firstDay.plusDays(offset + (long) i)
|
||||
: todayUtc.minusDays(offset + (long) i);
|
||||
rows.add(toRowDay(day, buckets.getOrDefault(day, new StatisticsTableBucket())));
|
||||
}
|
||||
|
||||
return new PageImpl<>(rows, PageRequest.of(page, size), totalPeriods);
|
||||
}
|
||||
|
||||
private Page<AdminStatisticsTableRowDto> buildMonthPage(LocalDate todayUtc, LocalDate firstDay, int page, int size) {
|
||||
private Page<AdminStatisticsTableRowDto> buildMonthPage(
|
||||
LocalDate todayUtc, LocalDate firstDay, int page, int size, boolean sortAscending) {
|
||||
YearMonth nowYm = YearMonth.from(todayUtc);
|
||||
YearMonth firstYm = YearMonth.from(firstDay);
|
||||
|
||||
@@ -115,8 +129,16 @@ public class AdminStatisticsTableService {
|
||||
}
|
||||
|
||||
int pageLen = (int) Math.min(size, totalMonths - offset);
|
||||
YearMonth newestOnPage = nowYm.minusMonths(offset);
|
||||
YearMonth oldestOnPage = nowYm.minusMonths(offset + pageLen - 1);
|
||||
|
||||
YearMonth oldestOnPage;
|
||||
YearMonth newestOnPage;
|
||||
if (sortAscending) {
|
||||
oldestOnPage = firstYm.plusMonths(offset);
|
||||
newestOnPage = firstYm.plusMonths(offset + pageLen - 1L);
|
||||
} else {
|
||||
newestOnPage = nowYm.minusMonths(offset);
|
||||
oldestOnPage = nowYm.minusMonths(offset + pageLen - 1L);
|
||||
}
|
||||
|
||||
long startSec = oldestOnPage.atDay(1).atStartOfDay(ZoneOffset.UTC).toEpochSecond();
|
||||
long endSec = newestOnPage.plusMonths(1).atDay(1).atStartOfDay(ZoneOffset.UTC).toEpochSecond();
|
||||
@@ -132,8 +154,9 @@ public class AdminStatisticsTableService {
|
||||
|
||||
List<AdminStatisticsTableRowDto> rows = new ArrayList<>(pageLen);
|
||||
for (int i = 0; i < pageLen; i++) {
|
||||
int k = offset + i;
|
||||
YearMonth ym = nowYm.minusMonths(k);
|
||||
YearMonth ym = sortAscending
|
||||
? firstYm.plusMonths(offset + (long) i)
|
||||
: nowYm.minusMonths(offset + (long) i);
|
||||
rows.add(toRowMonth(ym, buckets.getOrDefault(ym.toString(), new StatisticsTableBucket())));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user