Merge pull request #3878 from mempool/mononaut/full-mempool-graph

Add 'all time' option for mempool graph
This commit is contained in:
softsimon 2023-07-01 17:23:03 +02:00 committed by GitHub
commit 2e285c8d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 45 deletions

View File

@ -211,7 +211,7 @@ class StatisticsApi {
CAST(avg(vsize_1800) as DOUBLE) as vsize_1800, CAST(avg(vsize_1800) as DOUBLE) as vsize_1800,
CAST(avg(vsize_2000) as DOUBLE) as vsize_2000 \ CAST(avg(vsize_2000) as DOUBLE) as vsize_2000 \
FROM statistics \ FROM statistics \
WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \ ${interval === 'all' ? '' : `WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`} \
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \ GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
ORDER BY statistics.added DESC;`; ORDER BY statistics.added DESC;`;
} }
@ -259,7 +259,7 @@ class StatisticsApi {
vsize_1800, vsize_1800,
vsize_2000 \ vsize_2000 \
FROM statistics \ FROM statistics \
WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \ ${interval === 'all' ? '' : `WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`} \
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \ GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
ORDER BY statistics.added DESC;`; ORDER BY statistics.added DESC;`;
} }
@ -386,6 +386,17 @@ class StatisticsApi {
} }
} }
public async $listAll(): Promise<OptimizedStatistic[]> {
try {
const query = this.getQueryForDays(43200, 'all'); // 12h interval
const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$listAll() error' + (e instanceof Error ? e.message : e));
return [];
}
}
private mapStatisticToOptimizedStatistic(statistic: Statistic[]): OptimizedStatistic[] { private mapStatisticToOptimizedStatistic(statistic: Statistic[]): OptimizedStatistic[] {
return statistic.map((s) => { return statistic.map((s) => {
return { return {

View File

@ -15,10 +15,11 @@ class StatisticsRoutes {
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', this.$getStatisticsByTime.bind(this, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', this.$getStatisticsByTime.bind(this, '2y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', this.$getStatisticsByTime.bind(this, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', this.$getStatisticsByTime.bind(this, '3y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/4y', this.$getStatisticsByTime.bind(this, '4y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/4y', this.$getStatisticsByTime.bind(this, '4y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/all', this.$getStatisticsByTime.bind(this, 'all'))
; ;
} }
private async $getStatisticsByTime(time: '2h' | '24h' | '1w' | '1m' | '3m' | '6m' | '1y' | '2y' | '3y' | '4y', req: Request, res: Response) { private async $getStatisticsByTime(time: '2h' | '24h' | '1w' | '1m' | '3m' | '6m' | '1y' | '2y' | '3y' | '4y' | 'all', req: Request, res: Response) {
res.header('Pragma', 'public'); res.header('Pragma', 'public');
res.header('Cache-control', 'public'); res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
@ -26,10 +27,6 @@ class StatisticsRoutes {
try { try {
let result; let result;
switch (time as string) { switch (time as string) {
case '2h':
result = await statisticsApi.$list2H();
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
break;
case '24h': case '24h':
result = await statisticsApi.$list24H(); result = await statisticsApi.$list24H();
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
@ -58,8 +55,13 @@ class StatisticsRoutes {
case '4y': case '4y':
result = await statisticsApi.$list4Y(); result = await statisticsApi.$list4Y();
break; break;
case 'all':
result = await statisticsApi.$listAll();
break;
default: default:
result = await statisticsApi.$list2H(); result = await statisticsApi.$list2H();
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
break;
} }
res.json(result); res.json(result);
} catch (e) { } catch (e) {

View File

@ -20,39 +20,46 @@
<fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon> <fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon>
</a> </a>
</div> </div>
<div class="btn-group btn-group-toggle" name="radioBasic"> <div class="btn-toggle-rows" name="radioBasic">
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '2h'"> <div class="btn-group btn-group-toggle">
<input type="radio" [value]="'2h'" [routerLink]="['/graphs' | relativeUrl]" fragment="2h" formControlName="dateSpan"> 2H <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '2h'">
(LIVE) <input type="radio" [value]="'2h'" [routerLink]="['/graphs' | relativeUrl]" fragment="2h" formControlName="dateSpan"> 2H
</label> (LIVE)
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '24h'"> </label>
<input type="radio" [value]="'24h'" [routerLink]="['/graphs' | relativeUrl]" fragment="24h" formControlName="dateSpan"> <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
24H <input type="radio" [value]="'24h'" [routerLink]="['/graphs' | relativeUrl]" fragment="24h" formControlName="dateSpan">
</label> 24H
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1w'"> </label>
<input type="radio" [value]="'1w'" [routerLink]="['/graphs' | relativeUrl]" fragment="1w" formControlName="dateSpan"> 1W <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1w'">
</label> <input type="radio" [value]="'1w'" [routerLink]="['/graphs' | relativeUrl]" fragment="1w" formControlName="dateSpan"> 1W
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1m'"> </label>
<input type="radio" [value]="'1m'" [routerLink]="['/graphs' | relativeUrl]" fragment="1m" formControlName="dateSpan"> 1M <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
</label> <input type="radio" [value]="'1m'" [routerLink]="['/graphs' | relativeUrl]" fragment="1m" formControlName="dateSpan"> 1M
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3m'"> </label>
<input type="radio" [value]="'3m'" [routerLink]="['/graphs' | relativeUrl]" fragment="3m" formControlName="dateSpan"> 3M <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
</label> <input type="radio" [value]="'3m'" [routerLink]="['/graphs' | relativeUrl]" fragment="3m" formControlName="dateSpan"> 3M
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '6m'"> </label>
<input type="radio" [value]="'6m'" [routerLink]="['/graphs' | relativeUrl]" fragment="6m" formControlName="dateSpan"> 6M </div>
</label> <div class="btn-group btn-group-toggle">
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1y'"> <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
<input type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y" formControlName="dateSpan"> 1Y <input type="radio" [value]="'6m'" [routerLink]="['/graphs' | relativeUrl]" fragment="6m" formControlName="dateSpan"> 6M
</label> </label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '2y'"> <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1y'">
<input type="radio" [value]="'2y'" [routerLink]="['/graphs' | relativeUrl]" fragment="2y" formControlName="dateSpan"> 2Y <input type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y" formControlName="dateSpan"> 1Y
</label> </label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3y'"> <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '2y'">
<input type="radio" [value]="'3y'" [routerLink]="['/graphs' | relativeUrl]" fragment="3y" formControlName="dateSpan"> 3Y <input type="radio" [value]="'2y'" [routerLink]="['/graphs' | relativeUrl]" fragment="2y" formControlName="dateSpan"> 2Y
</label> </label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '4y'"> <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3y'">
<input type="radio" [value]="'4y'" [routerLink]="['/graphs' | relativeUrl]" fragment="4y" formControlName="dateSpan"> 4Y <input type="radio" [value]="'3y'" [routerLink]="['/graphs' | relativeUrl]" fragment="3y" formControlName="dateSpan"> 3Y
</label> </label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '4y'">
<input type="radio" [value]="'4y'" [routerLink]="['/graphs' | relativeUrl]" fragment="4y" formControlName="dateSpan"> 4Y
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
<input type="radio" [value]="'all'" [routerLink]="['/graphs' | relativeUrl]" fragment="all" formControlName="dateSpan"><span i18n="all">All</span>
</label>
</div>
</div> </div>
<div class="small-buttons"> <div class="small-buttons">
<div ngbDropdown #myDrop="ngbDropdown"> <div ngbDropdown #myDrop="ngbDropdown">

View File

@ -53,17 +53,17 @@
} }
} }
.formRadioGroup.mining { .formRadioGroup.mining {
@media (min-width: 991px) { @media (min-width: 1035px) {
position: relative; position: relative;
top: -100px; top: -100px;
} }
@media (min-width: 830px) and (max-width: 991px) { @media (min-width: 830px) and (max-width: 1035px) {
position: relative; position: relative;
top: 0px; top: 0px;
} }
} }
.formRadioGroup.no-menu { .formRadioGroup.no-menu {
@media (min-width: 991px) { @media (min-width: 1035px) {
position: relative; position: relative;
top: -33px; top: -33px;
} }
@ -183,3 +183,43 @@
} }
} }
} }
.btn-toggle-rows {
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: stretch;
.btn-group {
flex-grow: 1;
flex-shrink: 1;
}
@media (min-width: 500px) {
.btn-group:first-child > .btn:last-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.btn-group:last-child > .btn:first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
@media (max-width: 499px) {
flex-direction: column;
.btn-group:first-child > .btn:first-child {
border-bottom-left-radius: 0;
}
.btn-group:first-child > .btn:last-child {
border-bottom-right-radius: 0;
}
.btn-group:last-child > .btn:first-child {
border-top-left-radius: 0;
}
.btn-group:last-child > .btn:last-child {
border-top-right-radius: 0;
}
}
}

View File

@ -72,8 +72,10 @@ export class StatisticsComponent implements OnInit {
this.route this.route
.fragment .fragment
.subscribe((fragment) => { .subscribe((fragment) => {
if (['2h', '24h', '1w', '1m', '3m', '6m', '1y', '2y', '3y', '4y'].indexOf(fragment) > -1) { if (['2h', '24h', '1w', '1m', '3m', '6m', '1y', '2y', '3y', '4y', 'all'].indexOf(fragment) > -1) {
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
} else {
this.radioGroupForm.controls.dateSpan.setValue('2h', { emitEvent: false });
} }
}); });
@ -114,7 +116,12 @@ export class StatisticsComponent implements OnInit {
if (this.radioGroupForm.controls.dateSpan.value === '3y') { if (this.radioGroupForm.controls.dateSpan.value === '3y') {
return this.apiService.list3YStatistics$(); return this.apiService.list3YStatistics$();
} }
return this.apiService.list4YStatistics$(); if (this.radioGroupForm.controls.dateSpan.value === '4y') {
return this.apiService.list4YStatistics$();
}
if (this.radioGroupForm.controls.dateSpan.value === 'all') {
return this.apiService.listAllTimeStatistics$();
}
}) })
) )
.subscribe((mempoolStats: any) => { .subscribe((mempoolStats: any) => {

View File

@ -72,6 +72,10 @@ export class ApiService {
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/4y'); return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/4y');
} }
listAllTimeStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/all');
}
getTransactionTimes$(txIds: string[]): Observable<number[]> { getTransactionTimes$(txIds: string[]): Observable<number[]> {
let params = new HttpParams(); let params = new HttpParams();
txIds.forEach((txId: string) => { txIds.forEach((txId: string) => {

View File

@ -21,6 +21,7 @@ do for url in / \
'/api/v1/statistics/2y' \ '/api/v1/statistics/2y' \
'/api/v1/statistics/3y' \ '/api/v1/statistics/3y' \
'/api/v1/statistics/4y' \ '/api/v1/statistics/4y' \
'/api/v1/statistics/all' \
'/api/v1/mining/pools/24h' \ '/api/v1/mining/pools/24h' \
'/api/v1/mining/pools/3d' \ '/api/v1/mining/pools/3d' \
'/api/v1/mining/pools/1w' \ '/api/v1/mining/pools/1w' \