console: add components to manage query collections

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5658
GitOrigin-RevId: a962f85e53396cd1d2cd64f46b91d3c042641ab3
This commit is contained in:
Daniele Cammareri 2022-09-01 20:31:07 +02:00 committed by hasura-bot
parent 6ab128a7e3
commit 2e83b22591
45 changed files with 2274 additions and 1 deletions

View File

@ -9,6 +9,7 @@ import '../src/theme/tailwind.css';
import { store } from '../src/store';
import '../src/components/Common/Common.module.scss';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
import 'react-loading-skeleton/dist/skeleton.css';
const channel = addons.getChannel();
initialize();

View File

@ -16,10 +16,12 @@
"@hookform/resolvers": "2.8.10",
"@radix-ui/react-collapsible": "^0.1.1",
"@radix-ui/react-dialog": "^0.1.7",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@radix-ui/react-switch": "^0.1.5",
"@radix-ui/react-tabs": "^1.0.0",
"@radix-ui/react-tooltip": "^0.1.0",
"@reduxjs/toolkit": "^1.5.1",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.get": "^4.4.6",
"@xstate/react": "^2.0.0",
"ace-builds": "^1.4.11",
@ -43,6 +45,7 @@
"jsonwebtoken": "8.5.1",
"jwt-decode": "^3.0.0",
"less": "3.11.1",
"lodash.debounce": "^4.0.8",
"lodash.get": "4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.merge": "4.6.2",
@ -62,6 +65,7 @@
"react-hook-form": "7.15.4",
"react-icons": "^4.3.1",
"react-json-view": "^1.21.3",
"react-loading-skeleton": "^3.1.0",
"react-modal": "3.11.2",
"react-notification-system-redux": "2.0.1",
"react-progress-bar-plus": "1.3.1",
@ -3003,6 +3007,32 @@
"@f/map-obj": "^1.2.2"
}
},
"node_modules/@floating-ui/core": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
},
"node_modules/@floating-ui/dom": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
"dependencies": {
"@floating-ui/core": "^0.7.3"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
"integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
"dependencies": {
"@floating-ui/dom": "^0.5.3",
"use-isomorphic-layout-effect": "^1.1.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@ -4966,6 +4996,126 @@
"react": "^16.8 || ^17.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-1.0.0.tgz",
"integrity": "sha512-Ptben3TxPWrZLbInO7zjAK73kmjYuStsxfg6ujgt+EywJyREoibhZYnsSNqC+UiOtl4PdW/MOHhxVDtew5fouQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-menu": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-controllable-state": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-focus-guards": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-0.1.0.tgz",
@ -5018,6 +5168,297 @@
"react": "^16.8 || ^17.0"
}
},
"node_modules/@radix-ui/react-menu": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-1.0.0.tgz",
"integrity": "sha512-icW4C64T6nHh3Z4Q1fxO1RlSShouFF4UpUmPV8FLaJZfphDljannKErDuALDx4ClRLihAPZ9i+PrLNPoWS2DMA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-collection": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-direction": "1.0.0",
"@radix-ui/react-dismissable-layer": "1.0.0",
"@radix-ui/react-focus-guards": "1.0.0",
"@radix-ui/react-focus-scope": "1.0.0",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-popper": "1.0.0",
"@radix-ui/react-portal": "1.0.0",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-roving-focus": "1.0.0",
"@radix-ui/react-slot": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.4"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
"integrity": "sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0",
"@radix-ui/react-use-escape-keydown": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.0.tgz",
"integrity": "sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@floating-ui/react-dom": "0.7.2",
"@radix-ui/react-arrow": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0",
"@radix-ui/react-use-rect": "1.0.0",
"@radix-ui/react-use-size": "1.0.0",
"@radix-ui/rect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
"integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/rect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
"integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
"integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
"dependencies": {
"react-remove-scroll-bar": "^2.3.3",
"react-style-singleton": "^2.2.1",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.0",
"use-sidecar": "^1.1.2"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-0.1.4.tgz",
@ -10786,6 +11227,14 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
},
"node_modules/@types/lodash.debounce": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz",
"integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.get": {
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz",
@ -33876,6 +34325,14 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"node_modules/react-loading-skeleton": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw==",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/react-merge-refs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
@ -43622,6 +44079,28 @@
"@f/map-obj": "^1.2.2"
}
},
"@floating-ui/core": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
},
"@floating-ui/dom": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
"requires": {
"@floating-ui/core": "^0.7.3"
}
},
"@floating-ui/react-dom": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
"integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
"requires": {
"@floating-ui/dom": "^0.5.3",
"use-isomorphic-layout-effect": "^1.1.1"
}
},
"@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@ -45177,6 +45656,99 @@
"@radix-ui/react-use-escape-keydown": "0.1.0"
}
},
"@radix-ui/react-dropdown-menu": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-1.0.0.tgz",
"integrity": "sha512-Ptben3TxPWrZLbInO7zjAK73kmjYuStsxfg6ujgt+EywJyREoibhZYnsSNqC+UiOtl4PdW/MOHhxVDtew5fouQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-menu": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-controllable-state": "1.0.0"
},
"dependencies": {
"@radix-ui/primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-context": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.0"
}
},
"@radix-ui/react-primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.0"
}
},
"@radix-ui/react-slot": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0"
}
},
"@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-use-controllable-state": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.0"
}
},
"@radix-ui/react-use-layout-effect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
"requires": {
"@babel/runtime": "^7.13.10"
}
}
}
},
"@radix-ui/react-focus-guards": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-0.1.0.tgz",
@ -45217,6 +45789,225 @@
"@radix-ui/react-primitive": "0.1.4"
}
},
"@radix-ui/react-menu": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-1.0.0.tgz",
"integrity": "sha512-icW4C64T6nHh3Z4Q1fxO1RlSShouFF4UpUmPV8FLaJZfphDljannKErDuALDx4ClRLihAPZ9i+PrLNPoWS2DMA==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-collection": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-direction": "1.0.0",
"@radix-ui/react-dismissable-layer": "1.0.0",
"@radix-ui/react-focus-guards": "1.0.0",
"@radix-ui/react-focus-scope": "1.0.0",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-popper": "1.0.0",
"@radix-ui/react-portal": "1.0.0",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-roving-focus": "1.0.0",
"@radix-ui/react-slot": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.4"
},
"dependencies": {
"@radix-ui/primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-arrow": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
"integrity": "sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.0"
}
},
"@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-context": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-dismissable-layer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0",
"@radix-ui/react-use-escape-keydown": "1.0.0"
}
},
"@radix-ui/react-focus-guards": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-focus-scope": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0"
}
},
"@radix-ui/react-id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.0"
}
},
"@radix-ui/react-popper": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.0.tgz",
"integrity": "sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@floating-ui/react-dom": "0.7.2",
"@radix-ui/react-arrow": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0",
"@radix-ui/react-use-rect": "1.0.0",
"@radix-ui/react-use-size": "1.0.0",
"@radix-ui/rect": "1.0.0"
}
},
"@radix-ui/react-portal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.0"
}
},
"@radix-ui/react-presence": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0"
}
},
"@radix-ui/react-primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.0"
}
},
"@radix-ui/react-slot": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0"
}
},
"@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-use-escape-keydown": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.0"
}
},
"@radix-ui/react-use-layout-effect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-use-rect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
"integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/rect": "1.0.0"
}
},
"@radix-ui/react-use-size": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
"integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.0"
}
},
"@radix-ui/rect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
"integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"react-remove-scroll": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
"requires": {
"react-remove-scroll-bar": "^2.3.3",
"react-style-singleton": "^2.2.1",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.0",
"use-sidecar": "^1.1.2"
}
}
}
},
"@radix-ui/react-popper": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-0.1.4.tgz",
@ -49578,6 +50369,14 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
},
"@types/lodash.debounce": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz",
"integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==",
"requires": {
"@types/lodash": "*"
}
},
"@types/lodash.get": {
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz",
@ -67644,6 +68443,12 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-loading-skeleton": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw==",
"requires": {}
},
"react-merge-refs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",

View File

@ -70,6 +70,7 @@
"@hookform/resolvers": "2.8.10",
"@radix-ui/react-collapsible": "^0.1.1",
"@radix-ui/react-dialog": "^0.1.7",
"@radix-ui/react-dropdown-menu": "1.0.0",
"@radix-ui/react-switch": "^0.1.5",
"@radix-ui/react-tabs": "^1.0.0",
"@radix-ui/react-tooltip": "^0.1.0",
@ -97,6 +98,7 @@
"jsonwebtoken": "8.5.1",
"jwt-decode": "^3.0.0",
"less": "3.11.1",
"lodash.debounce": "^4.0.8",
"lodash.get": "4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.merge": "4.6.2",
@ -116,6 +118,7 @@
"react-hook-form": "7.15.4",
"react-icons": "^4.3.1",
"react-json-view": "^1.21.3",
"react-loading-skeleton": "^3.1.0",
"react-modal": "3.11.2",
"react-notification-system-redux": "2.0.1",
"react-progress-bar-plus": "1.3.1",
@ -182,6 +185,7 @@
"@types/jquery": "3.3.33",
"@types/jwt-decode": "2.2.1",
"@types/lodash": "^4.14.159",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.isequal": "^4.5.6",
"@types/lodash.merge": "^4.6.6",
"@types/lodash.pickby": "^4.6.7",

View File

@ -10,6 +10,7 @@ import { telemetryNotificationShown } from '../../telemetry/Actions';
import { showTelemetryNotification } from '../../telemetry/Notifications';
import globals from '../../Globals';
import styles from './App.module.scss';
import 'react-loading-skeleton/dist/skeleton.css';
import { theme } from '../UIKit/theme';

View File

@ -0,0 +1,23 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import { ComponentMeta } from '@storybook/react';
import { AllowListSidebar } from './AllowListSidebar';
import { handlers } from '../../../QueryCollections/hooks/useQueryCollections/mocks/handlers.mock';
export default {
title: 'Features/Allow List/Allow List Sidebar',
component: AllowListSidebar,
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(1500),
},
} as ComponentMeta<typeof AllowListSidebar>;
export const Primary = () => (
<AllowListSidebar selectedCollectionQuery="allowed_queries" />
);

View File

@ -0,0 +1,50 @@
import debounce from 'lodash.debounce';
import React from 'react';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import { useServerConfig } from '@/hooks';
import { AllowListSidebarHeader } from './AllowListSidebarHeader';
import { QueryCollectionList } from './QueryCollectionList';
import { AllowListSidebarSearchForm } from './AllowListSidebarSearchForm';
interface AllowListSidebarProps {
selectedCollectionQuery: string;
}
export const AllowListSidebar: React.FC<AllowListSidebarProps> = props => {
const { selectedCollectionQuery } = props;
const [search, setSearch] = React.useState('');
const debouncedSearch = React.useMemo(() => debounce(setSearch, 300), []);
const { data: configData, isLoading: isConfigLoading } = useServerConfig();
const renderInstructions =
!isConfigLoading && !configData?.is_allow_list_enabled;
return (
<div>
<AllowListSidebarHeader />
<AllowListSidebarSearchForm
setSearch={(searchString: string) => debouncedSearch(searchString)}
/>
<QueryCollectionList
selectedCollectionQuery={selectedCollectionQuery}
search={search}
/>
{renderInstructions && (
<IndicatorCard status="info">
<p>
Want to enable your allow list? You can set{' '}
<span className="text-red-600 font-mono bg-red-50 rounded px-1.5 py-0.5">
HASURA_GRAPHQL_ENABLE_ALLOWLIST
</span>{' '}
to{' '}
<span className="text-red-600 font-mono bg-red-50 rounded px-1.5 py-0.5">
true
</span>{' '}
so that your API will only allow accepted pre-selected operations.
</p>
</IndicatorCard>
)}
</div>
);
};

View File

@ -0,0 +1,36 @@
import { Button } from '@/new-components/Button';
import React from 'react';
import { FaFolderPlus } from 'react-icons/fa';
import { QueryCollectionCreateDialog } from './QueryCollectionCreateDialog';
import { AllowListStatus } from './AllowListStatus';
export const AllowListSidebarHeader = () => {
const [isCreateModalOpen, setIsCreateModalOpen] = React.useState(false);
return (
<>
{isCreateModalOpen && (
<QueryCollectionCreateDialog
onClose={() => setIsCreateModalOpen(false)}
/>
)}
<div className="flex items-center">
<span className="text-sm font-semibold text-muted uppercase tracking-wider">
Allow List
</span>
<div className="ml-1.5">
<AllowListStatus />
</div>
<div className="ml-auto">
<Button
icon={<FaFolderPlus />}
size="sm"
onClick={() => setIsCreateModalOpen(true)}
>
Add Collection
</Button>
</div>
</div>
</>
);
};

View File

@ -0,0 +1,40 @@
import z from 'zod';
import { Form, InputField } from '@/new-components/Form';
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import { FaSearch } from 'react-icons/fa';
const schema = z.object({
search: z.string(),
});
interface AllowListSidebarSearchFormProps {
setSearch: (search: string) => void;
}
const SearchInput: React.FC<AllowListSidebarSearchFormProps> = ({
setSearch,
}) => {
const { watch } = useFormContext();
const search = watch('search');
useEffect(() => {
setSearch(search);
}, [search]);
return (
<InputField
id="search"
placeholder="Search Collections..."
icon={<FaSearch />}
name="search"
/>
);
};
export const AllowListSidebarSearchForm: React.FC<AllowListSidebarSearchFormProps> =
({ setSearch }) => {
return (
<Form schema={schema} onSubmit={() => {}} className="p-0">
{() => <SearchInput setSearch={setSearch} />}
</Form>
);
};

View File

@ -0,0 +1,30 @@
import Skeleton from 'react-loading-skeleton';
import { useServerConfig } from '@/hooks';
import { Badge } from '@/new-components/Badge';
import { Tooltip } from '@/new-components/Tooltip';
import React from 'react';
import { FaExclamationTriangle } from 'react-icons/fa';
export const AllowListStatus = () => {
const { data: configData, isLoading, isError } = useServerConfig();
if (isLoading) {
return <Skeleton width={80} height={20} />;
}
if (isError) {
return (
<Tooltip tooltipContentChildren="Status unknown. Config API is currently unavailable.">
<Badge color="yellow" className="-ml-1 p-0">
<FaExclamationTriangle />
</Badge>
</Tooltip>
);
}
if (configData?.is_allow_list_enabled) {
return <Badge color="green">Enabled</Badge>;
}
return <Badge color="red">disabled</Badge>;
};

View File

@ -0,0 +1,63 @@
import React from 'react';
import z from 'zod';
import { Dialog } from '@/new-components/Dialog';
import { Form, InputField } from '@/new-components/Form';
import { useCreateQueryCollection } from '../../../QueryCollections/hooks/useCreateQueryCollection';
interface QueryCollectionCreateDialogProps {
onClose: () => void;
}
const schema = z.object({
name: z.string().min(1, 'Name is required'),
});
export const QueryCollectionCreateDialog: React.FC<QueryCollectionCreateDialogProps> =
props => {
const { onClose } = props;
const { createQueryCollection, isLoading } = useCreateQueryCollection();
return (
<Form schema={schema} onSubmit={() => {}}>
{({ watch, setError, trigger }) => {
const name = watch('name');
return (
<Dialog hasBackdrop title="Create Collection" onClose={onClose}>
<>
<div className="p-4">
<InputField
id="name"
name="name"
label="New Collection Name"
placeholder="New Collection Name..."
/>
</div>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Create Collection"
onClose={onClose}
onSubmit={async () => {
if (await trigger()) {
// TODO: remove as when proper form types will be available
createQueryCollection(name as string, {
addToAllowList: true,
onSuccess: () => {
onClose();
},
onError: error => {
setError('name', {
type: 'manual',
message: (error as Error).message,
});
},
});
}
}}
isLoading={isLoading}
/>
</>
</Dialog>
);
}}
</Form>
);
};

View File

@ -0,0 +1,30 @@
import clsx from 'clsx';
import React from 'react';
import { FaFolder, FaFolderOpen } from 'react-icons/fa';
interface QueryCollectionItemProps extends React.ComponentProps<'a'> {
name: string;
selected: boolean;
}
export const QueryCollectionItem: React.FC<QueryCollectionItemProps> =
props => {
const { name, selected, className, ...rest } = props;
const Icon = selected ? FaFolderOpen : FaFolder;
const textClassName = selected
? 'text-amber-500 hover:text-amber-600'
: 'hover:bg-gray-100 hover:text-gray-900';
return (
<a
className={clsx(
`cursor-pointer flex items-center text-muted rounded py-1.5 px-sm`,
textClassName,
className
)}
{...rest}
>
<Icon className="mr-1.5" />
{name}
</a>
);
};

View File

@ -0,0 +1,42 @@
import React from 'react';
import Skeleton from 'react-loading-skeleton';
import { useQueryCollections } from '../../../QueryCollections/hooks/useQueryCollections';
import { QueryCollectionItem } from './QueryCollectionItem';
interface QueryCollectionItemProps {
selectedCollectionQuery: string;
search: string;
}
export const QueryCollectionList = ({
selectedCollectionQuery,
search,
}: QueryCollectionItemProps) => {
const { data: queryCollections, isLoading, isError } = useQueryCollections();
if (isError) {
return null; // TOOD: we're waiting for error state design
}
if (isLoading) {
return <Skeleton width={200} height={20} />;
}
const matchingQueryCollections = (queryCollections || []).filter(
({ name }) => !search || name.includes(search)
);
return (
<div className="-mt-2 mb-xs">
{queryCollections &&
matchingQueryCollections.map(({ name }) => (
<QueryCollectionItem
href="#"
key={name}
name={name}
selected={name === selectedCollectionQuery}
/>
))}
</div>
);
};

View File

@ -0,0 +1 @@
export * from './AllowListSidebar';

View File

@ -0,0 +1,31 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import { ComponentMeta } from '@storybook/react';
import { QueryCollectionHeader } from './QueryCollectionHeader';
import { handlers } from '../../../QueryCollections/hooks/useQueryCollections/mocks/handlers.mock';
import { createMetadata } from '../../../QueryCollections/hooks/useQueryCollections/mocks/mockData';
export default {
title: 'Features/Allow List/Query Collection Header',
component: QueryCollectionHeader,
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(1500, 'http://localhost:8080'),
},
} as ComponentMeta<typeof QueryCollectionHeader>;
export const Primary = () => {
const metadata = createMetadata();
return (
metadata.metadata.query_collections?.[0] && (
<QueryCollectionHeader
queryCollection={metadata.metadata.query_collections?.[0]}
/>
)
);
};

View File

@ -0,0 +1,87 @@
import { getConfirmation } from '@/components/Common/utils/jsUtils';
import { QueryCollectionEntry } from '@/metadata/types';
import { Button } from '@/new-components/Button';
import { DropdownButton } from '@/new-components/DropdownButton';
import React, { useState } from 'react';
import { FaEllipsisH, FaPlusCircle } from 'react-icons/fa';
import { useDeleteQueryCollections } from '../../../QueryCollections/hooks/useDeleteQueryCollections';
import { QueryCollectionRenameDialog } from './QueryCollectionRenameDialog';
interface QueryCollectionHeaderProps {
queryCollection: QueryCollectionEntry;
}
export const QueryCollectionHeader: React.FC<QueryCollectionHeaderProps> =
props => {
const { queryCollection } = props;
const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);
const { deleteQueryCollection } = useDeleteQueryCollections();
return (
<>
{isRenameModalOpen && (
<QueryCollectionRenameDialog
currentName={queryCollection.name}
onClose={() => {
setIsRenameModalOpen(false);
}}
/>
)}
<div className="flex items-center mb-xs">
<div>
<h1 className="text-xl font-semibold">{queryCollection.name}</h1>
<p className="text-muted">
Add queries to the collection to create a safe list of operations
which can be run against your GraphQL API.
</p>
</div>
<div className="relative ml-auto mr-sm">
<DropdownButton
items={[
[
<div
className="font-semibold"
onClick={() => {
// this is a workaround for a weird but caused by interaction of radix ui dialog and dropdown menu
setTimeout(() => {
setIsRenameModalOpen(true);
}, 0);
}}
>
Edit Collection Name
</div>,
<div
className="font-semibold text-red-600"
onClick={() => {
const confirmMessage = `This will permanently delete the query collection "${queryCollection.name}"`;
const isOk = getConfirmation(
confirmMessage,
true,
queryCollection.name
);
if (isOk) {
deleteQueryCollection(queryCollection.name, {
onSuccess: () => {
// fire notification
},
onError: () => {
// fire notification
},
});
}
}}
>
Delete Collection
</div>,
],
]}
>
<FaEllipsisH />
</DropdownButton>
</div>
<Button mode="primary" icon={<FaPlusCircle />}>
Add Operation
</Button>
</div>
</>
);
};

View File

@ -0,0 +1,63 @@
import React from 'react';
import z from 'zod';
import { Dialog } from '@/new-components/Dialog';
import { Form, InputField } from '@/new-components/Form';
import { useRenameQueryCollection } from '../../../QueryCollections/hooks/useRenameQueryCollection';
interface QueryCollectionCreateDialogProps {
onClose: () => void;
currentName: string;
}
const schema = z.object({
name: z.string().min(1, 'Name is required'),
});
export const QueryCollectionRenameDialog: React.FC<QueryCollectionCreateDialogProps> =
props => {
const { onClose, currentName } = props;
const { renameQueryCollection, isLoading } = useRenameQueryCollection();
return (
<Form schema={schema} onSubmit={() => {}}>
{({ watch, setError, trigger }) => {
const name = watch('name');
return (
<Dialog hasBackdrop title="Rename Collection" onClose={onClose}>
<>
<div className="p-4">
<InputField
id="name"
name="name"
label="New Collection Name"
placeholder="New Collection Name..."
/>
</div>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Rename Collection"
onClose={onClose}
onSubmit={async () => {
if (await trigger()) {
// TODO: remove as when proper form types will be available
renameQueryCollection(currentName, name as string, {
onSuccess: () => {
onClose();
},
onError: error => {
setError('name', {
type: 'manual',
message: (error as Error).message,
});
},
});
}
}}
isLoading={isLoading}
/>
</>
</Dialog>
);
}}
</Form>
);
};

View File

@ -0,0 +1 @@
export * from './QueryCollectionHeader';

View File

@ -4,7 +4,7 @@ type ResponseBodyOnSuccess = {
message: 'success';
};
type ResponseBodyMetadataTypeError = {
export type ResponseBodyMetadataTypeError = {
code: string;
error: string;
path: string;

View File

@ -13,6 +13,7 @@ import type {
rsToDbRelDef,
ObjectRelationship,
ArrayRelationship,
QueryCollectionEntry,
} from '@/metadata/types';
import { MetadataResponse } from '..';
@ -264,6 +265,10 @@ export namespace MetadataSelector {
export const getAllDriversList = (m: MetadataResponse) =>
m.metadata?.sources.map(s => ({ source: s.name, kind: s.kind }));
export const getQueryCollections = (
m: MetadataResponse
): QueryCollectionEntry[] => m.metadata?.query_collections ?? [];
export const getOperationsFromQueryCollection =
(queryCollectionName: string) => (m: MetadataResponse) => {
const queryCollectionDefinition = m.metadata?.query_collections?.find(

View File

@ -0,0 +1 @@
export * from './useCreateQueryCollection';

View File

@ -0,0 +1,64 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Meta, Story } from '@storybook/react';
import { Button } from '@/new-components/Button';
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
import { useCreateQueryCollection } from './useCreateQueryCollection';
import { useQueryCollections } from '../useQueryCollections';
const UseQueryCollections: React.FC<{ name: string }> = ({ name }) => {
const { data: queryCollections } = useQueryCollections();
const { createQueryCollection, isSuccess, isLoading, error } =
useCreateQueryCollection();
return (
<div>
<ReactJson
name="Data"
src={{
query_collections: queryCollections?.map(
({ name: collectionName }) => collectionName
),
}}
/>
<ReactJson
name="Hook State"
src={{
isSuccess,
isLoading,
error: error?.message,
}}
/>
<Button onClick={() => createQueryCollection(name)}>
Create Collection
</Button>
</div>
);
};
export const Primary: Story = ({ collectionName }) => {
return <UseQueryCollections name={collectionName} />;
};
export default {
title: 'hooks/Query Collections/useCreateQueryCollection',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(),
},
argTypes: {
collectionName: {
defaultValue: 'test',
description: 'The name of the query collection to create',
control: {
type: 'text',
},
},
},
} as Meta;

View File

@ -0,0 +1,45 @@
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
export const useCreateQueryCollection = () => {
const { mutate, isSuccess, isLoading, error } = useMetadataMigration();
const { data: metadata } = useMetadata();
const createQueryCollection = async (
name: string,
options?: Parameters<typeof mutate>[1] & { addToAllowList?: boolean }
) => {
const { addToAllowList = false, ...mutateOptions } = options || {};
mutate(
{
query: {
...(metadata?.resource_version && {
resource_version: metadata.resource_version,
}),
type: 'bulk',
args: [
{
type: 'create_query_collection',
args: {
name,
definition: {
queries: [],
},
},
},
addToAllowList
? {
type: 'add_collection_to_allowlist',
args: {
collection: name,
},
}
: undefined,
].filter(op => op),
},
},
mutateOptions
);
};
return { createQueryCollection, isSuccess, isLoading, error };
};

View File

@ -0,0 +1,40 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
import { useCreateQueryCollection } from './useCreateQueryCollection';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useCreateQueryCollections', () => {
beforeEach(() => {
server.use(...handlers(0, ''));
});
test('should work correctly when creating a non-existing collection', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useCreateQueryCollection(),
{ wrapper }
);
await result.current.createQueryCollection('new-collection');
await waitForValueToChange(() => result.current.isSuccess);
expect(result.current.isSuccess).toBe(true);
});
test('should go in error state when creating an existing collection', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useCreateQueryCollection(),
{ wrapper }
);
await result.current.createQueryCollection('allowed_queries');
await waitForValueToChange(() => result.current.error);
expect(result.current.error).toBeDefined();
});
});

View File

@ -0,0 +1 @@
export * from './useDeleteQueryCollections';

View File

@ -0,0 +1,64 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Meta, Story } from '@storybook/react';
import { Button } from '@/new-components/Button';
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
import { useDeleteQueryCollections } from './useDeleteQueryCollections';
import { useQueryCollections } from '../useQueryCollections';
const UseQueryCollections: React.FC<{ name: string }> = ({ name }) => {
const { data: queryCollections } = useQueryCollections();
const { deleteQueryCollection, isSuccess, isLoading, error } =
useDeleteQueryCollections();
return (
<div>
<ReactJson
name="Data"
src={{
query_collections: queryCollections?.map(
({ name: collectionName }) => collectionName
),
}}
/>
<ReactJson
name="Hook State"
src={{
isSuccess,
isLoading,
error: error?.message,
}}
/>
<Button onClick={() => deleteQueryCollection(name)}>
Delete Collection
</Button>
</div>
);
};
export const Primary: Story = ({ collectionName }) => {
return <UseQueryCollections name={collectionName} />;
};
export default {
title: 'hooks/Query Collections/useDeleteQueryCollections',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(),
},
argTypes: {
collectionName: {
defaultValue: 'allowed_queries',
description: 'The name of the query collection to delete',
control: {
type: 'text',
},
},
},
} as Meta;

View File

@ -0,0 +1,40 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
import { useDeleteQueryCollections } from './useDeleteQueryCollections';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useDeleteQueryCollections', () => {
beforeEach(() => {
server.use(...handlers(0, ''));
});
test('should work correctly when deleting an existing collection', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useDeleteQueryCollections(),
{ wrapper }
);
await result.current.deleteQueryCollection('allowed_queries');
await waitForValueToChange(() => result.current.isSuccess);
expect(result.current.isSuccess).toBe(true);
});
test('should go in error state when deleting a non existing collection', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useDeleteQueryCollections(),
{ wrapper }
);
await result.current.deleteQueryCollection('not-existing');
await waitForValueToChange(() => result.current.error);
expect(result.current.error).toBeDefined();
});
});

View File

@ -0,0 +1,29 @@
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
export const useDeleteQueryCollections = () => {
const { data: metadata } = useMetadata();
const { mutate, isSuccess, isLoading, error } = useMetadataMigration({});
const deleteQueryCollection = async (
name: string,
options?: Parameters<typeof mutate>[1]
) => {
mutate(
{
query: {
...(metadata?.resource_version && {
resource_version: metadata.resource_version,
}),
type: 'drop_query_collection',
args: {
collection: name,
cascade: true,
},
},
},
options
);
};
return { deleteQueryCollection, isSuccess, isLoading, error };
};

View File

@ -0,0 +1 @@
export * from './useQueryCollections';

View File

@ -0,0 +1,91 @@
import { rest } from 'msw';
import { TMigration } from '../../../../MetadataAPI/hooks/useMetadataMigration';
import {
alreadyExistsResponse,
createMetadata,
notExistsResponse,
configApiEnabled,
} from './mockData';
export const handlers = (delay = 0, url = 'http://localhost:8080') => {
const metadata = createMetadata();
return [
rest.get(`${url}/v1alpha1/config`, (req, res, ctx) => {
return res(ctx.delay(delay), ctx.status(200), ctx.json(configApiEnabled));
}),
rest.post(`${url}/v1/metadata`, (req, res, ctx) => {
const body = req.body as TMigration['query'];
switch (body?.type) {
case 'export_metadata':
return res(ctx.delay(delay), ctx.json(metadata));
case 'bulk':
const name = body?.args?.[0]?.args?.name;
if (
(metadata.metadata.query_collections || []).find(
c => c.name === name
)
) {
return res(
ctx.delay(delay),
ctx.status(400),
ctx.json(alreadyExistsResponse(name))
);
}
(metadata.metadata.query_collections || []).push({
name,
definition: {
queries: [],
},
});
return res(ctx.delay(delay), ctx.json(metadata));
case 'drop_query_collection':
const collection = body.args.collection;
if (
!(metadata.metadata.query_collections || []).find(
c => c.name === collection
)
) {
return res(
ctx.delay(delay),
ctx.status(400),
ctx.json(notExistsResponse(collection))
);
}
metadata.metadata.query_collections = (
metadata.metadata.query_collections || []
).filter(c => c.name !== collection);
return res(ctx.delay(delay), ctx.json(metadata));
case 'rename_query_collection':
const fromName = body?.args?.name;
if (
!(metadata.metadata.query_collections || []).find(
c => c.name === fromName
)
) {
return res(
ctx.delay(delay),
ctx.status(400),
ctx.json(notExistsResponse(fromName))
);
}
metadata.metadata.query_collections = (
metadata.metadata.query_collections || []
).map(c =>
c.name === fromName ? { ...c, name: body.args.new_name } : c
);
return res(ctx.delay(delay), ctx.json(metadata));
default:
return res(ctx.delay(delay), ctx.status(500));
}
}),
];
};

View File

@ -0,0 +1,62 @@
import { MetadataResponse } from '@/features/MetadataAPI';
import { ServerConfig } from '@/hooks';
import { ResponseBodyMetadataTypeError } from '../../../../CronTriggers/components/Form/mocks/types';
export const createMetadata = (): MetadataResponse => ({
resource_version: 1,
metadata: {
version: 3,
sources: [],
remote_schemas: [],
inherited_roles: [],
allowlist: [
{
collection: 'allowed_queries',
scope: {
global: true,
},
},
{
collection: 'allowed_queries',
scope: {
global: false,
roles: ['user'],
},
},
],
query_collections: [
{ name: 'allowed_queries', definition: { queries: [] } },
{ name: 'other_queries', definition: { queries: [] } },
],
},
});
export const createResponse: {
type: string;
version: number;
args: Record<string, unknown>;
} = {
type: 'export_metadata',
version: 2,
args: {},
};
export const alreadyExistsResponse = (
name: string
): ResponseBodyMetadataTypeError => ({
path: '$.args.name',
error: `query collection with name "${name}" already exists`,
code: 'already-exists',
});
export const notExistsResponse = (
name: string
): ResponseBodyMetadataTypeError => ({
path: '$.args.collection',
error: `query collection with name "${name}" does not exist`,
code: 'not-exists',
});
export const configApiEnabled: Partial<ServerConfig> = {
is_allow_list_enabled: true,
};

View File

@ -0,0 +1,37 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Meta, Story } from '@storybook/react';
import { handlers } from './mocks/handlers.mock';
import { useQueryCollections } from './useQueryCollections';
const UseQueryCollections: React.FC = () => {
const { data, isLoading, isError } = useQueryCollections();
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Error</div>;
}
return data ? <ReactJson name="query_collections" src={data} /> : null;
};
export const Primary: Story = () => {
return <UseQueryCollections />;
};
export default {
title: 'hooks/Query Collections/useQueryCollections',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(),
},
} as Meta;

View File

@ -0,0 +1,30 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from './mocks/handlers.mock';
import { useQueryCollections } from './useQueryCollections';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useQueryCollections', () => {
beforeEach(() => {
server.use(...handlers(0, ''));
});
test('should return the query collection when called without errors', async () => {
const { result, waitForValueToChange } = renderHook(
() => useQueryCollections(),
{ wrapper }
);
await waitForValueToChange(() => result.current.isSuccess);
const queryCollections = result.current.data;
expect(queryCollections).toHaveLength(2);
expect(queryCollections?.[0]?.name).toEqual('allowed_queries');
expect(queryCollections?.[1]?.name).toEqual('other_queries');
});
});

View File

@ -0,0 +1,9 @@
import { MetadataSelector, useMetadata } from '@/features/MetadataAPI';
export const useQueryCollections = () => {
const { data, isLoading, isError, isRefetching, isSuccess } = useMetadata(
MetadataSelector.getQueryCollections
);
return { data, isLoading: isLoading || isRefetching, isError, isSuccess };
};

View File

@ -0,0 +1 @@
export * from './useRenameQueryCollection';

View File

@ -0,0 +1,74 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Meta, Story } from '@storybook/react';
import { Button } from '@/new-components/Button';
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
import { useRenameQueryCollection } from './useRenameQueryCollection';
import { useQueryCollections } from '../useQueryCollections';
const UseQueryCollections: React.FC<{ name: string; newName: string }> = ({
name,
newName,
}) => {
const { data: queryCollections } = useQueryCollections();
const { renameQueryCollection, isSuccess, isLoading, error } =
useRenameQueryCollection();
return (
<div>
<ReactJson
name="Data"
src={{
query_collections: queryCollections?.map(
({ name: collectionName }) => collectionName
),
}}
/>
<ReactJson
name="Hook State"
src={{
isSuccess,
isLoading,
error: error?.message,
}}
/>
<Button onClick={() => renameQueryCollection(name, newName)}>
Rename Collection
</Button>
</div>
);
};
export const Primary: Story = ({ name, newName }) => {
return <UseQueryCollections name={name} newName={newName} />;
};
export default {
title: 'hooks/Query Collections/useRenameQueryCollection',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(),
},
argTypes: {
name: {
defaultValue: 'allowed_queries',
description: 'The name of the query collection to rename',
control: {
type: 'text',
},
},
newName: {
defaultValue: 'queries_allowed',
description: 'The new name of the query collection',
control: {
type: 'text',
},
},
},
} as Meta;

View File

@ -0,0 +1,30 @@
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
export const useRenameQueryCollection = () => {
const { data: metadata } = useMetadata();
const { mutate, isSuccess, isLoading, error } = useMetadataMigration({});
const renameQueryCollection = async (
name: string,
newName: string,
options?: Parameters<typeof mutate>[1]
) => {
mutate(
{
query: {
...(metadata?.resource_version && {
resource_version: metadata.resource_version,
}),
type: 'rename_query_collection',
args: {
name,
new_name: newName,
},
},
},
options
);
};
return { renameQueryCollection, isSuccess, isLoading, error };
};

View File

@ -0,0 +1,40 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
import { useRenameQueryCollection } from './useRenameQueryCollection';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useRenameQueryCollection', () => {
beforeEach(() => {
server.use(...handlers(0, ''));
});
test('should work correctly when renaming an existing collection', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useRenameQueryCollection(),
{ wrapper }
);
await result.current.renameQueryCollection('allowed_queries', 'new-name');
await waitForValueToChange(() => result.current.isSuccess);
expect(result.current.isSuccess).toBe(true);
});
test('should go in error state when renaming a non-existing collection', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useRenameQueryCollection(),
{ wrapper }
);
await result.current.renameQueryCollection('not-existing', 'new-name');
await waitForValueToChange(() => result.current.error);
expect(result.current.error).toBeDefined();
});
});

View File

@ -15,6 +15,7 @@ export interface ServerConfig {
is_function_permissions_inferred: boolean;
is_admin_secret_set: boolean;
is_auth_hook_set: boolean;
is_allow_list_enabled: boolean;
is_remote_schema_permissions_enabled: boolean;
is_jwt_set: boolean;
experimental_features: ExperimentalFeature[];

View File

@ -69,6 +69,7 @@ export const metadataQueryTypes = [
'add_existing_table_or_view',
'create_query_collection',
'drop_query_collection',
'rename_query_collection',
'add_query_to_collection',
'drop_query_from_collection',
'add_collection_to_allowlist',

View File

@ -0,0 +1,130 @@
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
import { action } from '@storybook/addon-actions';
import { TemplateStoriesFactory } from '@/utils/StoryUtils';
import { Badge } from '@/new-components/Badge/';
<Meta
title="components/Badge ⚛️"
component={Badge}
parameters={{
docs: { source: { type: 'code' } },
chromatic: { disableSnapshot: true },
}}
decorators={[Story => <div className={'p-4'}>{Story()}</div>]}
argTypes={{
content: {
table: {
disable: true,
},
},
}}
/>
# Badge ⚛️
- [🧰 Overview](#-overview)
- [🔁 States](#-states)
- [🎭 Variants](#-variants)
- [⚙️ API](#%EF%B8%8F-api)
- [🧪 Testing](#-testing)
- [🐙 Code on Github](https://github.com/hasura/graphql-engine-mono/tree/main/console/src/new-components/Badge/Badge.tsx)
## 🧰 Overview
A component that displays a badge of a particular color.
### Basic usage
```ts
import { Badge } from '@/new-components/Badge';
```
```tsx
<Badge color='green'>
The badge label</span>
</Badge>
```
<Canvas>
<Story name="Overview">
<div>
<Badge color={'green'} onClick={action('onClick')}>
Hello from badge!
</Badge>
</div>
</Story>
</Canvas>
export const Template = ({ content, ...rest }) => (
<Badge {...rest}>{content}</Badge>
);
export const stories = {
Green: {
content: 'Green Badge',
color: 'green',
},
Red: {
content: 'Red Badge',
color: 'red',
},
'API playground': {
content: 'The badge content',
color: 'indigo',
},
};
#### 🚦 Usage
- The badge could be used to represent tags or positive/negative status, like a feature that can be enabled or disabled
- You specify the color of the badge, then background and foreground colors are derived from it
- You can also specify an onClick handler to be called when the badge is clicked
### 🎭 Color
<Canvas>
<Story
name="Variant - Color"
args={{
Green: stories['Green'],
Red: stories['Red'],
}}
>
{TemplateStoriesFactory(Template).bind({})}
</Story>
</Canvas>
## ⚙️ API
<Canvas>
<Story name="API playground" args={stories['API playground']}>
{Template.bind({})}
</Story>
</Canvas>
<details open>
<summary className="mdx-collapsible-section">
<p className="mdx-collapsible-section__chevron">
<strong>&gt;</strong>
</p>
<p className="mdx-collapsible-section__label">
<strong>Show/hide</strong> props
</p>
</summary>
<ArgsTable story="API playground" />
</details>
### 🧪 Snapshot 📸
<Canvas>
<Story
name="Testing - Snapshot"
args={stories}
parameters={{
chromatic: { disableSnapshot: false },
}}
>
{TemplateStoriesFactory(Template).bind({})}
</Story>
</Canvas>

View File

@ -0,0 +1,36 @@
import React from 'react';
import clsx from 'clsx';
type BadgeColor = 'green' | 'red' | 'yellow' | 'indigo' | 'gray';
interface BadgeProps extends React.ComponentProps<'span'> {
/**
* The color of the basge
*/
color: BadgeColor;
}
const badgeClassnames: Record<BadgeColor, string> = {
green: 'bg-green-100 text-green-800',
red: 'bg-red-100 text-red-800',
yellow: 'bg-yellow-100 text-yellow-800',
gray: 'bg-gray-100 text-gray-800',
indigo: 'bg-indigo-100 text-indigo-800',
};
export const Badge: React.FC<React.PropsWithChildren<BadgeProps>> = props => {
const { color, children, ...rest } = props;
return (
<span
{...rest}
className={clsx(
'inline-flex items-center mr-sm px-sm py-0.5 rounded-full text-sm tracking-wide font-semibold',
badgeClassnames[color],
rest.className,
rest.onClick && 'cursor-pointer'
)}
>
{children}
</span>
);
};

View File

@ -0,0 +1 @@
export * from './Badge';

View File

@ -0,0 +1,94 @@
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
import { action } from '@storybook/addon-actions';
import { TemplateStoriesFactory } from '@/utils/StoryUtils';
import { DropdownButton } from '@/new-components/DropdownButton';
<Meta
title="components/Dropdown Button 🧬"
component={DropdownButton}
parameters={{
docs: { source: { type: 'code' } },
chromatic: { disableSnapshot: true },
}}
decorators={[Story => <div className={'p-4'}>{Story()}</div>]}
/>
# DropdownButton ⚛️
- [🧰 Overview](#-overview)
- [🐙 Code on Github](https://github.com/hasura/graphql-engine-mono/tree/main/console/src/new-components/DropdownButton/DropdownButton.tsx)
## 🧰 Overview
A component that display a button that opens a dropdown menu.
### Basic usage
```ts
import { DropdownButton } from '@/new-components/DropdownButton';
```
```tsx
<DropdownButton
items={[
['Action', <span className="text-red-600">Destructive Action</span>],
['Another action'],
]}
>
The DropdownButton label
</DropdownButton>
```
<Canvas>
<Story name="Overview">
<div>
<DropdownButton
items={[
['Action', <span className="text-red-600">Destructive Action</span>],
['Another action'],
]}
>
The DropdownButton label
</DropdownButton>
</div>
</Story>
</Canvas>
#### 🚦 Usage
- The dropdown button is used for opening a contextual menu with list of actions
- The `items` prop is an array of array of react nodes (each array is a group of items) that will be rendered in the dropdown
- The Button prop is an object of props forwarded to the button component
- The children of this component are forwarded to the button component
## ⚙️ API
export const Template = args => <DropdownButton {...args} />;
<Canvas>
<Story
name="API playground"
args={{
items: [
['Action', <span className="text-red-600">Destructive</span>],
['Another action', 'Another action'],
],
children: 'Button label',
}}
>
{Template.bind({})}
</Story>
</Canvas>
<details open>
<summary className="mdx-collapsible-section">
<p className="mdx-collapsible-section__chevron">
<strong>&gt;</strong>
</p>
<p className="mdx-collapsible-section__label">
<strong>Show/hide</strong> props
</p>
</summary>
<ArgsTable story="API playground" />
</details>

View File

@ -0,0 +1,37 @@
import React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Button } from '@/new-components/Button';
interface DropdownButtonProps {
buttonProps?: React.ComponentProps<typeof Button>;
items: React.ReactNode[][];
}
export const DropdownButton: React.FC<DropdownButtonProps> = ({
children,
buttonProps,
items,
}) => (
<DropdownMenuPrimitive.Root>
<DropdownMenuPrimitive.Trigger>
<Button {...buttonProps}>{children}</Button>
</DropdownMenuPrimitive.Trigger>
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content align="start">
<div className="origin-top-left absolute left-0 z-10 mt-xs w-max max-w-xs rounded shadow-md bg-white ring-1 ring-gray-300 divide-y divide-gray-300 focus:outline-none">
{items.map(group => (
<div className="py-1">
{group.map(item => (
<DropdownMenuPrimitive.Item>
<div className="cursor-pointer flex items-center mx-1 px-xs py-xs rounded whitespace-nowrap hover:bg-gray-100">
{item}
</div>
</DropdownMenuPrimitive.Item>
))}
</div>
))}
</div>
</DropdownMenuPrimitive.Content>
</DropdownMenuPrimitive.Portal>
</DropdownMenuPrimitive.Root>
);

View File

@ -0,0 +1 @@
export * from './DropdownButton';