diff --git a/package-lock.json b/package-lock.json index 57b6be4..139901f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "@effect/platform": "^0.57.4", "@effect/platform-node": "^0.51.13", "@effect/schema": "^0.68.1", + "@js-temporal/polyfill": "^0.4.4", "@observablehq/framework": "^1.9.0", "d3-dsv": "^3.0.1", "d3-time-format": "^4.1.0", @@ -793,6 +794,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-temporal/polyfill": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.4.4.tgz", + "integrity": "sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==", + "dependencies": { + "jsbi": "^4.3.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@observablehq/framework": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@observablehq/framework/-/framework-1.9.0.tgz", @@ -2578,6 +2591,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "node_modules/jsdom": { "version": "23.2.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.2.0.tgz", @@ -3474,8 +3492,7 @@ "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tsx": { "version": "4.15.6", diff --git a/package.json b/package.json index 452fb86..41f56a2 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@effect/platform": "^0.57.4", "@effect/platform-node": "^0.51.13", "@effect/schema": "^0.68.1", + "@js-temporal/polyfill": "^0.4.4", "@observablehq/framework": "^1.9.0", "d3-dsv": "^3.0.1", "d3-time-format": "^4.1.0", diff --git a/src/data/requests.json.ts b/src/data/requests.json.ts index 07f025e..3685d8e 100644 --- a/src/data/requests.json.ts +++ b/src/data/requests.json.ts @@ -2,8 +2,9 @@ import { HttpClient, Terminal } from '@effect/platform' import { NodeTerminal } from '@effect/platform-node' import { Schema } from '@effect/schema' import { Effect } from 'effect' +import * as Temporal from '../lib/Temporal.js' -const Requests = Schema.Array(Schema.Struct({})) +const Requests = Schema.Array(Schema.Struct({ timestamp: Temporal.InstantFromStringSchema })) const program = Effect.gen(function* () { const terminal = yield* Terminal.Terminal diff --git a/src/lib/Temporal.ts b/src/lib/Temporal.ts new file mode 100644 index 0000000..fb063f8 --- /dev/null +++ b/src/lib/Temporal.ts @@ -0,0 +1,21 @@ +import { ParseResult, Schema } from '@effect/schema' +import { Temporal } from '@js-temporal/polyfill' + +export const { Instant } = Temporal + +export type Instant = Temporal.Instant + +export const InstantFromSelfSchema = Schema.instanceOf(Temporal.Instant) + +export const InstantFromStringSchema: Schema.Schema = Schema.transformOrFail( + Schema.String, + InstantFromSelfSchema, + { + decode: (date, _, ast) => + ParseResult.try({ + try: () => Instant.from(date), + catch: () => new ParseResult.Type(ast, date), + }), + encode: instant => ParseResult.succeed(instant.toString()), + }, +) diff --git a/src/requests.md b/src/requests.md index 5efa231..cef0662 100644 --- a/src/requests.md +++ b/src/requests.md @@ -7,7 +7,11 @@ toc: false # Review requests 🤞 ```js -const requests = FileAttachment('./data/requests.json').json() +const parseTimestamp = d3.utcParse('%Y-%m-%dT%H:%M:%S.%LZ') + +const requests = FileAttachment('./data/requests.json') + .json() + .then(data => data.map(request => ({ ...request, timestamp: parseTimestamp(request.timestamp) }))) ```
@@ -16,3 +20,29 @@ const requests = FileAttachment('./data/requests.json').json() ${requests.length.toLocaleString("en-US")}
+ +```js +function requestsTimeline({ width } = {}) { + return Plot.plot({ + title: 'Requests per week', + width: Math.max(width, 600), + height: 400, + color: {}, + y: { grid: true, label: 'Requests' }, + x: { label: '' }, + marks: [ + Plot.rectY( + requests, + Plot.binX({ y: 'count' }, { x: 'timestamp', interval: d3.utcWeek, fill: 'var(--theme-foreground-focus)' }), + ), + Plot.ruleY([0]), + ], + }) +} +``` + +
+
+ ${resize((width) => requestsTimeline({width}))} +
+