This commit is contained in:
@@ -45,7 +45,8 @@ public class AdminAnalyticsController {
|
|||||||
public ResponseEntity<Map<String, Object>> getStatisticsTable(
|
public ResponseEntity<Map<String, Object>> getStatisticsTable(
|
||||||
@RequestParam String mode,
|
@RequestParam String mode,
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@RequestParam(defaultValue = "50") int size) {
|
@RequestParam(defaultValue = "50") int size,
|
||||||
|
@RequestParam(defaultValue = "desc") String sortDir) {
|
||||||
|
|
||||||
AdminStatisticsTableService.TableMode tableMode;
|
AdminStatisticsTableService.TableMode tableMode;
|
||||||
try {
|
try {
|
||||||
@@ -54,10 +55,16 @@ public class AdminAnalyticsController {
|
|||||||
return ResponseEntity.badRequest().build();
|
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 cappedSize = Math.min(200, Math.max(1, size));
|
||||||
int safePage = Math.max(0, page);
|
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<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("content", dtoPage.getContent());
|
response.put("content", dtoPage.getContent());
|
||||||
@@ -68,6 +75,7 @@ public class AdminAnalyticsController {
|
|||||||
response.put("hasNext", dtoPage.hasNext());
|
response.put("hasNext", dtoPage.hasNext());
|
||||||
response.put("hasPrevious", dtoPage.hasPrevious());
|
response.put("hasPrevious", dtoPage.hasPrevious());
|
||||||
response.put("mode", tableMode.name());
|
response.put("mode", tableMode.name());
|
||||||
|
response.put("sortDir", dir);
|
||||||
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Native aggregate queries for the admin statistics-by-day/month table.
|
* 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}).
|
* 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
|
@Repository
|
||||||
public class AdminStatisticsTableRepository {
|
public class AdminStatisticsTableRepository {
|
||||||
|
|||||||
@@ -40,14 +40,17 @@ public class AdminStatisticsTableService {
|
|||||||
MONTH
|
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 todayUtc = LocalDate.now(ZoneOffset.UTC);
|
||||||
LocalDate firstDay = resolveFirstProjectDay(todayUtc);
|
LocalDate firstDay = resolveFirstProjectDay(todayUtc);
|
||||||
|
|
||||||
if (mode == TableMode.DAY) {
|
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) {
|
private LocalDate resolveFirstProjectDay(LocalDate todayUtc) {
|
||||||
@@ -62,7 +65,8 @@ public class AdminStatisticsTableService {
|
|||||||
return d;
|
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;
|
long totalPeriods = ChronoUnit.DAYS.between(firstDay, todayUtc) + 1;
|
||||||
if (totalPeriods < 1) {
|
if (totalPeriods < 1) {
|
||||||
totalPeriods = 1;
|
totalPeriods = 1;
|
||||||
@@ -74,8 +78,16 @@ public class AdminStatisticsTableService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int pageLen = (int) Math.min(size, totalPeriods - offset);
|
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 startSec = oldestOnPage.atStartOfDay(ZoneOffset.UTC).toEpochSecond();
|
||||||
long endSec = newestOnPage.plusDays(1).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);
|
List<AdminStatisticsTableRowDto> rows = new ArrayList<>(pageLen);
|
||||||
for (int i = 0; i < pageLen; i++) {
|
for (int i = 0; i < pageLen; i++) {
|
||||||
int k = offset + i;
|
LocalDate day = sortAscending
|
||||||
LocalDate day = todayUtc.minusDays(k);
|
? firstDay.plusDays(offset + (long) i)
|
||||||
|
: todayUtc.minusDays(offset + (long) i);
|
||||||
rows.add(toRowDay(day, buckets.getOrDefault(day, new StatisticsTableBucket())));
|
rows.add(toRowDay(day, buckets.getOrDefault(day, new StatisticsTableBucket())));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PageImpl<>(rows, PageRequest.of(page, size), totalPeriods);
|
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 nowYm = YearMonth.from(todayUtc);
|
||||||
YearMonth firstYm = YearMonth.from(firstDay);
|
YearMonth firstYm = YearMonth.from(firstDay);
|
||||||
|
|
||||||
@@ -115,8 +129,16 @@ public class AdminStatisticsTableService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int pageLen = (int) Math.min(size, totalMonths - offset);
|
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 startSec = oldestOnPage.atDay(1).atStartOfDay(ZoneOffset.UTC).toEpochSecond();
|
||||||
long endSec = newestOnPage.plusMonths(1).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);
|
List<AdminStatisticsTableRowDto> rows = new ArrayList<>(pageLen);
|
||||||
for (int i = 0; i < pageLen; i++) {
|
for (int i = 0; i < pageLen; i++) {
|
||||||
int k = offset + i;
|
YearMonth ym = sortAscending
|
||||||
YearMonth ym = nowYm.minusMonths(k);
|
? firstYm.plusMonths(offset + (long) i)
|
||||||
|
: nowYm.minusMonths(offset + (long) i);
|
||||||
rows.add(toRowMonth(ym, buckets.getOrDefault(ym.toString(), new StatisticsTableBucket())));
|
rows.add(toRowMonth(ym, buckets.getOrDefault(ym.toString(), new StatisticsTableBucket())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user