From fb4c1ee06f69e881015741d06c832d5ac9399969 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Fri, 13 Oct 2023 15:27:49 +0100 Subject: [PATCH 001/134] Bump version number to v0.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94a745536..885e5add2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "playfetch", - "version": "0.11.0", + "version": "0.12.0", "author": "Yello XYZ Ltd", "private": true, "repository": { From c5bcf2e4ae72ac5b527cd8576e92236d7fc01795 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Fri, 13 Oct 2023 15:31:21 +0100 Subject: [PATCH 002/134] Clean up v0.11 data migrations after push to prod --- src/server/datastore/migration.ts | 4 +--- src/server/datastore/providers.ts | 30 ++++++++---------------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/server/datastore/migration.ts b/src/server/datastore/migration.ts index eb6b4f8c1..20624ea73 100644 --- a/src/server/datastore/migration.ts +++ b/src/server/datastore/migration.ts @@ -15,6 +15,4 @@ import { migrateUsers } from './users' import { migrateVersions } from './versions' import { migrateWorkspaces } from './workspaces' -export async function runDataMigrations(postMerge: boolean) { - await migrateProviders(postMerge) -} +export async function runDataMigrations(postMerge: boolean) {} diff --git a/src/server/datastore/providers.ts b/src/server/datastore/providers.ts index 85d4ff97f..d34fddfbb 100644 --- a/src/server/datastore/providers.ts +++ b/src/server/datastore/providers.ts @@ -33,30 +33,20 @@ type ProviderMetadata = { } export async function migrateProviders(postMerge: boolean) { + if (postMerge) { + return + } const datastore = getDatastore() const [allProviders] = await datastore.runQuery(datastore.createQuery(Entity.PROVIDER)) for (const providerData of allProviders) { - const metadata: ProviderMetadata = postMerge ? JSON.parse(providerData.metadata) : {} - if (!postMerge) { - const customModels = JSON.parse(providerData.customModels) as CustomModel[] - if (customModels.length > 0) { - metadata.customModels = customModels - } - const environment = providerData.environment as string | null - if (environment !== null) { - metadata.environment = environment - } - } await getDatastore().save( toProviderData( providerData.userID, providerData.provider, providerData.apiKey, - metadata, + JSON.parse(providerData.metadata), providerData.cost, - getID(providerData), - postMerge ? undefined : providerData.environment, - postMerge ? undefined : JSON.parse(providerData.customModels) + getID(providerData) ) ) } @@ -99,9 +89,7 @@ const toProviderData = ( apiKey: string | null, metadata: ProviderMetadata, cost: number, - providerID?: number, - environment?: string | null, - customModels?: CustomModel[] + providerID?: number ) => ({ key: buildKey(Entity.PROVIDER, providerID), data: { @@ -109,11 +97,9 @@ const toProviderData = ( provider, apiKey, metadata: JSON.stringify(metadata), - cost, - environment, - customModels: customModels ? JSON.stringify(customModels) : customModels, + cost }, - excludeFromIndexes: ['apiKey', 'metadata', 'environment', 'customModels'], // TODO remove environment/customModels after push + excludeFromIndexes: ['apiKey', 'metadata'], }) const toAvailableProvider = (data: any): AvailableProvider => { From e340e8fb06dac5cde51d7d1c389fa70e28b43d78 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Fri, 13 Oct 2023 15:33:09 +0100 Subject: [PATCH 003/134] Run npm update --- package-lock.json | 209 +++++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90dd0c1e8..cd722c6f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "playfetch", - "version": "0.11.0", + "version": "0.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "playfetch", - "version": "0.11.0", + "version": "0.12.0", "dependencies": { "@anthropic-ai/sdk": "^0.5.3", "@google-cloud/aiplatform": "^2.14.0", @@ -114,9 +114,9 @@ } }, "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { - "version": "18.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", - "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==" + "version": "18.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.5.tgz", + "integrity": "sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==" }, "node_modules/@babel/code-frame": { "version": "7.22.13", @@ -195,18 +195,18 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", - "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -214,10 +214,10 @@ "@babel/generator": "^7.23.0", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.0", + "@babel/helpers": "^7.23.2", "@babel/parser": "^7.23.0", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", + "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -420,13 +420,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", - "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", + "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0" }, "engines": { @@ -699,9 +699,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", - "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -724,9 +724,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", - "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", @@ -1019,9 +1019,9 @@ } }, "node_modules/@google-cloud/storage": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.2.0.tgz", - "integrity": "sha512-g3j/LoGPydCo5MUfWS1GupBjjPmesKWIo6dH8kUWiVMBmvSG4dpwXVL1NvG1DZTkJSiyjvqRWi9lHJxRfxWbsw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.3.0.tgz", + "integrity": "sha512-el97xKaVLEpSyXcPE9ZuEiBjNeX20wY3KJ2E9oebYNSMWa968PRD2GjePl0qGJIKqIbMobSIwpNzUS0fSQ38pw==", "dependencies": { "@google-cloud/paginator": "^5.0.0", "@google-cloud/projectify": "^4.0.0", @@ -1876,9 +1876,9 @@ } }, "node_modules/@pinecone-database/pinecone": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pinecone-database/pinecone/-/pinecone-1.1.0.tgz", - "integrity": "sha512-THg+D3cSYVCMmphroOEBQOU9UsOhABYcrExZyurcz8cZ3znipDyJuiX9F3CavysnQa5DTzQEZxcH1YmEMGW8mg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@pinecone-database/pinecone/-/pinecone-1.1.1.tgz", + "integrity": "sha512-8af+2fAAkkHC1BIe+op8Tk4YLvJYK1qM2ITuwVmFdNVcHG6oHdXlZdR8GGdOLfYSVuLf4aZid7xhjYUSBqux8Q==", "dependencies": { "@edge-runtime/types": "^2.2.3", "@sinclair/typebox": "^0.29.0", @@ -1893,9 +1893,9 @@ } }, "node_modules/@pinecone-database/pinecone/node_modules/@types/node": { - "version": "18.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", - "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==" + "version": "18.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.5.tgz", + "integrity": "sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==" }, "node_modules/@pinecone-database/pinecone/node_modules/typescript": { "version": "4.9.5", @@ -2251,9 +2251,9 @@ } }, "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.3.tgz", + "integrity": "sha512-Wny3a2UXn5FEA1l7gc6BbpoV5mD1XijZqgkp4TRgDCDL5r3B5ieOFGUX5h3n78Tr1MEG7BfvoM8qeztdvNU0fw==", "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -2449,9 +2449,9 @@ } }, "node_modules/@types/sanitize-html": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.9.1.tgz", - "integrity": "sha512-XSLD0a9P8c+rKUM09KIi5Nd8mOHLHNgXb1G04rpXWa/GqQVpM+knrS9KR9ptj1CeC3gXWGZn75ApH3H6qNbhYA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.9.2.tgz", + "integrity": "sha512-7TAQFoXdwjSvebOl0oKh5QXGrI+uyTc8Here+WcR9vpLEE7wxpoK6Vuvw++dsmL+Yw8K91x76tLoWchD5pqpRg==", "dev": true, "dependencies": { "htmlparser2": "^8.0.0" @@ -2484,9 +2484,9 @@ "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", + "version": "17.0.28", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz", + "integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -3350,9 +3350,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001546", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", - "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", + "version": "1.0.30001547", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", + "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", "funding": [ { "type": "opencollective", @@ -3610,14 +3610,6 @@ "node": ">=10" } }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -3975,9 +3967,9 @@ } }, "node_modules/define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", "dependencies": { "get-intrinsic": "^1.2.1", "gopd": "^1.0.1", @@ -4191,9 +4183,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.543", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.543.tgz", - "integrity": "sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g==" + "version": "1.4.553", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.553.tgz", + "integrity": "sha512-HiRdtyKS2+VhiXvjhMvvxiMC33FJJqTA5EB2YHgFZW6v7HkK4Q9Ahv2V7O2ZPgAjw+MyCJVMQvigj13H8t+wvA==" }, "node_modules/emittery": { "version": "0.13.1", @@ -4726,11 +4718,11 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5085,11 +5077,11 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dependencies": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" }, @@ -5141,9 +5133,9 @@ } }, "node_modules/fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "engines": { "node": "*" }, @@ -5176,9 +5168,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.6", @@ -7482,9 +7477,9 @@ } }, "node_modules/jose": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.2.tgz", - "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.3.tgz", + "integrity": "sha512-RZJdL9Qjd1sqNdyiVteRGV/bnWtik/+PJh1JP4kT6+x1QQMn+7ryueRys5BEueuayvSVY8CWGCisCDazeRLTuw==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -7705,9 +7700,9 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dependencies": { "json-buffer": "3.0.1" } @@ -8186,9 +8181,9 @@ } }, "node_modules/node-abi": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", - "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.50.0.tgz", + "integrity": "sha512-2Gxu7Eq7vnBIRfYSmqPruEllMM14FjOQFJSoqdGWthVn+tmwEXzmdPpya6cvvwf0uZA3F5N1fMFr9mijZBplFA==", "dependencies": { "semver": "^7.3.5" }, @@ -8272,9 +8267,9 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, "node_modules/nodemailer": { - "version": "6.9.5", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.5.tgz", - "integrity": "sha512-/dmdWo62XjumuLc5+AYQZeiRj+PRR8y8qKtFCOyuOl1k/hckZd8durUUHs/ucKx6/8kN+wFxqKJlQ/LK/qR5FA==", + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.6.tgz", + "integrity": "sha512-s7pDtWwe5fLMkQUhw8TkWB/wnZ7SRdd9HRZslq/s24hlZvBP3j32N/ETLmnqTpmj4xoBZL9fOWyCIZ7r2HORHg==", "engines": { "node": ">=6.0.0" } @@ -8488,9 +8483,9 @@ } }, "node_modules/openai": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.11.1.tgz", - "integrity": "sha512-GU0HQWbejXuVAQlDjxIE8pohqnjptFDIm32aPlNT1H9ucMz1VJJD0DaTJRQsagNaJ97awWjjVLEG7zCM6sm4SA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.12.1.tgz", + "integrity": "sha512-EAoUwm4dtiWvFwBhOCK/VfF8sj1ZU8+aAIJnfT4NyeTfrt1DM/6Gdd6fOZWTjBYryTAqu9Vpb5+9Wu6JMtm/gA==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -8506,14 +8501,14 @@ } }, "node_modules/openai/node_modules/@types/node": { - "version": "18.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", - "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==" + "version": "18.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.5.tgz", + "integrity": "sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==" }, "node_modules/openid-client": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.0.tgz", - "integrity": "sha512-uFTkN/iqgKvSnmpVAS/T6SNThukRMBcmymTQ71Ngus1F60tdtKVap7zCrleocY+fogPtpmoxi5Q1YdrgYuTlkA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", + "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", "dependencies": { "jose": "^4.15.1", "lru-cache": "^6.0.0", @@ -8852,6 +8847,14 @@ } } }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-nested": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", @@ -9405,9 +9408,9 @@ } }, "node_modules/react-smooth": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.4.tgz", - "integrity": "sha512-OkFsrrMBTvQUwEJthE1KXSOj79z57yvEWeFefeXPib+RmQEI9B1Ub1PgzlzzUyBOvl/TjXt5nF2hmD4NsgAh8A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.5.tgz", + "integrity": "sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==", "dependencies": { "fast-equals": "^5.0.0", "react-transition-group": "2.9.0" @@ -9605,9 +9608,9 @@ } }, "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -11139,11 +11142,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/yargs": { From 65c62c235c0d76f729a344fcc034fdc3ce1da5e9 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 09:53:39 +0100 Subject: [PATCH 004/134] Rename Feedback button to Support --- components/sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sidebar.tsx b/components/sidebar.tsx index eda709908..7a6ed35c3 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -16,7 +16,7 @@ export default function Sidebar({ children }: { children: ReactNode }) { export function FeedbackSection() { return ( - + ) } From 8b695c56ce169bee5d8406b402317520bb3a8b76 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 09:56:53 +0100 Subject: [PATCH 005/134] Add sidebar in admin panel --- components/admin/adminSidebar.tsx | 16 +++++++ pages/admin/index.tsx | 70 +++++++++++++++++-------------- 2 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 components/admin/adminSidebar.tsx diff --git a/components/admin/adminSidebar.tsx b/components/admin/adminSidebar.tsx new file mode 100644 index 000000000..bee9d3dd7 --- /dev/null +++ b/components/admin/adminSidebar.tsx @@ -0,0 +1,16 @@ +import fileIcon from '@/public/file.svg' +import UserSidebarItem from '../userSidebarItem' +import { useLoggedInUser } from '@/src/client/context/userContext' +import { FeedbackSection, SidebarButton, SidebarSection } from '../sidebar' + +export default function AdminSidebar({}: {}) { + return ( + <> +
+ + + +
+ + ) +} diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index e145563b2..281dc5fc6 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -14,6 +14,7 @@ import { AdminRoute } from '@/src/client/clientRoute' import Link from 'next/link' import Icon from '@/components/icon' import chainIcon from '@/public/chainSmall.svg' +import AdminSidebar from '@/components/admin/adminSidebar' export const getServerSideProps = withAdminSession(async () => { const initialWaitlistUsers = await getUsersWithoutAccess() @@ -51,44 +52,49 @@ export default function Admin({ initialWaitlistUsers }: { initialWaitlistUsers: return ( <> -
+
Admin -
- Analytics Dashboard - Analytics Reports - Search Console - Server Logs - {addedEmail && } -
- - - -
- {waitlistUsers.length > 0 && ( - <> - -
- {waitlistUsers.map(user => ( - - -
{user.email}
-
{user.fullName}
-
- grantWaitlistUserAccess(user)} - disabled={adding} - /> -
-
- ))} +
+ +
+
+ Analytics Dashboard + Analytics Reports + Search Console + Server Logs + {addedEmail && } +
+ + +
- - )} + {waitlistUsers.length > 0 && ( + <> + +
+ {waitlistUsers.map(user => ( + + +
{user.email}
+
{user.fullName}
+
+ grantWaitlistUserAccess(user)} + disabled={adding} + /> +
+
+ ))} +
+ + )} +
+
setDialogPrompt(undefined)} /> From 475cfe654d0061568c9ee27fe7bb3661badc0651 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 10:03:21 +0100 Subject: [PATCH 006/134] Factor out waitlist component --- components/admin/waitlist.tsx | 75 +++++++++++++++++++++++++++++++++ pages/admin/index.tsx | 78 ++++------------------------------- 2 files changed, 84 insertions(+), 69 deletions(-) create mode 100644 components/admin/waitlist.tsx diff --git a/components/admin/waitlist.tsx b/components/admin/waitlist.tsx new file mode 100644 index 000000000..7d6846e28 --- /dev/null +++ b/components/admin/waitlist.tsx @@ -0,0 +1,75 @@ +import TextInput from '@/components/textInput' +import api from '@/src/client/admin/api' +import { Fragment, useState } from 'react' +import { PendingButton } from '@/components/button' +import { CheckValidEmail } from '@/src/common/formatting' +import { User } from '@/types' +import Label from '@/components/label' +import ModalDialog, { DialogPrompt } from '@/components/modalDialog' +import { UserAvatar } from '@/components/userSidebarItem' + +export default function Waitlist({ initialWaitlistUsers }: { initialWaitlistUsers: User[] }) { + const [waitlistUsers, setWaitlistUsers] = useState(initialWaitlistUsers) + const [fullName, setFullName] = useState('') + const [email, setEmail] = useState('') + const [addedEmail, setAddedEmail] = useState('') + const [dialogPrompt, setDialogPrompt] = useState() + const [adding, setAdding] = useState(false) + + const promptAddUser = (email: string, fullName: string, callback?: () => Promise) => + setDialogPrompt({ + title: + 'Grant user access? They will NOT be notified by email automatically ' + + 'and they will NOT automatically be added as a test user for Google Authentication.', + confirmTitle: 'Proceed', + callback: async () => { + setAdding(true) + await api.addUser(email.trim(), fullName.trim()) + setAddedEmail(email) + await callback?.() + setAdding(false) + }, + }) + + const addUser = () => promptAddUser(email, fullName) + + const grantWaitlistUserAccess = (user: User) => + promptAddUser(user.email, user.fullName, () => api.getWaitlistUsers().then(setWaitlistUsers)) + + const gridConfig = 'grid grid-cols-[28px_240px_minmax(0,1fr)_160px]' + + return ( + <> +
+ {addedEmail && } +
+ + + +
+ {waitlistUsers.length > 0 && ( + <> + +
+ {waitlistUsers.map(user => ( + + +
{user.email}
+
{user.fullName}
+
+ grantWaitlistUserAccess(user)} + disabled={adding} + /> +
+
+ ))} +
+ + )} +
+ setDialogPrompt(undefined)} /> + + ) +} diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 281dc5fc6..49f66eb07 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,20 +1,14 @@ import { withAdminSession } from '@/src/server/session' -import TextInput from '@/components/textInput' -import api from '@/src/client/admin/api' -import { Fragment, ReactNode, useState } from 'react' -import { PendingButton } from '@/components/button' -import { CheckValidEmail } from '@/src/common/formatting' +import { ReactNode } from 'react' import { User } from '@/types' import { getUsersWithoutAccess } from '@/src/server/datastore/users' -import Label from '@/components/label' -import ModalDialog, { DialogPrompt } from '@/components/modalDialog' -import { UserAvatar } from '@/components/userSidebarItem' import TopBar, { TopBarAccessoryItem, TopBarBackItem } from '@/components/topBar' import { AdminRoute } from '@/src/client/clientRoute' import Link from 'next/link' import Icon from '@/components/icon' import chainIcon from '@/public/chainSmall.svg' import AdminSidebar from '@/components/admin/adminSidebar' +import Waitlist from '@/components/admin/waitlist' export const getServerSideProps = withAdminSession(async () => { const initialWaitlistUsers = await getUsersWithoutAccess() @@ -23,33 +17,6 @@ export const getServerSideProps = withAdminSession(async () => { }) export default function Admin({ initialWaitlistUsers }: { initialWaitlistUsers: User[] }) { - const [waitlistUsers, setWaitlistUsers] = useState(initialWaitlistUsers) - const [fullName, setFullName] = useState('') - const [email, setEmail] = useState('') - const [addedEmail, setAddedEmail] = useState('') - const [dialogPrompt, setDialogPrompt] = useState() - const [adding, setAdding] = useState(false) - - const promptAddUser = (email: string, fullName: string, callback?: () => Promise) => - setDialogPrompt({ - title: - 'Grant user access? They will NOT be notified by email automatically ' + - 'and they will NOT automatically be added as a test user for Google Authentication.', - confirmTitle: 'Proceed', - callback: async () => { - setAdding(true) - await api.addUser(email.trim(), fullName.trim()) - setAddedEmail(email) - await callback?.() - setAdding(false) - }, - }) - - const addUser = () => promptAddUser(email, fullName) - - const grantWaitlistUserAccess = (user: User) => - promptAddUser(user.email, user.fullName, () => api.getWaitlistUsers().then(setWaitlistUsers)) - return ( <>
@@ -60,44 +27,17 @@ export default function Admin({ initialWaitlistUsers }: { initialWaitlistUsers:
-
-
- Analytics Dashboard - Analytics Reports - Search Console - Server Logs - {addedEmail && } -
- - - -
- {waitlistUsers.length > 0 && ( - <> - -
- {waitlistUsers.map(user => ( - - -
{user.email}
-
{user.fullName}
-
- grantWaitlistUserAccess(user)} - disabled={adding} - /> -
-
- ))} -
- - )} +
+
+ Analytics Dashboard + Analytics Reports + Search Console + Server Logs
+
- setDialogPrompt(undefined)} /> ) } From 9671bf9a0c3f7f0aaedfc82c8dbcaf24a7215641 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 10:08:44 +0100 Subject: [PATCH 007/134] Move admin links to sidebar --- components/admin/adminSidebar.tsx | 13 +++++++++---- pages/admin/index.tsx | 18 ------------------ 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/components/admin/adminSidebar.tsx b/components/admin/adminSidebar.tsx index bee9d3dd7..e3dcfc956 100644 --- a/components/admin/adminSidebar.tsx +++ b/components/admin/adminSidebar.tsx @@ -1,13 +1,18 @@ -import fileIcon from '@/public/file.svg' -import UserSidebarItem from '../userSidebarItem' -import { useLoggedInUser } from '@/src/client/context/userContext' +import { AdminRoute } from '@/src/client/clientRoute' import { FeedbackSection, SidebarButton, SidebarSection } from '../sidebar' +import linkIcon from '@/public/chainSmall.svg' export default function AdminSidebar({}: {}) { return ( <>
- + + + + + + +
diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 49f66eb07..41a347b74 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,12 +1,7 @@ import { withAdminSession } from '@/src/server/session' -import { ReactNode } from 'react' import { User } from '@/types' import { getUsersWithoutAccess } from '@/src/server/datastore/users' import TopBar, { TopBarAccessoryItem, TopBarBackItem } from '@/components/topBar' -import { AdminRoute } from '@/src/client/clientRoute' -import Link from 'next/link' -import Icon from '@/components/icon' -import chainIcon from '@/public/chainSmall.svg' import AdminSidebar from '@/components/admin/adminSidebar' import Waitlist from '@/components/admin/waitlist' @@ -28,12 +23,6 @@ export default function Admin({ initialWaitlistUsers }: { initialWaitlistUsers:
-
- Analytics Dashboard - Analytics Reports - Search Console - Server Logs -
@@ -41,10 +30,3 @@ export default function Admin({ initialWaitlistUsers }: { initialWaitlistUsers: ) } - -const ExternalLink = ({ href, children }: { href: string; children: ReactNode }) => ( - - - {children} - -) From eb251b19082a66da09958b0dfea2979bd4901c71 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 10:10:51 +0100 Subject: [PATCH 008/134] Allow to specify target for sidebar links --- components/admin/adminSidebar.tsx | 13 +++++++++---- components/sidebar.tsx | 24 ++++++++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/components/admin/adminSidebar.tsx b/components/admin/adminSidebar.tsx index e3dcfc956..6bde9ff3d 100644 --- a/components/admin/adminSidebar.tsx +++ b/components/admin/adminSidebar.tsx @@ -7,12 +7,17 @@ export default function AdminSidebar({}: {}) { <>
- - - + + + - +
diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 7a6ed35c3..fdedb04ab 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from 'react' +import { HTMLAttributeAnchorTarget, ReactNode } from 'react' import Icon from './icon' import { StaticImageData } from 'next/image' import Link from 'next/link' @@ -51,6 +51,7 @@ export function SidebarButton({ active = false, onClick, link, + target, actionComponent, }: { title: string @@ -58,6 +59,7 @@ export function SidebarButton({ active?: boolean onClick?: () => void link?: string + target?: HTMLAttributeAnchorTarget actionComponent?: ReactNode }) { const activeClass = 'bg-blue-50 ' @@ -65,7 +67,7 @@ export function SidebarButton({ const baseClass = 'flex gap-1 items-center pl-3 p-1 cursor-pointer select-none rounded-lg group w-[220px]' const className = `${active ? activeClass : baseHoverClass} ${baseClass}` return ( - +
{icon && }
@@ -77,6 +79,20 @@ export function SidebarButton({ ) } -function LinkWrapper({ link, children }: { link?: string; children: ReactNode }) { - return link ? {children} : <>{children} +function LinkWrapper({ + link, + target, + children, +}: { + link?: string + target?: HTMLAttributeAnchorTarget + children: ReactNode +}) { + return link ? ( + + {children} + + ) : ( + <>{children} + ) } From 1cd0affccc405978fef2bff1677547d1ad51e035 Mon Sep 17 00:00:00 2001 From: Stef Smet Date: Mon, 16 Oct 2023 10:17:52 +0100 Subject: [PATCH 009/134] Make stroke thinner --- components/endpoints/analyticsDashboards.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/endpoints/analyticsDashboards.tsx b/components/endpoints/analyticsDashboards.tsx index b99851c11..d3379fec6 100644 --- a/components/endpoints/analyticsDashboards.tsx +++ b/components/endpoints/analyticsDashboards.tsx @@ -74,8 +74,8 @@ export default function AnalyticsDashboards({ range={dayRange} callback={toggleDayRange}> - - + + } /> @@ -88,7 +88,7 @@ export default function AnalyticsDashboards({ range={dayRange} callback={toggleDayRange}> - + } /> From 90f6991fd82cafef56f6ca06e47eebda09163f62 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 10:43:37 +0100 Subject: [PATCH 010/134] Allow to toggle between waitlist and active users Keeping datastore query and content the same for now. --- components/admin/activeUsers.tsx | 29 +++++++++++++++++++ components/admin/adminSidebar.tsx | 15 ++++++++-- pages/admin/index.tsx | 47 +++++++++++++++++++++++++++---- pages/api/admin/getActiveUsers.ts | 11 ++++++++ 4 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 components/admin/activeUsers.tsx create mode 100644 pages/api/admin/getActiveUsers.ts diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx new file mode 100644 index 000000000..f7cb59aad --- /dev/null +++ b/components/admin/activeUsers.tsx @@ -0,0 +1,29 @@ +import { Fragment } from 'react' +import { User } from '@/types' +import Label from '@/components/label' +import { UserAvatar } from '@/components/userSidebarItem' + +export default function ActiveUsers({ activeUsers }: { activeUsers: User[] }) { + const gridConfig = 'grid grid-cols-[28px_240px_minmax(0,1fr)]' + + return ( + <> +
+ {activeUsers.length > 0 && ( + <> + +
+ {activeUsers.map(user => ( + + +
{user.email}
+
{user.fullName}
+
+ ))} +
+ + )} +
+ + ) +} diff --git a/components/admin/adminSidebar.tsx b/components/admin/adminSidebar.tsx index 6bde9ff3d..d12e66cfa 100644 --- a/components/admin/adminSidebar.tsx +++ b/components/admin/adminSidebar.tsx @@ -1,12 +1,23 @@ import { AdminRoute } from '@/src/client/clientRoute' import { FeedbackSection, SidebarButton, SidebarSection } from '../sidebar' -import linkIcon from '@/public/chainSmall.svg' +import linkIcon from '@/public/chain.svg' +import userIcon from '@/public/user.svg' -export default function AdminSidebar({}: {}) { +export default function AdminSidebar({ + onSelectWaitlist, + onSelectActiveUsers, +}: { + onSelectWaitlist: () => void + onSelectActiveUsers: () => void +}) { return ( <>
+ + + + { + const { w: waitlist } = ParseNumberQuery(query) + + const initialActiveItem = waitlist ? 'waitlist' : 'activeUsers' -export const getServerSideProps = withAdminSession(async () => { const initialWaitlistUsers = await getUsersWithoutAccess() - return { props: { initialWaitlistUsers } } + return { props: { initialActiveItem, initialWaitlistUsers } } }) -export default function Admin({ initialWaitlistUsers }: { initialWaitlistUsers: User[] }) { +export default function Admin({ + initialActiveItem, + initialWaitlistUsers, +}: { + initialActiveItem: ActiveItem + initialWaitlistUsers: User[] +}) { + const [activeItem, setActiveItem] = useState(initialActiveItem) + + const router = useRouter() + + const { w: waitlist } = ParseNumberQuery(router.query) + const currentQueryState = waitlist + const [query, setQuery] = useState(currentQueryState) + if (currentQueryState !== query) { + setActiveItem(waitlist ? 'waitlist' : 'activeUsers') + setQuery(currentQueryState) + } + + const selectItem = (item: ActiveItem) => { + setActiveItem(item) + router.push(`/admin${item === 'waitlist' ? '?w=1' : ''}`, undefined, { shallow: true }) + } + return ( <>
@@ -21,9 +54,13 @@ export default function Admin({ initialWaitlistUsers }: { initialWaitlistUsers:
- + selectItem('waitlist')} + onSelectActiveUsers={() => selectItem('activeUsers')} + />
- + {activeItem === 'activeUsers' && } + {activeItem === 'waitlist' && }
diff --git a/pages/api/admin/getActiveUsers.ts b/pages/api/admin/getActiveUsers.ts new file mode 100644 index 000000000..362068326 --- /dev/null +++ b/pages/api/admin/getActiveUsers.ts @@ -0,0 +1,11 @@ +import { getUsersWithoutAccess } from '@/src/server/datastore/users' +import { withAdminUserRoute } from '@/src/server/session' +import { User } from '@/types' +import type { NextApiRequest, NextApiResponse } from 'next' + +async function getActiveUsers(_: NextApiRequest, res: NextApiResponse) { + const activeUsers = await getUsersWithoutAccess() + res.json(activeUsers) +} + +export default withAdminUserRoute(getActiveUsers) From 073295e53f7a140339c1f0ed040faa480703f66b Mon Sep 17 00:00:00 2001 From: Stef Smet Date: Mon, 16 Oct 2023 11:07:50 +0100 Subject: [PATCH 011/134] adding dot pattern --- components/chains/chainEditor.tsx | 2 +- public/dotPattern.png | Bin 0 -> 285 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 public/dotPattern.png diff --git a/components/chains/chainEditor.tsx b/components/chains/chainEditor.tsx index 6f72ec695..2a8c8a0d0 100644 --- a/components/chains/chainEditor.tsx +++ b/components/chains/chainEditor.tsx @@ -68,7 +68,7 @@ export default function ChainEditor({ showVersions={showVersions} setShowVersions={setShowVersions} /> -
+
Start
{nodes.map((_, index, nodes) => ( Z&H|6fVg?393lL^>oo1K-6l5$8 za(7}_cTVOdki(Mh==1_Z0b?DemT;nIj~3debe&(z1Y-m5M&RiCG-M&NHMAT@hH;W?%{x^4r#& zsWr{J_I Date: Mon, 16 Oct 2023 11:08:08 +0100 Subject: [PATCH 012/134] npm run format --- components/chains/queryChainNodeEditor.tsx | 7 +------ components/prompts/promptPanel.tsx | 3 +-- pages/api/public/endpoint.ts | 2 +- src/client/hooks/useModifiedVersion.tsx | 1 - src/common/providerMetadata.tsx | 4 +--- src/server/datastore/providers.ts | 2 +- 6 files changed, 5 insertions(+), 14 deletions(-) diff --git a/components/chains/queryChainNodeEditor.tsx b/components/chains/queryChainNodeEditor.tsx index cef686de3..90f7d60bb 100644 --- a/components/chains/queryChainNodeEditor.tsx +++ b/components/chains/queryChainNodeEditor.tsx @@ -5,12 +5,7 @@ import { EmbeddingModel, QueryChainItem, QueryProvider } from '@/types' import TextInput from '../textInput' import Label from '../label' import DropdownMenu from '../dropdownMenu' -import { - EmbeddingModels, - QueryProviders, - LabelForProvider, - ProviderForModel, -} from '@/src/common/providerMetadata' +import { EmbeddingModels, QueryProviders, LabelForProvider, ProviderForModel } from '@/src/common/providerMetadata' import { useCheckProviderAvailable } from '@/src/client/hooks/useAvailableProviders' import { ProviderWarning } from '../prompts/promptPanel' import RangeInput from '../rangeInput' diff --git a/components/prompts/promptPanel.tsx b/components/prompts/promptPanel.tsx index 5dac19e27..3ff176417 100644 --- a/components/prompts/promptPanel.tsx +++ b/components/prompts/promptPanel.tsx @@ -238,8 +238,7 @@ export function ProviderWarning({ buttonTitle='Add API Key' onClick={() => router.push(ClientRoute.Settings)}> - An API key is required to use this{' '} - {(ModelProviders as string[]).includes(provider) ? 'model' : 'vector store'}. + An API key is required to use this {(ModelProviders as string[]).includes(provider) ? 'model' : 'vector store'}. ) diff --git a/pages/api/public/endpoint.ts b/pages/api/public/endpoint.ts index 6626414e4..f3f0dc9dd 100644 --- a/pages/api/public/endpoint.ts +++ b/pages/api/public/endpoint.ts @@ -95,7 +95,7 @@ async function endpoint(req: NextApiRequest, res: NextApiResponse) { res.setHeader('Content-Type', 'application/json') } - const salt = (value: number | bigint) => (BigInt(value) ^ BigInt(endpoint.id)) + const salt = (value: number | bigint) => BigInt(value) ^ BigInt(endpoint.id) const continuationID = continuation ? Number(salt(BigInt(continuation))) : undefined const versionID = endpoint.versionID const inputs = typeof req.body === 'string' ? {} : (req.body as PromptInputs) diff --git a/src/client/hooks/useModifiedVersion.tsx b/src/client/hooks/useModifiedVersion.tsx index 65687a924..6528c80e5 100644 --- a/src/client/hooks/useModifiedVersion.tsx +++ b/src/client/hooks/useModifiedVersion.tsx @@ -16,6 +16,5 @@ export default function useModifiedVersion { switch (provider) { diff --git a/src/server/datastore/providers.ts b/src/server/datastore/providers.ts index d34fddfbb..194dab34b 100644 --- a/src/server/datastore/providers.ts +++ b/src/server/datastore/providers.ts @@ -97,7 +97,7 @@ const toProviderData = ( provider, apiKey, metadata: JSON.stringify(metadata), - cost + cost, }, excludeFromIndexes: ['apiKey', 'metadata'], }) From 5610652ea304400d509e177c44d39f5a74f29232 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 11:16:25 +0100 Subject: [PATCH 013/134] Implement active users query --- components/admin/activeUsers.tsx | 11 ++++++---- pages/admin/index.tsx | 19 +++++++++------- pages/api/admin/getActiveUsers.ts | 11 ---------- src/server/datastore/users.ts | 36 +++++++++++++++++++++++++++++-- src/server/datastore/versions.ts | 8 +++++++ types/index.ts | 13 +++++++++-- 6 files changed, 71 insertions(+), 27 deletions(-) delete mode 100644 pages/api/admin/getActiveUsers.ts diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index f7cb59aad..8270e6b4e 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -1,17 +1,20 @@ import { Fragment } from 'react' -import { User } from '@/types' +import { ActiveUser } from '@/types' import Label from '@/components/label' import { UserAvatar } from '@/components/userSidebarItem' +import { FormatDate } from '@/src/common/formatting' -export default function ActiveUsers({ activeUsers }: { activeUsers: User[] }) { - const gridConfig = 'grid grid-cols-[28px_240px_minmax(0,1fr)]' +export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] }) { + const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_240px]' + + const startDate = Math.min(...activeUsers.map(user => user.startTimestamp)) return ( <>
{activeUsers.length > 0 && ( <> - +
{activeUsers.map(user => ( diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 3e8cb9d3c..06aa9b150 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,6 +1,6 @@ import { withAdminSession } from '@/src/server/session' -import { User } from '@/types' -import { getUsersWithoutAccess } from '@/src/server/datastore/users' +import { ActiveUser, User } from '@/types' +import { getActiveUsers, getUsersWithoutAccess } from '@/src/server/datastore/users' import TopBar, { TopBarAccessoryItem, TopBarBackItem } from '@/components/topBar' import AdminSidebar from '@/components/admin/adminSidebar' import Waitlist from '@/components/admin/waitlist' @@ -16,17 +16,20 @@ export const getServerSideProps = withAdminSession(async ({ query }) => { const initialActiveItem = waitlist ? 'waitlist' : 'activeUsers' - const initialWaitlistUsers = await getUsersWithoutAccess() + const activeUsers = await getActiveUsers() + const waitlistUsers = await getUsersWithoutAccess() - return { props: { initialActiveItem, initialWaitlistUsers } } + return { props: { initialActiveItem, activeUsers, waitlistUsers } } }) export default function Admin({ initialActiveItem, - initialWaitlistUsers, + activeUsers, + waitlistUsers, }: { initialActiveItem: ActiveItem - initialWaitlistUsers: User[] + activeUsers: ActiveUser[] + waitlistUsers: User[] }) { const [activeItem, setActiveItem] = useState(initialActiveItem) @@ -59,8 +62,8 @@ export default function Admin({ onSelectActiveUsers={() => selectItem('activeUsers')} />
- {activeItem === 'activeUsers' && } - {activeItem === 'waitlist' && } + {activeItem === 'activeUsers' && } + {activeItem === 'waitlist' && }
diff --git a/pages/api/admin/getActiveUsers.ts b/pages/api/admin/getActiveUsers.ts deleted file mode 100644 index 362068326..000000000 --- a/pages/api/admin/getActiveUsers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getUsersWithoutAccess } from '@/src/server/datastore/users' -import { withAdminUserRoute } from '@/src/server/session' -import { User } from '@/types' -import type { NextApiRequest, NextApiResponse } from 'next' - -async function getActiveUsers(_: NextApiRequest, res: NextApiResponse) { - const activeUsers = await getUsersWithoutAccess() - res.json(activeUsers) -} - -export default withAdminUserRoute(getActiveUsers) diff --git a/src/server/datastore/users.ts b/src/server/datastore/users.ts index 03e961b40..34318b00b 100644 --- a/src/server/datastore/users.ts +++ b/src/server/datastore/users.ts @@ -1,7 +1,17 @@ -import { User } from '@/types' -import { Entity, buildKey, getDatastore, getEntity, getID, getKeyedEntity, getOrderedEntities } from './datastore' +import { ActiveUser, IsRawPromptVersion, RawChainVersion, RawPromptVersion, User } from '@/types' +import { + Entity, + buildKey, + getDatastore, + getEntity, + getID, + getKeyedEntities, + getKeyedEntity, + getOrderedEntities, +} from './datastore' import { addWorkspaceForUser } from './workspaces' import { uploadImageURLToStorage } from '../storage' +import { getRecentVersions } from './versions' export async function migrateUsers(postMerge: boolean) { if (postMerge) { @@ -98,3 +108,25 @@ export async function getUsersWithoutAccess() { const usersData = await getOrderedEntities(Entity.USER, 'hasAccess', false) return usersData.map(toUser) } + +export async function getActiveUsers(limit = 100): Promise { + const recentVersions = await getRecentVersions(limit) + const usersData = await getKeyedEntities(Entity.USER, [...new Set(recentVersions.map(version => version.userID))]) + return usersData.map(usersData => toActiveUser(usersData, recentVersions)) +} + +const toActiveUser = (userData: any, recentVersions: Awaited>): ActiveUser => { + const user = toUser(userData) + + const userVersions = recentVersions.filter(version => version.userID === user.id) + const versionCount = userVersions.length + const lastActive = userVersions[0].timestamp + const startTimestamp = userVersions.slice(-1)[0].timestamp + + const promptVersions = userVersions.filter(IsRawPromptVersion) + const promptCount = new Set(promptVersions.map(version => version.parentID)).size + const chainVersions = userVersions.filter(version => !IsRawPromptVersion(version)) + const chainCount = new Set(chainVersions.map(version => version.parentID)).size + + return { ...user, lastActive, startTimestamp, versionCount, promptCount, chainCount } +} diff --git a/src/server/datastore/versions.ts b/src/server/datastore/versions.ts index 54e3c07f4..9b99d0a03 100644 --- a/src/server/datastore/versions.ts +++ b/src/server/datastore/versions.ts @@ -339,3 +339,11 @@ export async function deleteVersionForUser(userID: number, versionID: number) { await updateChainOnDeletedVersion(parentID, versionID) } } + +export async function getRecentVersions(limit: number): Promise<(RawPromptVersion | RawChainVersion)[]> { + const datastore = getDatastore() + const [recentVersionsData] = await datastore.runQuery( + datastore.createQuery(Entity.VERSION).order('createdAt', { descending: true }).limit(limit) + ) + return recentVersionsData.map(data => toVersion(data, [], [])) +} diff --git a/types/index.ts b/types/index.ts index 36e6f3c1a..22fae2f71 100644 --- a/types/index.ts +++ b/types/index.ts @@ -137,11 +137,12 @@ export type Prompts = { export type RawPromptVersion = Version & { prompts: Prompts; config: PromptConfig } export type RawChainVersion = Version & { items: ChainItemWithInputs[] } +export const IsRawPromptVersion = (version: RawPromptVersion | RawChainVersion): version is RawPromptVersion => + 'prompts' in version && version.prompts !== undefined && version.prompts !== null export type PromptVersion = RawPromptVersion & { usedInChain: string | null; usedAsEndpoint: boolean } export type ChainVersion = RawChainVersion & { usedAsEndpoint: boolean } -export const IsPromptVersion = (version: PromptVersion | ChainVersion): version is PromptVersion => - 'prompts' in version && version.prompts !== undefined && version.prompts !== null +export const IsPromptVersion = (version: PromptVersion | ChainVersion): version is PromptVersion => IsRawPromptVersion(version) export type PromptInputs = { [name: string]: string } @@ -280,3 +281,11 @@ export type Analytics = { recentUsage: Usage[] aggregatePreviousUsage: Usage } + +export type ActiveUser = User & { + lastActive: number + startTimestamp: number + versionCount: number + promptCount: number + chainCount: number +} From f32940992c515bcd6d954f3060228bbcaa6ad205 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 11:25:37 +0100 Subject: [PATCH 014/134] Render user statistics --- components/admin/activeUsers.tsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index 8270e6b4e..5041140c9 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -1,11 +1,11 @@ -import { Fragment } from 'react' +import { Fragment, ReactNode } from 'react' import { ActiveUser } from '@/types' import Label from '@/components/label' import { UserAvatar } from '@/components/userSidebarItem' import { FormatDate } from '@/src/common/formatting' export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] }) { - const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_240px]' + const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px]' const startDate = Math.min(...activeUsers.map(user => user.startTimestamp)) @@ -16,11 +16,22 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] <>
+ + + + + + + {activeUsers.map(user => ( -
{user.email}
-
{user.fullName}
+ {user.email} + {user.fullName} + {FormatDate(user.lastActive, false)} + {user.versionCount} + {user.promptCount} + {user.chainCount}
))}
@@ -30,3 +41,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] ) } + +const TableCell = ({ children, center }: { children?: ReactNode; center?: boolean }) => ( +
{children}
+) From e8bce3960567695cefc298809fdb92631f8f049a Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 11:48:10 +0100 Subject: [PATCH 015/134] Fetch number of comments made for active users --- components/admin/activeUsers.tsx | 4 +++- src/server/datastore/comments.ts | 13 +++++++++++++ src/server/datastore/users.ts | 33 +++++++++++++++++++++++++------- types/index.ts | 1 + 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index 5041140c9..e0b2ef4a6 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -5,7 +5,7 @@ import { UserAvatar } from '@/components/userSidebarItem' import { FormatDate } from '@/src/common/formatting' export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] }) { - const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px]' + const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px]' const startDate = Math.min(...activeUsers.map(user => user.startTimestamp)) @@ -20,6 +20,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] + @@ -29,6 +30,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] {user.email} {user.fullName} {FormatDate(user.lastActive, false)} + {user.commentCount} {user.versionCount} {user.promptCount} {user.chainCount} diff --git a/src/server/datastore/comments.ts b/src/server/datastore/comments.ts index 202e20c0c..b08a7ae0c 100644 --- a/src/server/datastore/comments.ts +++ b/src/server/datastore/comments.ts @@ -1,6 +1,7 @@ import { Comment, CommentAction } from '@/types' import { Entity, buildKey, getDatastore, getID, getTimestamp } from './datastore' import { ensurePromptOrChainAccess } from './chains' +import { PropertyFilter } from '@google-cloud/datastore' export async function migrateComments() { const datastore = getDatastore() @@ -81,3 +82,15 @@ export const toComment = (data: any): Comment => ({ itemIndex: data.itemIndex ?? null, startIndex: data.startIndex ?? null, }) + +export async function getRecentComments(since: number, limit: number): Promise { + const datastore = getDatastore() + const [recentVersionsData] = await datastore.runQuery( + datastore + .createQuery(Entity.COMMENT) + .filter(new PropertyFilter('createdAt', '>=', new Date(since))) + .order('createdAt', { descending: true }) + .limit(limit) + ) + return recentVersionsData.map(toComment) +} diff --git a/src/server/datastore/users.ts b/src/server/datastore/users.ts index 34318b00b..89399d09e 100644 --- a/src/server/datastore/users.ts +++ b/src/server/datastore/users.ts @@ -1,4 +1,4 @@ -import { ActiveUser, IsRawPromptVersion, RawChainVersion, RawPromptVersion, User } from '@/types' +import { ActiveUser, IsRawPromptVersion, User } from '@/types' import { Entity, buildKey, @@ -12,6 +12,7 @@ import { import { addWorkspaceForUser } from './workspaces' import { uploadImageURLToStorage } from '../storage' import { getRecentVersions } from './versions' +import { getRecentComments } from './comments' export async function migrateUsers(postMerge: boolean) { if (postMerge) { @@ -111,22 +112,40 @@ export async function getUsersWithoutAccess() { export async function getActiveUsers(limit = 100): Promise { const recentVersions = await getRecentVersions(limit) - const usersData = await getKeyedEntities(Entity.USER, [...new Set(recentVersions.map(version => version.userID))]) - return usersData.map(usersData => toActiveUser(usersData, recentVersions)) + const startTimestamp = recentVersions.slice(-1)[0].timestamp + + const recentComments = await getRecentComments(startTimestamp, limit) + const usersData = await getKeyedEntities(Entity.USER, [ + ...new Set([...recentVersions.map(version => version.userID), ...recentComments.map(comment => comment.userID)]), + ]) + return usersData + .map(usersData => toActiveUser(usersData, recentVersions, recentComments)) + .sort((a, b) => b.lastActive - a.lastActive) } -const toActiveUser = (userData: any, recentVersions: Awaited>): ActiveUser => { +const toActiveUser = ( + userData: any, + recentVersions: Awaited>, + recentComments: Awaited> +): ActiveUser => { const user = toUser(userData) const userVersions = recentVersions.filter(version => version.userID === user.id) const versionCount = userVersions.length - const lastActive = userVersions[0].timestamp - const startTimestamp = userVersions.slice(-1)[0].timestamp + + const userComments = recentComments.filter(comment => comment.userID === user.id) + const commentCount = userComments.length + + const lastActive = Math.max(userVersions[0]?.timestamp ?? 0, userComments[0]?.timestamp ?? 0) + const startTimestamp = Math.min( + userVersions.slice(-1)[0]?.timestamp ?? Number.MAX_VALUE, + userComments.slice(-1)[0]?.timestamp ?? Number.MAX_VALUE + ) const promptVersions = userVersions.filter(IsRawPromptVersion) const promptCount = new Set(promptVersions.map(version => version.parentID)).size const chainVersions = userVersions.filter(version => !IsRawPromptVersion(version)) const chainCount = new Set(chainVersions.map(version => version.parentID)).size - return { ...user, lastActive, startTimestamp, versionCount, promptCount, chainCount } + return { ...user, lastActive, startTimestamp, commentCount, versionCount, promptCount, chainCount } } diff --git a/types/index.ts b/types/index.ts index 22fae2f71..3a2329147 100644 --- a/types/index.ts +++ b/types/index.ts @@ -285,6 +285,7 @@ export type Analytics = { export type ActiveUser = User & { lastActive: number startTimestamp: number + commentCount: number versionCount: number promptCount: number chainCount: number From 6f5014876eb5c6cc27310539d079b7dce78a6fae Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 11:52:08 +0100 Subject: [PATCH 016/134] Fetch number of endpoints created by active users --- components/admin/activeUsers.tsx | 4 +++- src/server/datastore/endpoints.ts | 14 +++++++++++++- src/server/datastore/users.ts | 30 +++++++++++++++++++++++------- types/index.ts | 3 ++- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index e0b2ef4a6..6aeee2f73 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -5,7 +5,7 @@ import { UserAvatar } from '@/components/userSidebarItem' import { FormatDate } from '@/src/common/formatting' export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] }) { - const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px]' + const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px_100px]' const startDate = Math.min(...activeUsers.map(user => user.startTimestamp)) @@ -24,6 +24,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] + {activeUsers.map(user => ( @@ -34,6 +35,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] {user.versionCount} {user.promptCount} {user.chainCount} + {user.endpointCount} ))} diff --git a/src/server/datastore/endpoints.ts b/src/server/datastore/endpoints.ts index 340cb92c6..0008cb889 100644 --- a/src/server/datastore/endpoints.ts +++ b/src/server/datastore/endpoints.ts @@ -1,5 +1,5 @@ import { Endpoint } from '@/types' -import { and } from '@google-cloud/datastore' +import { PropertyFilter, and } from '@google-cloud/datastore' import { Entity, buildFilter, @@ -212,3 +212,15 @@ export const toEndpoint = (data: any): Endpoint => ({ useCache: data.useCache, useStreaming: data.useStreaming, }) + +export async function getRecentEndpoints(since: number, limit: number): Promise { + const datastore = getDatastore() + const [recentVersionsData] = await datastore.runQuery( + datastore + .createQuery(Entity.ENDPOINT) + .filter(new PropertyFilter('createdAt', '>=', new Date(since))) + .order('createdAt', { descending: true }) + .limit(limit) + ) + return recentVersionsData.map(toEndpoint) +} diff --git a/src/server/datastore/users.ts b/src/server/datastore/users.ts index 89399d09e..3eee10eda 100644 --- a/src/server/datastore/users.ts +++ b/src/server/datastore/users.ts @@ -13,6 +13,7 @@ import { addWorkspaceForUser } from './workspaces' import { uploadImageURLToStorage } from '../storage' import { getRecentVersions } from './versions' import { getRecentComments } from './comments' +import { getRecentEndpoints } from './endpoints' export async function migrateUsers(postMerge: boolean) { if (postMerge) { @@ -115,18 +116,25 @@ export async function getActiveUsers(limit = 100): Promise { const startTimestamp = recentVersions.slice(-1)[0].timestamp const recentComments = await getRecentComments(startTimestamp, limit) + const recentEndpoints = await getRecentEndpoints(startTimestamp, limit) + const usersData = await getKeyedEntities(Entity.USER, [ - ...new Set([...recentVersions.map(version => version.userID), ...recentComments.map(comment => comment.userID)]), + ...new Set([ + ...recentVersions.map(version => version.userID), + ...recentComments.map(comment => comment.userID), + ...recentEndpoints.map(endpoint => endpoint.userID), + ]), ]) return usersData - .map(usersData => toActiveUser(usersData, recentVersions, recentComments)) + .map(usersData => toActiveUser(usersData, recentVersions, recentComments, recentEndpoints)) .sort((a, b) => b.lastActive - a.lastActive) } const toActiveUser = ( userData: any, recentVersions: Awaited>, - recentComments: Awaited> + recentComments: Awaited>, + recentEndpoints: Awaited> ): ActiveUser => { const user = toUser(userData) @@ -136,10 +144,18 @@ const toActiveUser = ( const userComments = recentComments.filter(comment => comment.userID === user.id) const commentCount = userComments.length - const lastActive = Math.max(userVersions[0]?.timestamp ?? 0, userComments[0]?.timestamp ?? 0) + const userEndpoints = recentEndpoints.filter(endpoint => endpoint.userID === user.id) + const endpointCount = userEndpoints.length + + const lastActive = Math.max( + ...[userVersions[0]?.timestamp ?? 0, userComments[0]?.timestamp ?? 0, userEndpoints[0]?.timestamp ?? 0] + ) const startTimestamp = Math.min( - userVersions.slice(-1)[0]?.timestamp ?? Number.MAX_VALUE, - userComments.slice(-1)[0]?.timestamp ?? Number.MAX_VALUE + ...[ + userVersions.slice(-1)[0]?.timestamp ?? Number.MAX_VALUE, + userComments.slice(-1)[0]?.timestamp ?? Number.MAX_VALUE, + userEndpoints.slice(-1)[0]?.timestamp ?? Number.MAX_VALUE, + ] ) const promptVersions = userVersions.filter(IsRawPromptVersion) @@ -147,5 +163,5 @@ const toActiveUser = ( const chainVersions = userVersions.filter(version => !IsRawPromptVersion(version)) const chainCount = new Set(chainVersions.map(version => version.parentID)).size - return { ...user, lastActive, startTimestamp, commentCount, versionCount, promptCount, chainCount } + return { ...user, lastActive, startTimestamp, commentCount, endpointCount, versionCount, promptCount, chainCount } } diff --git a/types/index.ts b/types/index.ts index 3a2329147..7887d8818 100644 --- a/types/index.ts +++ b/types/index.ts @@ -285,8 +285,9 @@ export type Analytics = { export type ActiveUser = User & { lastActive: number startTimestamp: number - commentCount: number versionCount: number promptCount: number chainCount: number + commentCount: number + endpointCount: number } From a11bc71de866381085764c97aa50eb444b51cbd6 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 11:58:40 +0100 Subject: [PATCH 017/134] Make filter optional in internal datastore helpers --- src/server/datastore/datastore.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server/datastore/datastore.ts b/src/server/datastore/datastore.ts index 3bcd0d3c1..3529792b4 100644 --- a/src/server/datastore/datastore.ts +++ b/src/server/datastore/datastore.ts @@ -39,6 +39,9 @@ export const buildKey = (type: string, id?: number) => getDatastore().key([type, export const buildFilter = (key: string, value: {}) => new PropertyFilter(key, '=', value) +const filterQuery = (query: Query, filter: EntityFilter | undefined) => + filter !== undefined ? query.filter(filter) : query + const projectQuery = (query: Query, keys: string[]) => (keys.length > 0 ? query.select(keys) : query) const orderQuery = (query: Query, sortKeys: string[]) => @@ -46,20 +49,20 @@ const orderQuery = (query: Query, sortKeys: string[]) => const buildQuery = ( type: string, - filter: EntityFilter, + filter: EntityFilter | undefined, limit: number, sortKeys: string[], selectKeys: string[], transaction?: Transaction ) => projectQuery( - orderQuery((transaction ?? getDatastore()).createQuery(type).filter(filter).limit(limit), sortKeys), + orderQuery(filterQuery((transaction ?? getDatastore()).createQuery(type), filter).limit(limit), sortKeys), selectKeys ) const getInternalFilteredEntities = ( type: string, - filter: EntityFilter, + filter?: EntityFilter, limit = 100, sortKeys = [] as string[], selectKeys = [] as string[], From 99be9fc7a612c70a82536fa446a46a2a3967a644 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 12:05:58 +0100 Subject: [PATCH 018/134] Factor out helper to query unfiltered recent items --- src/server/datastore/comments.ts | 11 ++--------- src/server/datastore/datastore.ts | 3 +++ src/server/datastore/endpoints.ts | 10 ++-------- src/server/datastore/logs.ts | 2 +- src/server/datastore/versions.ts | 6 ++---- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/server/datastore/comments.ts b/src/server/datastore/comments.ts index b08a7ae0c..c0912ad5c 100644 --- a/src/server/datastore/comments.ts +++ b/src/server/datastore/comments.ts @@ -1,5 +1,5 @@ import { Comment, CommentAction } from '@/types' -import { Entity, buildKey, getDatastore, getID, getTimestamp } from './datastore' +import { Entity, buildKey, getDatastore, getID, getRecentEntities, getTimestamp } from './datastore' import { ensurePromptOrChainAccess } from './chains' import { PropertyFilter } from '@google-cloud/datastore' @@ -84,13 +84,6 @@ export const toComment = (data: any): Comment => ({ }) export async function getRecentComments(since: number, limit: number): Promise { - const datastore = getDatastore() - const [recentVersionsData] = await datastore.runQuery( - datastore - .createQuery(Entity.COMMENT) - .filter(new PropertyFilter('createdAt', '>=', new Date(since))) - .order('createdAt', { descending: true }) - .limit(limit) - ) + const recentVersionsData = await getRecentEntities(Entity.COMMENT, limit, new Date(since)) return recentVersionsData.map(toComment) } diff --git a/src/server/datastore/datastore.ts b/src/server/datastore/datastore.ts index 3529792b4..78474b65c 100644 --- a/src/server/datastore/datastore.ts +++ b/src/server/datastore/datastore.ts @@ -72,6 +72,9 @@ const getInternalFilteredEntities = ( .runQuery(buildQuery(type, filter, limit, sortKeys, selectKeys)) .then(([entities]) => entities) +export const getRecentEntities = (type: string, limit?: number, since?: Date, sortKey = 'createdAt') => + getInternalFilteredEntities(type, since ? new PropertyFilter(sortKey, '>=', since) : undefined, limit, [sortKey]) + export const getFilteredEntities = (type: string, filter: EntityFilter, limit?: number) => getInternalFilteredEntities(type, filter, limit) diff --git a/src/server/datastore/endpoints.ts b/src/server/datastore/endpoints.ts index 0008cb889..8c59edbc2 100644 --- a/src/server/datastore/endpoints.ts +++ b/src/server/datastore/endpoints.ts @@ -10,6 +10,7 @@ import { getFilteredEntity, getID, getKeyedEntity, + getRecentEntities, getTimestamp, } from './datastore' import { getUniqueNameWithFormat } from './prompts' @@ -214,13 +215,6 @@ export const toEndpoint = (data: any): Endpoint => ({ }) export async function getRecentEndpoints(since: number, limit: number): Promise { - const datastore = getDatastore() - const [recentVersionsData] = await datastore.runQuery( - datastore - .createQuery(Entity.ENDPOINT) - .filter(new PropertyFilter('createdAt', '>=', new Date(since))) - .order('createdAt', { descending: true }) - .limit(limit) - ) + const recentVersionsData = await getRecentEntities(Entity.ENDPOINT, limit, new Date(since)) return recentVersionsData.map(toEndpoint) } diff --git a/src/server/datastore/logs.ts b/src/server/datastore/logs.ts index 287ecff41..cc8e4989d 100644 --- a/src/server/datastore/logs.ts +++ b/src/server/datastore/logs.ts @@ -7,7 +7,7 @@ export async function migrateLogs(postMerge: boolean) { return } const datastore = getDatastore() - const [allLogs] = await datastore.runQuery(datastore.createQuery(Entity.LOG).order('createdAt', { descending: true })) + const [allLogs] = await datastore.runQuery(datastore.createQuery(Entity.LOG)) for (const logData of allLogs) { await getDatastore().save( toLogData( diff --git a/src/server/datastore/versions.ts b/src/server/datastore/versions.ts index 9b99d0a03..337106376 100644 --- a/src/server/datastore/versions.ts +++ b/src/server/datastore/versions.ts @@ -10,6 +10,7 @@ import { getID, getKeyedEntities, getKeyedEntity, + getRecentEntities, getTimestamp, } from './datastore' import { toRun } from './runs' @@ -341,9 +342,6 @@ export async function deleteVersionForUser(userID: number, versionID: number) { } export async function getRecentVersions(limit: number): Promise<(RawPromptVersion | RawChainVersion)[]> { - const datastore = getDatastore() - const [recentVersionsData] = await datastore.runQuery( - datastore.createQuery(Entity.VERSION).order('createdAt', { descending: true }).limit(limit) - ) + const recentVersionsData = await getRecentEntities(Entity.VERSION, limit) return recentVersionsData.map(data => toVersion(data, [], [])) } From dd79626129ac694c5b9be3b5bdc9a6547051eb56 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 12:07:42 +0100 Subject: [PATCH 019/134] Remove feedback section in admin sidebar --- __tests__/consumePartialRuns.test.ts | 2 +- components/admin/activeUsers.tsx | 32 +++++++++++++++++------ components/admin/adminSidebar.tsx | 5 ++-- src/client/context/globalPopupContext.tsx | 2 +- src/common/providerMetadata.tsx | 5 +++- styles/globals.css | 4 ++- types/index.ts | 3 ++- 7 files changed, 37 insertions(+), 16 deletions(-) diff --git a/__tests__/consumePartialRuns.test.ts b/__tests__/consumePartialRuns.test.ts index 193010ae5..452b426b4 100644 --- a/__tests__/consumePartialRuns.test.ts +++ b/__tests__/consumePartialRuns.test.ts @@ -10,7 +10,7 @@ const testConsumeStream = ( numberOfInputs = 1 ) => test(`Test ${testDescription}`, async () => { - const inputs = Array.from({ length: numberOfInputs }, () => ({} as PromptInputs)) + const inputs = Array.from({ length: numberOfInputs }, () => ({}) as PromptInputs) const streamReader = new ReadableStream({ start(controller) { for (const data of streamedData) { diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index 6aeee2f73..baf32dd49 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -17,14 +17,30 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[]
- - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + {activeUsers.map(user => ( diff --git a/components/admin/adminSidebar.tsx b/components/admin/adminSidebar.tsx index d12e66cfa..57b7b1cf7 100644 --- a/components/admin/adminSidebar.tsx +++ b/components/admin/adminSidebar.tsx @@ -1,5 +1,5 @@ import { AdminRoute } from '@/src/client/clientRoute' -import { FeedbackSection, SidebarButton, SidebarSection } from '../sidebar' +import { SidebarButton, SidebarSection } from '../sidebar' import linkIcon from '@/public/chain.svg' import userIcon from '@/public/user.svg' @@ -27,10 +27,9 @@ export default function AdminSidebar({ - + -
) diff --git a/src/client/context/globalPopupContext.tsx b/src/client/context/globalPopupContext.tsx index 5938895e2..3b4fa7672 100644 --- a/src/client/context/globalPopupContext.tsx +++ b/src/client/context/globalPopupContext.tsx @@ -40,7 +40,7 @@ export type WithDismiss = { withDismiss: (callback: () => void) => () => void } export function useGlobalPopupProvider(): readonly [ GlobalPopupContextType, GlobalPopupProps, - PropsType | undefined + PropsType | undefined, ] { const [popupRender, setPopupRender] = useState>() const [popupProps, setPopupProps] = useState() diff --git a/src/common/providerMetadata.tsx b/src/common/providerMetadata.tsx index 1ebfae161..aec8f81f7 100644 --- a/src/common/providerMetadata.tsx +++ b/src/common/providerMetadata.tsx @@ -100,7 +100,10 @@ export const IsModelDisabled = (model: LanguageModel, providers: AvailableModelP return !!customModel && !customModel.enabled } -export const IsModelAvailable = (model: LanguageModel | EmbeddingModel, providers: AvailableModelProvider[]): boolean => +export const IsModelAvailable = ( + model: LanguageModel | EmbeddingModel, + providers: AvailableModelProvider[] +): boolean => isCustomModel(model) ? customModelFromProviders(model, providers)?.enabled ?? false : IsProviderAvailable(ProviderForModel(model), providers) diff --git a/styles/globals.css b/styles/globals.css index f8cc7b55e..8919194eb 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -13,5 +13,7 @@ } #nprogress .peg { - box-shadow: 0 0 10px #ff4de2, 0 0 5px #ff4de2; + box-shadow: + 0 0 10px #ff4de2, + 0 0 5px #ff4de2; } diff --git a/types/index.ts b/types/index.ts index 7887d8818..b191d8f50 100644 --- a/types/index.ts +++ b/types/index.ts @@ -142,7 +142,8 @@ export const IsRawPromptVersion = (version: RawPromptVersion | RawChainVersion): export type PromptVersion = RawPromptVersion & { usedInChain: string | null; usedAsEndpoint: boolean } export type ChainVersion = RawChainVersion & { usedAsEndpoint: boolean } -export const IsPromptVersion = (version: PromptVersion | ChainVersion): version is PromptVersion => IsRawPromptVersion(version) +export const IsPromptVersion = (version: PromptVersion | ChainVersion): version is PromptVersion => + IsRawPromptVersion(version) export type PromptInputs = { [name: string]: string } From 2aee16a56d96865b61c8f0821fb872cdfc0cce0a Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 13:19:04 +0100 Subject: [PATCH 020/134] Allow to have single user as active item in admin --- components/admin/activeUsers.tsx | 2 +- components/admin/userMetrics.tsx | 59 ++++++++++++++++++++++++++++++++ pages/admin/index.tsx | 59 +++++++++++++++++++++++--------- 3 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 components/admin/userMetrics.tsx diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index baf32dd49..f68875964 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -14,7 +14,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[]
{activeUsers.length > 0 && ( <> - +
diff --git a/components/admin/userMetrics.tsx b/components/admin/userMetrics.tsx new file mode 100644 index 000000000..10c071e17 --- /dev/null +++ b/components/admin/userMetrics.tsx @@ -0,0 +1,59 @@ +import { ReactNode } from 'react' +import { ActiveUser } from '@/types' +import Label from '@/components/label' +import { UserAvatar } from '@/components/userSidebarItem' +import { FormatDate } from '@/src/common/formatting' + +export default function UserMetrics({ user }: { user: ActiveUser }) { + const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px_100px]' + + const startDate = user.startTimestamp + + return ( + <> +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {user.email} + {user.fullName} + {FormatDate(user.lastActive, false)} + {user.commentCount} + {user.versionCount} + {user.promptCount} + {user.chainCount} + {user.endpointCount} +
+
+ + ) +} + +const TableCell = ({ children, center }: { children?: ReactNode; center?: boolean }) => ( +
{children}
+) diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 06aa9b150..74ceee97d 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -8,17 +8,24 @@ import { ParseNumberQuery } from '@/src/client/clientRoute' import { useRouter } from 'next/router' import { useState } from 'react' import ActiveUsers from '@/components/admin/activeUsers' +import UserMetrics from '@/components/admin/userMetrics' -type ActiveItem = 'waitlist' | 'activeUsers' +const WaitlistItem = 'waitlist' +const ActiveUsersItem = 'activeUsers' +type ActiveItem = typeof WaitlistItem | typeof ActiveUsersItem | ActiveUser export const getServerSideProps = withAdminSession(async ({ query }) => { - const { w: waitlist } = ParseNumberQuery(query) - - const initialActiveItem = waitlist ? 'waitlist' : 'activeUsers' + const { w: waitlist, u: userID } = ParseNumberQuery(query) const activeUsers = await getActiveUsers() const waitlistUsers = await getUsersWithoutAccess() + const initialActiveItem: ActiveItem = waitlist + ? WaitlistItem + : userID + ? { ...activeUsers.find(user => user.id === userID)! } + : ActiveUsersItem + return { props: { initialActiveItem, activeUsers, waitlistUsers } } }) @@ -35,19 +42,34 @@ export default function Admin({ const router = useRouter() - const { w: waitlist } = ParseNumberQuery(router.query) - const currentQueryState = waitlist + const selectItem = (item: ActiveItem) => { + setActiveItem(item) + router.push( + `/admin${item === WaitlistItem ? '?w=1' : item !== ActiveUsersItem ? `?u=${item.id}` : ''}`, + undefined, + { shallow: true } + ) + } + + const selectUser = (userID: number) => { + const user = activeUsers.find(user => user.id === userID) + if (user) { + selectItem(user) + } + } + + const { w: waitlist, u: userID } = ParseNumberQuery(router.query) + const currentQueryState = waitlist ? WaitlistItem : userID ?? ActiveUsersItem const [query, setQuery] = useState(currentQueryState) if (currentQueryState !== query) { - setActiveItem(waitlist ? 'waitlist' : 'activeUsers') + if (userID) { + selectUser(userID) + } else { + selectItem(waitlist ? WaitlistItem : ActiveUsersItem) + } setQuery(currentQueryState) } - const selectItem = (item: ActiveItem) => { - setActiveItem(item) - router.push(`/admin${item === 'waitlist' ? '?w=1' : ''}`, undefined, { shallow: true }) - } - return ( <>
@@ -58,12 +80,17 @@ export default function Admin({
selectItem('waitlist')} - onSelectActiveUsers={() => selectItem('activeUsers')} + onSelectWaitlist={() => selectItem(WaitlistItem)} + onSelectActiveUsers={() => selectItem(ActiveUsersItem)} />
- {activeItem === 'activeUsers' && } - {activeItem === 'waitlist' && } + {activeItem === WaitlistItem ? ( + + ) : activeItem === ActiveUsersItem ? ( + + ) : ( + + )}
From 43b987fa1ca43d08a2518476b9ccb8b10912b856 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 13:33:16 +0100 Subject: [PATCH 021/134] Allow to select user from active users table --- components/admin/activeUsers.tsx | 22 +++++++++++++++------- pages/admin/index.tsx | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index f68875964..b9f692b6d 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -4,8 +4,14 @@ import Label from '@/components/label' import { UserAvatar } from '@/components/userSidebarItem' import { FormatDate } from '@/src/common/formatting' -export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] }) { - const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px_100px]' +export default function ActiveUsers({ + activeUsers, + onSelectUser, +}: { + activeUsers: ActiveUser[] + onSelectUser: (userID: number) => void +}) { + const gridConfig = 'grid grid-cols-[44px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px_100px]' const startDate = Math.min(...activeUsers.map(user => user.startTimestamp)) @@ -15,7 +21,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] {activeUsers.length > 0 && ( <> -
+
@@ -42,8 +48,8 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] {activeUsers.map(user => ( - - +
onSelectUser(user.id)}> + {user.email} {user.fullName} {FormatDate(user.lastActive, false)} @@ -52,7 +58,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] {user.promptCount} {user.chainCount} {user.endpointCount} - +
))}
@@ -63,5 +69,7 @@ export default function ActiveUsers({ activeUsers }: { activeUsers: ActiveUser[] } const TableCell = ({ children, center }: { children?: ReactNode; center?: boolean }) => ( -
{children}
+
+ {children} +
) diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 74ceee97d..39afc7222 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -87,7 +87,7 @@ export default function Admin({ {activeItem === WaitlistItem ? ( ) : activeItem === ActiveUsersItem ? ( - + ) : ( )} From 2aabc3bcf0cdc1fee9416e91d4419a5b6be5bc86 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 13:38:35 +0100 Subject: [PATCH 022/134] Simplify query logic --- pages/admin/index.tsx | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 39afc7222..a10dbb8e2 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -42,30 +42,22 @@ export default function Admin({ const router = useRouter() - const selectItem = (item: ActiveItem) => { - setActiveItem(item) + const selectItem = (item: typeof WaitlistItem | typeof ActiveUsersItem | number) => { router.push( - `/admin${item === WaitlistItem ? '?w=1' : item !== ActiveUsersItem ? `?u=${item.id}` : ''}`, + `/admin${item === WaitlistItem ? '?w=1' : item !== ActiveUsersItem ? `?u=${item}` : ''}`, undefined, { shallow: true } ) } - const selectUser = (userID: number) => { - const user = activeUsers.find(user => user.id === userID) - if (user) { - selectItem(user) - } - } - const { w: waitlist, u: userID } = ParseNumberQuery(router.query) const currentQueryState = waitlist ? WaitlistItem : userID ?? ActiveUsersItem const [query, setQuery] = useState(currentQueryState) if (currentQueryState !== query) { if (userID) { - selectUser(userID) + setActiveItem(activeUsers.find(user => user.id === userID)!) } else { - selectItem(waitlist ? WaitlistItem : ActiveUsersItem) + setActiveItem(waitlist ? WaitlistItem : ActiveUsersItem) } setQuery(currentQueryState) } @@ -87,7 +79,7 @@ export default function Admin({ {activeItem === WaitlistItem ? ( ) : activeItem === ActiveUsersItem ? ( - + ) : ( )} From 3a56f775c0f835dc18ef4fb1bd40d1e0e3e05ce9 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 13:52:51 +0100 Subject: [PATCH 023/134] Add admin endpoint to fetch user metrics --- ...{userMetrics.tsx => activeUserMetrics.tsx} | 19 ++++++++++-- pages/admin/index.tsx | 30 +++++++++++-------- pages/api/admin/getUserMetrics.ts | 11 +++++++ src/client/admin/api.ts | 6 +++- src/server/datastore/users.ts | 8 ++++- types/index.ts | 4 +++ 6 files changed, 61 insertions(+), 17 deletions(-) rename components/admin/{userMetrics.tsx => activeUserMetrics.tsx} (82%) create mode 100644 pages/api/admin/getUserMetrics.ts diff --git a/components/admin/userMetrics.tsx b/components/admin/activeUserMetrics.tsx similarity index 82% rename from components/admin/userMetrics.tsx rename to components/admin/activeUserMetrics.tsx index 10c071e17..30f3d1d53 100644 --- a/components/admin/userMetrics.tsx +++ b/components/admin/activeUserMetrics.tsx @@ -1,10 +1,20 @@ import { ReactNode } from 'react' -import { ActiveUser } from '@/types' +import { ActiveUser, UserMetrics } from '@/types' import Label from '@/components/label' import { UserAvatar } from '@/components/userSidebarItem' import { FormatDate } from '@/src/common/formatting' +import Icon from '../icon' +import backIcon from '@/public/back.svg' -export default function UserMetrics({ user }: { user: ActiveUser }) { +export default function ActiveUserMetrics({ + user, + metrics, + onDismiss, +}: { + user: ActiveUser + metrics: UserMetrics + onDismiss: () => void +}) { const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px_100px]' const startDate = user.startTimestamp @@ -12,7 +22,10 @@ export default function UserMetrics({ user }: { user: ActiveUser }) { return ( <>
- +
diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index a10dbb8e2..ffee348a5 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,6 +1,6 @@ import { withAdminSession } from '@/src/server/session' -import { ActiveUser, User } from '@/types' -import { getActiveUsers, getUsersWithoutAccess } from '@/src/server/datastore/users' +import { ActiveUser, User, UserMetrics } from '@/types' +import { getActiveUsers, getMetricsForUser, getUsersWithoutAccess } from '@/src/server/datastore/users' import TopBar, { TopBarAccessoryItem, TopBarBackItem } from '@/components/topBar' import AdminSidebar from '@/components/admin/adminSidebar' import Waitlist from '@/components/admin/waitlist' @@ -8,7 +8,8 @@ import { ParseNumberQuery } from '@/src/client/clientRoute' import { useRouter } from 'next/router' import { useState } from 'react' import ActiveUsers from '@/components/admin/activeUsers' -import UserMetrics from '@/components/admin/userMetrics' +import ActiveUserMetrics from '@/components/admin/activeUserMetrics' +import api from '@/src/client/admin/api' const WaitlistItem = 'waitlist' const ActiveUsersItem = 'activeUsers' @@ -26,36 +27,41 @@ export const getServerSideProps = withAdminSession(async ({ query }) => { ? { ...activeUsers.find(user => user.id === userID)! } : ActiveUsersItem - return { props: { initialActiveItem, activeUsers, waitlistUsers } } + const initialUserMetrics = userID ? await getMetricsForUser(userID) : null + + return { props: { initialActiveItem, initialUserMetrics, activeUsers, waitlistUsers } } }) export default function Admin({ initialActiveItem, + initialUserMetrics, activeUsers, waitlistUsers, }: { initialActiveItem: ActiveItem + initialUserMetrics: UserMetrics | null activeUsers: ActiveUser[] waitlistUsers: User[] }) { const [activeItem, setActiveItem] = useState(initialActiveItem) + const [userMetrics, setUserMetrics] = useState(initialUserMetrics) const router = useRouter() const selectItem = (item: typeof WaitlistItem | typeof ActiveUsersItem | number) => { - router.push( - `/admin${item === WaitlistItem ? '?w=1' : item !== ActiveUsersItem ? `?u=${item}` : ''}`, - undefined, - { shallow: true } - ) + router.push(`/admin${item === WaitlistItem ? '?w=1' : item !== ActiveUsersItem ? `?u=${item}` : ''}`, undefined, { + shallow: true, + }) } const { w: waitlist, u: userID } = ParseNumberQuery(router.query) const currentQueryState = waitlist ? WaitlistItem : userID ?? ActiveUsersItem const [query, setQuery] = useState(currentQueryState) if (currentQueryState !== query) { + setUserMetrics(null) if (userID) { setActiveItem(activeUsers.find(user => user.id === userID)!) + api.getUserMetrics(userID).then(setUserMetrics) } else { setActiveItem(waitlist ? WaitlistItem : ActiveUsersItem) } @@ -80,9 +86,9 @@ export default function Admin({ ) : activeItem === ActiveUsersItem ? ( - ) : ( - - )} + ) : userMetrics ? ( + router.back()} /> + ) : null}
diff --git a/pages/api/admin/getUserMetrics.ts b/pages/api/admin/getUserMetrics.ts new file mode 100644 index 000000000..885677835 --- /dev/null +++ b/pages/api/admin/getUserMetrics.ts @@ -0,0 +1,11 @@ +import { getMetricsForUser } from '@/src/server/datastore/users' +import { withAdminUserRoute } from '@/src/server/session' +import { UserMetrics } from '@/types' +import type { NextApiRequest, NextApiResponse } from 'next' + +async function getUserMetrics(req: NextApiRequest, res: NextApiResponse) { + const userMetrics = await getMetricsForUser(req.body.userID) + res.json(userMetrics) +} + +export default withAdminUserRoute(getUserMetrics) diff --git a/src/client/admin/api.ts b/src/client/admin/api.ts index dc283ece0..e4e16c440 100644 --- a/src/client/admin/api.ts +++ b/src/client/admin/api.ts @@ -1,4 +1,5 @@ import { postToAPI } from '@/src/client/api' +import { User, UserMetrics } from '@/types' const post = (apiCall: Function, json: any = {}) => { return postToAPI('/api/admin', apiCall.name, json, 'json') @@ -8,9 +9,12 @@ const api = { addUser: async function (email: string, fullName: string) { return post(this.addUser, { email, fullName }) }, - getWaitlistUsers: async function () { + getWaitlistUsers: async function (): Promise { return post(this.getWaitlistUsers) }, + getUserMetrics: async function (userID: number): Promise { + return post(this.getUserMetrics, { userID }) + }, } export default api diff --git a/src/server/datastore/users.ts b/src/server/datastore/users.ts index 3eee10eda..2b8315269 100644 --- a/src/server/datastore/users.ts +++ b/src/server/datastore/users.ts @@ -1,9 +1,10 @@ -import { ActiveUser, IsRawPromptVersion, User } from '@/types' +import { ActiveUser, IsRawPromptVersion, User, UserMetrics } from '@/types' import { Entity, buildKey, getDatastore, getEntity, + getEntityCount, getID, getKeyedEntities, getKeyedEntity, @@ -165,3 +166,8 @@ const toActiveUser = ( return { ...user, lastActive, startTimestamp, commentCount, endpointCount, versionCount, promptCount, chainCount } } + +export async function getMetricsForUser(userID: number): Promise { + const workspaceCount = await getEntityCount(Entity.WORKSPACE, 'userID', userID) + return { workspaceCount } +} diff --git a/types/index.ts b/types/index.ts index b191d8f50..e084928fd 100644 --- a/types/index.ts +++ b/types/index.ts @@ -292,3 +292,7 @@ export type ActiveUser = User & { commentCount: number endpointCount: number } + +export type UserMetrics = { + workspaceCount: number +} From a346c4cd001ccd53a80c9f0ed544414d5a95c2bd Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 14:39:50 +0100 Subject: [PATCH 024/134] Include workspace and project count in metrics --- components/admin/activeUserMetrics.tsx | 58 +++++++------------------- src/server/datastore/datastore.ts | 7 +++- src/server/datastore/users.ts | 15 ++++++- types/index.ts | 4 +- 4 files changed, 35 insertions(+), 49 deletions(-) diff --git a/components/admin/activeUserMetrics.tsx b/components/admin/activeUserMetrics.tsx index 30f3d1d53..995dd6e44 100644 --- a/components/admin/activeUserMetrics.tsx +++ b/components/admin/activeUserMetrics.tsx @@ -1,4 +1,3 @@ -import { ReactNode } from 'react' import { ActiveUser, UserMetrics } from '@/types' import Label from '@/components/label' import { UserAvatar } from '@/components/userSidebarItem' @@ -15,10 +14,6 @@ export default function ActiveUserMetrics({ metrics: UserMetrics onDismiss: () => void }) { - const gridConfig = 'grid grid-cols-[28px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px_100px]' - - const startDate = user.startTimestamp - return ( <>
@@ -26,47 +21,22 @@ export default function ActiveUserMetrics({ Back to Active Users -
- - - - - - - - - - - - - - - - - - - - - - - - - - - {user.email} - {user.fullName} - {FormatDate(user.lastActive, false)} - {user.commentCount} - {user.versionCount} - {user.promptCount} - {user.chainCount} - {user.endpointCount} +
+
+ + +
+ + + +
) } - -const TableCell = ({ children, center }: { children?: ReactNode; center?: boolean }) => ( -
{children}
-) diff --git a/src/server/datastore/datastore.ts b/src/server/datastore/datastore.ts index 78474b65c..d839cf345 100644 --- a/src/server/datastore/datastore.ts +++ b/src/server/datastore/datastore.ts @@ -131,13 +131,16 @@ export const getKeyedEntities = async (type: string, ids: number[], transaction? export const getKeyedEntity = async (type: string, id: number, transaction?: Transaction) => getKeyedEntities(type, [id], transaction).then(([entity]) => entity) -export const getEntityCount = async (type: string, key: string, value: {}) => { +export const getFilteredEntityCount = async (type: string, filter: EntityFilter) => { const datastore = getDatastore() - const query = datastore.createQuery(type).filter(buildFilter(key, value)) + const query = datastore.createQuery(type).filter(filter) const [[{ count }]] = await datastore.runAggregationQuery(new AggregateQuery(query).count('count')) return count } +export const getEntityCount = async (type: string, key: string, value: {}) => + getFilteredEntityCount(type, buildFilter(key, value)) + export const allocateID = async (type: string, transaction?: Transaction) => { const [[key]] = await (transaction ?? getDatastore()).allocateIds(buildKey(type), 1) return getID({ key }) diff --git a/src/server/datastore/users.ts b/src/server/datastore/users.ts index 2b8315269..890afff43 100644 --- a/src/server/datastore/users.ts +++ b/src/server/datastore/users.ts @@ -1,10 +1,12 @@ import { ActiveUser, IsRawPromptVersion, User, UserMetrics } from '@/types' import { Entity, + buildFilter, buildKey, getDatastore, getEntity, getEntityCount, + getFilteredEntityCount, getID, getKeyedEntities, getKeyedEntity, @@ -15,6 +17,7 @@ import { uploadImageURLToStorage } from '../storage' import { getRecentVersions } from './versions' import { getRecentComments } from './comments' import { getRecentEndpoints } from './endpoints' +import { PropertyFilter, and } from '@google-cloud/datastore' export async function migrateUsers(postMerge: boolean) { if (postMerge) { @@ -168,6 +171,14 @@ const toActiveUser = ( } export async function getMetricsForUser(userID: number): Promise { - const workspaceCount = await getEntityCount(Entity.WORKSPACE, 'userID', userID) - return { workspaceCount } + const createdWorkspaceCount = await getEntityCount(Entity.WORKSPACE, 'userID', userID) + const workspaceAccessCount = await getFilteredEntityCount( + Entity.ACCESS, + and([buildFilter('userID', userID), buildFilter('kind', 'workspace')]) + ) + const projectAccessCount = await getFilteredEntityCount( + Entity.ACCESS, + and([buildFilter('userID', userID), buildFilter('kind', 'project')]) + ) + return { createdWorkspaceCount, workspaceAccessCount, projectAccessCount } } diff --git a/types/index.ts b/types/index.ts index e084928fd..bc9272317 100644 --- a/types/index.ts +++ b/types/index.ts @@ -294,5 +294,7 @@ export type ActiveUser = User & { } export type UserMetrics = { - workspaceCount: number + createdWorkspaceCount: number + workspaceAccessCount: number + projectAccessCount: number } From 209934b990263ad757539e314b125b5b4354ced4 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 14:42:14 +0100 Subject: [PATCH 025/134] Navigate home rather than back from admin panel Now that you can navigate within the admin panel, it's better for the top bar button to take you straight home. --- pages/admin/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index ffee348a5..1dc4b0fbe 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -4,7 +4,7 @@ import { getActiveUsers, getMetricsForUser, getUsersWithoutAccess } from '@/src/ import TopBar, { TopBarAccessoryItem, TopBarBackItem } from '@/components/topBar' import AdminSidebar from '@/components/admin/adminSidebar' import Waitlist from '@/components/admin/waitlist' -import { ParseNumberQuery } from '@/src/client/clientRoute' +import ClientRoute, { ParseNumberQuery } from '@/src/client/clientRoute' import { useRouter } from 'next/router' import { useState } from 'react' import ActiveUsers from '@/components/admin/activeUsers' @@ -72,7 +72,7 @@ export default function Admin({ <>
- + router.push(ClientRoute.Home)} /> Admin From c5985b5d2b45e738710432b4d7f895d15d53c118 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 15:32:51 +0100 Subject: [PATCH 026/134] Add recent projects query and admin panel table --- components/admin/activeUserMetrics.tsx | 12 +++++- components/admin/activeUsers.tsx | 10 ++--- components/admin/adminSidebar.tsx | 4 ++ components/admin/recentProjects.tsx | 54 ++++++++++++++++++++++++++ pages/admin/index.tsx | 47 ++++++++++++++++------ src/server/datastore/projects.ts | 35 ++++++++++++++++- src/server/datastore/users.ts | 13 ++++++- types/index.ts | 8 ++++ 8 files changed, 163 insertions(+), 20 deletions(-) create mode 100644 components/admin/recentProjects.tsx diff --git a/components/admin/activeUserMetrics.tsx b/components/admin/activeUserMetrics.tsx index 995dd6e44..cbf1fa11d 100644 --- a/components/admin/activeUserMetrics.tsx +++ b/components/admin/activeUserMetrics.tsx @@ -21,22 +21,32 @@ export default function ActiveUserMetrics({ Back to Active Users -
+
+
+
+
+
+
+ + + +
) } + diff --git a/components/admin/activeUsers.tsx b/components/admin/activeUsers.tsx index b9f692b6d..a9260b539 100644 --- a/components/admin/activeUsers.tsx +++ b/components/admin/activeUsers.tsx @@ -1,4 +1,4 @@ -import { Fragment, ReactNode } from 'react' +import { ReactNode } from 'react' import { ActiveUser } from '@/types' import Label from '@/components/label' import { UserAvatar } from '@/components/userSidebarItem' @@ -11,7 +11,7 @@ export default function ActiveUsers({ activeUsers: ActiveUser[] onSelectUser: (userID: number) => void }) { - const gridConfig = 'grid grid-cols-[44px_minmax(0,1fr)_200px_100px_100px_100px_100px_100px_100px]' + const gridConfig = 'grid grid-cols-[44px_200px_minmax(0,1fr)_100px_100px_100px_100px_100px_100px]' const startDate = Math.min(...activeUsers.map(user => user.startTimestamp)) @@ -24,10 +24,10 @@ export default function ActiveUsers({
- + - + @@ -50,8 +50,8 @@ export default function ActiveUsers({ {activeUsers.map(user => (
onSelectUser(user.id)}> - {user.email} {user.fullName} + {user.email} {FormatDate(user.lastActive, false)} {user.commentCount} {user.versionCount} diff --git a/components/admin/adminSidebar.tsx b/components/admin/adminSidebar.tsx index 57b7b1cf7..c9567c812 100644 --- a/components/admin/adminSidebar.tsx +++ b/components/admin/adminSidebar.tsx @@ -2,13 +2,16 @@ import { AdminRoute } from '@/src/client/clientRoute' import { SidebarButton, SidebarSection } from '../sidebar' import linkIcon from '@/public/chain.svg' import userIcon from '@/public/user.svg' +import fileIcon from '@/public/file.svg' export default function AdminSidebar({ onSelectWaitlist, onSelectActiveUsers, + onSelectRecentProjects, }: { onSelectWaitlist: () => void onSelectActiveUsers: () => void + onSelectRecentProjects: () => void }) { return ( <> @@ -18,6 +21,7 @@ export default function AdminSidebar({ + project.timestamp)) + + return ( + <> +
+ {recentProjects.length > 0 && ( + <> + +
+ + + + + + + + + + + + + + {recentProjects.map(project => ( +
+ + {project.name} + {FormatDate(project.timestamp, false)} + {project.workspace} + {project.creator} +
+ ))} +
+ + )} +
+ + ) +} + +const TableCell = ({ children }: { children?: ReactNode }) => ( +
+ {children} +
+) diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 1dc4b0fbe..c71ad7a73 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,5 +1,5 @@ import { withAdminSession } from '@/src/server/session' -import { ActiveUser, User, UserMetrics } from '@/types' +import { ActiveUser, RecentProject, User, UserMetrics } from '@/types' import { getActiveUsers, getMetricsForUser, getUsersWithoutAccess } from '@/src/server/datastore/users' import TopBar, { TopBarAccessoryItem, TopBarBackItem } from '@/components/topBar' import AdminSidebar from '@/components/admin/adminSidebar' @@ -10,26 +10,32 @@ import { useState } from 'react' import ActiveUsers from '@/components/admin/activeUsers' import ActiveUserMetrics from '@/components/admin/activeUserMetrics' import api from '@/src/client/admin/api' +import { getRecentProjects } from '@/src/server/datastore/projects' +import RecentProjects from '@/components/admin/recentProjects' const WaitlistItem = 'waitlist' const ActiveUsersItem = 'activeUsers' -type ActiveItem = typeof WaitlistItem | typeof ActiveUsersItem | ActiveUser +const RecentProjectsItem = 'recentProjects' +type ActiveItem = typeof WaitlistItem | typeof ActiveUsersItem | ActiveUser | typeof RecentProjectsItem export const getServerSideProps = withAdminSession(async ({ query }) => { - const { w: waitlist, u: userID } = ParseNumberQuery(query) + const { w: waitlist, u: userID, r: projects } = ParseNumberQuery(query) const activeUsers = await getActiveUsers() const waitlistUsers = await getUsersWithoutAccess() + const recentProjects = await getRecentProjects() const initialActiveItem: ActiveItem = waitlist ? WaitlistItem + : projects + ? RecentProjectsItem : userID ? { ...activeUsers.find(user => user.id === userID)! } : ActiveUsersItem const initialUserMetrics = userID ? await getMetricsForUser(userID) : null - return { props: { initialActiveItem, initialUserMetrics, activeUsers, waitlistUsers } } + return { props: { initialActiveItem, initialUserMetrics, activeUsers, waitlistUsers, recentProjects } } }) export default function Admin({ @@ -37,25 +43,39 @@ export default function Admin({ initialUserMetrics, activeUsers, waitlistUsers, + recentProjects, }: { initialActiveItem: ActiveItem initialUserMetrics: UserMetrics | null activeUsers: ActiveUser[] waitlistUsers: User[] + recentProjects: RecentProject[] }) { const [activeItem, setActiveItem] = useState(initialActiveItem) const [userMetrics, setUserMetrics] = useState(initialUserMetrics) const router = useRouter() - const selectItem = (item: typeof WaitlistItem | typeof ActiveUsersItem | number) => { - router.push(`/admin${item === WaitlistItem ? '?w=1' : item !== ActiveUsersItem ? `?u=${item}` : ''}`, undefined, { - shallow: true, - }) + const selectItem = (item: typeof WaitlistItem | typeof ActiveUsersItem | typeof RecentProjectsItem | number) => { + router.push( + `/admin${ + item === WaitlistItem + ? '?w=1' + : item === RecentProjectsItem + ? '?r=1' + : item !== ActiveUsersItem + ? `?u=${item}` + : '' + }`, + undefined, + { + shallow: true, + } + ) } - const { w: waitlist, u: userID } = ParseNumberQuery(router.query) - const currentQueryState = waitlist ? WaitlistItem : userID ?? ActiveUsersItem + const { w: waitlist, u: userID, r: projects } = ParseNumberQuery(router.query) + const currentQueryState = waitlist ? WaitlistItem : projects ? RecentProjectsItem : userID ?? ActiveUsersItem const [query, setQuery] = useState(currentQueryState) if (currentQueryState !== query) { setUserMetrics(null) @@ -63,7 +83,7 @@ export default function Admin({ setActiveItem(activeUsers.find(user => user.id === userID)!) api.getUserMetrics(userID).then(setUserMetrics) } else { - setActiveItem(waitlist ? WaitlistItem : ActiveUsersItem) + setActiveItem(waitlist ? WaitlistItem : projects ? RecentProjectsItem : ActiveUsersItem) } setQuery(currentQueryState) } @@ -72,7 +92,7 @@ export default function Admin({ <>
- router.push(ClientRoute.Home)} /> + router.push(ClientRoute.Home)} /> Admin @@ -80,12 +100,15 @@ export default function Admin({ selectItem(WaitlistItem)} onSelectActiveUsers={() => selectItem(ActiveUsersItem)} + onSelectRecentProjects={() => selectItem(RecentProjectsItem)} />
{activeItem === WaitlistItem ? ( ) : activeItem === ActiveUsersItem ? ( + ) : activeItem === RecentProjectsItem ? ( + ) : userMetrics ? ( router.back()} /> ) : null} diff --git a/src/server/datastore/projects.ts b/src/server/datastore/projects.ts index 8ad5e58b6..66a9c6b1e 100644 --- a/src/server/datastore/projects.ts +++ b/src/server/datastore/projects.ts @@ -10,9 +10,10 @@ import { getKeyedEntities, getKeyedEntity, getOrderedEntities, + getRecentEntities, getTimestamp, } from './datastore' -import { ActiveProject, Project, User } from '@/types' +import { ActiveProject, Project, RecentProject, User } from '@/types' import ShortUniqueId from 'short-unique-id' import { getAccessibleObjectIDs, @@ -330,3 +331,35 @@ export async function deleteProjectForUser(userID: number, projectID: number) { buildKey(Entity.PROJECT, projectID), ]) } + +export async function getRecentProjects(limit = 100): Promise { + const recentProjectsData = await getRecentEntities(Entity.PROJECT, limit, undefined, 'lastEditedAt') + + const workspacesData = await getKeyedEntities(Entity.WORKSPACE, [ + ...new Set([...recentProjectsData.map(projectData => projectData.workspaceID)]), + ]) + + const usersData = await getKeyedEntities(Entity.USER, [ + ...new Set([...workspacesData.map(workspaceData => workspaceData.userID)]), + ]) + + return recentProjectsData + .map(projectData => toRecentProject(projectData, workspacesData, usersData)) + .sort((a, b) => b.timestamp - a.timestamp) +} + +const toRecentProject = ( + projectData: any, + workspacesData: any[], + usersData: any[] +): RecentProject => { + const project = toProject(projectData, 0) + + const workspaceData = workspacesData.find(workspace => getID(workspace) === project.workspaceID) + const workspace = workspaceData.name + + const userData = usersData.find(user => getID(user) === workspaceData.userID) + const creator = userData.fullName + + return { ...project, workspace, creator } +} diff --git a/src/server/datastore/users.ts b/src/server/datastore/users.ts index 890afff43..738cd60bd 100644 --- a/src/server/datastore/users.ts +++ b/src/server/datastore/users.ts @@ -180,5 +180,16 @@ export async function getMetricsForUser(userID: number): Promise { Entity.ACCESS, and([buildFilter('userID', userID), buildFilter('kind', 'project')]) ) - return { createdWorkspaceCount, workspaceAccessCount, projectAccessCount } + const createdVersionCount = await getEntityCount(Entity.VERSION, 'userID', userID) + const createdCommentCount = await getEntityCount(Entity.COMMENT, 'userID', userID) + const createdEndpointCount = await getEntityCount(Entity.ENDPOINT, 'userID', userID) + + return { + createdWorkspaceCount, + workspaceAccessCount, + projectAccessCount, + createdVersionCount, + createdCommentCount, + createdEndpointCount, + } } diff --git a/types/index.ts b/types/index.ts index bc9272317..7c127a339 100644 --- a/types/index.ts +++ b/types/index.ts @@ -297,4 +297,12 @@ export type UserMetrics = { createdWorkspaceCount: number workspaceAccessCount: number projectAccessCount: number + createdVersionCount: number + createdCommentCount: number + createdEndpointCount: number +} + +export type RecentProject = Project & { + workspace: string + creator: string } From 49679a8b59dfea58c63fd4816bfe005ee4c66aaf Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 15:33:54 +0100 Subject: [PATCH 027/134] Split up recent activity and analytics sections --- components/admin/adminSidebar.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/components/admin/adminSidebar.tsx b/components/admin/adminSidebar.tsx index c9567c812..604ecc61d 100644 --- a/components/admin/adminSidebar.tsx +++ b/components/admin/adminSidebar.tsx @@ -19,16 +19,13 @@ export default function AdminSidebar({ - + - - + + + + From 0d5b6168f285e9a84113263593d09da175000c06 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 15:58:33 +0100 Subject: [PATCH 028/134] Allow to get metrics for specific project --- components/admin/recentProjectMetrics.tsx | 38 +++++++++++++ components/admin/recentProjects.tsx | 19 +++++-- pages/admin/index.tsx | 68 ++++++++++++++--------- pages/api/admin/getProjectMetrics.ts | 11 ++++ src/client/admin/api.ts | 5 +- src/server/datastore/projects.ts | 30 +++++----- types/index.ts | 6 ++ 7 files changed, 134 insertions(+), 43 deletions(-) create mode 100644 components/admin/recentProjectMetrics.tsx create mode 100644 pages/api/admin/getProjectMetrics.ts diff --git a/components/admin/recentProjectMetrics.tsx b/components/admin/recentProjectMetrics.tsx new file mode 100644 index 000000000..c8d96f76f --- /dev/null +++ b/components/admin/recentProjectMetrics.tsx @@ -0,0 +1,38 @@ +import { ProjectMetrics, RecentProject } from '@/types' +import Label from '@/components/label' +import Icon from '../icon' +import backIcon from '@/public/back.svg' + +export default function RecentProjectMetrics({ + project, + metrics, + onDismiss, +}: { + project: RecentProject + metrics: ProjectMetrics + onDismiss: () => void +}) { + return ( + <> +
+ +
+
+ +
+
+ + + +
+
+
+ + ) +} + diff --git a/components/admin/recentProjects.tsx b/components/admin/recentProjects.tsx index efb00f9ab..98435ba00 100644 --- a/components/admin/recentProjects.tsx +++ b/components/admin/recentProjects.tsx @@ -5,7 +5,13 @@ import { FormatDate } from '@/src/common/formatting' import Icon from '../icon' import fileIcon from '@/public/file.svg' -export default function RecentProjects({ recentProjects }: { recentProjects: RecentProject[] }) { +export default function RecentProjects({ + recentProjects, + onSelectProject, +}: { + recentProjects: RecentProject[] + onSelectProject: (projectID: number) => void +}) { const gridConfig = 'grid grid-cols-[40px_minmax(0,1fr)_200px_200px_200px]' const startDate = Math.min(...recentProjects.map(project => project.timestamp)) @@ -17,7 +23,7 @@ export default function RecentProjects({ recentProjects }: { recentProjects: Rec <>
- + @@ -31,8 +37,13 @@ export default function RecentProjects({ recentProjects }: { recentProjects: Rec {recentProjects.map(project => ( -
- +
onSelectProject(project.id)}> + + + {project.name} {FormatDate(project.timestamp, false)} {project.workspace} diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index c71ad7a73..26bb510ce 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,5 +1,5 @@ import { withAdminSession } from '@/src/server/session' -import { ActiveUser, RecentProject, User, UserMetrics } from '@/types' +import { ActiveUser, ProjectMetrics, RecentProject, User, UserMetrics } from '@/types' import { getActiveUsers, getMetricsForUser, getUsersWithoutAccess } from '@/src/server/datastore/users' import TopBar, { TopBarAccessoryItem, TopBarBackItem } from '@/components/topBar' import AdminSidebar from '@/components/admin/adminSidebar' @@ -10,49 +10,59 @@ import { useState } from 'react' import ActiveUsers from '@/components/admin/activeUsers' import ActiveUserMetrics from '@/components/admin/activeUserMetrics' import api from '@/src/client/admin/api' -import { getRecentProjects } from '@/src/server/datastore/projects' +import { getMetricsForProject, getRecentProjects } from '@/src/server/datastore/projects' import RecentProjects from '@/components/admin/recentProjects' +import RecentProjectMetrics from '@/components/admin/recentProjectMetrics' const WaitlistItem = 'waitlist' const ActiveUsersItem = 'activeUsers' const RecentProjectsItem = 'recentProjects' -type ActiveItem = typeof WaitlistItem | typeof ActiveUsersItem | ActiveUser | typeof RecentProjectsItem +type ActiveItem = typeof WaitlistItem | typeof ActiveUsersItem | typeof RecentProjectsItem | ActiveUser | RecentProject +const activeItemIsUser = (item: ActiveItem): item is ActiveUser => typeof item === 'object' && 'fullName' in item +const activeItemIsProject = (item: ActiveItem): item is RecentProject => typeof item === 'object' && 'name' in item export const getServerSideProps = withAdminSession(async ({ query }) => { - const { w: waitlist, u: userID, r: projects } = ParseNumberQuery(query) + const { w: waitlist, p: projects, i: itemID } = ParseNumberQuery(query) const activeUsers = await getActiveUsers() const waitlistUsers = await getUsersWithoutAccess() const recentProjects = await getRecentProjects() + const activeUser = activeUsers.find(user => user.id === itemID) + const recentProject = recentProjects.find(project => project.id === itemID) + const initialActiveItem: ActiveItem = waitlist ? WaitlistItem : projects ? RecentProjectsItem - : userID - ? { ...activeUsers.find(user => user.id === userID)! } - : ActiveUsersItem + : activeUser ?? recentProject ?? ActiveUsersItem - const initialUserMetrics = userID ? await getMetricsForUser(userID) : null + const initialUserMetrics = activeUser ? await getMetricsForUser(activeUser.id) : null + const initialProjectMetrics = recentProject + ? await getMetricsForProject(recentProject.id, recentProject.workspaceID) + : null - return { props: { initialActiveItem, initialUserMetrics, activeUsers, waitlistUsers, recentProjects } } + return { props: { initialActiveItem, initialUserMetrics, initialProjectMetrics, activeUsers, waitlistUsers, recentProjects } } }) export default function Admin({ initialActiveItem, initialUserMetrics, + initialProjectMetrics, activeUsers, - waitlistUsers, recentProjects, + waitlistUsers, }: { initialActiveItem: ActiveItem initialUserMetrics: UserMetrics | null + initialProjectMetrics: ProjectMetrics | null activeUsers: ActiveUser[] waitlistUsers: User[] recentProjects: RecentProject[] }) { const [activeItem, setActiveItem] = useState(initialActiveItem) const [userMetrics, setUserMetrics] = useState(initialUserMetrics) + const [projectMetrics, setProjectMetrics] = useState(initialProjectMetrics) const router = useRouter() @@ -62,9 +72,9 @@ export default function Admin({ item === WaitlistItem ? '?w=1' : item === RecentProjectsItem - ? '?r=1' + ? '?p=1' : item !== ActiveUsersItem - ? `?u=${item}` + ? `?i=${item}` : '' }`, undefined, @@ -74,14 +84,20 @@ export default function Admin({ ) } - const { w: waitlist, u: userID, r: projects } = ParseNumberQuery(router.query) - const currentQueryState = waitlist ? WaitlistItem : projects ? RecentProjectsItem : userID ?? ActiveUsersItem + const { w: waitlist, p: projects, i: itemID } = ParseNumberQuery(router.query) + const currentQueryState = waitlist ? WaitlistItem : projects ? RecentProjectsItem : itemID ?? ActiveUsersItem const [query, setQuery] = useState(currentQueryState) if (currentQueryState !== query) { setUserMetrics(null) - if (userID) { - setActiveItem(activeUsers.find(user => user.id === userID)!) - api.getUserMetrics(userID).then(setUserMetrics) + setProjectMetrics(null) + const activeUser = activeUsers.find(user => user.id === itemID) + const recentProject = recentProjects.find(project => project.id === itemID) + if (activeUser) { + setActiveItem(activeUser) + api.getUserMetrics(activeUser.id).then(setUserMetrics) + } else if (recentProject) { + setActiveItem(recentProject) + api.getProjectMetrics(recentProject.id, recentProject.workspaceID).then(setProjectMetrics) } else { setActiveItem(waitlist ? WaitlistItem : projects ? RecentProjectsItem : ActiveUsersItem) } @@ -103,15 +119,17 @@ export default function Admin({ onSelectRecentProjects={() => selectItem(RecentProjectsItem)} />
- {activeItem === WaitlistItem ? ( - - ) : activeItem === ActiveUsersItem ? ( - - ) : activeItem === RecentProjectsItem ? ( - - ) : userMetrics ? ( + {activeItem === WaitlistItem && } + {activeItem === ActiveUsersItem && } + {activeItem === RecentProjectsItem && ( + + )} + {userMetrics && activeItemIsUser(activeItem) && ( router.back()} /> - ) : null} + )} + {projectMetrics && activeItemIsProject(activeItem) && ( + router.back()} /> + )}
diff --git a/pages/api/admin/getProjectMetrics.ts b/pages/api/admin/getProjectMetrics.ts new file mode 100644 index 000000000..8d5e22706 --- /dev/null +++ b/pages/api/admin/getProjectMetrics.ts @@ -0,0 +1,11 @@ +import { getMetricsForProject } from '@/src/server/datastore/projects' +import { withAdminUserRoute } from '@/src/server/session' +import { ProjectMetrics } from '@/types' +import type { NextApiRequest, NextApiResponse } from 'next' + +async function getProjectMetrics(req: NextApiRequest, res: NextApiResponse) { + const projectMetrics = await getMetricsForProject(req.body.projectID, req.body.workspaceID) + res.json(projectMetrics) +} + +export default withAdminUserRoute(getProjectMetrics) diff --git a/src/client/admin/api.ts b/src/client/admin/api.ts index e4e16c440..c73a5a937 100644 --- a/src/client/admin/api.ts +++ b/src/client/admin/api.ts @@ -1,5 +1,5 @@ import { postToAPI } from '@/src/client/api' -import { User, UserMetrics } from '@/types' +import { ProjectMetrics, User, UserMetrics } from '@/types' const post = (apiCall: Function, json: any = {}) => { return postToAPI('/api/admin', apiCall.name, json, 'json') @@ -15,6 +15,9 @@ const api = { getUserMetrics: async function (userID: number): Promise { return post(this.getUserMetrics, { userID }) }, + getProjectMetrics: async function (projectID: number, workspaceID: number): Promise { + return post(this.getProjectMetrics, { projectID, workspaceID }) + }, } export default api diff --git a/src/server/datastore/projects.ts b/src/server/datastore/projects.ts index 66a9c6b1e..3dbfd777f 100644 --- a/src/server/datastore/projects.ts +++ b/src/server/datastore/projects.ts @@ -5,6 +5,7 @@ import { buildKey, getDatastore, getEntities, + getEntityCount, getEntityKeys, getID, getKeyedEntities, @@ -13,7 +14,7 @@ import { getRecentEntities, getTimestamp, } from './datastore' -import { ActiveProject, Project, RecentProject, User } from '@/types' +import { ActiveProject, Project, ProjectMetrics, RecentProject, User } from '@/types' import ShortUniqueId from 'short-unique-id' import { getAccessibleObjectIDs, @@ -86,11 +87,6 @@ async function getProjectAndWorkspaceUsers(projectID: number, workspaceID: numbe return users.sort((a, b) => a.fullName.localeCompare(b.fullName)).map(toUser) } -export async function getProjectUsers(projectID: number): Promise { - const projectData = await getTrustedProjectData(projectID) - return getProjectAndWorkspaceUsers(projectID, projectData.workspaceID) -} - async function loadEndpoints(projectID: number, apiKeyDev: string) { const endpoints = await getOrderedEntities(Entity.ENDPOINT, 'projectID', projectID) const usages = await getEntities(Entity.USAGE, 'projectID', projectID) @@ -348,18 +344,26 @@ export async function getRecentProjects(limit = 100): Promise { .sort((a, b) => b.timestamp - a.timestamp) } -const toRecentProject = ( - projectData: any, - workspacesData: any[], - usersData: any[] -): RecentProject => { +const toRecentProject = (projectData: any, workspacesData: any[], usersData: any[]): RecentProject => { const project = toProject(projectData, 0) - + const workspaceData = workspacesData.find(workspace => getID(workspace) === project.workspaceID) const workspace = workspaceData.name - + const userData = usersData.find(user => getID(user) === workspaceData.userID) const creator = userData.fullName return { ...project, workspace, creator } } + +export async function getMetricsForProject(projectID: number, workspaceID: number): Promise { + const promptCount = await getEntityCount(Entity.PROMPT, 'projectID', projectID) + const chainCount = await getEntityCount(Entity.CHAIN, 'projectID', projectID) + const endpointCount = await getEntityCount(Entity.ENDPOINT, 'projectID', projectID) + + return { + promptCount, + chainCount, + endpointCount, + } +} diff --git a/types/index.ts b/types/index.ts index 7c127a339..45883f02e 100644 --- a/types/index.ts +++ b/types/index.ts @@ -306,3 +306,9 @@ export type RecentProject = Project & { workspace: string creator: string } + +export type ProjectMetrics = { + promptCount: number + chainCount: number + endpointCount: number +} From 96b09075c5846b84a748abdadd353e9b5b695a70 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 16:07:21 +0100 Subject: [PATCH 029/134] Include endpoint analytics with project metrics --- components/admin/activeUserMetrics.tsx | 21 +++++++++----------- components/admin/recentProjectMetrics.tsx | 15 +++++++++----- components/endpoints/analyticsDashboards.tsx | 5 +++-- src/server/datastore/projects.ts | 4 ++++ types/index.ts | 1 + 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/components/admin/activeUserMetrics.tsx b/components/admin/activeUserMetrics.tsx index cbf1fa11d..6b467efc3 100644 --- a/components/admin/activeUserMetrics.tsx +++ b/components/admin/activeUserMetrics.tsx @@ -28,25 +28,22 @@ export default function ActiveUserMetrics({ {user.fullName} ({user.email})
-
-
- - - + + +
- - - + + +
) } - diff --git a/components/admin/recentProjectMetrics.tsx b/components/admin/recentProjectMetrics.tsx index c8d96f76f..33d1aa47b 100644 --- a/components/admin/recentProjectMetrics.tsx +++ b/components/admin/recentProjectMetrics.tsx @@ -2,6 +2,8 @@ import { ProjectMetrics, RecentProject } from '@/types' import Label from '@/components/label' import Icon from '../icon' import backIcon from '@/public/back.svg' +import AnalyticsDashboards from '../endpoints/analyticsDashboards' +import { FormatDate } from '@/src/common/formatting' export default function RecentProjectMetrics({ project, @@ -22,17 +24,20 @@ export default function RecentProjectMetrics({
+
- - - + + +
+
+ +
) } - diff --git a/components/endpoints/analyticsDashboards.tsx b/components/endpoints/analyticsDashboards.tsx index d3379fec6..e96dbe8e0 100644 --- a/components/endpoints/analyticsDashboards.tsx +++ b/components/endpoints/analyticsDashboards.tsx @@ -27,7 +27,7 @@ export default function AnalyticsDashboards({ refreshAnalytics, }: { analytics?: Analytics - refreshAnalytics: (dayRange: number) => void + refreshAnalytics?: (dayRange: number) => void }) { const margin = { left: 0, right: 0, top: 10, bottom: 0 } @@ -57,7 +57,8 @@ export default function AnalyticsDashboards({ const dayRange = recentUsage.length const canToggleDayRange = dayRange === 7 || recentUsage.slice(-2 * 7).reduce((acc, usage) => acc + usage.requests, 0) > 0 - const toggleDayRange = canToggleDayRange ? () => refreshAnalytics(dayRange === 30 ? 7 : 30) : undefined + const toggleDayRange = + canToggleDayRange && refreshAnalytics ? () => refreshAnalytics(dayRange === 30 ? 7 : 30) : undefined const formatRequestPayload = (payload: any[]) => `${payload[0].value + payload[1].value} requests${payload[0].value > 0 ? ` (${payload[0].value} failures)` : ''}` diff --git a/src/server/datastore/projects.ts b/src/server/datastore/projects.ts index 3dbfd777f..33aa36ef1 100644 --- a/src/server/datastore/projects.ts +++ b/src/server/datastore/projects.ts @@ -31,6 +31,7 @@ import { ensureWorkspaceAccess } from './workspaces' import { toUsage } from './usage' import { StripVariableSentinels } from '@/src/common/formatting' import { Key } from '@google-cloud/datastore' +import { getAnalyticsForProject } from './analytics' export async function migrateProjects(postMerge: boolean) { if (postMerge) { @@ -361,9 +362,12 @@ export async function getMetricsForProject(projectID: number, workspaceID: numbe const chainCount = await getEntityCount(Entity.CHAIN, 'projectID', projectID) const endpointCount = await getEntityCount(Entity.ENDPOINT, 'projectID', projectID) + const analytics = await getAnalyticsForProject(0, projectID, true) + return { promptCount, chainCount, endpointCount, + analytics, } } diff --git a/types/index.ts b/types/index.ts index 45883f02e..59d531601 100644 --- a/types/index.ts +++ b/types/index.ts @@ -311,4 +311,5 @@ export type ProjectMetrics = { promptCount: number chainCount: number endpointCount: number + analytics: Analytics } From 5930c847ca12cfc62342df1315ea062d6b63dbd1 Mon Sep 17 00:00:00 2001 From: Hannes Verlinde Date: Mon, 16 Oct 2023 16:19:48 +0100 Subject: [PATCH 030/134] Include list of active users with project metrics --- components/admin/activeUserMetrics.tsx | 2 +- components/admin/activeUsers.tsx | 15 +++++++++++---- components/admin/recentProjectMetrics.tsx | 10 +++++++--- components/admin/recentProjects.tsx | 2 +- pages/admin/index.tsx | 13 ++++++++++--- src/server/datastore/projects.ts | 7 ++++++- src/server/datastore/users.ts | 22 +++++++++++++--------- types/index.ts | 1 + 8 files changed, 50 insertions(+), 22 deletions(-) diff --git a/components/admin/activeUserMetrics.tsx b/components/admin/activeUserMetrics.tsx index 6b467efc3..92dd76d18 100644 --- a/components/admin/activeUserMetrics.tsx +++ b/components/admin/activeUserMetrics.tsx @@ -21,7 +21,7 @@ export default function ActiveUserMetrics({ Back to Active Users -
+