Skip to content
This repository was archived by the owner on Jan 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 additions & 26 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Deploy Observable Dashboard to Cloudflare Pages

on:
push:
branches: [ main ]
branches: [main]
schedule:
- cron: "0 20 * * *"
- cron: '0 20 * * *'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -19,28 +19,28 @@ jobs:
environment: default

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Build project
run: npm run build # Assumes your project has a build script in package.json
env:
API_TOKEN: ${{ secrets.API_TOKEN }}

- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: spark-dashboard # Replace with your Cloudflare Pages project name
directory: ./dist # Replace with your output directory after build
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Build project
run: npm run build # Assumes your project has a build script in package.json
env:
API_TOKEN: ${{ secrets.API_TOKEN }}

- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: spark-dashboard # Replace with your Cloudflare Pages project name
directory: ./dist # Replace with your output directory after build

27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Test Observable Dashboard

on:
push:
pull_request:

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
environment: default

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: npm ci

- name: Test project
run: npm test
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/.observablehq
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"semi": false
}
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This is an [Observable Framework](https://observablehq.com/framework) app runnin

## Development

To start the local preview server, run:
To start the local preview server, run:

```
npm run dev
Expand Down Expand Up @@ -47,11 +47,11 @@ A typical Framework project looks like this:

## Command reference

| Command | Description |
| ----------------- | -------------------------------------------------------- |
| `npm install` | Install or reinstall dependencies |
| `npm run dev` | Start local preview server |
| `npm run build` | Build your static site, generating `./dist` |
| `npm run deploy` | Deploy your app to Observable |
| `npm run clean` | Clear the local data loader cache |
| `npm run observable` | Run commands like `observable help` |
| Command | Description |
| -------------------- | ------------------------------------------- |
| `npm install` | Install or reinstall dependencies |
| `npm run dev` | Start local preview server |
| `npm run build` | Build your static site, generating `./dist` |
| `npm run deploy` | Deploy your app to Observable |
| `npm run clean` | Clear the local data loader cache |
| `npm run observable` | Run commands like `observable help` |
22 changes: 12 additions & 10 deletions observablehq.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { jsonFetcher } from "./src/data/json-fetcher.js";
import { getDateXDaysAgo } from "./src/utils/date-utils.js";
import { jsonFetcher } from './src/data/json-fetcher.js'
import { getDateXDaysAgo } from './src/utils/date-utils.js'

const start = '2024-04-07';
const end = getDateXDaysAgo(1);
const start = '2024-04-07'
const end = getDateXDaysAgo(1)

const result = await jsonFetcher(`https://stats.filspark.com/miners/retrieval-success-rate/summary?from=${start}&to=${end}`)
const providerPaths = result.map(provider => `/provider/${provider.miner_id}`);
const result = await jsonFetcher(
`https://stats.filspark.com/miners/retrieval-success-rate/summary?from=${start}&to=${end}`,
)
const providerPaths = result.map((provider) => `/provider/${provider.miner_id}`)

// See https://observablehq.com/framework/config for documentation.
export default {
// The app’s title; used in the sidebar and webpage titles.
title: "spark-dash",
title: 'spark-dash',

// The pages and sections in the sidebar. If you don’t specify this option,
// all pages will be listed in alphabetical order. Listing pages explicitly
Expand All @@ -29,10 +31,10 @@ export default {
head: '<link rel="icon" href="media/spark-with-bbox.png" type="image/png" sizes="32x32"><script defer data-domain="dashboard.filspark.com" src="https://plausible.io/js/script.js"></script>',

// The path to the source root.
root: "src",
root: 'src',

// Some additional configuration options and their defaults:
theme: "dark", // try "light", "dark", "slate", etc.
theme: 'dark', // try "light", "dark", "slate", etc.
// header: "", // what to show in the header (HTML)
// footer: "Built with Observable.", // what to show in the footer (HTML)
sidebar: false, // whether to show the sidebar
Expand All @@ -44,4 +46,4 @@ export default {
// typographer: false, // smart quotes and other typographic improvements
// cleanUrls: true, // drop .html from URLs
dynamicPaths: providerPaths,
};
}
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"build": "observable build",
"dev": "observable preview",
"deploy": "observable deploy",
"observable": "observable"
"observable": "observable",
"test": "prettier --check ."
},
"dependencies": {
"@observablehq/framework": "^1.12.0",
Expand All @@ -16,6 +17,7 @@
"p-retry": "^6.2.1"
},
"devDependencies": {
"prettier": "^3.4.2",
"rimraf": "^5.0.5"
},
"engines": {
Expand Down
66 changes: 33 additions & 33 deletions src/components/histogram.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,58 @@
import * as Plot from 'npm:@observablehq/plot'
import * as d3 from 'd3'

export function Histogram (events, { width, title, thresholds }) {
const data = events.flatMap(d => {
let res = Array.from([ { type: 'HTTP or Graphsync', value: d.success_rate * 100 }]);
export function Histogram(events, { width, title, thresholds }) {
const data = events.flatMap((d) => {
const res = Array.from([
{ type: 'HTTP or Graphsync', value: d.success_rate * 100 },
])
// We only want to count the http success rate if it is not null
// When querying the summary per miner, an http of null means that the miner has never been tested using http
// A value of 0 means that the miner has been tested at some point in time but has never been successful.
// A value of 0 means that the miner has been tested at some point in time but has never been successful.
if (d.success_rate_http != null) {
res.push({ type: 'HTTP only', value: d.success_rate_http * 100 })
res.push({ type: 'HTTP only', value: d.success_rate_http * 100 })
}
return res
}
)
})

// We want to create a number of evenly spaced bins (thresholds) in which we can collect each success rate value into
const binnedData = Array.from(new Set(data.map(item => item.type))).flatMap(type => {
const groupData = data.filter(d => d.type === type)
const bins = d3
.bin()
// The rates are percentage values, so the domain of the bins will be 0 to 100
.domain([0, 100])
.thresholds(thresholds)(groupData.map(d => d.value))
// We want to create a number of evenly spaced bins (thresholds) in which we can collect each success rate value into
const binnedData = Array.from(new Set(data.map((item) => item.type))).flatMap(
(type) => {
const groupData = data.filter((d) => d.type === type)
const bins = d3
.bin()
// The rates are percentage values, so the domain of the bins will be 0 to 100
.domain([0, 100])
.thresholds(thresholds)(groupData.map((d) => d.value))

return bins.map(bin => ({
type,
threshold: `${bin.x0}–${bin.x1}`,
count: bin.length
}))
})
return bins.map((bin) => ({
type,
threshold: `${bin.x0}–${bin.x1}`,
count: bin.length,
}))
},
)

return Plot.plot({
marks: [
Plot.barY(
binnedData,
{
fx: 'threshold',
y: 'count',
x: 'type',
fill: 'type'
}
)
Plot.barY(binnedData, {
fx: 'threshold',
y: 'count',
x: 'type',
fill: 'type',
}),
],
y: { grid: true },
x: {
axis: null,
padding: 0.1
padding: 0.1,
},
color: {
legend: true,
label: 'Type'
label: 'Type',
},
width,
title,
facet: { label: 'Rate Ranges in %' }
facet: { label: 'Rate Ranges in %' },
})
}
16 changes: 8 additions & 8 deletions src/components/line-graph.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Plot from 'npm:@observablehq/plot'

export function LineGraph (events, { width, height, title, start, end } = {}) {
export function LineGraph(events, { width, height, title, start, end } = {}) {
const startDate = new Date(start)
const endDate = new Date(end)
const filteredEvents = events.filter((event) => {
Expand All @@ -11,13 +11,13 @@ export function LineGraph (events, { width, height, title, start, end } = {}) {
...filteredEvents.map((event) => ({
day: event.day,
success_rate: event.success_rate,
type: 'HTTP or Graphsync'
type: 'HTTP or Graphsync',
})),
...filteredEvents.map((event) => ({
day: event.day,
success_rate_http: event.success_rate_http,
type: 'HTTP only'
}))
type: 'HTTP only',
})),
]

return Plot.plot({
Expand All @@ -32,14 +32,14 @@ export function LineGraph (events, { width, height, title, start, end } = {}) {
x: 'day',
y: 'success_rate',
stroke: 'type',
curve: 'linear'
curve: 'linear',
}),
Plot.lineY(combinedData, {
x: 'day',
y: 'success_rate_http',
stroke: 'type',
curve: 'linear'
})
]
curve: 'linear',
}),
],
})
}
28 changes: 17 additions & 11 deletions src/data/[provider]-spark-retrieval-timings-summary.json.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import pRetry from 'p-retry';
import { jsonFetcher } from "./json-fetcher.js";
import { getDateXDaysAgo } from "../utils/date-utils.js";
import { parseArgs } from "node:util";
import pRetry from 'p-retry'
import { jsonFetcher } from './json-fetcher.js'
import { getDateXDaysAgo } from '../utils/date-utils.js'
import { parseArgs } from 'node:util'

const {
values: { provider }
values: { provider },
} = parseArgs({
options: { provider: { type: "string" } }
});
options: { provider: { type: 'string' } },
})

const start = '2025-01-14';
const end = getDateXDaysAgo(1);
const start = '2025-01-14'
const end = getDateXDaysAgo(1)

const summary = await pRetry(() => jsonFetcher(`https://stats.filspark.com/miner/${provider}/retrieval-timings/summary?from=${start}&to=${end}`), { retries: 3 });
const summary = await pRetry(
() =>
jsonFetcher(
`https://stats.filspark.com/miner/${provider}/retrieval-timings/summary?from=${start}&to=${end}`,
),
{ retries: 3 },
)

process.stdout.write(JSON.stringify(summary));
process.stdout.write(JSON.stringify(summary))
Loading
Loading