From f9a6110c69d25ca91ee03529cfdaba023eb2fa2a Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Feb 2022 11:20:19 +0900 Subject: [PATCH 1/9] Remove debug return which break the UX the first time we open mempool --- backend/src/api/blocks.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index cd72d91e0..198f2a204 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -301,8 +301,6 @@ class Blocks { if (memPool.isInSync()) { diskCache.$saveCacheToDisk(); } - - return; } } From c704bfedeb11e4e46605772696e047fd1e25dd68 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Feb 2022 12:56:26 +0900 Subject: [PATCH 2/9] Download pool logos from github --- frontend/.gitignore | 1 + frontend/sync-assets.js | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/frontend/.gitignore b/frontend/.gitignore index 64b7777ea..789881ddd 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -53,6 +53,7 @@ src/resources/assets.minimal.json src/resources/assets-testnet.json src/resources/assets-testnet.minimal.json src/resources/pools.json +src/resources/mining-pools/* # environment config mempool-frontend-config.json diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index b1dc4090d..c37b987a2 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -33,6 +33,35 @@ function download(filename, url) { }); } +function downloadMiningPoolLogos() { + const options = { + host: 'api.github.com', + path: '/repos/mempool/mining-pools/contents/', + method: 'GET', + headers: {'user-agent': 'node.js'} + }; + + https.get(options, (response) => { + let chunks_of_data = []; + + response.on('data', (fragments) => { + chunks_of_data.push(fragments); + }); + + response.on('end', () => { + let response_body = Buffer.concat(chunks_of_data); + const poolLogos = JSON.parse(response_body.toString()); + for (const poolLogo of poolLogos) { + download(`${PATH}/mining-pools/${poolLogo.name}`, poolLogo.download_url); + } + }); + + response.on('error', (error) => { + throw new Error(error); + }); + }) +} + const poolsJsonUrl = 'https://raw.githubusercontent.com/btccom/Blockchain-Known-Pools/master/pools.json'; let assetsJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.json'; let assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.minimal.json'; @@ -55,4 +84,5 @@ console.log('Downloading testnet assets'); download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl); console.log('Downloading testnet assets minimal'); download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl); - +console.log('Downloading mining pool logos'); +downloadMiningPoolLogos(); From 37ba43d0eb62f33dbf05bfd48c701617dabbf472 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Mon, 7 Feb 2022 20:50:10 -0800 Subject: [PATCH 3/9] Adjust merge messages and add tags for pushes and PRs --- .github/workflows/cypress.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 9173f8827..49ef7ac17 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -24,6 +24,7 @@ jobs: - name: ${{ matrix.browser }} browser tests (Mempool) uses: cypress-io/github-action@v2 with: + tag: ${{ github.event_name }} working-directory: frontend build: npm run config:defaults:mempool start: npm run start:local-staging @@ -39,6 +40,7 @@ jobs: browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} @@ -47,6 +49,7 @@ jobs: uses: cypress-io/github-action@v2 if: always() with: + tag: ${{ github.event_name }} working-directory: frontend build: npm run config:defaults:liquid start: npm run start:local-staging @@ -61,6 +64,7 @@ jobs: browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} @@ -69,6 +73,7 @@ jobs: uses: cypress-io/github-action@v2 if: always() with: + tag: ${{ github.event_name }} working-directory: frontend build: npm run config:defaults:bisq start: npm run start:local-staging @@ -81,6 +86,7 @@ jobs: browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} From 0c7a907451f3741b404ef04b949cbd3783dc4116 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Mon, 7 Feb 2022 21:15:40 -0800 Subject: [PATCH 4/9] Update Cypress event trigers to master and PRs --- .github/workflows/cypress.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 49ef7ac17..0cd95e568 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -1,7 +1,10 @@ name: Cypress Tests -on: [push, pull_request] - +on: + push: + branches: + - master + pull_request: jobs: cypress: runs-on: ${{ matrix.os }} From e513b464d83531f0fb174c1cef0101ad81a3faa8 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Mon, 7 Feb 2022 21:24:05 -0800 Subject: [PATCH 5/9] Add tests status badge to the top level README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85cadb729..af4ae224f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# The Mempool Open Source Projectâ„¢ +# The Mempool Open Source Projectâ„¢ [![mempool](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/ry4br7/master&style=flat-square)](https://dashboard.cypress.io/projects/ry4br7/runs) Mempool is the fully featured visualizer, explorer, and API service running on [mempool.space](https://mempool.space/), an open source project developed and operated for the benefit of the Bitcoin community, with a focus on the emerging transaction fee market to help our transition into a multi-layer ecosystem. From c9ad316ed569779ab9e3967360d38951885fa158 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 2 Feb 2022 17:49:38 +0900 Subject: [PATCH 6/9] Allow /tv view timespan to be changed through url fragment --- .../television/television.component.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/television/television.component.ts b/frontend/src/app/components/television/television.component.ts index ae5d9abfd..a2f1dcdd8 100644 --- a/frontend/src/app/components/television/television.component.ts +++ b/frontend/src/app/components/television/television.component.ts @@ -4,7 +4,8 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface'; import { StateService } from 'src/app/services/state.service'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; -import { Observable } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { switchMap } from 'rxjs/operators'; @Component({ selector: 'app-television', @@ -21,13 +22,30 @@ export class TelevisionComponent implements OnInit { private apiService: ApiService, private stateService: StateService, private seoService: SeoService, + private route: ActivatedRoute ) { } ngOnInit() { this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`); this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']); - this.apiService.list2HStatistics$() + this.route.fragment + .pipe( + switchMap(() => { + switch (this.route.snapshot.fragment) { + case '2h': return this.apiService.list2HStatistics$(); + case '24h': return this.apiService.list24HStatistics$(); + case '1w': return this.apiService.list1WStatistics$(); + case '1m': return this.apiService.list1MStatistics$(); + case '3m': return this.apiService.list3MStatistics$(); + case '6m': return this.apiService.list6MStatistics$(); + case '1y': return this.apiService.list1YStatistics$(); + case '2y': return this.apiService.list2YStatistics$(); + case '3y': return this.apiService.list3YStatistics$(); + default: return this.apiService.list2HStatistics$(); + } + }) + ) .subscribe((mempoolStats) => { this.mempoolStats = mempoolStats; }); From aa77faf3140b964f4e27e8c2996fe4bd688221a5 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 5 Feb 2022 19:06:27 +0900 Subject: [PATCH 7/9] Use switchMap param instead of re-reading this.route.snapshot.fragment --- .../src/app/components/television/television.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/television/television.component.ts b/frontend/src/app/components/television/television.component.ts index a2f1dcdd8..e219a0b51 100644 --- a/frontend/src/app/components/television/television.component.ts +++ b/frontend/src/app/components/television/television.component.ts @@ -31,8 +31,8 @@ export class TelevisionComponent implements OnInit { this.route.fragment .pipe( - switchMap(() => { - switch (this.route.snapshot.fragment) { + switchMap((fragment) => { + switch (fragment) { case '2h': return this.apiService.list2HStatistics$(); case '24h': return this.apiService.list24HStatistics$(); case '1w': return this.apiService.list1WStatistics$(); From cd9eaf816bc8d3f96b9113db3de58ba4b38809a3 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 9 Feb 2022 10:18:51 +0900 Subject: [PATCH 8/9] Refactor TV component subscription --- .../television/television.component.html | 2 +- .../television/television.component.ts | 71 +++++++++++-------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/frontend/src/app/components/television/television.component.html b/frontend/src/app/components/television/television.component.html index c9caa54ef..7da6e2d38 100644 --- a/frontend/src/app/components/television/television.component.html +++ b/frontend/src/app/components/television/television.component.html @@ -7,7 +7,7 @@ [height]="600" [left]="60" [right]="10" - [data]="mempoolStats && mempoolStats.length ? mempoolStats : null" + [data]="statsSubscription$ | async" [showZoom]="false" > diff --git a/frontend/src/app/components/television/television.component.ts b/frontend/src/app/components/television/television.component.ts index e219a0b51..f7cdcf074 100644 --- a/frontend/src/app/components/television/television.component.ts +++ b/frontend/src/app/components/television/television.component.ts @@ -5,7 +5,9 @@ import { StateService } from 'src/app/services/state.service'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; import { ActivatedRoute } from '@angular/router'; -import { switchMap } from 'rxjs/operators'; +import { map, startWith, switchMap, tap } from 'rxjs/operators'; +import { interval, merge, Observable } from 'rxjs'; +import { isArray } from 'src/app/shared/pipes/bytes-pipe/utils'; @Component({ selector: 'app-television', @@ -15,7 +17,8 @@ import { switchMap } from 'rxjs/operators'; export class TelevisionComponent implements OnInit { mempoolStats: OptimizedMempoolStats[] = []; - mempoolVsizeFeesData: any; + statsSubscription$: Observable; + fragment: string; constructor( private websocketService: WebsocketService, @@ -25,36 +28,46 @@ export class TelevisionComponent implements OnInit { private route: ActivatedRoute ) { } + refreshStats(time: number, fn: Observable) { + return interval(time).pipe(startWith(0), switchMap(() => fn)); + } + ngOnInit() { this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`); this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']); - this.route.fragment - .pipe( - switchMap((fragment) => { - switch (fragment) { - case '2h': return this.apiService.list2HStatistics$(); - case '24h': return this.apiService.list24HStatistics$(); - case '1w': return this.apiService.list1WStatistics$(); - case '1m': return this.apiService.list1MStatistics$(); - case '3m': return this.apiService.list3MStatistics$(); - case '6m': return this.apiService.list6MStatistics$(); - case '1y': return this.apiService.list1YStatistics$(); - case '2y': return this.apiService.list2YStatistics$(); - case '3y': return this.apiService.list3YStatistics$(); - default: return this.apiService.list2HStatistics$(); - } - }) - ) - .subscribe((mempoolStats) => { - this.mempoolStats = mempoolStats; - }); - - this.stateService.live2Chart$ - .subscribe((mempoolStats) => { - this.mempoolStats.unshift(mempoolStats); - this.mempoolStats = this.mempoolStats.slice(0, this.mempoolStats.length - 1); - }); + this.statsSubscription$ = merge( + this.stateService.live2Chart$, + this.route.fragment + .pipe( + tap(fragment => { this.fragment = fragment; }), + switchMap((fragment) => { + const minute = 60000; const hour = 3600000; + switch (fragment) { + case '2h': return this.apiService.list2HStatistics$(); + case '24h': return this.apiService.list24HStatistics$(); + case '1w': return this.refreshStats(5 * minute, this.apiService.list1WStatistics$()); + case '1m': return this.refreshStats(30 * minute, this.apiService.list1MStatistics$()); + case '3m': return this.refreshStats(2 * hour, this.apiService.list3MStatistics$()); + case '6m': return this.refreshStats(3 * hour, this.apiService.list6MStatistics$()); + case '1y': return this.refreshStats(8 * hour, this.apiService.list1YStatistics$()); + case '2y': return this.refreshStats(8 * hour, this.apiService.list2YStatistics$()); + case '3y': return this.refreshStats(12 * hour, this.apiService.list3YStatistics$()); + default: return this.apiService.list2HStatistics$(); + } + }) + ) + ) + .pipe( + map(stats => { + if (isArray(stats)) { + this.mempoolStats = stats as OptimizedMempoolStats[]; + } else if (['2h', '24h'].includes(this.fragment)) { + this.mempoolStats.unshift(stats as OptimizedMempoolStats); + this.mempoolStats = this.mempoolStats.slice(0, this.mempoolStats.length - 1); + } + return this.mempoolStats; + }) + ); } - } From 055c587351f81ee9211b0a2cb0e5586b048c59bf Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 10 Feb 2022 00:04:14 +0900 Subject: [PATCH 9/9] Fix bug when loading `/tv` and cleanup Observable flow --- .../television/television.component.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/components/television/television.component.ts b/frontend/src/app/components/television/television.component.ts index f7cdcf074..3c0c88ec8 100644 --- a/frontend/src/app/components/television/television.component.ts +++ b/frontend/src/app/components/television/television.component.ts @@ -5,19 +5,20 @@ import { StateService } from 'src/app/services/state.service'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; import { ActivatedRoute } from '@angular/router'; -import { map, startWith, switchMap, tap } from 'rxjs/operators'; +import { map, scan, startWith, switchMap, tap } from 'rxjs/operators'; import { interval, merge, Observable } from 'rxjs'; -import { isArray } from 'src/app/shared/pipes/bytes-pipe/utils'; +import { ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'app-television', templateUrl: './television.component.html', - styleUrls: ['./television.component.scss'] + styleUrls: ['./television.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class TelevisionComponent implements OnInit { mempoolStats: OptimizedMempoolStats[] = []; - statsSubscription$: Observable; + statsSubscription$: Observable; fragment: string; constructor( @@ -37,14 +38,13 @@ export class TelevisionComponent implements OnInit { this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']); this.statsSubscription$ = merge( - this.stateService.live2Chart$, + this.stateService.live2Chart$.pipe(map(stats => [stats])), this.route.fragment .pipe( - tap(fragment => { this.fragment = fragment; }), + tap(fragment => { this.fragment = fragment ?? '2h'; }), switchMap((fragment) => { const minute = 60000; const hour = 3600000; switch (fragment) { - case '2h': return this.apiService.list2HStatistics$(); case '24h': return this.apiService.list24HStatistics$(); case '1w': return this.refreshStats(5 * minute, this.apiService.list1WStatistics$()); case '1m': return this.refreshStats(30 * minute, this.apiService.list1MStatistics$()); @@ -53,20 +53,20 @@ export class TelevisionComponent implements OnInit { case '1y': return this.refreshStats(8 * hour, this.apiService.list1YStatistics$()); case '2y': return this.refreshStats(8 * hour, this.apiService.list2YStatistics$()); case '3y': return this.refreshStats(12 * hour, this.apiService.list3YStatistics$()); - default: return this.apiService.list2HStatistics$(); + default /* 2h */: return this.apiService.list2HStatistics$(); } }) ) ) .pipe( - map(stats => { - if (isArray(stats)) { - this.mempoolStats = stats as OptimizedMempoolStats[]; + scan((mempoolStats, newStats) => { + if (newStats.length > 1) { + mempoolStats = newStats; } else if (['2h', '24h'].includes(this.fragment)) { - this.mempoolStats.unshift(stats as OptimizedMempoolStats); - this.mempoolStats = this.mempoolStats.slice(0, this.mempoolStats.length - 1); + mempoolStats.unshift(newStats[0]); + mempoolStats = mempoolStats.slice(0, mempoolStats.length - 1); } - return this.mempoolStats; + return mempoolStats; }) ); }