mirror of
https://github.com/plausible/analytics.git
synced 2025-03-14 10:06:38 +00:00
Automatically generate Typescript types for v2 API query schema (#4574)
* Generate types from query schema * Flip the query schema so private is static * Ensure private schema stays private * Refactor comment, json schema utils
This commit is contained in:
1
.github/workflows/node.yml
vendored
1
.github/workflows/node.yml
vendored
@ -26,6 +26,7 @@ jobs:
|
||||
node-version: ${{steps.versions.outputs.nodejs}}
|
||||
- run: npm install --prefix ./assets
|
||||
- run: npm install --prefix ./tracker
|
||||
- run: npm run generate-types --prefix ./assets && git diff --exit-code -- ./assets/js/types
|
||||
- run: npm run typecheck --prefix ./assets
|
||||
- run: npm run lint --prefix ./assets
|
||||
- run: npm run check-format --prefix ./assets
|
||||
|
157
assets/js/types/query-api.d.ts
vendored
Normal file
157
assets/js/types/query-api.d.ts
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by json-schema-to-typescript.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||
* and run json-schema-to-typescript to regenerate this file.
|
||||
*/
|
||||
|
||||
export type Metric =
|
||||
| "time_on_page"
|
||||
| "visitors"
|
||||
| "visits"
|
||||
| "pageviews"
|
||||
| "views_per_visit"
|
||||
| "bounce_rate"
|
||||
| "visit_duration"
|
||||
| "events"
|
||||
| "percentage"
|
||||
| "conversion_rate"
|
||||
| "group_conversion_rate";
|
||||
export type DateRangeShorthand = "30m" | "realtime" | "all" | "day" | "7d" | "30d" | "month" | "6mo" | "12mo" | "year";
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
export type DateTimeRange = [string, string];
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
export type DateRange = [string, string];
|
||||
export type Dimensions = SimpleFilterDimensions | CustomPropertyFilterDimensions | GoalDimension | TimeDimensions;
|
||||
export type SimpleFilterDimensions =
|
||||
| "event:name"
|
||||
| "event:page"
|
||||
| "event:hostname"
|
||||
| "visit:source"
|
||||
| "visit:channel"
|
||||
| "visit:referrer"
|
||||
| "visit:utm_medium"
|
||||
| "visit:utm_source"
|
||||
| "visit:utm_campaign"
|
||||
| "visit:utm_content"
|
||||
| "visit:utm_term"
|
||||
| "visit:screen"
|
||||
| "visit:device"
|
||||
| "visit:browser"
|
||||
| "visit:browser_version"
|
||||
| "visit:os"
|
||||
| "visit:os_version"
|
||||
| "visit:country"
|
||||
| "visit:region"
|
||||
| "visit:city"
|
||||
| "visit:country_name"
|
||||
| "visit:region_name"
|
||||
| "visit:city_name"
|
||||
| "visit:entry_page"
|
||||
| "visit:exit_page"
|
||||
| "visit:entry_page_hostname"
|
||||
| "visit:exit_page_hostname";
|
||||
export type CustomPropertyFilterDimensions = string;
|
||||
export type GoalDimension = "event:goal";
|
||||
export type TimeDimensions = "time" | "time:month" | "time:week" | "time:day" | "time:hour";
|
||||
export type FilterTree = FilterEntry | FilterAndOr | FilterNot;
|
||||
export type FilterEntry = FilterWithoutGoals | FilterWithGoals;
|
||||
/**
|
||||
* @minItems 3
|
||||
* @maxItems 3
|
||||
*/
|
||||
export type FilterWithoutGoals = [
|
||||
FilterOperationWithoutGoals | ("matches_wildcard" | "matches_wildcard_not"),
|
||||
SimpleFilterDimensions | CustomPropertyFilterDimensions,
|
||||
Clauses
|
||||
];
|
||||
/**
|
||||
* filter operation
|
||||
*/
|
||||
export type FilterOperationWithoutGoals = "is_not" | "contains_not" | "matches" | "matches_not";
|
||||
export type Clauses = (string | number)[];
|
||||
/**
|
||||
* @minItems 3
|
||||
* @maxItems 3
|
||||
*/
|
||||
export type FilterWithGoals = [
|
||||
FilterOperationWithGoals,
|
||||
GoalDimension | SimpleFilterDimensions | CustomPropertyFilterDimensions,
|
||||
Clauses
|
||||
];
|
||||
/**
|
||||
* filter operation
|
||||
*/
|
||||
export type FilterOperationWithGoals = "is" | "contains";
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
export type FilterAndOr = ["and" | "or", [FilterTree, ...FilterTree[]]];
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
export type FilterNot = ["not", FilterTree];
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
export type OrderByEntry = [
|
||||
Metric | SimpleFilterDimensions | CustomPropertyFilterDimensions | TimeDimensions,
|
||||
"asc" | "desc"
|
||||
];
|
||||
|
||||
export interface QueryApiSchema {
|
||||
/**
|
||||
* Domain of site to query
|
||||
*/
|
||||
site_id: string;
|
||||
/**
|
||||
* List of metrics to query
|
||||
*
|
||||
* @minItems 1
|
||||
*/
|
||||
metrics: [Metric, ...Metric[]];
|
||||
date?: string;
|
||||
/**
|
||||
* Date range to query
|
||||
*/
|
||||
date_range: DateRangeShorthand | DateTimeRange | DateRange;
|
||||
/**
|
||||
* What to group the results by. Same as `property` in Plausible API v1
|
||||
*/
|
||||
dimensions?: Dimensions[];
|
||||
/**
|
||||
* How to drill into your data
|
||||
*/
|
||||
filters?: FilterTree[];
|
||||
/**
|
||||
* How to order query results
|
||||
*/
|
||||
order_by?: OrderByEntry[];
|
||||
include?: {
|
||||
time_labels?: boolean;
|
||||
imports?: boolean;
|
||||
/**
|
||||
* If set, returns the total number of result rows rows before pagination under `meta.total_rows`
|
||||
*/
|
||||
total_rows?: boolean;
|
||||
};
|
||||
pagination?: {
|
||||
/**
|
||||
* Number of rows to limit result to.
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* Pagination offset.
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
}
|
370
assets/package-lock.json
generated
370
assets/package-lock.json
generated
@ -65,6 +65,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"json-schema-to-typescript": "^15.0.2",
|
||||
"prettier": "^3.3.3",
|
||||
"stylelint": "^16.8.1",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
@ -102,6 +103,23 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||
"version": "11.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz",
|
||||
"integrity": "sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jsdevtools/ono": "^7.1.3",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/philsturgeon"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
@ -782,6 +800,96 @@
|
||||
"deprecated": "Use @eslint/object-schema instead",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||
@ -1580,6 +1688,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsdevtools/ono": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
|
||||
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jsonurl/jsonurl": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@jsonurl/jsonurl/-/jsonurl-1.1.7.tgz",
|
||||
@ -1618,6 +1732,16 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.6",
|
||||
"license": "MIT",
|
||||
@ -2355,12 +2479,24 @@
|
||||
"parse5": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
|
||||
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz",
|
||||
@ -4441,6 +4577,12 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
@ -5454,6 +5596,22 @@
|
||||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
@ -6538,6 +6696,21 @@
|
||||
"set-function-name": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
|
||||
@ -8311,6 +8484,73 @@
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-schema-to-typescript": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.2.tgz",
|
||||
"integrity": "sha512-+cRBw+bBJ3k783mZroDIgz1pLNPB4hvj6nnbHTWwEVl0dkW8qdZ+M9jWhBb+Y0FAdHvNsXACga3lewGO8lktrw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "^11.5.5",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"glob": "^10.3.12",
|
||||
"is-glob": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimist": "^1.2.8",
|
||||
"prettier": "^3.2.5"
|
||||
},
|
||||
"bin": {
|
||||
"json2ts": "dist/src/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-to-typescript/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-to-typescript/node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-to-typescript/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@ -8661,9 +8901,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.6",
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
@ -8948,6 +9201,12 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
|
||||
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@ -9018,6 +9277,28 @@
|
||||
"version": "1.0.7",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
@ -10068,6 +10349,27 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/string-width/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@ -10180,6 +10482,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
@ -11218,6 +11533,57 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
|
@ -9,7 +9,8 @@
|
||||
"eslint": "eslint js/**",
|
||||
"stylelint": "stylelint css/**",
|
||||
"lint": "npm run eslint && npm run stylelint",
|
||||
"typecheck": "tsc --noEmit --pretty"
|
||||
"typecheck": "tsc --noEmit --pretty",
|
||||
"generate-types": "json2ts ../priv/json-schemas/query-api-schema.json ../assets/js/types/query-api.d.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.10",
|
||||
@ -68,6 +69,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"json-schema-to-typescript": "^15.0.2",
|
||||
"prettier": "^3.3.3",
|
||||
"stylelint": "^16.8.1",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
|
44
lib/plausible/stats/json-schema/utils.ex
Normal file
44
lib/plausible/stats/json-schema/utils.ex
Normal file
@ -0,0 +1,44 @@
|
||||
defmodule Plausible.Stats.JSONSchema.Utils do
|
||||
@moduledoc """
|
||||
Module for traversing and modifying JSON schemas.
|
||||
"""
|
||||
|
||||
@type json :: map() | list() | String.t() | number() | boolean() | nil
|
||||
@type transform_fun :: (json() -> json() | :remove)
|
||||
|
||||
@spec traverse(map(), transform_fun()) :: map() | :remove
|
||||
def traverse(json, fun) when is_map(json) do
|
||||
result =
|
||||
Enum.reduce(json, %{}, fn {k, v}, acc ->
|
||||
case traverse(v, fun) do
|
||||
:remove -> acc
|
||||
transformed_v -> Map.put(acc, k, transformed_v)
|
||||
end
|
||||
end)
|
||||
|
||||
case result do
|
||||
map when map_size(map) == 0 -> fun.(%{})
|
||||
map -> fun.(map)
|
||||
end
|
||||
end
|
||||
|
||||
@spec traverse(list(), transform_fun()) :: list() | :remove
|
||||
def traverse(json, fun) when is_list(json) do
|
||||
result =
|
||||
Enum.reduce(json, [], fn v, acc ->
|
||||
case traverse(v, fun) do
|
||||
:remove -> acc
|
||||
transformed_v -> [transformed_v | acc]
|
||||
end
|
||||
end)
|
||||
|> Enum.reverse()
|
||||
|
||||
case result do
|
||||
[] -> fun.([])
|
||||
list -> fun.(list)
|
||||
end
|
||||
end
|
||||
|
||||
@spec traverse(String.t() | number() | boolean() | nil, transform_fun()) :: json() | :remove
|
||||
def traverse(value, fun), do: fun.(value)
|
||||
end
|
@ -5,37 +5,20 @@ defmodule Plausible.Stats.JSONSchema do
|
||||
Note that `internal` queries expose some metrics, filter types and other features not
|
||||
available on the public API.
|
||||
"""
|
||||
alias Plausible.Stats.JSONSchema.Utils
|
||||
|
||||
@external_resource "priv/json-schemas/query-api-schema.json"
|
||||
|
||||
@raw_public_schema Application.app_dir(:plausible, "priv/json-schemas/query-api-schema.json")
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|
||||
@raw_internal_schema Application.app_dir(:plausible, "priv/json-schemas/query-api-schema.json")
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
@raw_public_schema Utils.traverse(@raw_internal_schema, fn
|
||||
%{"$comment" => "only :internal"} -> :remove
|
||||
value -> value
|
||||
end)
|
||||
@internal_query_schema ExJsonSchema.Schema.resolve(@raw_internal_schema)
|
||||
@public_query_schema ExJsonSchema.Schema.resolve(@raw_public_schema)
|
||||
|
||||
@internal_query_schema @raw_public_schema
|
||||
# Add overrides for things allowed in the internal API
|
||||
|> JSONPointer.add!(
|
||||
"#/definitions/filter_operation_without_goals/enum/0",
|
||||
"matches_wildcard"
|
||||
)
|
||||
|> JSONPointer.add!(
|
||||
"#/definitions/filter_operation_without_goals/enum/0",
|
||||
"matches_wildcard_not"
|
||||
)
|
||||
|> JSONPointer.add!("#/definitions/metric/oneOf/0", %{
|
||||
"const" => "time_on_page"
|
||||
})
|
||||
|> JSONPointer.add!("#/definitions/date_range/oneOf/0", %{
|
||||
"const" => "30m"
|
||||
})
|
||||
|> JSONPointer.add!("#/definitions/date_range/oneOf/0", %{
|
||||
"const" => "realtime"
|
||||
})
|
||||
|> JSONPointer.add!("#/properties/date", %{"type" => "string"})
|
||||
|> ExJsonSchema.Schema.resolve()
|
||||
|
||||
def validate(schema_type, params) do
|
||||
case ExJsonSchema.Validator.validate(schema(schema_type), params) do
|
||||
:ok -> :ok
|
||||
|
@ -15,8 +15,16 @@
|
||||
"uniqueItems": true,
|
||||
"description": "List of metrics to query"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"$comment": "only :internal"
|
||||
},
|
||||
"date_range": {
|
||||
"$ref": "#/definitions/date_range",
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/date_range_shorthand" },
|
||||
{ "$ref": "#/definitions/date_time_range" },
|
||||
{ "$ref": "#/definitions/date_range" }
|
||||
],
|
||||
"description": "Date range to query"
|
||||
},
|
||||
"dimensions": {
|
||||
@ -41,6 +49,7 @@
|
||||
},
|
||||
"include": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"time_labels": {
|
||||
"type": "boolean",
|
||||
@ -58,6 +67,7 @@
|
||||
},
|
||||
"pagination": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
@ -77,8 +87,52 @@
|
||||
"required": ["site_id", "metrics", "date_range"],
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"date_time_range": {
|
||||
"type": "array",
|
||||
"additionalItems": false,
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"markdownDescription": "A list of two ISO8601 datetimes.",
|
||||
"examples": [["2024-01-01T00:00:00+03:00", "2024-01-02T12:00:00+03:00"]],
|
||||
"items": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
]
|
||||
},
|
||||
"date_range": {
|
||||
"type": "array",
|
||||
"additionalItems": false,
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"markdownDescription": "A list of two ISO8601 dates.",
|
||||
"examples": [["2024-09-01", "2024-09-30"]],
|
||||
"items": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
}
|
||||
]
|
||||
},
|
||||
"date_range_shorthand": {
|
||||
"oneOf": [
|
||||
{
|
||||
"const": "30m",
|
||||
"$comment": "only :internal"
|
||||
},
|
||||
{
|
||||
"const": "realtime",
|
||||
"$comment": "only :internal"
|
||||
},
|
||||
{
|
||||
"const": "all",
|
||||
"description": "Since the start of stats in Plausible"
|
||||
@ -110,37 +164,15 @@
|
||||
{
|
||||
"const": "year",
|
||||
"description": "Since the start of this year"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"additionalItems": false,
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
]
|
||||
},
|
||||
"markdownDescription": "A list of two ISO8601 dates or timestamps to determine the query date range.",
|
||||
"examples": [
|
||||
["2024-01-01", "2024-01-31"],
|
||||
[
|
||||
"2024-01-01T00:00:00+03:00",
|
||||
"2024-01-02T12:00:00+03:00"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"oneOf": [
|
||||
{
|
||||
"const": "time_on_page",
|
||||
"$comment": "only :internal"
|
||||
},
|
||||
{
|
||||
"const": "visitors",
|
||||
"description": "Metric counting the number of unique visitors"
|
||||
@ -243,6 +275,11 @@
|
||||
"type": ["string", "integer"]
|
||||
}
|
||||
},
|
||||
"filter_operation_wildcard": {
|
||||
"type": "string",
|
||||
"enum": ["matches_wildcard", "matches_wildcard_not"],
|
||||
"description": "filter operation"
|
||||
},
|
||||
"filter_operation_without_goals": {
|
||||
"type": "string",
|
||||
"enum": ["is_not", "contains_not", "matches", "matches_not"],
|
||||
@ -259,7 +296,15 @@
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"items": [
|
||||
{ "$ref": "#/definitions/filter_operation_without_goals" },
|
||||
{
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/filter_operation_without_goals" },
|
||||
{
|
||||
"$ref": "#/definitions/filter_operation_wildcard",
|
||||
"$comment": "only :internal"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/simple_filter_dimensions" },
|
||||
|
43
test/plausible/stats/json-schema/utils_test.exs
Normal file
43
test/plausible/stats/json-schema/utils_test.exs
Normal file
@ -0,0 +1,43 @@
|
||||
defmodule Plausible.Stats.JSONSchema.UtilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Plausible.Stats.JSONSchema
|
||||
|
||||
describe "traversing" do
|
||||
test "transform 'fn value -> value end' does not drop anything" do
|
||||
json = %{foo: %{bar: [0, ""], baz: nil, pax: %{}}}
|
||||
assert JSONSchema.Utils.traverse(json, fn value -> value end) == json
|
||||
end
|
||||
|
||||
test "can remove specific items, keeping the original order of lists" do
|
||||
assert JSONSchema.Utils.traverse(
|
||||
%{
|
||||
foo: [
|
||||
"a",
|
||||
%{type: "string", "$comment": "only :internal"},
|
||||
%{type: "number"}
|
||||
]
|
||||
},
|
||||
fn
|
||||
%{"$comment": "only :internal"} -> :remove
|
||||
value -> value
|
||||
end
|
||||
) == %{foo: ["a", %{type: "number"}]}
|
||||
end
|
||||
|
||||
test "can transform specific items" do
|
||||
assert JSONSchema.Utils.traverse(
|
||||
%{
|
||||
foo: [
|
||||
%{type: "string", "$comment": "anything"},
|
||||
%{type: "number"}
|
||||
]
|
||||
},
|
||||
fn
|
||||
%{"$comment": "anything"} -> %{type: "number", "$comment": "transformed"}
|
||||
value -> value
|
||||
end
|
||||
) == %{foo: [%{type: "number", "$comment": "transformed"}, %{type: "number"}]}
|
||||
end
|
||||
end
|
||||
end
|
@ -896,7 +896,7 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
|
||||
}
|
||||
|> check_error(
|
||||
site,
|
||||
"Invalid date_range '[\"2021-02-03T00:00:00Z\", \"2021-02-04\"]'."
|
||||
"#/date_range: Invalid date range [\"2021-02-03T00:00:00Z\", \"2021-02-04\"]"
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
defmodule PlausibleWeb.Api.InternalController.SchemaForDocsTest do
|
||||
use PlausibleWeb.ConnCase, async: true
|
||||
use Plausible.Repo
|
||||
|
||||
describe "GET /api/docs/query/schema.json" do
|
||||
test "returns public schema in json format and it parses", %{conn: conn} do
|
||||
conn = get(conn, "/api/docs/query/schema.json")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
assert %{"$schema" => "http://json-schema.org/draft-07/schema#", "type" => "object"} =
|
||||
response
|
||||
end
|
||||
|
||||
test "public schema does not contain any unexpected nodes", %{conn: conn} do
|
||||
conn = get(conn, "/api/docs/query/schema.json")
|
||||
refute response(conn, 200) =~ ~s/"$comment":"only :internal"/
|
||||
refute response(conn, 200) =~ ~s/"realtime"/
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user