Compare commits

..

599 Commits

Author SHA1 Message Date
Szilárd Dóró
43705b992d Merge pull request #1509 from nhost/changeset-release/main
chore: update versions
2023-01-12 12:41:41 +01:00
github-actions[bot]
2e999e8715 chore: update versions 2023-01-12 10:14:41 +00:00
Pilou
0370696d5c Merge pull request #1511 from nhost/chore/unlink-packages
chore(changeset): stop linking packages
2023-01-12 11:12:44 +01:00
Pierre-Louis Mercereau
f62131d55a chore(changeset): stop linking packages 2023-01-12 10:59:57 +01:00
Szilárd Dóró
86d077ac00 Merge pull request #1508 from nhost/renovate-changesets
chore: create changesest from Renovate bumps
2023-01-12 10:10:35 +01:00
szilarddoro
200e9f774c chore(deps): update dependency @types/react-dom to v18.0.10 2023-01-12 08:49:58 +00:00
Szilárd Dóró
bc1235de3b Merge pull request #1433 from nhost/renovate/react-dom-18.x
chore(deps): update dependency @types/react-dom to v18.0.10
2023-01-12 09:48:10 +01:00
Szilárd Dóró
fce58ebaea remove changeset, CI generates it 2023-01-12 09:47:53 +01:00
Szilárd Dóró
452e281120 chore(dashboard): add changeset 2023-01-12 09:47:04 +01:00
Szilárd Dóró
9a338e54c9 Merge pull request #1492 from nhost/renovate/vitest-monorepo
chore(deps): update vitest monorepo to ^0.27.0
2023-01-12 09:45:14 +01:00
Szilárd Dóró
baeebf980d Merge pull request #1507 from nhost/changeset-release/main
chore: update versions
2023-01-12 09:27:25 +01:00
github-actions[bot]
ac92c6ee61 chore: update versions 2023-01-12 01:38:20 +00:00
Guido Curcio
1ddaf680c0 Merge pull request #1471 from nhost/fix(dashboard)/workspace-creation-redirection-delete 2023-01-11 22:36:50 -03:00
Guido Curcio
c6e6194d8e mutating -> updating for signaling changes in course. 2023-01-11 09:05:43 -03:00
Pilou
83deea8b45 Merge pull request #1501 from nhost/chore/exclude-functions-from-workspace
chore: exclude functions from workspace
2023-01-11 11:42:07 +01:00
Pierre-Louis Mercereau
acbaabcf85 chore: update lockfile 2023-01-11 10:44:36 +01:00
Pierre-Louis Mercereau
3534501f37 chore: force using turborepo v1 2023-01-11 10:31:10 +01:00
Pierre-Louis Mercereau
27bc23cbbc chore: exclude functions from workspace 2023-01-11 10:15:47 +01:00
Szilárd Dóró
6450223558 Merge pull request #1498 from nhost/changeset-release/main
chore: update versions
2023-01-11 08:48:36 +01:00
Guido Curcio
a62a85a777 add comments to effects and router changes. 2023-01-11 02:07:15 -03:00
Guido Curcio
ae24f83953 fix changing application name redirect to 404, fix 404 flash when changing workspace name. 2023-01-11 01:33:28 -03:00
Guido Curcio
fc60d7a782 2023-01-10 19:14:00 -03:00
Guido Curcio
6be8a998df 2023-01-10 19:13:09 -03:00
Guido Curcio
ea091f6251 2023-01-10 19:11:02 -03:00
Guido Curcio
8175c052f7 2023-01-10 18:50:45 -03:00
github-actions[bot]
e6605a6ed0 chore: update versions 2023-01-10 16:43:20 +00:00
Szilárd Dóró
1cba0e6492 Merge pull request #1497 from nhost/fix/database-ui-hasura-metadata
fix(dashboard): don't break the table creation process
2023-01-10 17:41:38 +01:00
Szilárd Dóró
179c90fcdb fix(dashboard): update inline snapshot 2023-01-10 17:13:29 +01:00
Szilárd Dóró
85f0f943a1 chore(dashboard): add changeset 2023-01-10 16:04:15 +01:00
Szilárd Dóró
c4c23fde31 fix(dashboard): don't break table creation
don't break table creation when referencing a table that is not in the `public` schema
2023-01-10 15:39:05 +01:00
Szilárd Dóró
e0b94c3e90 Merge pull request #1493 from nhost/changeset-release/main
chore: update versions
2023-01-10 09:33:58 +01:00
github-actions[bot]
113d638532 chore: update versions 2023-01-09 17:20:13 +00:00
Pilou
d87448916f Merge pull request #1486 from nhost/chore/bump-start-server-and-test
chore(deps): bump start-server-and-test
2023-01-09 18:17:47 +01:00
Pilou
af4292658c Merge pull request #1495 from nhost/fix/export-types
Export commonly used types
2023-01-09 18:17:01 +01:00
Pilou
f735bcd2ea Merge pull request #1485 from nhost/fix/explicit-types
fix: 🐛 add explicit types to React hooks and Vue composables
2023-01-09 18:00:48 +01:00
Pierre-Louis Mercereau
66fb74af86 Merge branch 'main' into fix/explicit-types 2023-01-09 16:30:20 +01:00
Pierre-Louis Mercereau
791eac30bb Merge branch 'main' into chore/bump-start-server-and-test 2023-01-09 16:29:57 +01:00
Pierre-Louis Mercereau
da4ad889d7 Merge branch 'main' into fix/export-types 2023-01-09 16:27:31 +01:00
Pilou
9ef111760c Merge pull request #1490 from nhost/test/correct-forgot-password
test: correct forgot-password test
2023-01-09 16:26:57 +01:00
Pierre-Louis Mercereau
c2706c7d97 chore: export commonly used types 2023-01-09 16:26:01 +01:00
Pilou
683b8768c4 Merge pull request #1482 from nhost/chore/access-token-cookie
chore: store the session in a cookie to avoid refetching the jwt on every ssr call
2023-01-09 16:11:39 +01:00
renovate[bot]
6d9df237a8 chore(deps): update vitest monorepo to ^0.27.0 2023-01-09 13:34:33 +00:00
Szilárd Dóró
220ae37aa7 Merge pull request #1491 from nhost/changeset-release/main
chore: update versions
2023-01-09 14:26:56 +01:00
Pierre-Louis Mercereau
d0d94d9239 chore: use email+password sign-up 2023-01-09 13:27:28 +01:00
Pierre-Louis Mercereau
aed3d1f147 chore: wrap 2023-01-09 11:59:49 +01:00
github-actions[bot]
d07bf08e45 chore: update versions 2023-01-09 10:46:27 +00:00
Szilárd Dóró
f2183250d2 Merge pull request #1470 from nhost/fix(dashboard)/sign-out
fix(dashboard): Resetting the cache when signing out.
2023-01-09 11:44:48 +01:00
Pierre-Louis Mercereau
d2bb5ecfae refactor: unnest code blocks 2023-01-09 11:39:33 +01:00
Pierre-Louis Mercereau
02d0db0cf0 revert: remove line 2023-01-09 11:20:28 +01:00
Pierre-Louis Mercereau
441005d5c3 chore: another attempt 2023-01-09 10:59:38 +01:00
Pierre-Louis Mercereau
eea8708549 chore: visit 2023-01-09 10:31:44 +01:00
Szilárd Dóró
5f3f9390aa chore(dashboard): updated changeset 2023-01-09 09:42:36 +01:00
Pierre-Louis Mercereau
1c5b0560ed chore: 10 attempts 2023-01-09 09:38:19 +01:00
Pierre-Louis Mercereau
1bfdf21b99 test: correct forgot-password test 2023-01-09 09:33:40 +01:00
Pierre-Louis Mercereau
efd522a38a chore: update changesets 2023-01-06 16:53:21 +01:00
Pierre-Louis Mercereau
55c35fa9c5 chore(deps): bump start-server-and-test 2023-01-06 16:45:06 +01:00
Pierre-Louis Mercereau
d42c27ae99 fix: 🐛 add explicit types to React hooks and Vue composables 2023-01-06 13:56:19 +01:00
Pierre-Louis Mercereau
927be4a2c9 chore: store the session in a cookie 2023-01-06 10:47:09 +01:00
Guido Curcio
e44352abbd typo in CreateWorkspaceFormProps Save -> Create 2023-01-05 23:48:35 -03:00
Guido Curcio
f9289f3c32 Merge branch 'fix(dashboard)/workspace-creation-redirection-delete' of https://github.com/nhost/nhost into fix(dashboard)/workspace-creation-redirection-delete 2023-01-05 23:47:01 -03:00
Guido Curcio
8ff06e5637 disable create workspace button if error on input. 2023-01-05 23:45:32 -03:00
Guido Curcio
49e4633bca handle when workspace name is already taken. 2023-01-05 23:41:23 -03:00
Guido Curcio
7ae7a7206c Update dashboard/src/components/home/CreateWorkspaceForm/CreateWorkspaceForm.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-05 23:31:02 -03:00
Guido Curcio
43d7e7babf Update dashboard/src/components/workspace/WorkspaceSection.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-05 23:30:54 -03:00
Guido Curcio
463a51ce7c Update dashboard/src/components/home/CreateWorkspaceForm/CreateWorkspaceForm.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-05 23:30:47 -03:00
Guido Curcio
86e9d9d47f Update dashboard/src/components/home/CreateWorkspaceForm/CreateWorkspaceForm.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-05 23:30:42 -03:00
Guido Curcio
f99b72cd7c useUserData instead of nhost.auth.getUser() 2023-01-05 23:29:42 -03:00
Guido Curcio
0dc2f3ff29 remove unused file 2023-01-05 23:24:11 -03:00
Guido Curcio
d0f8081101 new changeset 2023-01-05 23:16:09 -03:00
Guido Curcio
84ebfb79d0 reorder calls when signing out 2023-01-05 23:14:35 -03:00
Pilou
3c78d0ef46 Merge pull request #1476 from nhost/test/reset-password
test: add forgot password test
2023-01-05 17:07:47 +01:00
Szilárd Dóró
e9a26fc995 Merge pull request #1477 from nhost/fix/versions
fix(deps): revert major bumps
2023-01-05 13:46:39 +01:00
Szilárd Dóró
b0794507f5 fix(deps): revert major bumps 2023-01-05 13:43:00 +01:00
Szilárd Dóró
824e222e9d Merge pull request #1472 from nhost/changeset-release/main
chore: update versions
2023-01-05 13:39:35 +01:00
github-actions[bot]
16a99d7d0f chore: update versions 2023-01-05 11:58:13 +00:00
Johan Eliasson
cda5c3d274 Merge pull request #1475 from nhost/dashboard-ib8ga98sid
fix(dashboard): create new user
2023-01-05 12:56:53 +01:00
Johan Eliasson
3d3791286d changeset 2023-01-05 12:08:34 +01:00
Pierre-Louis Mercereau
ad28bf2166 test: add forgot password test 2023-01-05 10:59:52 +01:00
Szilárd Dóró
17bfa83204 Merge pull request #1414 from nhost/feat/permission-editor
feat(dashboard): Permission Editor
2023-01-05 10:38:09 +01:00
Johan Eliasson
6cd64e76ff Merge pull request #1450 from nhost/chatgpt-iyv8asd
[chatgpt] added tests
2023-01-05 10:26:16 +01:00
Pilou
a4bf50cf23 Merge pull request #1461 from nhost/feat/image-transformation
feat: image transformation parameters
2023-01-05 09:47:19 +01:00
Pierre-Louis Mercereau
113baafd84 Merge branch 'main' into feat/image-transformation 2023-01-05 09:10:56 +01:00
Johan Eliasson
87c2b31821 fix 2023-01-05 08:50:12 +01:00
Johan Eliasson
8a6bc3625c fix 2023-01-05 08:42:42 +01:00
Pilou
bdfda8aced Merge pull request #1444 from nhost/fix/non-iso-8859-1-names
Fix: convert non ISO-8859-1 file names
2023-01-05 08:24:52 +01:00
Szilárd Dóró
ca090436af Merge pull request #1469 from nhost/changeset-release/main 2023-01-04 22:51:49 +01:00
Johan Eliasson
55f85a04ea fix 2023-01-04 22:10:41 +01:00
Johan Eliasson
73f95cfa3b jsdom tests 2023-01-04 22:07:47 +01:00
Guido Curcio
dbd3ded515 add patch changeset for dashboard. 2023-01-04 17:41:20 -03:00
Guido Curcio
5399fac211 remove AddWorkspace.tsx file and imports. 2023-01-04 17:39:01 -03:00
Guido Curcio
52e3127a34 fix(dashboard): workspaces creation, new form, correct redirects. 2023-01-04 17:34:35 -03:00
github-actions[bot]
3fb12c189b chore: update versions 2023-01-04 19:22:01 +00:00
Guido Curcio
c4d5366b22 Merge pull request #1468 from nhost/fix/twitter 2023-01-04 14:20:19 -05:00
Szilárd Dóró
bd68e916cf chore(dashboard): cleanup unused GQL file 2023-01-04 20:04:52 +01:00
Szilárd Dóró
7cadd9447b fix(dashboard): display Twitter provider settings 2023-01-04 19:52:21 +01:00
Szilárd Dóró
b649f178e0 Merge pull request #1454 from nhost/changeset-release/main
chore: update versions
2023-01-04 14:43:52 +01:00
Szilárd Dóró
7432c6477c fix(dashboard): change truncation type 2023-01-04 10:33:13 +01:00
Szilárd Dóró
c3aa6126fe chore(dashboard): improve autocomplete equality check 2023-01-04 09:34:58 +01:00
Szilárd Dóró
0f3cf887c1 fix(dashboard): improve boolean transformation 2023-01-04 09:31:55 +01:00
Szilárd Dóró
5cd311b69a feat(dashboard): add support for manual relationships 2023-01-04 09:25:30 +01:00
Szilárd Dóró
057fda178f fix(dashboard): get rid of MUI warning, add preview label 2023-01-03 20:56:10 +01:00
Szilárd Dóró
241b14a004 feat(dashboard): add read-only permissions support 2023-01-03 20:52:04 +01:00
github-actions[bot]
1f5e1e3d42 chore: update versions 2023-01-03 18:20:01 +00:00
Nuno Pato
5727b0b0fe Merge pull request #1426 from nhost/feat/add-functions-to-logs-dashboard
feat(dashboard): add functions to logs
2023-01-03 17:18:24 -01:00
Szilárd Dóró
10b56089fa feat(dashboard): initial read-only mode implementation 2023-01-03 16:35:57 +01:00
Szilárd Dóró
973df1ed5a Merge remote-tracking branch 'origin/main' into feat/permission-editor 2023-01-03 16:09:26 +01:00
Pierre-Louis Mercereau
8f681b83e8 chore: re-enable quality, as it is implemented 2023-01-03 13:50:29 +01:00
Pilou
2f38ed56f5 Merge pull request #1459 from nhost/fix/reuse-file-upload
fix: 🐛 allow useFileUpload to be reused
2023-01-03 13:41:20 +01:00
Pierre-Louis Mercereau
21501624e6 chore: update changeset 2023-01-03 13:31:16 +01:00
Pierre-Louis Mercereau
464530dacb chore: deactivate unavailable image transformation parameters 2023-01-03 13:28:24 +01:00
Pierre-Louis Mercereau
0f2fc3dfec docs: image transformation parameters 2023-01-03 13:18:18 +01:00
Pierre-Louis Mercereau
5cb71f1dc8 chore: correct changeset 2023-01-03 13:14:32 +01:00
Pierre-Louis Mercereau
83e0a4d33e feat: image transformation parameters 2023-01-03 13:11:04 +01:00
Guido Curcio
16502ea175 Merge pull request #1382 from nhost/feat/auth-management 2023-01-03 06:16:57 -05:00
Guido Curcio
beee0407df Merge branch 'main' into feat/auth-management 2023-01-03 05:13:46 -05:00
Guido Curcio
3990b1ffbb Update dashboard/src/components/users/CreateUserForm/CreateUserForm.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-03 06:12:22 -03:00
Guido Curcio
1fb03708e3 Update dashboard/src/components/users/EditUserPasswordForm/EditUserPasswordForm.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-03 06:12:15 -03:00
Szilárd Dóró
e9ef254c6d Merge branch 'main' into feat/permission-editor 2023-01-03 09:45:44 +01:00
Szilárd Dóró
d42719ee65 Merge pull request #1453 from nhost/feat/custom-jwt-secret
feat(dashboard): add JWT secret editor modal
2023-01-03 09:30:09 +01:00
Guido Curcio
72ff489ea8 fix inconsistent padding in modals. 2023-01-03 03:43:11 -03:00
Pilou
c9bf2dde0e Merge pull request #1458 from nhost/refactor/do-not-over-export
Only export what is required by the user or `@nhost/nextjs`
2023-01-02 22:04:52 +01:00
Pierre-Louis Mercereau
613533d377 chore: do not cache documentation build 2023-01-02 21:22:54 +01:00
Pierre-Louis Mercereau
8568354718 fix: 🐛 allow useFileUpload to be reused 2023-01-02 21:03:58 +01:00
Pierre-Louis Mercereau
1be6d32455 Only export what is required by the user or @nhost/nextjs 2023-01-02 16:19:00 +01:00
Pilou
812a6e5005 Merge pull request #1452 from nhost/fix/improve-missing-react-provider-error
fix: improve missing React provider error
2023-01-02 15:54:48 +01:00
Szilárd Dóró
34cc230b61 fix(dashboard): improve responsive layout 2023-01-02 15:29:02 +01:00
Szilárd Dóró
898a7c835f chore(dashboard): improve JWT secret validation 2023-01-02 14:41:28 +01:00
Szilárd Dóró
7766624bc5 feat(dashboard): add JWT secret editor modal 2023-01-02 14:34:36 +01:00
Pierre-Louis Mercereau
2e8f73df38 chore: changeset 2023-01-02 14:25:29 +01:00
Pierre-Louis Mercereau
6a419e060e fix: improve missing React provider error 2023-01-02 14:23:49 +01:00
Guido Curcio
43480ca735 spacing on action buttons 2023-01-02 09:07:21 -03:00
Guido Curcio
efc42d77fd fix loading state on EditUserForm (drawer) 2023-01-02 08:42:45 -03:00
Guido Curcio
31e2523eca spacing on create user modal 2023-01-02 08:19:54 -03:00
Guido Curcio
fbf4f40ab7 Merge branch 'feat/auth-management' of https://github.com/nhost/nhost into feat/auth-management 2023-01-02 08:15:31 -03:00
Guido Curcio
cbe203e720 fix alt props and spacing on users table 2023-01-02 08:14:49 -03:00
Szilárd Dóró
09af118452 fix(dashboard): use booleans when operator is _is_null 2023-01-02 12:01:31 +01:00
Szilárd Dóró
20d0c3d09b fix(dashboard): select appearance in rule group editor 2023-01-02 11:47:35 +01:00
Szilárd Dóró
378a6684b0 Merge pull request #1451 from nhost/changeset-release/main
chore: update versions
2023-01-02 11:33:20 +01:00
Szilárd Dóró
d92891b223 chore(dashboard): add changeset 2023-01-02 10:48:25 +01:00
github-actions[bot]
1999ae09e6 chore: update versions 2023-01-02 09:47:28 +00:00
Szilárd Dóró
aef86dc822 Merge remote-tracking branch 'origin/main' into feat/permission-editor 2023-01-02 10:46:25 +01:00
Szilárd Dóró
0fe48a0833 Merge pull request #1425 from nhost/fix/refresh-after-provisioning
fix(dashboard): provisioning status polling
2023-01-02 10:46:12 +01:00
Szilárd Dóró
7bbf6dbf1c Merge pull request #1387 from nhost/changeset-release/main
chore: update versions
2023-01-02 10:43:56 +01:00
Szilárd Dóró
a3499c4628 fix(dashboard): improve rule group editor scrollability 2023-01-02 10:43:47 +01:00
Szilárd Dóró
689dc873b3 Update integrations/react-apollo/CHANGELOG.md
Co-authored-by: Pilou <24897252+plmercereau@users.noreply.github.com>
2023-01-02 10:07:28 +01:00
Szilárd Dóró
a0747d02e0 Update integrations/react-urql/CHANGELOG.md
Co-authored-by: Pilou <24897252+plmercereau@users.noreply.github.com>
2023-01-02 10:07:23 +01:00
Szilárd Dóró
be5bd1e446 Update integrations/apollo/CHANGELOG.md
Co-authored-by: Pilou <24897252+plmercereau@users.noreply.github.com>
2023-01-02 10:07:18 +01:00
Szilárd Dóró
52ccfdec89 Merge branch 'main' into fix/refresh-after-provisioning 2023-01-02 10:05:46 +01:00
Szilárd Dóró
2c60591580 fix(deps): revert major bumps 2023-01-02 10:02:12 +01:00
Szilárd Dóró
3cac6f69bd fix(dashboard): improve warning message 2023-01-02 09:43:19 +01:00
Szilárd Dóró
71ff71ccd2 Merge branch 'main' into feat/permission-editor 2023-01-02 09:36:43 +01:00
Szilárd Dóró
da575ca262 feat(dashboard): add unsupported rules to request 2023-01-02 09:36:27 +01:00
Johan Eliasson
5020566725 update 2022-12-31 19:32:18 +01:00
Johan Eliasson
eb5915aa03 revert code 2022-12-31 19:17:20 +01:00
Johan Eliasson
458ee7fe6c remove jsdom 2022-12-31 14:40:10 +01:00
Johan Eliasson
ea7eb18f36 test update 2022-12-31 14:39:37 +01:00
Johan Eliasson
18f5414411 missing imports 2022-12-31 14:04:52 +01:00
Johan Eliasson
a7ce6d85f4 missing imports 2022-12-31 12:56:04 +01:00
Johan Eliasson
2f348c660a tests and refactoring 2022-12-31 12:36:57 +01:00
github-actions[bot]
6140bc5b3b chore: update versions 2022-12-27 13:49:10 +00:00
Pilou
9f7780ec91 Merge pull request #1438 from nhost/ci/renovate
ci: improve renovate configuration
2022-12-27 14:47:35 +01:00
Pierre-Louis Mercereau
7c07d09ea4 test: add test from user 2022-12-27 14:35:17 +01:00
Pierre-Louis Mercereau
13876ed523 fix: Allow uploading files with non ISO 8859-1 names 2022-12-27 14:11:08 +01:00
Pilou
abc7d0c7a5 Merge pull request #1442 from nhost/chore/docgen-vite
chore: build docgen with vite
2022-12-27 09:44:03 +01:00
Pilou
074a36ea48 Merge pull request #1432 from nhost/fix/remove-accept-encoding-header
Fix/allow override of the accept-encoding header
2022-12-27 09:43:39 +01:00
Pierre-Louis Mercereau
64e806dc27 chore: build docgen with vite 2022-12-26 21:16:02 +01:00
Pilou
bd0e9748b6 Merge pull request #1440 from nhost/renovate-changesets
chore: create changesest from Renovate bumps
2022-12-26 21:04:45 +01:00
plmercereau
b21222b378 chore(deps): update dependency @types/node to v16 2022-12-26 20:01:13 +00:00
Pilou
7e217db128 Merge pull request #1294 from nhost/renovate/node-18.x
chore(deps): update dependency @types/node to v16
2022-12-26 20:59:25 +01:00
Pierre-Louis Mercereau
56c716d9fa Merge branch 'main' into renovate/node-18.x 2022-12-26 20:23:00 +01:00
Pilou
14ecbd1fb9 Merge pull request #1439 from nhost/renovate-changesets
chore: create changesest from Renovate bumps
2022-12-26 20:19:58 +01:00
plmercereau
a0242c4d6f chore(deps): update dependency tsup to v6 2022-12-26 19:13:36 +00:00
Pilou
4800b4a756 Merge pull request #1306 from nhost/renovate/tsup-6.x
chore(deps): update dependency tsup to v6
2022-12-26 20:12:14 +01:00
Pierre-Louis Mercereau
5b318d17d4 Merge branch 'main' into renovate/node-18.x 2022-12-26 20:08:46 +01:00
Pierre-Louis Mercereau
2f9be4f760 fix: @types/node v16 2022-12-26 20:06:00 +01:00
Pierre-Louis Mercereau
64777b6f30 ci: improve renovate configuration 2022-12-26 19:59:48 +01:00
Pilou
7e1489353e Merge pull request #1437 from nhost/ci/renovate-changeset-pr
ci: create renovate changeset pr
2022-12-26 19:44:20 +01:00
Pierre-Louis Mercereau
c53306a497 ci: correct PAT secret 2022-12-26 19:43:55 +01:00
Pierre-Louis Mercereau
83345579d0 ci: create renovate changeset pr 2022-12-26 19:41:08 +01:00
Pilou
2b4b9e0385 Merge pull request #1304 from nhost/renovate/graphql-16.x
chore(deps): update dependency graphql to v16
2022-12-26 19:08:25 +01:00
renovate[bot]
922349f550 chore(deps): update dependency graphql to v16 2022-12-26 17:47:32 +00:00
Pilou
d613f3fd04 Merge pull request #1434 from nhost/renovate/jsonwebtoken-9.x
ci: correct gh action
2022-12-26 18:41:24 +01:00
Pierre-Louis Mercereau
4d8a47777e ci: correct gh action 2022-12-26 18:40:44 +01:00
Pilou
229a7ab1f7 Merge pull request #1417 from nhost/renovate/jsonwebtoken-9.x
fix(deps): update dependency jsonwebtoken to v9
2022-12-26 18:38:09 +01:00
Pierre-Louis Mercereau
3dabb7b53a ci: adapt renovate action 2022-12-26 18:37:49 +01:00
renovate[bot]
abc3d6ce60 chore(deps): update dependency tsup to v6 2022-12-26 17:33:02 +00:00
renovate[bot]
a529b654bc chore(deps): update dependency @types/react-dom to v18.0.10 2022-12-26 17:31:33 +00:00
Pilou
08d49bd1fd Merge pull request #1408 from nhost/renovate/vitest-monorepo
chore(deps): update dependency vitest to ^0.26.0
2022-12-26 18:24:07 +01:00
Pierre-Louis Mercereau
03435a2c66 chore: bump vitest to latest version 2022-12-26 14:55:39 +01:00
Pierre-Louis Mercereau
66208d6840 chore: allow custom values for Accept-Encoding 2022-12-26 13:15:04 +01:00
Pierre-Louis Mercereau
5be9abb0fa chore: changeset 2022-12-26 12:59:23 +01:00
Pierre-Louis Mercereau
8e504b5328 Merge branch 'main' into fix/remove-accept-encoding-header 2022-12-26 12:55:58 +01:00
renovate[bot]
0e3eb7204a fix(deps): update dependency jsonwebtoken to v9 2022-12-23 23:51:28 +00:00
Szilárd Dóró
b112ba0af4 feat(dashboard): extend "unsupported" functionality 2022-12-23 17:24:23 +01:00
Guido Curcio
70cfeb1fcf Update dashboard/src/components/users/UsersBody/UsersBody.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-23 12:38:12 -03:00
Szilárd Dóró
e6d990faa7 chore(dashboard): unsupported objects in permissions 2022-12-23 16:28:14 +01:00
Szilárd Dóró
b45da7e360 fix(dashboard): correct _is_null negation 2022-12-23 16:14:27 +01:00
Guido Curcio
3116562b58 fix service urls; pagination props 2022-12-23 11:31:43 -03:00
Guido Curcio
693e40d385 Merge branch 'main' into feat/auth-management 2022-12-23 11:28:53 -03:00
Szilárd Dóró
ff186a8d09 feat(dashboard): add support for the _not operator 2022-12-23 14:48:03 +01:00
Szilárd Dóró
3061771908 fix(dashboard): improve validation 2022-12-23 14:14:58 +01:00
Szilárd Dóró
c681cc9bef chore(dashboard): remove code blocking submission 2022-12-23 11:22:15 +01:00
Szilárd Dóró
3a80504427 feat(dashboard): add form validation to permission editor 2022-12-23 11:21:53 +01:00
Nuno Pato
9a1aa7bb2e changeset 2022-12-22 20:08:28 -01:00
Nuno Pato
98345f2e78 dashboard: add functions to the logs dashboard 2022-12-22 20:05:39 -01:00
Guido Curcio
f29abe6238 add changeset 2022-12-22 16:51:19 -03:00
Szilárd Dóró
8956d47bce fix(dashboard): start polling manually
reference issue: https://github.com/apollographql/apollo-client/issues/9819
2022-12-22 18:22:00 +01:00
Szilárd Dóró
dd0738d5f7 chore(dashboard): add changeset 2022-12-22 17:57:36 +01:00
Szilárd Dóró
11d77d6011 fix(dashboard): provisioning status polling 2022-12-22 17:53:07 +01:00
renovate[bot]
a78cd2f18f chore(deps): update dependency vitest to ^0.26.0 2022-12-22 16:01:51 +00:00
Szilárd Dóró
e025c5857f chore(dashboard): simplify section components 2022-12-22 16:54:23 +01:00
Pilou
6ef340daad Merge pull request #1424 from nhost/ci/retry-cli-install
ci: retry the Nhost CLI installation
2022-12-22 16:31:46 +01:00
Pierre-Louis Mercereau
a96e3c9163 ci: retry the Nhost CLI installation 2022-12-22 16:21:36 +01:00
Szilárd Dóró
c3c95053dc fix(dashboard): improve permission editor UX 2022-12-22 15:30:12 +01:00
Szilárd Dóró
b27e94c712 chore(dashboard): add subtitle to column presets 2022-12-22 15:04:30 +01:00
Szilárd Dóró
279cf78aa5 fix(dashboard): clear dirty state when cancelling 2022-12-22 14:20:35 +01:00
Szilárd Dóró
8817adddf6 Merge remote-tracking branch 'origin/main' into feat/permission-editor 2022-12-22 14:12:03 +01:00
Guido Curcio
229c47cf16 set up redirect from /users/<userId> -> users?userId=<userId> 2022-12-22 10:04:56 -03:00
Szilárd Dóró
1388f11508 feat(dashboard): backend only permissions
- improved the conversion from Nhost's data structure to Hasura's
2022-12-22 13:58:53 +01:00
Guido Curcio
e5e705350d fix: style inconsistencies in users page 2022-12-22 09:56:37 -03:00
Guido Curcio
4f81b0695d add responsive breakpoints to users's table, user's drawer 2022-12-22 09:51:47 -03:00
Szilárd Dóró
c2bfed6e1f feat(dashboard): add support for local migrations 2022-12-22 13:38:59 +01:00
Szilárd Dóró
97dc261fcd feat(dashboard): persist permissions 2022-12-22 13:04:28 +01:00
Szilárd Dóró
4ca93c2773 feat(dashboard): delete permissions 2022-12-22 12:27:14 +01:00
Szilárd Dóró
9c5b6532d3 Merge pull request #1422 from nhost/fix/persistent-drawer
fix(dashboard): close modals when navigating
2022-12-22 11:02:40 +01:00
Szilárd Dóró
f8b32584b4 Merge branch 'main' into feat/permission-editor 2022-12-22 10:55:09 +01:00
Szilárd Dóró
889df8ca4d fix(dashboard): don't break unit tests 2022-12-22 10:54:02 +01:00
Pilou
b998e09e10 Merge pull request #1421 from nhost/chore/integrations-labeler
ci: label integrations
2022-12-22 10:48:33 +01:00
Szilárd Dóró
9e0486a362 chore(dashboard): add changeset 2022-12-22 10:01:12 +01:00
Szilárd Dóró
74037cec68 fix(dashboard): close modals when navigating 2022-12-22 10:00:21 +01:00
Guido Curcio
800db1b300 remove page query param from url if currentPage is equal to 1. 2022-12-22 03:02:53 -03:00
Guido Curcio
a40baa8c63 show up to 3 providers in users table, render a chip with remainder length 2022-12-22 02:23:17 -03:00
Guido Curcio
5cc06609c2 fix(EditUserForm): copy button. 2022-12-22 01:51:17 -03:00
Pierre-Louis Mercereau
819e68b501 ci: label integrations 2022-12-21 19:36:05 +01:00
Guido Curcio
efa68aab83 fix(roles): only show alert when editing role. 2022-12-21 14:15:48 -03:00
Guido Curcio
3a696d366a clicking on auth button of sidebar goes to page 1 2022-12-21 13:55:15 -03:00
Guido Curcio
e3e21b6164 upper bound on number of pages 2022-12-21 13:44:50 -03:00
Guido Curcio
9259663c76 missing key prop on providers 2022-12-21 13:43:28 -03:00
Guido Curcio
26dd7faf05 retrigger effect when nr of pages changes 2022-12-21 13:31:30 -03:00
Pilou
10cc213933 Merge pull request #1418 from nhost/renovate/mermaid-9.x
fix(deps): update dependency mermaid to v9
2022-12-21 16:50:53 +01:00
Guido Curcio
1b5cb93761 include alert when editing roles 2022-12-21 12:18:51 -03:00
Pilou
4157c012fd Merge pull request #1406 from nhost/chore/deprecate-axios
chore: deprecate axios
2022-12-21 16:02:26 +01:00
Szilárd Dóró
8de1be4910 feat(dashboard); add custom claims to column presets 2022-12-21 15:42:46 +01:00
Pilou
9515096349 Update .changeset/kind-cycles-explain.md
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-12-21 15:38:33 +01:00
Pilou
4dd5617855 Update .changeset/kind-cycles-explain.md
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-12-21 15:37:29 +01:00
Szilárd Dóró
d0f9ffba73 feat(dashboard): initial column preset section 2022-12-21 14:56:55 +01:00
renovate[bot]
01dc358842 fix(deps): update dependency mermaid to v9 2022-12-21 13:22:15 +00:00
Pilou
925a1808e6 Merge pull request #1413 from nhost/renovate/vite-4.x
chore(deps): update dependency vite to v4
2022-12-21 14:10:23 +01:00
Pilou
ac80f88727 Merge pull request #1410 from nhost/chore/remove-query-string
chore: remove unused query-string dependency
2022-12-21 13:12:23 +01:00
Pilou
3078247629 Merge pull request #1412 from nhost/test/docker-compose
test(docker-compose): more retries
2022-12-21 13:12:05 +01:00
Pierre-Louis Mercereau
144c0084d2 chore: add Nhost prefix to client types 2022-12-21 12:27:19 +01:00
Pierre-Louis Mercereau
bbdfb77a07 refactor: types 2022-12-21 12:22:27 +01:00
Guido Curcio
b6df9e2e8c fix: correct onPageChange prop comments. 2022-12-21 08:21:39 -03:00
Guido Curcio
48f15eb849 remove unhelpful comments 2022-12-21 08:16:19 -03:00
Guido Curcio
141642d40d show only two providers in users table 2022-12-21 08:07:23 -03:00
Guido Curcio
def4a3a2ea fix: password change error handling, refetch, and closing dialog; createdAt in users body; use internal ban state for unbanning user; implement error handling for odd pages from URL & side-effect for user id queries; add comments to side-effects. 2022-12-21 08:02:50 -03:00
Szilárd Dóró
fcb4d167e7 feat(dashboard): added aggregation queries and root field customization 2022-12-21 10:45:44 +01:00
Pilou
b5a9c1be47 Update packages/nhost-js/src/clients/functions.ts
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-12-20 18:45:36 +01:00
Szilárd Dóró
c5137c6c45 fix(dashboard): fix async initialization 2022-12-20 16:26:57 +01:00
Szilárd Dóró
297c2a965d fix(dashboard): fix async initialization issues 2022-12-20 16:06:28 +01:00
Pierre-Louis Mercereau
5b5e7d9640 chore: update vite-related libraries 2022-12-20 15:38:28 +01:00
Guido Curcio
b876a4ada1 handle pagination between pages (parse & mutate url) 2022-12-20 11:02:35 -03:00
Pierre-Louis Mercereau
c02e0c63f2 refactor: simplify 2022-12-20 14:48:47 +01:00
Guido Curcio
7e064355ba show banned users on users table 2022-12-20 10:47:27 -03:00
renovate[bot]
788482fab2 chore(deps): update dependency vite to v4 2022-12-20 13:40:30 +00:00
Pilou
16d94821b8 Merge pull request #1411 from nhost/renovate/tj-actions-changed-files-35.x
chore(deps): update tj-actions/changed-files action to v35
2022-12-20 14:30:16 +01:00
Guido Curcio
0f6ece6b8c remove pagination when there are no users from filter. 2022-12-20 10:28:46 -03:00
Pierre-Louis Mercereau
5c4ab54c90 test(docker-compose): more retries 2022-12-20 14:28:05 +01:00
Guido Curcio
793e7392da error handling for creating users 2022-12-20 10:24:53 -03:00
renovate[bot]
5941568bbb chore(deps): update tj-actions/changed-files action to v35 2022-12-20 13:15:40 +00:00
Pierre-Louis Mercereau
1a9e1fde1d chore: remove unused query-string dependency 2022-12-20 14:12:29 +01:00
Guido Curcio
a9bbd1303e close drawer after deleting user; track disabled state of an user. 2022-12-20 09:30:41 -03:00
Guido Curcio
b0ed2b6f14 button instead of IconButton for password change 2022-12-20 09:03:33 -03:00
Szilárd Dóró
9194be4816 feat(dashboard): fetch table permissions 2022-12-20 12:55:47 +01:00
Guido Curcio
3e951eab4f fix: don't hide the OAuth methods section; remove the active chip. 2022-12-20 08:42:13 -03:00
Guido Curcio
cd7a198715 fix: fix showing minutes instead of month; correct format. 2022-12-20 08:36:36 -03:00
Guido Curcio
7c4d05a25e fix: loading states, remove sign-in methods, fix default role, move handlers to specific component. 2022-12-20 07:15:43 -03:00
Szilárd Dóró
199fd0d491 fix(dashboard): correct relationship discovery 2022-12-20 10:36:48 +01:00
Pierre-Louis Mercereau
32c0632526 docs: correct changeset 2022-12-20 10:28:09 +01:00
Pierre-Louis Mercereau
19cca7f45d chore: changeset 2022-12-20 10:22:00 +01:00
Szilárd Dóró
cd4b58674a feat(dashboard): integrate RuleGroupEditor 2022-12-20 10:15:14 +01:00
Pierre-Louis Mercereau
191580a819 Merge branch 'main' into chore/deprecate-axios 2022-12-20 10:12:29 +01:00
Pilou
4a57861354 Merge pull request #1396 from nhost/integrations-iaubsd98hasd
Integrations and README updates
2022-12-20 10:11:48 +01:00
Pierre-Louis Mercereau
d96b817476 refactor: simplify 2022-12-20 10:04:19 +01:00
renovate[bot]
db6db8d860 chore(deps): update dependency @types/node to v18 2022-12-20 08:35:37 +00:00
Pierre-Louis Mercereau
fe405ba123 fix: docgen and tsconfig 2022-12-20 09:33:53 +01:00
Pilou
972a5f652f Merge pull request #1281 from nhost/renovate/typescript-4.x
chore(deps): update dependency typescript to v4.9.4
2022-12-20 09:32:34 +01:00
Pierre-Louis Mercereau
7a2c140524 Merge branch 'main' into chore/deprecate-axios 2022-12-20 09:26:41 +01:00
Pierre-Louis Mercereau
3d717d68a9 Merge branch 'main' into integrations-iaubsd98hasd 2022-12-20 09:22:29 +01:00
Pierre-Louis Mercereau
13b6a47bef fix: ignore duplicate type 2022-12-20 09:13:50 +01:00
Pierre-Louis Mercereau
2a6caa47bd chore: deprecate axios 2022-12-20 09:00:59 +01:00
Szilárd Dóró
4f20d8640d feat(dashboard): added Radio and RadioGroup 2022-12-19 17:04:57 +01:00
Szilárd Dóró
5e8ae336a2 feat(dashboard): added form for role editing 2022-12-19 15:54:35 +01:00
Szilárd Dóró
1ff73f4f00 chore(dashboard): improved table appearance 2022-12-19 14:29:41 +01:00
Szilárd Dóró
7ce44ae1b1 feat(dashboard): improve table customizability 2022-12-19 14:16:51 +01:00
Szilárd Dóró
1b847617b6 feat(dashboard): added default role table 2022-12-19 13:59:58 +01:00
Szilárd Dóró
df53ec2954 feat(dashboard): started working on permissions layout 2022-12-19 12:49:31 +01:00
Szilárd Dóró
7b4c32816e feat(dashboard): started working on permission editor 2022-12-19 11:54:12 +01:00
Szilárd Dóró
dbbfaef451 Merge pull request #1373 from nhost/feat/rule-group-editor
feat(dashboard): Rule Group Editor
2022-12-19 11:52:16 +01:00
Szilárd Dóró
b4c07f1723 Merge branch 'main' into feat/rule-group-editor 2022-12-19 11:51:18 +01:00
Szilárd Dóró
e983eb53d9 chore(dashboard): remove "is" prefix 2022-12-19 11:09:18 +01:00
Szilárd Dóró
49867fdcf7 fix(dashboard): add missing inputValue for freeSolo input 2022-12-19 11:07:50 +01:00
Szilárd Dóró
cc428d73ee fix(dashboard): async initialization 2022-12-19 11:02:16 +01:00
Pilou
940a1db0fc Merge branch 'main' into integrations-iaubsd98hasd 2022-12-19 11:00:35 +01:00
Pilou
d70f29a408 Merge pull request #1400 from nhost/contributors-readme-action-PL6rn2c5lN
docs(contributor): contributors readme action update
2022-12-19 10:58:17 +01:00
Pilou
52cb055520 Merge pull request #1402 from nhost/contributors-readme-action-IrWb62uoZo
docs(contributor): contributors readme action update
2022-12-19 10:57:46 +01:00
github-actions[bot]
8c406237a2 docs(contributor): contrib-readme-action has updated readme 2022-12-19 09:57:07 +00:00
Pilou
d036e282e5 Merge pull request #1395 from nhost/fix-asu9hda0sd
revert code to previously working
2022-12-19 10:56:53 +01:00
Szilárd Dóró
39530cd8e8 feat(dashboard): support Hasura array operators 2022-12-19 10:16:36 +01:00
github-actions[bot]
8b58627608 docs(contributor): contrib-readme-action has updated readme 2022-12-19 08:06:33 +00:00
Calvin Lang
c4e2d87e5c Removed accept headers * in axios requests 2022-12-19 16:06:26 +08:00
Johan Eliasson
66b0378d38 Merge pull request #1397 from muttenzer/docs/event-trigger-env-var
Replace deprecated "NHOST_BACKEND_URL" in event trigger example
2022-12-19 09:06:09 +01:00
Timo M
22d7a36247 update hasura event trigger video using the correct env var 2022-12-17 17:48:36 +01:00
Timo M
7d2eb2de66 update webhook url format image 2022-12-17 17:45:09 +01:00
Timo M
4bba002c30 replace NHOST_BACKEND_URL with NHOST_FUNCTIONS_URL env var 2022-12-17 17:43:42 +01:00
Johan Eliasson
881a3344d4 update lock file 2022-12-17 11:00:45 +01:00
Johan Eliasson
d1562d33fb update 2022-12-17 10:57:06 +01:00
Johan Eliasson
21ced66f22 moved apollo and react-apollo to integrations/ folder 2022-12-17 10:50:13 +01:00
Johan Eliasson
2a2d86904d revert to previously working 2022-12-17 10:46:12 +01:00
Johan Eliasson
82d46f716b Merge pull request #1381 from nhost/docs-ui8asud9
docs update
2022-12-17 10:42:33 +01:00
Johan Eliasson
5e26810868 using authentication everywhere 2022-12-17 10:36:39 +01:00
Johan Eliasson
d590258371 correct query 2022-12-17 10:34:06 +01:00
Johan Eliasson
697ef57cb8 Update docs/docs/authentication/users.mdx
Co-authored-by: Guido Curcio <guidomaurocurcio@gmail.com>
2022-12-17 10:29:28 +01:00
Johan Eliasson
93002dc8c3 Update docs/docs/authentication/users.mdx
Co-authored-by: Guido Curcio <guidomaurocurcio@gmail.com>
2022-12-17 10:29:09 +01:00
Guido Curcio
357e0933ff pass roles with payload; handle adding and removing roles from user 2022-12-17 03:18:53 -03:00
Johan Eliasson
baa1937d06 Merge pull request #1228 from nhost/react-urql
urql
2022-12-16 22:11:29 +01:00
Johan Eliasson
03e5662df9 Merge branch 'main' into react-urql 2022-12-16 22:10:54 +01:00
Pilou
e0711bdfc8 Merge pull request #1372 from kematzy/docs-update-vue-quickstart
Update of Vue Quickstart documentation
2022-12-16 22:05:55 +01:00
Johan Eliasson
caca27fde3 Merge branch 'main' into docs-ui8asud9 2022-12-16 21:55:55 +01:00
Johan Eliasson
b0d51033c6 typo fix 2022-12-16 21:43:02 +01:00
Johan Eliasson
088f9394fc Update docs/docs/graphql/permissions.mdx
Co-authored-by: Siarhei Lipchyk <contact@siarhei.dev>
2022-12-16 21:42:07 +01:00
Szilárd Dóró
a91361f971 feat(dashboard): add support for Hasura permission object converter 2022-12-16 21:10:04 +01:00
Pilou
a4f5be6ab9 Merge pull request #1390 from nhost/fix/strict-mode
fix: React 18 strict mode and other improvements
2022-12-16 19:09:55 +01:00
Szilárd Dóró
dbbccbf1cd fix(dashboard): mobile responsive design 2022-12-16 13:50:22 +01:00
Guido Curcio
7d8f82b99d user.lastSeen fix 2022-12-16 09:36:22 -03:00
Szilárd Dóró
a70dc7b352 chore(dashboard): improve rule group editor design 2022-12-16 12:57:03 +01:00
Pierre-Louis Mercereau
54df0df42b fix: 🐛 React 18 strict mode and other improvements 2022-12-16 12:08:04 +01:00
Szilárd Dóró
0bbb2598fd feat(dashboard): finalize Hasura permission converter 2022-12-16 11:01:25 +01:00
Guido Curcio
e10480b761 missing refetch when deleting user. 2022-12-15 15:02:05 -03:00
Guido Curcio
1343abbe50 comments & types 2022-12-15 12:33:04 -03:00
Szilárd Dóró
4ba34cc827 feat(dashboard): working on Hasura converters 2022-12-15 16:00:11 +01:00
Guido Curcio
fa37546139 handle banning users 2022-12-15 11:22:55 -03:00
Szilárd Dóró
42ece48ce3 feat(dashboard): added support for text truncation 2022-12-15 15:05:24 +01:00
Guido Curcio
112526a984 render email & password only if email and no social oauth. 2022-12-15 11:04:40 -03:00
Szilárd Dóró
f97ab31f69 feat(dashboard): added support for _c* operators 2022-12-15 10:21:09 +01:00
Johan Eliasson
21dc1ecd6b image didn't exist 2022-12-15 10:01:24 +01:00
Szilárd Dóró
c11adbe3e2 chore(dashboard): improve initialization 2022-12-15 09:58:27 +01:00
Johan Eliasson
6078e9c207 new sys env vars 2022-12-15 09:22:00 +01:00
Johan Eliasson
450582dc43 update 2022-12-15 09:12:11 +01:00
Johan Eliasson
ddec0e1be1 update 2022-12-15 09:09:31 +01:00
Johan Eliasson
698154b24b permissions updated 2022-12-15 09:05:42 +01:00
Johan Eliasson
9c87b0f67b updates 2022-12-14 20:48:43 +01:00
Pilou
85afe3d216 Merge pull request #1368 from nhost/refactor/less-peer-dependencies
refactor: less peer dependencies
2022-12-14 19:19:09 +01:00
Pierre-Louis Mercereau
0773e5215f Merge branch 'main' into refactor/less-peer-dependencies 2022-12-14 19:01:31 +01:00
Pierre-Louis Mercereau
10f25fcc4e chore: remove needless paths 2022-12-14 18:59:57 +01:00
Johan Eliasson
2ee90d6ea3 removed unused files 2022-12-14 18:16:33 +01:00
Johan Eliasson
5db5323a1d updates 2022-12-14 18:14:40 +01:00
Guido Curcio
0c74806245 fix showing limit in pagination rather than total users. 2022-12-14 13:31:47 -03:00
Guido Curcio
5df84d7f50 fix remoteAppUser type 2022-12-14 13:08:39 -03:00
Guido Curcio
d58de8fcf8 join totalUsers query with main users query 2022-12-14 12:55:49 -03:00
Szilárd Dóró
ed93d4b583 feat(dashboard): add onInitialized to autocomplete 2022-12-14 16:15:40 +01:00
Szilárd Dóró
1ec1953eaa chore(dashboard): improve default value error tolerance 2022-12-14 15:41:17 +01:00
Szilárd Dóró
63e9c3933e chore(dashboard): improved column autocomplete value management 2022-12-14 15:33:29 +01:00
Guido Curcio
85674c4d90 handle users providers other than email (e.g. github); handle removed providers from a user. 2022-12-14 11:32:28 -03:00
Guido Curcio
5a1d3b9bfc handle anonymous users 2022-12-14 11:06:35 -03:00
Guido Curcio
942570ed29 show table header on not found search strings 2022-12-14 10:33:29 -03:00
Szilárd Dóró
1b1620f633 chore(dashboard): cleaned up default value functionality 2022-12-14 14:24:41 +01:00
Johan Eliasson
e8e8d661e1 small exchange refactor 2022-12-14 13:50:44 +01:00
Johan Eliasson
ba08ec7f5c comments 2022-12-14 13:44:39 +01:00
Johan Eliasson
fb0c98c21d moved react-urql to integrations 2022-12-14 13:37:20 +01:00
Johan Eliasson
c9f575c40c database settings update 2022-12-14 13:24:25 +01:00
Johan Eliasson
6c8bed7ecc Update packages/react-urql/src/provider.tsx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-14 13:23:37 +01:00
Guido Curcio
3058eee48f 25 limit, loading screen 2022-12-14 09:21:14 -03:00
Szilárd Dóró
d5a712f7ef feat(dashboard): add support for default values 2022-12-14 13:07:11 +01:00
Johan Eliasson
83422f5ee6 updated README 2022-12-14 09:42:01 +01:00
Johan Eliasson
51909a6a8f remove 2022-12-14 09:34:05 +01:00
Johan Eliasson
2f30797556 update 2022-12-14 09:28:35 +01:00
Johan Eliasson
0b8f7d1661 Merge branch 'main' into react-urql 2022-12-14 09:27:54 +01:00
Johan Eliasson
52ee9d84b6 demo img 2022-12-14 09:11:17 +01:00
Szilárd Dóró
d9612b28b0 chore(dashboard): update pnpm-lock.yaml 2022-12-14 09:07:56 +01:00
Szilárd Dóró
0034791493 Merge remote-tracking branch 'origin/main' into feat/rule-group-editor 2022-12-14 09:06:15 +01:00
Johan Eliasson
80fed14a6b updated 2022-12-14 09:03:30 +01:00
Szilárd Dóró
d457ada435 Merge pull request #1380 from nhost/changeset-release/main
chore: update versions
2022-12-14 08:57:35 +01:00
github-actions[bot]
b41e5a9df5 chore: update versions 2022-12-14 07:46:43 +00:00
Szilárd Dóró
0c8ace1bd4 Merge pull request #1379 from nhost/fix/dashboard-urls
fix(dashboard): use correct service URLs
2022-12-14 08:45:12 +01:00
Johan Eliasson
3f800a068b update 2022-12-13 22:31:42 +01:00
Szilárd Dóró
7d490fe569 fix(dashboard): use correct GraphQL slug and admin secret 2022-12-13 21:34:11 +01:00
Szilárd Dóró
d6527122db fix(dashboard): use correct service URLs 2022-12-13 21:21:12 +01:00
Johan Eliasson
3211140dec remove changeset for now 2022-12-13 21:10:41 +01:00
Johan Eliasson
469352cd81 merge 2022-12-13 21:09:44 +01:00
Szilárd Dóró
88400f6b7c chore(dashboard): add delay to queries 2022-12-13 17:41:16 +01:00
Szilárd Dóró
f8c8a06d71 feat(dashboard): permission variable queries 2022-12-13 17:24:14 +01:00
Szilárd Dóró
ebc1730fce Merge pull request #1375 from nhost/changeset-release/main
chore: update versions
2022-12-13 16:55:15 +01:00
Pierre-Louis Mercereau
c1cd1e813c docs: update changelog 2022-12-13 16:34:41 +01:00
Pierre-Louis Mercereau
e08a074474 chore: un-bump major versions 2022-12-13 16:33:30 +01:00
Szilárd Dóró
2f819865bc chore(dashboard): move handlers to mocks folder 2022-12-13 16:32:01 +01:00
Szilárd Dóró
3888f3041f chore(dashboard): improve operator selectors 2022-12-13 16:15:01 +01:00
Guido Curcio
bacb1b9720 fix number of pages going to 0 on nextPageClick 2022-12-13 11:53:42 -03:00
Guido Curcio
e119e4fc18 styles on userBody 2022-12-13 11:19:23 -03:00
Pierre-Louis Mercereau
569c4004f6 Merge branch 'main' into refactor/less-peer-dependencies 2022-12-13 14:10:31 +01:00
github-actions[bot]
95932fa3f2 chore: update versions 2022-12-13 13:06:10 +00:00
Pilou
99402b77d1 Merge pull request #1376 from nhost/chore/docgen
chore: remove generated documentation from the repository
2022-12-13 14:04:48 +01:00
Szilárd Dóró
f6fb2cd8e6 Merge pull request #1377 from nhost/fix/local-development-region
fix(dashboard): don't break the UI when project is not loaded yet
2022-12-13 13:49:38 +01:00
Szilárd Dóró
5c2cf59b41 chore(dashboard): update CHANGELOG.md 2022-12-13 13:11:50 +01:00
Szilárd Dóró
a6d31dc260 fix(dashboard): don't break the UI when project is not loaded yet 2022-12-13 13:10:54 +01:00
Guido Curcio
b1fe2be963 users body loader, pagination elemnts; input fix for pages > 9 2022-12-13 09:07:36 -03:00
Szilárd Dóró
872e50b635 chore(dashboard): improve Chip in autocomplete 2022-12-13 13:01:44 +01:00
Pierre-Louis Mercereau
bd73557a47 Merge branch 'main' into chore/docgen 2022-12-13 12:01:04 +01:00
Guido Curcio
9b6e8ab3bc slotProps for pagination component 2022-12-13 07:50:43 -03:00
Pilou
c95bab70c2 Merge pull request #1366 from nhost/refactor/merge-core
refactor: decommission `@nhost/core`
2022-12-13 10:50:36 +01:00
Szilárd Dóró
52d4b5de45 Merge pull request #1347 from nhost/changeset-release/main
chore: update versions
2022-12-13 10:29:25 +01:00
Szilárd Dóró
fe0742e278 chore(dashboard): update pnpm-lock.yaml 2022-12-13 10:15:29 +01:00
Szilárd Dóró
ded57d3b24 Merge remote-tracking branch 'origin/main' into feat/rule-group-editor 2022-12-13 10:13:03 +01:00
Szilárd Dóró
c30abaea22 chore(dashboard): add reference to storybook to the README 2022-12-13 10:11:31 +01:00
Szilárd Dóró
6b4ab50f74 fix(packages): nextjs and react-apollo versions 2022-12-13 09:50:42 +01:00
github-actions[bot]
ceba605d0b chore: update versions 2022-12-13 08:47:12 +00:00
Szilárd Dóró
9249a85ee5 Merge pull request #1371 from nhost/fix/aws-name-null-reference
fix(dashboard): don't break UI when region is nullish
2022-12-13 09:45:32 +01:00
Guido Curcio
d2c4b7cad1 custom styles for pagination in users tab 2022-12-13 02:26:32 -03:00
Guido Curcio
59d737696a onChangePage handler for Pagination 2022-12-13 02:13:57 -03:00
Kematzy
22de3214f1 Minor typo & formatting changes to standardise 2022-12-13 11:52:08 +08:00
Kematzy
cf880f992f Update Vue Quickstart docs 2022-12-13 11:50:51 +08:00
Szilárd Dóró
f4d70f88e9 chore(dashboard): add changeset 2022-12-12 22:43:06 +01:00
Szilárd Dóró
0d09b80b12 fix(dashboard): fix nullish region in local mode 2022-12-12 22:42:09 +01:00
Pierre-Louis Mercereau
195adfb04a chore: remove generated documentation from the repository 2022-12-12 21:26:21 +01:00
Pilou
aee4cdcb72 Update .changeset/modern-days-eat.md
Co-authored-by: Johan Eliasson <johan@nhost.io>
2022-12-12 20:20:03 +01:00
Pilou
b09930c8a4 Merge pull request #1369 from nhost/contributors-readme-action-TvJAKaD1lO
docs(contributor): contributors readme action update
2022-12-12 19:06:31 +01:00
github-actions[bot]
687951281e docs(contributor): contrib-readme-action has updated readme 2022-12-12 18:05:20 +00:00
Pilou
c0f05acd9b Merge pull request #1365 from badgifter/patch-windowslive-url
Windows Live Provider displayed link updated to match backend url
2022-12-12 19:05:01 +01:00
Szilárd Dóró
87af60cc03 feat(dashboard): add experimental multiselect autocomplete 2022-12-12 17:30:09 +01:00
Pierre-Louis Mercereau
3d8dd39995 fix: dashboard tests 2022-12-12 17:00:41 +01:00
Pierre-Louis Mercereau
65687beecc chore: rewrite the pr so it fits with Nuno's mind 2022-12-12 16:41:16 +01:00
Szilárd Dóró
62aa859737 feat(dashboard): support for boolean input field 2022-12-12 15:53:50 +01:00
Pierre-Louis Mercereau
d8c2d369aa chore: changeset 2022-12-12 15:43:55 +01:00
Pierre-Louis Mercereau
a4e4926aeb Merge branch 'main' into refactor/merge-core 2022-12-12 15:28:26 +01:00
Guido Curcio
35cd76e562 UsersBodyProps types 2022-12-12 11:16:56 -03:00
Guido Curcio
266bbe837d type users, add comments UsersBodyProps, memo limit & offset. 2022-12-12 11:15:31 -03:00
Guido Curcio
caf785a938 feat: Pagination common component. 2022-12-12 10:48:06 -03:00
Szilárd Dóró
9bc3e755df feat(dashboard): add dynamic rule group editor input type 2022-12-12 14:43:22 +01:00
Pilou
4a9471cc16 Create rude-singers-carry.md 2022-12-12 14:42:55 +01:00
Szilárd Dóró
638a7ac11d feat(dashboard): finalize relationship discovery 2022-12-12 14:19:02 +01:00
Bad Gifter
567e370bdc microsoft changed to windowslive 2022-12-12 14:04:35 +03:00
Bad Gifter
a91f2db0e2 Windows Live Provider displayed link updated to match backend url 2022-12-12 13:49:08 +03:00
Szilárd Dóró
4e49c8db50 fix(dashboard): correct column autocomplete input value 2022-12-12 11:28:49 +01:00
Szilárd Dóró
210f65b4db feat(dashboard): added column autocomplete to rule group editor 2022-12-12 10:50:15 +01:00
Szilárd Dóró
1b6482126f feat(dashboard): add relationship discovery 2022-12-12 10:10:39 +01:00
Guido Curcio
96f9c1a55d handle pagination when searching for users 2022-12-12 06:08:35 -03:00
Guido Curcio
731460b20d handle pagination in users' table 2022-12-12 06:05:22 -03:00
Guido Curcio
1537d46b1d handle states of search: case insensitive query, preserve search query. 2022-12-12 04:29:12 -03:00
Guido Curcio
632def158d render activated providers from users. 2022-12-12 02:38:53 -03:00
Guido Curcio
39271a67e2 render all available roles from the app, check roles form user. 2022-12-12 02:11:43 -03:00
Guido Curcio
9e25c4f386 render verified checkbox as helpertext except when errors; disable prop on phoneNumberVerified when no phoneNumber. 2022-12-12 01:26:48 -03:00
Guido Curcio
dd58a4ac7f handle mutation of displayName, email, and avatarUrl; render avatar url if not default=blank. 2022-12-12 01:14:04 -03:00
Guido Curcio
b9c3567baa update gql query with locale, lastSeen, emailVerified, and phoneNumberVerified 2022-12-12 00:56:00 -03:00
Guido Curcio
108937789a EditUserForm: roles & sign-in methods styles. 2022-12-12 00:20:59 -03:00
Guido Curcio
e651745a7e no users found; default & allowed roles 2022-12-11 23:15:29 -03:00
Szilárd Dóró
699debb2b8 fix(dashboard): correct group rendering 2022-12-09 16:12:05 +01:00
Szilárd Dóró
3e08dc7f8c feat(dashboard): improve navigation in popover 2022-12-09 14:26:46 +01:00
Szilárd Dóró
6928b48781 fix(dashboard): custom autocomplete props 2022-12-09 14:14:58 +01:00
Szilárd Dóró
02886350ff chore(dashboard): use custom autocomplete for column selector 2022-12-09 14:11:32 +01:00
Szilárd Dóró
b3672f8246 feat(dashboard): column selector metadata support 2022-12-09 12:09:43 +01:00
Guido Curcio
6091b4a8e8 handle delete users & refetch main users query 2022-12-09 06:46:01 -03:00
Szilárd Dóró
aea99ad2c8 Merge pull request #1361 from nhost/fix/apple-sign-in-mutation
fix(dashboard): do not show error when submitting Apple provider settings
2022-12-09 09:45:33 +01:00
Szilárd Dóró
594488e435 chore(dashboard): add changeset 2022-12-09 09:29:32 +01:00
Szilárd Dóró
bb83b0f81a fix(dashboard): do not show error when submitting Apple provider settings 2022-12-09 09:18:46 +01:00
Guido Curcio
82ddcbd180 handle search strings, static UsersBodyHeader, nullish render on no users 2022-12-09 03:48:34 -03:00
Guido Curcio
8aa7aafa3b add form footer to EditUserForm 2022-12-09 03:10:23 -03:00
Guido Curcio
183cb4b26a EditUserPasswordForm: handle change password edits, remote gql client, toast on edit. 2022-12-09 03:00:20 -03:00
Guido Curcio
3a7377c6e2 EditUserForm: add avatar with letters, change password button & modal. 2022-12-08 23:15:05 -03:00
Guido Curcio
1529f58c33 fetch remote project users on page load 2022-12-08 23:00:26 -03:00
Guido Curcio
95af5421d1 UsersBody: render first two letters of displayname if no avatar 2022-12-08 22:51:11 -03:00
Guido Curcio
feb39404db first section of user's drawer 2022-12-08 22:26:54 -03:00
Pilou
0384d7c7c4 Merge pull request #1360 from nhost/contributors-readme-action-cDMbyYcNKN
docs(contributor): contributors readme action update
2022-12-08 19:40:40 +01:00
Pilou
7e356a9604 Merge pull request #1357 from nhost/contributors-readme-action-sNfze0TcKw
docs(contributor): contributors readme action update
2022-12-08 19:29:19 +01:00
github-actions[bot]
013e55a307 docs(contributor): contrib-readme-action has updated readme 2022-12-08 18:28:57 +00:00
Pilou
2a71257cde Merge pull request #1358 from nhost/fix/workspace-packages
fix: use workspace to resolve internal dependencies
2022-12-08 19:28:41 +01:00
Pierre-Louis Mercereau
583a4401d0 docs: add explanation to the CI workflow 2022-12-08 18:50:01 +01:00
Pierre-Louis Mercereau
914e91a0b0 ci: correct test 2022-12-08 18:40:49 +01:00
Pierre-Louis Mercereau
98698213e2 ci: run all e2e tests when lockfile changed 2022-12-08 18:29:54 +01:00
Pierre-Louis Mercereau
756daa97cd fix: use workspace to resolve internal dependencies 2022-12-08 16:58:47 +01:00
github-actions[bot]
ab5a2b119c docs(contributor): contrib-readme-action has updated readme 2022-12-08 15:18:12 +00:00
Johan Eliasson
ffdcce1463 Merge pull request #1352 from wollerman/patch-1
Add a section on managing local seed data
2022-12-08 16:17:54 +01:00
Guido Curcio
15b3100c63 pass on data from previously fetched users table 2022-12-08 12:17:22 -03:00
Guido Curcio
f7ef7d106d open drawer on edit user 2022-12-08 11:18:11 -03:00
Szilárd Dóró
0dbbcc5595 chore(dashboard): improve column autocomplete 2022-12-08 15:16:13 +01:00
Szilárd Dóró
a3dcb6106e Merge pull request #1355 from nhost/chore/revert-types-react-bump
chore(dashboard): revert `@types/react` bump
2022-12-08 15:03:24 +01:00
Szilárd Dóró
208d224763 chore(dashboard): revert @types/react bump 2022-12-08 14:46:36 +01:00
Szilárd Dóró
816f8d069d fix(dashboard): pnpm-lock file 2022-12-08 14:20:28 +01:00
Guido Curcio
72c31622cd render user's table with dropdown trigger 2022-12-08 09:25:07 -03:00
Guido Curcio
6959461e3f handle creating users (CreateUserForm) 2022-12-08 08:40:13 -03:00
Guido Curcio
103472ac77 empty state for new users page 2022-12-08 07:57:42 -03:00
Szilárd Dóró
2ebf99ff8f chore(dashboard): improved column autocomplete 2022-12-08 09:32:51 +01:00
renovate[bot]
c13e492bbf chore(deps): update dependency typescript to v4.9.4 2022-12-08 01:41:29 +00:00
Matt Wollerman
564d000bfc add link to overview description of seed data 2022-12-07 20:21:09 -05:00
Szilárd Dóró
63476a2351 feat(dashboard): start work on column autocomplete 2022-12-07 16:49:55 +01:00
Matt Wollerman
266fda07ab add a section on managing local seed data 2022-12-07 08:45:40 -05:00
Szilárd Dóró
782252c059 chore(dashboard): improved Rule Group editor 2022-12-07 14:42:20 +01:00
Szilárd Dóró
e86978a1ff chore(dashboard): upgrade storybook
- chore(dashboard): improve mobile layout of the rule group editor
2022-12-07 13:32:05 +01:00
Szilárd Dóró
84cfd11953 chore(dashboard): extend rule group editor story 2022-12-07 13:12:13 +01:00
Szilárd Dóró
9a43e136f6 fix(dashboard): fixed rule group editor form structure 2022-12-07 11:22:50 +01:00
Szilárd Dóró
e9cff26fa0 chore(dashboard): improve rule group editor layout 2022-12-07 10:11:54 +01:00
Szilárd Dóró
3d32bca2b3 fix(dashboard): fix rule group editor delete button 2022-12-06 16:11:08 +01:00
Szilárd Dóró
4021feaf38 feat(dashboard): rule editor implementation 2022-12-06 14:02:07 +01:00
Pilou
6174e1ddcc Merge pull request #1341 from nhost/refactor/nhost-react-providers
refactor: nhost react providers
2022-12-06 12:57:26 +01:00
Pierre-Louis Mercereau
af2e3eae37 chore: minor bump 2022-12-06 12:39:30 +01:00
Pilou
d2b4c126f3 Merge pull request #1346 from nhost/chore/prettier-ignore-lockfile
chore: prettier ignore pnpm-lock.yaml
2022-12-06 11:01:00 +01:00
Pierre-Louis Mercereau
7f2e182c47 chore: prettier ignore pnpm-lock.yaml 2022-12-06 10:54:16 +01:00
Szilárd Dóró
306ec74356 chore(dashboard): update import paths 2022-12-06 10:16:42 +01:00
Szilárd Dóró
ae40bd54d4 Merge pull request #1302 from nhost/renovate/react-18.x
chore(deps): update dependency @types/react to v18.0.26
2022-12-06 10:01:27 +01:00
Pilou
b5cc47078a Update .changeset/big-pumpkins-march.md
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-06 09:59:25 +01:00
Pierre-Louis Mercereau
7f251111e2 chore: 🤖 changeset 2022-12-06 09:41:40 +01:00
Pierre-Louis Mercereau
c03dacc3a3 refactor: use NhostProvider in React and Nextjs 2022-12-06 09:39:44 +01:00
Pilou
8b9e1a0ce8 Merge pull request #1339 from nhost/contributors-readme-action-oSzExU64y7
docs(contributor): contributors readme action update
2022-12-06 07:04:10 +01:00
github-actions[bot]
cf9cfec330 docs(contributor): contrib-readme-action has updated readme 2022-12-06 05:52:28 +00:00
Pilou
1c94f56c59 Merge pull request #1338 from iangabrielsanchez/patch-1
Fix typo
2022-12-06 06:52:14 +01:00
Ian Gabriel Sanchez
f06d5deba3 Fix typo 2022-12-06 10:15:59 +08:00
Nuno Pato
8ff58d7f23 Merge pull request #1330 from nhost/changeset-release/main
chore: update versions
2022-12-05 15:22:02 -01:00
github-actions[bot]
8dd1c7415b chore: update versions 2022-12-05 15:56:54 +00:00
Nuno Pato
ebd2749e38 Merge pull request #1329 from nhost/fix/hasura-duplicate-slug
fix(dashboard): use correct Hasura slug
2022-12-05 14:55:28 -01:00
Szilárd Dóró
80b604adda fix(dashboard): use correct Hasura slug 2022-12-05 16:35:35 +01:00
Nuno Pato
9d73050792 Merge pull request #1322 from nhost/changeset-release/main
chore: update versions
2022-12-05 14:20:47 -01:00
renovate[bot]
2a86b8876c chore(deps): update dependency @types/react to v18.0.26 2022-12-05 14:47:07 +00:00
github-actions[bot]
91a1a41f5d chore: update versions 2022-12-05 14:41:11 +00:00
Nuno Pato
22e9c27c81 Merge pull request #1328 from nhost/fix/graphiql-editor
fix(dashboard): prevent error on GraphQL page
2022-12-05 13:39:28 -01:00
Szilárd Dóró
2d2beb53d2 fix(dashboard): prevent error on GraphQL page 2022-12-05 15:26:37 +01:00
Pilou
b403b0d6a0 Merge pull request #1327 from nhost/ci/unambiguous-git-log
chore: don't use PAT in renovate post PR
2022-12-05 14:21:43 +01:00
Pierre-Louis Mercereau
4dbac55cb4 chore: don't use PAT in renovate post PR 2022-12-05 14:20:56 +01:00
Pilou
c6e31ac741 Merge pull request #1324 from nhost/renovate/xstate-vue-2.x
fix(deps): update dependency @xstate/vue to v2
2022-12-05 13:06:56 +01:00
Pilou
0d3e8b3992 Merge pull request #1325 from nhost/ci/unambiguous-git-log
chore(gh-actions): `git log` unabiguously
2022-12-05 12:58:15 +01:00
Pierre-Louis Mercereau
b2afd14d61 chore(gh-actions): git log unabiguously 2022-12-05 12:57:46 +01:00
renovate[bot]
f28f28b6ee fix(deps): update dependency @xstate/vue to v2 2022-12-05 11:52:06 +00:00
Pilou
834b959271 Merge pull request #1323 from nhost/renovate/vueuse-core-9.x
fix(deps): update dependency @vueuse/core to v9
2022-12-05 12:44:43 +01:00
renovate[bot]
4dbc9ccc87 fix(deps): update dependency @vueuse/core to v9 2022-12-05 11:23:06 +00:00
Pierre-Louis Mercereau
2764a1c4b6 Merge branch 'main' into refactor/merge-core 2022-12-05 12:17:21 +01:00
Nuno Pato
1666ca2ec5 Merge pull request #1250 from nhost/chore/deprecate-old-dns-name
chore(dashboard): deprecate old DNS name
2022-12-05 10:10:37 -01:00
Pilou
346791d4d5 Merge pull request #1321 from nhost/renovate/pnpm-find-workspace-dir-5.x
fix(deps): update dependency @pnpm/find-workspace-dir to v5
2022-12-05 11:55:33 +01:00
Pierre-Louis Mercereau
94bdafe22f chore: adapt sync-versions to find-workspace-dir 5 2022-12-05 11:51:44 +01:00
renovate[bot]
33782e9d41 fix(deps): update dependency @pnpm/find-workspace-dir to v5 2022-12-05 10:46:45 +00:00
Johan Eliasson
ea02e1e104 Merge pull request #1311 from nhost/example-serverless-functions-stripe-webhooks
example(serverless functions): Stripe Webhooks
2022-12-05 11:39:36 +01:00
Johan Eliasson
98bf6e3792 Update examples/serverless-functions/functions/stripe-webhook/index.ts
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-12-05 11:39:26 +01:00
Johan Eliasson
d9dcafd643 Update examples/serverless-functions/functions/stripe-webhook/index.ts
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-12-05 11:39:20 +01:00
Johan Eliasson
4f3d97b5ad Merge pull request #1316 from nhost/elitan-patch-3
docs clarifications
2022-12-05 11:38:51 +01:00
Nuno Pato
d1801ceae9 dashboard: fix link 2022-12-05 09:12:40 -01:00
Johan Eliasson
1abc68992f Update users.mdx 2022-12-05 08:15:14 +01:00
Johan Eliasson
8ed965c669 keep migrations file here, otherwise nhost dev won't work 2022-12-03 14:44:22 +01:00
Johan Eliasson
c59f622feb fix 2022-12-03 13:48:40 +01:00
Johan Eliasson
9a30edd038 example 2022-12-03 13:47:42 +01:00
Johan Eliasson
4b2d2a4f55 merge 2022-12-03 13:36:02 +01:00
Johan Eliasson
eba2bd05b8 update 2022-12-03 13:35:04 +01:00
Johan Eliasson
84a1b28261 Create breezy-donuts-try.md 2022-12-03 13:34:20 +01:00
Johan Eliasson
ff3b9df41e cleanup 2022-12-03 13:32:06 +01:00
Szilárd Dóró
99ee9fd10d fix(dashboard): move Brazilian flag 2022-12-02 20:29:58 +01:00
Nuno Pato
9608a327c9 dashboard: add brazilian flag 2022-12-02 18:27:14 -01:00
Szilárd Dóró
492b83ef58 fix(dashboard): correct GraphQL URL 2022-12-02 20:25:11 +01:00
Pierre-Louis Mercereau
a0901914ac Merge branch 'main' into refactor/merge-core 2022-12-02 16:09:32 +01:00
Szilárd Dóró
f1272947dd chore(deps): update @types/react and @types/react-dom 2022-12-02 10:17:32 +01:00
Johan Eliasson
e5041bfd30 Merge branch 'main' into react-urql 2022-12-02 09:59:38 +01:00
Johan Eliasson
3d7cc74feb env vars 2022-12-02 09:42:38 +01:00
Johan Eliasson
d007202783 update 2022-12-02 09:40:26 +01:00
Johan Eliasson
38e92a705d works outside strict mode now 2022-12-02 09:38:16 +01:00
Johan Eliasson
74cc63833a package updates 2022-12-01 22:05:28 +01:00
Johan Eliasson
425320bbb5 lockfile update 2022-12-01 21:31:57 +01:00
Johan Eliasson
499352ad8a Merge branch 'main' into react-urql 2022-12-01 21:30:53 +01:00
Szilárd Dóró
c9f88326b2 chore(dashboard): migrate app URLs to service URLs 2022-11-30 17:12:58 +01:00
Szilárd Dóró
ac8efcbdd5 chore(dashboard): add changeset 2022-11-30 16:13:30 +01:00
Szilárd Dóró
9bc346e8d4 fix(dashboard): minor layout fixes in provider alert 2022-11-30 16:12:11 +01:00
Nuno Pato
efed987d31 fix linter 2022-11-30 12:12:48 -01:00
Nuno Pato
bdab7da7d3 fix linter 2022-11-30 12:02:10 -01:00
Nuno Pato
c2d9993968 asd 2022-11-30 11:53:30 -01:00
Nuno Pato
508ba62207 update copy 2022-11-30 11:52:41 -01:00
Nuno Pato
a3318de06e incorporate minor changes from comments 2022-11-30 11:49:25 -01:00
Pierre-Louis Mercereau
26d577d7ae Merge branch 'main' into refactor/merge-core 2022-11-30 11:43:30 +01:00
Nuno Pato
fa9f7ca052 Deprecate old DNS name 2022-11-30 03:01:17 -01:00
Pierre-Louis Mercereau
622c48a94b chore: update changeset 2022-11-29 16:18:18 +01:00
Pierre-Louis Mercereau
e1a87a05b1 chore: update lock file 2022-11-29 16:15:49 +01:00
Pierre-Louis Mercereau
2148317282 Merge branch 'main' into refactor/merge-core 2022-11-29 16:15:05 +01:00
Pierre-Louis Mercereau
5f9c6c8346 Merge branch 'main' into refactor/merge-core 2022-11-28 21:16:06 +01:00
Johan Eliasson
2e30371086 Merge branch 'main' into react-urql 2022-11-28 17:04:51 +01:00
Pierre-Louis Mercereau
57db5b83d4 chore: 🤖 changeset 2022-11-28 15:39:21 +01:00
Johan Eliasson
66659bb293 update 2022-11-27 21:31:14 +01:00
Johan Eliasson
579d4f3170 update 2022-11-27 21:14:10 +01:00
Johan Eliasson
bb56548603 urql 2022-11-27 21:12:07 +01:00
Pierre-Louis Mercereau
3cab18713a Merge branch 'main' into refactor/merge-core 2022-11-24 17:33:18 +01:00
Pierre-Louis Mercereau
fb94dae43a chore: merge main 2022-11-24 17:17:04 +01:00
Pierre-Louis Mercereau
f694846eae chore: 🤖 remove hasura-storage-js to hasura-auth-js 2022-11-24 16:54:13 +01:00
Pierre-Louis Mercereau
322ab50138 Merge branch 'main' into refactor/merge-core 2022-11-22 09:52:45 +01:00
Pierre-Louis Mercereau
435efd2bc5 fix: 🐛 Set same-site cookie to lax 2022-11-21 15:22:05 +01:00
Szilárd Dóró
5501a5937e fix(docgen): complex types in markdown tables 2022-11-16 12:27:24 +01:00
Pierre-Louis Mercereau
80a6808a82 Merge branch 'main' into refactor/merge-core 2022-11-16 10:26:11 +01:00
Pierre-Louis Mercereau
987bd70312 Merge branch 'main' into refactor/merge-core 2022-11-15 14:14:58 +01:00
Pierre-Louis Mercereau
feb22e62c1 fix: add missing oauth provider: azuread 2022-11-14 23:48:45 +01:00
Pierre-Louis Mercereau
e7d4c77a6d refactor: 💡 restructure hasura-auth-js after the core merger 2022-11-14 23:20:49 +01:00
Pierre-Louis Mercereau
628e32464d Merge branch 'main' into refactor/merge-core 2022-11-14 21:45:05 +01:00
Pierre-Louis Mercereau
dc82043254 chore: only include the auth client typedoc 2022-11-09 22:13:20 +01:00
Pierre-Louis Mercereau
997e9d58a8 refactor: 💡 remove @nhost/core 2022-11-09 22:09:17 +01:00
Pierre-Louis Mercereau
8a3f1706fe chore: add auth as a dependency to storage 2022-11-09 22:01:11 +01:00
Pierre-Louis Mercereau
12cbe4d534 chore: lint 2022-11-09 21:52:15 +01:00
Pierre-Louis Mercereau
9f21931201 refactor: copy tests from core 2022-11-09 21:46:32 +01:00
Pierre-Louis Mercereau
09351e1910 Merge branch 'main' into refactor/merge-core 2022-11-09 20:49:33 +01:00
Pierre-Louis Mercereau
1c2ea5a407 refactor: 💡 merge @nhost/core 2022-11-09 20:49:13 +01:00
1035 changed files with 25121 additions and 21261 deletions

View File

@@ -7,4 +7,4 @@
"baseBranch": "main", "baseBranch": "main",
"updateInternalDependencies": "patch", "updateInternalDependencies": "patch",
"ignore": [] "ignore": []
} }

View File

@@ -35,8 +35,11 @@ runs:
fi fi
- name: Install Nhost CLI - name: Install Nhost CLI
if: ${{ steps.check-nhost-cli.outputs.installed == 'false' }} if: ${{ steps.check-nhost-cli.outputs.installed == 'false' }}
shell: bash uses: nick-fields/retry@v2
run: bash <(curl --silent -L https://raw.githubusercontent.com/nhost/cli/main/get.sh) ${{ inputs.version }} with:
timeout_minutes: 3
max_attempts: 10
command: bash <(curl --silent -L https://raw.githubusercontent.com/nhost/cli/main/get.sh) ${{ inputs.version }}
- name: Set custom configuration - name: Set custom configuration
if: ${{ inputs.config }} if: ${{ inputs.config }}
shell: bash shell: bash

7
.github/labeler.yml vendored
View File

@@ -12,11 +12,14 @@ examples:
sdk: sdk:
- packages/**/* - packages/**/*
integrations:
- integrations/**/*
react: react:
- '{packages,examples}/*react*/**/*' - '{packages,examples,integrations}/*react*/**/*'
nextjs: nextjs:
- '{packages,examples}/*next*/**/*' - '{packages,examples}/*next*/**/*'
vue: vue:
- '{packages,examples}/*vue*/**/*' - '{packages,examples,integrations}/*vue*/**/*'

View File

@@ -8,9 +8,16 @@
}, },
"ignoreDeps": [ "ignoreDeps": [
"pnpm", "pnpm",
"node" "node",
"@types/node"
], ],
"labels": [ "labels": [
"dependencies" "dependencies"
],
"enabledManagers": [
"npm",
"dockerfile",
"docker-compose",
"github-actions"
] ]
} }

View File

@@ -36,13 +36,23 @@ jobs:
TURBO_TOKEN: ${{ env.TURBO_TOKEN }} TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }} TURBO_TEAM: ${{ env.TURBO_TEAM }}
BUILD: 'all' BUILD: 'all'
- name: Check if the pnpm lockfile changed
id: changed-lockfile
uses: tj-actions/changed-files@v35
with:
files: pnpm-lock.yaml
# * Determine a pnpm filter argument for packages that have been modified.
# * If the lockfile has changed, we don't filter anything in order to run all the e2e tests.
- name: filter packages
id: filter-packages
if: steps.changed-lockfile.outputs.any_changed != 'true' && github.event_name == 'pull_request'
run: echo "filter=${{ format('--filter=...[origin/{0}]', github.base_ref) }}" >> $GITHUB_OUTPUT
# * List packagesthat has an `e2e` script, except the root, and return an array of their name and path # * List packagesthat has an `e2e` script, except the root, and return an array of their name and path
# * In a PR, only include packages that have been modified, and their dependencies # * In a PR, only include packages that have been modified, and their dependencies
- name: List examples with an e2e script - name: List examples with an e2e script
id: set-matrix id: set-matrix
run: | run: |
FILTER_MODIFIED="${{ github.event_name == 'pull_request' && format('--filter=...[origin/{0}]', github.base_ref) || '' }}" PACKAGES=$(pnpm recursive list --depth -1 --parseable --filter='!nhost-root' ${{ steps.filter-packages.outputs.filter }} \
PACKAGES=$(pnpm recursive list --depth -1 --parseable --filter='!nhost-root' $FILTER_MODIFIED \
| xargs -I@ realpath --relative-to=$PWD @ \ | xargs -I@ realpath --relative-to=$PWD @ \
| xargs -I@ jq "if (.scripts.e2e | length) != 0 then {name: .name, path: \"@\"} else null end" @/package.json \ | xargs -I@ jq "if (.scripts.e2e | length) != 0 then {name: .name, path: \"@\"} else null end" @/package.json \
| awk "!/null/" \ | awk "!/null/" \

View File

@@ -1,15 +0,0 @@
name: Add contributors
on:
push:
branches:
- main
jobs:
contrib-readme-job:
runs-on: ubuntu-latest
name: A job to automate contrib in readme
steps:
- name: Contribute List
uses: akhilmhdh/contributors-readme-action@v2.3.6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -32,7 +32,7 @@ jobs:
- name: Determine bumps - name: Determine bumps
id: bumps id: bumps
run: | run: |
LAST_NON_PR_SHA=$(git log --no-merges main origin/${{ github.head_ref }} --format=format:%h | head -2 | tail -1) LAST_NON_PR_SHA=$(git log --no-merges main origin/${{ github.head_ref }} --format=format:%h -- | head -2 | tail -1)
echo "result<<EOF" >> $GITHUB_OUTPUT echo "result<<EOF" >> $GITHUB_OUTPUT
pnpm recursive list --depth -1 --parseable \ pnpm recursive list --depth -1 --parseable \
--filter='!nhost-root' \ --filter='!nhost-root' \
@@ -62,8 +62,28 @@ jobs:
${{ github.event.pull_request.title }} ${{ github.event.pull_request.title }}
EOF EOF
- uses: stefanzweifel/git-auto-commit-action@v4 - name: Create Pull Request
if: steps.bumps.outputs.result != '' id: cpr
uses: peter-evans/create-pull-request@v4
with: with:
commit_message: ${{ github.event.pull_request.title }} token: ${{ secrets.GH_PAT }}
branch: main commit-message: ${{ github.event.pull_request.title }}
branch: renovate-changesets
delete-branch: true
title: 'chore: create changesest from Renovate bumps'
labels: |
dependencies
body: |
This PR creates the changesets from the Renovate dependencies that have been merged to main.
- name: Enable Pull Request Automerge
if: steps.cpr.outputs.pull-request-operation == 'created'
uses: peter-evans/enable-pull-request-automerge@v2
with:
token: ${{ secrets.GH_PAT }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
- name: Auto approve
if: steps.cpr.outputs.pull-request-operation == 'created'
uses: juliangruber/approve-pull-request-action@v2
with:
github-token: ${{ secrets.GH_PAT }}
number: ${{ steps.cpr.outputs.pull-request-number }}

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
prefer-workspace-packages = true

View File

@@ -14,4 +14,5 @@ package.json
tsconfig.json tsconfig.json
tsconfig.*.json tsconfig.*.json
*.d.ts *.d.ts
.next .next
**/pnpm-lock.yaml

435
README.md
View File

@@ -101,14 +101,17 @@ Nhost is frontend agnostic, which means Nhost works with all frontend frameworks
## Nhost Clients ## Nhost Clients
- [JavaScript/TypeScript SDK](https://docs.nhost.io/reference/javascript) - [JavaScript/TypeScript](https://docs.nhost.io/reference/javascript)
- [Dart and Flutter SDK](https://github.com/nhost/nhost-dart) - [Dart and Flutter](https://github.com/nhost/nhost-dart)
- [Nhost React](https://docs.nhost.io/reference/react) - [React](https://docs.nhost.io/reference/react)
- [Nhost Next.js](https://docs.nhost.io/reference/nextjs) - [Next.js](https://docs.nhost.io/reference/nextjs)
- [Nhost Vue](https://docs.nhost.io/reference/vue) - [Vue](https://docs.nhost.io/reference/vue)
## Integrations ## Integrations
- [Apollo](./integrations/apollo#nhostapollo)
- [React Apollo](./integrations/react-apollo#nhostreact-apollo)
- [React URQL](./integrations/react-urql#nhostreact-urql)
- [Stripe GraphQL API](./integrations/stripe-graphql-js#nhoststripe-graphql-js) - [Stripe GraphQL API](./integrations/stripe-graphql-js#nhoststripe-graphql-js)
- [Google Translation GraphQL API](./integrations/google-translation#nhostgoogle-translation) - [Google Translation GraphQL API](./integrations/google-translation#nhostgoogle-translation)
@@ -127,8 +130,8 @@ Also, follow Nhost on [GitHub Discussions](https://github.com/nhost/nhost/discus
This repository, and most of our other open source projects, are licensed under the MIT license. This repository, and most of our other open source projects, are licensed under the MIT license.
<a href="https://runacap.com/ross-index/q1-2022/" target="_blank" rel="noopener"> <a href="https://runacap.com/ross-index/" target="_blank" rel="noopener" >
<img style="width: 260px; height: 56px" src="https://runacap.com/wp-content/uploads/2022/06/ROSS_badge_black_Q1_2022.svg" alt="ROSS Index - Fastest Growing Open-Source Startups in Q1 2022 | Runa Capital" width="260" height="56" /> <img style="width: 260px; height: 56px" src="https://runacap.com/wp-content/uploads/2022/06/ROSS_black_edition_badge.svg" alt="ROSS Index - Fastest Growing Open-Source Startups | Runa Capital" width="260" height="56" />
</a> </a>
### How to contribute ### How to contribute
@@ -141,416 +144,8 @@ Here are some ways of contributing to making Nhost better:
### Contributors ### Contributors
<!-- readme: contributors -start --> <a href="https://github.com/nhost/nhost/graphs/contributors">
<table> <p align="center">
<tr> <img width="720" src="https://contrib.rocks/image?repo=nhost/nhost" alt="A table of avatars from the project's contributors" />
<td align="center"> </p>
<a href="https://github.com/plmercereau"> </a>
<img src="https://avatars.githubusercontent.com/u/24897252?v=4" width="100;" alt="plmercereau"/>
<br />
<sub><b>Pilou</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/elitan">
<img src="https://avatars.githubusercontent.com/u/331818?v=4" width="100;" alt="elitan"/>
<br />
<sub><b>Johan Eliasson</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/szilarddoro">
<img src="https://avatars.githubusercontent.com/u/310881?v=4" width="100;" alt="szilarddoro"/>
<br />
<sub><b>Szilárd Dóró</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nunopato">
<img src="https://avatars.githubusercontent.com/u/1523504?v=4" width="100;" alt="nunopato"/>
<br />
<sub><b>Nuno Pato</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/gdangelo">
<img src="https://avatars.githubusercontent.com/u/4352286?v=4" width="100;" alt="gdangelo"/>
<br />
<sub><b>Grégory D'Angelo</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ejkkan">
<img src="https://avatars.githubusercontent.com/u/32518962?v=4" width="100;" alt="ejkkan"/>
<br />
<sub><b>Erik Magnusson</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/guicurcio">
<img src="https://avatars.githubusercontent.com/u/20285232?v=4" width="100;" alt="guicurcio"/>
<br />
<sub><b>Guido Curcio</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/subatuba21">
<img src="https://avatars.githubusercontent.com/u/34824571?v=4" width="100;" alt="subatuba21"/>
<br />
<sub><b>Subha Das</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/sebagudelo">
<img src="https://avatars.githubusercontent.com/u/43288271?v=4" width="100;" alt="sebagudelo"/>
<br />
<sub><b>Sebagudelo</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mrinalwahal">
<img src="https://avatars.githubusercontent.com/u/9859731?v=4" width="100;" alt="mrinalwahal"/>
<br />
<sub><b>Mrinal Wahal</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/timpratim">
<img src="https://avatars.githubusercontent.com/u/32492961?v=4" width="100;" alt="timpratim"/>
<br />
<sub><b>Pratim</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/chrtze">
<img src="https://avatars.githubusercontent.com/u/3797215?v=4" width="100;" alt="chrtze"/>
<br />
<sub><b>Christopher Möller</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/GavanWilhite">
<img src="https://avatars.githubusercontent.com/u/2085119?v=4" width="100;" alt="GavanWilhite"/>
<br />
<sub><b>Gavan Wilhite</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/FuzzyReason">
<img src="https://avatars.githubusercontent.com/u/62517920?v=4" width="100;" alt="FuzzyReason"/>
<br />
<sub><b>Vadim Smirnov</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/macmac49">
<img src="https://avatars.githubusercontent.com/u/831190?v=4" width="100;" alt="macmac49"/>
<br />
<sub><b>Macmac49</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/subhendukundu">
<img src="https://avatars.githubusercontent.com/u/20059141?v=4" width="100;" alt="subhendukundu"/>
<br />
<sub><b>Subhendu Kundu</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/heygambo">
<img src="https://avatars.githubusercontent.com/u/449438?v=4" width="100;" alt="heygambo"/>
<br />
<sub><b>Christian Gambardella</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dbarrosop">
<img src="https://avatars.githubusercontent.com/u/6246622?v=4" width="100;" alt="dbarrosop"/>
<br />
<sub><b>David Barroso</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/hajek-raven">
<img src="https://avatars.githubusercontent.com/u/7288737?v=4" width="100;" alt="hajek-raven"/>
<br />
<sub><b>Filip Hájek</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/MelodicCrypter">
<img src="https://avatars.githubusercontent.com/u/18341500?v=4" width="100;" alt="MelodicCrypter"/>
<br />
<sub><b>Hugh Caluscusin</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jerryjappinen">
<img src="https://avatars.githubusercontent.com/u/1101002?v=4" width="100;" alt="jerryjappinen"/>
<br />
<sub><b>Jerry Jäppinen</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mdp18">
<img src="https://avatars.githubusercontent.com/u/11698527?v=4" width="100;" alt="mdp18"/>
<br />
<sub><b>Max</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mustafa-hanif">
<img src="https://avatars.githubusercontent.com/u/30019262?v=4" width="100;" alt="mustafa-hanif"/>
<br />
<sub><b>Mustafa Hanif</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nbourdin">
<img src="https://avatars.githubusercontent.com/u/5602476?v=4" width="100;" alt="nbourdin"/>
<br />
<sub><b>Nicolas Bourdin</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/piromsurang">
<img src="https://avatars.githubusercontent.com/u/17776837?v=4" width="100;" alt="piromsurang"/>
<br />
<sub><b>Piromsurang Rungserichai</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Savinvadim1312">
<img src="https://avatars.githubusercontent.com/u/16936043?v=4" width="100;" alt="Savinvadim1312"/>
<br />
<sub><b>Savin Vadim</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Svarto">
<img src="https://avatars.githubusercontent.com/u/24279217?v=4" width="100;" alt="Svarto"/>
<br />
<sub><b>Svarto</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/muttenzer">
<img src="https://avatars.githubusercontent.com/u/49474412?v=4" width="100;" alt="muttenzer"/>
<br />
<sub><b>Muttenzer</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/alexander-mart">
<img src="https://avatars.githubusercontent.com/u/14993551?v=4" width="100;" alt="alexander-mart"/>
<br />
<sub><b>Alexander Mart</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ahmic">
<img src="https://avatars.githubusercontent.com/u/13452362?v=4" width="100;" alt="ahmic"/>
<br />
<sub><b>Amir Ahmic</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/akd-io">
<img src="https://avatars.githubusercontent.com/u/30059155?v=4" width="100;" alt="akd-io"/>
<br />
<sub><b>Anders Kjær Damgaard</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Sonichigo">
<img src="https://avatars.githubusercontent.com/u/53110238?v=4" width="100;" alt="Sonichigo"/>
<br />
<sub><b>Animesh Pathak</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/chrisli-03">
<img src="https://avatars.githubusercontent.com/u/11177048?v=4" width="100;" alt="chrisli-03"/>
<br />
<sub><b>Chris</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/massless">
<img src="https://avatars.githubusercontent.com/u/44389?v=4" width="100;" alt="massless"/>
<br />
<sub><b>Chris Wetherell</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rustyb">
<img src="https://avatars.githubusercontent.com/u/53086?v=4" width="100;" alt="rustyb"/>
<br />
<sub><b>Colin Broderick</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/daguitosama">
<img src="https://avatars.githubusercontent.com/u/34744883?v=4" width="100;" alt="daguitosama"/>
<br />
<sub><b>Dago</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/dminkovsky">
<img src="https://avatars.githubusercontent.com/u/218725?v=4" width="100;" alt="dminkovsky"/>
<br />
<sub><b>Dmitry Minkovsky</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dohomi">
<img src="https://avatars.githubusercontent.com/u/489221?v=4" width="100;" alt="dohomi"/>
<br />
<sub><b>Dominic Garms</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/gaurav1999">
<img src="https://avatars.githubusercontent.com/u/20752142?v=4" width="100;" alt="gaurav1999"/>
<br />
<sub><b>Gaurav Agrawal</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/alveshelio">
<img src="https://avatars.githubusercontent.com/u/8176422?v=4" width="100;" alt="alveshelio"/>
<br />
<sub><b>Helio Alves</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nkhdo">
<img src="https://avatars.githubusercontent.com/u/26102306?v=4" width="100;" alt="nkhdo"/>
<br />
<sub><b>Hoang Do</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/eltociear">
<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="100;" alt="eltociear"/>
<br />
<sub><b>Ikko Ashimine</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/jladuval">
<img src="https://avatars.githubusercontent.com/u/1935359?v=4" width="100;" alt="jladuval"/>
<br />
<sub><b>Jacob Duval</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/leothorp">
<img src="https://avatars.githubusercontent.com/u/12928449?v=4" width="100;" alt="leothorp"/>
<br />
<sub><b>Leo Thorp</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/LucasBois1">
<img src="https://avatars.githubusercontent.com/u/44686060?v=4" width="100;" alt="LucasBois1"/>
<br />
<sub><b>Lucas Bois</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/MarcelloTheArcane">
<img src="https://avatars.githubusercontent.com/u/21159570?v=4" width="100;" alt="MarcelloTheArcane"/>
<br />
<sub><b>Max Reynolds</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nachoaldamav">
<img src="https://avatars.githubusercontent.com/u/22749943?v=4" width="100;" alt="nachoaldamav"/>
<br />
<sub><b>Nacho Aldama</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ghoshnirmalya">
<img src="https://avatars.githubusercontent.com/u/6391763?v=4" width="100;" alt="ghoshnirmalya"/>
<br />
<sub><b>Nirmalya Ghosh</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/quentin-decre">
<img src="https://avatars.githubusercontent.com/u/1137511?v=4" width="100;" alt="quentin-decre"/>
<br />
<sub><b>Quentin Decré</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/elephant3">
<img src="https://avatars.githubusercontent.com/u/48279149?v=4" width="100;" alt="elephant3"/>
<br />
<sub><b>Siarhei Lipchyk</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/altschuler">
<img src="https://avatars.githubusercontent.com/u/956928?v=4" width="100;" alt="altschuler"/>
<br />
<sub><b>Simon Altschuler</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/atapas">
<img src="https://avatars.githubusercontent.com/u/3633137?v=4" width="100;" alt="atapas"/>
<br />
<sub><b>Tapas Adhikary</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/uulwake">
<img src="https://avatars.githubusercontent.com/u/22399181?v=4" width="100;" alt="uulwake"/>
<br />
<sub><b>Ulrich Wake</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/kwarabei">
<img src="https://avatars.githubusercontent.com/u/102731455?v=4" width="100;" alt="kwarabei"/>
<br />
<sub><b>Vadim</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/TheRedLancer">
<img src="https://avatars.githubusercontent.com/u/58493767?v=4" width="100;" alt="TheRedLancer"/>
<br />
<sub><b>Zach Burnaby</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/komninoschat">
<img src="https://avatars.githubusercontent.com/u/29049104?v=4" width="100;" alt="komninoschat"/>
<br />
<sub><b>Komninos</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/meesvandongen">
<img src="https://avatars.githubusercontent.com/u/35409045?v=4" width="100;" alt="meesvandongen"/>
<br />
<sub><b>Meesvandongen</b></sub>
</a>
</td></tr>
</table>
<!-- readme: contributors -end -->

View File

@@ -19,7 +19,9 @@ module.exports = {
'*.spec.ts', '*.spec.ts',
'*.spec.tsx', '*.spec.tsx',
'tests/**/*.ts', 'tests/**/*.ts',
'tests/**/*.d.ts' 'tests/**/*.d.ts',
'e2e/**/*.ts',
'e2e/**/*.d.ts'
], ],
plugins: ['@typescript-eslint', 'cypress'], plugins: ['@typescript-eslint', 'cypress'],
extends: ['plugin:cypress/recommended'], extends: ['plugin:cypress/recommended'],

View File

@@ -1,6 +1,4 @@
module.exports = { module.exports = {
'(packages|integrations)/(docgen|hasura-auth-js|hasura-storage-js|nextjs|nhost-js|react|core|vue)/src/**/*.{js,ts,jsx,tsx}':
['pnpm docgen', 'git add docs'],
'(nhost-cloud.yaml|**/nhost/config.yaml)': () => [ '(nhost-cloud.yaml|**/nhost/config.yaml)': () => [
'pnpm sync-versions', 'pnpm sync-versions',
"git add ':(glob)**/nhost/config.yaml'" "git add ':(glob)**/nhost/config.yaml'"

View File

@@ -28,22 +28,44 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"sourceMap": true, "sourceMap": true,
"types": ["node"], "types": [
"node"
],
"typeRoots": [ "typeRoots": [
"./node_modules/@types", "**/*/dist", "**/*/build", "**/*/.next", "**/*/umd" "./node_modules/@types",
"**/*/dist",
"**/*/build",
"**/*/.next",
"**/*/umd"
], ],
"paths": { "paths": {
"@nhost/apollo": ["../packages/apollo/src/index.ts"], "@nhost/apollo": [
"@nhost/core": ["../packages/core/src/index.ts"], "../integrations/apollo/src/index.ts"
"@nhost/docgen": ["../packages/docgen/src/index.ts"], ],
"@nhost/hasura-auth-js": ["../packages/hasura-auth-js/src/index.ts"], "@nhost/docgen": [
"@nhost/hasura-storage-js": ["../packages/hasura-storage-js/src/index.ts"], "../packages/docgen/src/index.ts"
"@nhost/nextjs": ["../packages/nextjs/src/index.ts"], ],
"@nhost/nhost-js": ["../packages/nhost-js/src/index.ts"], "@nhost/hasura-auth-js": [
"@nhost/react": ["../packages/react/src/index.ts"], "../packages/hasura-auth-js/src/index.ts"
"@nhost/react-apollo": ["../packages/react-apollo/src/index.ts"], ],
"@nhost/react-auth": ["../packages/react-auth/src/index.ts"], "@nhost/hasura-storage-js": [
"@nhost/vue": ["../packages/vue/src/index.ts"] "../packages/hasura-storage-js/src/index.ts"
],
"@nhost/nextjs": [
"../packages/nextjs/src/index.ts"
],
"@nhost/nhost-js": [
"../packages/nhost-js/src/index.ts"
],
"@nhost/react": [
"../packages/react/src/index.ts"
],
"@nhost/react-apollo": [
"../integrations/react-apollo/src/index.ts"
],
"@nhost/vue": [
"../packages/vue/src/index.ts"
]
} }
}, },
"exclude": [ "exclude": [

View File

@@ -1,3 +1,4 @@
import replace from '@rollup/plugin-replace'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
@@ -18,7 +19,9 @@ export default defineConfig({
tsconfigPaths(), tsconfigPaths(),
dts({ dts({
exclude: ['**/*.spec.ts', '**/*.test.ts', '**/tests/**'], exclude: ['**/*.spec.ts', '**/*.test.ts', '**/tests/**'],
entryRoot: 'src' entryRoot: 'src',
// Was defaulting to true until version 1.7
skipDiagnostics: true
}) })
], ],
test: { test: {
@@ -41,6 +44,12 @@ export default defineConfig({
}, },
rollupOptions: { rollupOptions: {
external: (id) => deps.some((dep) => id.startsWith(dep)), external: (id) => deps.some((dep) => id.startsWith(dep)),
plugins: [
replace({
preventAssignment: true,
'exports.hasOwnProperty(': 'Object.prototype.hasOwnProperty.call(exports,'
})
],
output: { output: {
globals: { globals: {
graphql: 'graphql', graphql: 'graphql',

View File

@@ -6,6 +6,7 @@ module.exports = {
'@storybook/addon-links', '@storybook/addon-links',
'@storybook/addon-essentials', '@storybook/addon-essentials',
'@storybook/addon-interactions', '@storybook/addon-interactions',
'storybook-addon-next-router',
{ {
/** /**
* Fix Storybook issue with PostCSS@8 * Fix Storybook issue with PostCSS@8
@@ -38,4 +39,10 @@ module.exports = {
}, },
}; };
}, },
env: (config) => ({
...config,
NEXT_PUBLIC_ENV: 'dev',
NEXT_PUBLIC_NHOST_BACKEND_URL: 'http://localhost:1337',
NEXT_PUBLIC_NHOST_PLATFORM: 'false',
}),
}; };

View File

@@ -2,9 +2,25 @@ import '@fontsource/inter';
import '@fontsource/inter/500.css'; import '@fontsource/inter/500.css';
import '@fontsource/inter/700.css'; import '@fontsource/inter/700.css';
import { CssBaseline, ThemeProvider } from '@mui/material'; import { CssBaseline, ThemeProvider } from '@mui/material';
import { NhostApolloProvider } from '@nhost/react-apollo';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Buffer } from 'buffer';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import { RouterContext } from 'next/dist/shared/lib/router-context';
import '../src/styles/globals.css';
import defaultTheme from '../src/theme/default'; import defaultTheme from '../src/theme/default';
global.Buffer = Buffer;
initialize({ onUnhandledRequest: 'bypass' });
const queryClient = new QueryClient();
export const parameters = { export const parameters = {
nextRouter: {
Provider: RouterContext.Provider,
isReady: true,
},
actions: { argTypesRegex: '^on[A-Z].*' }, actions: { argTypesRegex: '^on[A-Z].*' },
controls: { controls: {
matchers: { matchers: {
@@ -14,11 +30,25 @@ export const parameters = {
}, },
}; };
export const withMuiTheme = (Story) => ( export const decorators = [
<ThemeProvider theme={defaultTheme}> (Story) => (
<CssBaseline /> <ThemeProvider theme={defaultTheme}>
<Story /> <CssBaseline />
</ThemeProvider> <Story />
); </ThemeProvider>
),
export const decorators = [withMuiTheme]; (Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
(Story) => (
<NhostApolloProvider
fetchPolicy="cache-first"
graphqlUrl="http://localhost:1337/v1/graphql"
>
<Story />
</NhostApolloProvider>
),
mswDecorator,
];

View File

@@ -1,5 +1,141 @@
# @nhost/dashboard # @nhost/dashboard
## 0.9.5
### Patch Changes
- 200e9f77: chore(deps): update dependency @types/react-dom to v18.0.10
- Updated dependencies [200e9f77]
- @nhost/nextjs@1.13.2
- @nhost/react-apollo@4.13.2
## 0.9.4
### Patch Changes
- dbd3ded5: fix(dashboard): workspaces creation, new form, correct redirects.
## 0.9.3
### Patch Changes
- 85f0f943: fix(dashboard): don't break the table creation process
## 0.9.2
### Patch Changes
- Updated dependencies [d42c27ae]
- Updated dependencies [927be4a2]
- @nhost/nextjs@1.13.1
- @nhost/react-apollo@4.13.1
## 0.9.1
### Patch Changes
- d0f80811: fix(dashboard): don't show error when signing out the user
## 0.9.0
### Minor Changes
- d92891b2: feat(dashboard): add Permission Editor to the Database section
### Patch Changes
- 3d379128: fix(dashboard): create new user
- @nhost/react-apollo@4.13.0
- @nhost/nextjs@1.13.0
## 0.8.1
### Patch Changes
- 7cadd944: fix(dashboard): display Twitter provider settings
## 0.8.0
### Minor Changes
- 9a1aa7bb: add functions to the log dashboard
- f29abe62: feat(dashboard): Users Management v2
### Patch Changes
- 7766624b: feat(dashboard): add JWT secret editor modal
- @nhost/react-apollo@4.12.1
- @nhost/nextjs@1.12.1
## 0.7.13
### Patch Changes
- dd0738d5: fix(dashboard): provisioning status polling
## 0.7.12
### Patch Changes
- b21222b3: chore(deps): update dependency @types/node to v16
- 9e0486a3: fix(dashboard): close modals when navigating
- Updated dependencies [b21222b3]
- Updated dependencies [65687bee]
- Updated dependencies [54df0df4]
- @nhost/nextjs@1.12.0
- @nhost/react-apollo@4.12.0
## 0.7.11
### Patch Changes
- d6527122: fix(dashboard): use correct service URLs
## 0.7.10
### Patch Changes
- Updated dependencies [57db5b83]
- @nhost/nextjs@1.11.0
- @nhost/nhost-js@1.7.0
- @nhost/react@0.17.0
- @nhost/react-apollo@4.11.0
## 0.7.9
### Patch Changes
- a6d31dc2: fix(dashboard): don't break the UI when project is not loaded yet
## 0.7.8
### Patch Changes
- 7f251111: Use `NhostProvider` instead of `NhostReactProvider` and `NhostNextProvider`
`NhostReactProvider` and `NhostNextProvider` are now deprecated
- f4d70f88: fix(dashboard): do not break when region is nullish
- 4a9471cc: Windows Live Provider displayed link updated to match backend url
- 594488e4: fix(dashboard): do not show error when submitting Apple provider settings
- Updated dependencies [7f251111]
- @nhost/nextjs@1.10.0
- @nhost/react@0.16.0
- @nhost/react-apollo@4.10.0
## 0.7.7
### Patch Changes
- 80b604ad: fix(dashboard): use correct Hasura slug
## 0.7.6
### Patch Changes
- 2d2beb53: fix(dashboard): prevent error on GraphQL page
- ac8efcbd: chore(dashboard): deprecate old DNS name
## 0.7.5 ## 0.7.5
### Patch Changes ### Patch Changes

View File

@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
RUN apk update RUN apk update
WORKDIR /app WORKDIR /app
RUN yarn global add turbo RUN yarn global add turbo@1
COPY . . COPY . .
RUN turbo prune --scope="@nhost/dashboard" --docker RUN turbo prune --scope="@nhost/dashboard" --docker

View File

@@ -37,6 +37,14 @@ NEXT_PUBLIC_ENV=dev
NEXT_PUBLIC_NHOST_PLATFORM=false NEXT_PUBLIC_NHOST_PLATFORM=false
``` ```
### Storybook
Components are documented using [Storybook](https://storybook.js.org/). To run Storybook, run the following command:
```bash
pnpm storybook
```
### Full list of environment variables ### Full list of environment variables
| Name | Description | | Name | Description |

View File

@@ -66,6 +66,11 @@ module.exports = withBundleAnalyzer({
destination: '/:workspaceSlug/:appSlug/settings/environment-variables', destination: '/:workspaceSlug/:appSlug/settings/environment-variables',
permanent: true, permanent: true,
}, },
{
source: '/:workspaceSlug/:appSlug/users/:userId',
destination: '/:workspaceSlug/:appSlug/users?userId=:userId',
permanent: true,
},
]; ];
}, },
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/dashboard", "name": "@nhost/dashboard",
"version": "0.7.5", "version": "0.9.5",
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
@@ -13,11 +13,11 @@
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only", "codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
"nhost:dev": "nhost dev -d", "nhost:dev": "nhost dev -d",
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.", "format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
"storybook": "start-storybook -p 6006", "storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook" "build-storybook": "build-storybook"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.6.2", "@apollo/client": "^3.7.3",
"@codemirror/language": "^6.3.0", "@codemirror/language": "^6.3.0",
"@emotion/cache": "^11.10.5", "@emotion/cache": "^11.10.5",
"@emotion/react": "^11.10.5", "@emotion/react": "^11.10.5",
@@ -34,10 +34,7 @@
"@mui/material": "^5.10.14", "@mui/material": "^5.10.14",
"@mui/system": "^5.10.14", "@mui/system": "^5.10.14",
"@mui/x-date-pickers": "^5.0.8", "@mui/x-date-pickers": "^5.0.8",
"@nhost/core": "workspace:*",
"@nhost/nextjs": "workspace:*", "@nhost/nextjs": "workspace:*",
"@nhost/nhost-js": "workspace:*",
"@nhost/react": "workspace:*",
"@nhost/react-apollo": "workspace:*", "@nhost/react-apollo": "workspace:*",
"@segment/snippet": "^4.15.3", "@segment/snippet": "^4.15.3",
"@stripe/react-stripe-js": "^1.10.0", "@stripe/react-stripe-js": "^1.10.0",
@@ -53,7 +50,7 @@
"cross-fetch": "^3.1.5", "cross-fetch": "^3.1.5",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"generate-password": "^1.7.0", "generate-password": "^1.7.0",
"graphiql": "^2.1.0", "graphiql": "^2.2.0",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"graphql-request": "^4.3.0", "graphql-request": "^4.3.0",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
@@ -91,31 +88,31 @@
"@graphql-codegen/typescript-operations": "^2.5.1", "@graphql-codegen/typescript-operations": "^2.5.1",
"@graphql-codegen/typescript-react-apollo": "^3.3.1", "@graphql-codegen/typescript-react-apollo": "^3.3.1",
"@next/bundle-analyzer": "^12.3.1", "@next/bundle-analyzer": "^12.3.1",
"@storybook/addon-actions": "^6.5.13", "@storybook/addon-actions": "^6.5.14",
"@storybook/addon-essentials": "^6.5.13", "@storybook/addon-essentials": "^6.5.14",
"@storybook/addon-interactions": "^6.5.13", "@storybook/addon-interactions": "^6.5.14",
"@storybook/addon-links": "^6.5.13", "@storybook/addon-links": "^6.5.14",
"@storybook/addon-postcss": "^2.0.0", "@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.13", "@storybook/builder-webpack5": "^6.5.14",
"@storybook/manager-webpack5": "^6.5.13", "@storybook/manager-webpack5": "^6.5.14",
"@storybook/react": "^6.5.13", "@storybook/react": "^6.5.14",
"@storybook/testing-library": "^0.0.13", "@storybook/testing-library": "^0.0.13",
"@testing-library/dom": "^8.19.0", "@testing-library/dom": "^8.19.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
"@types/lodash.debounce": "^4.0.7", "@types/lodash.debounce": "^4.0.7",
"@types/node": "^18.11.9", "@types/node": "^16.11.7",
"@types/pluralize": "^0.0.29", "@types/pluralize": "^0.0.29",
"@types/react": "18.0.25", "@types/react": "18.0.25",
"@types/react-dom": "18.0.9", "@types/react-dom": "18.0.10",
"@types/react-table": "^7.7.12", "@types/react-table": "^7.7.12",
"@types/testing-library__jest-dom": "^5.14.5", "@types/testing-library__jest-dom": "^5.14.5",
"@types/validator": "^13.7.10", "@types/validator": "^13.7.10",
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/parser": "^5.43.0",
"@vitejs/plugin-react": "^2.2.0", "@vitejs/plugin-react": "^3.0.0",
"@vitest/coverage-c8": "^0.25.2", "@vitest/coverage-c8": "^0.27.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"babel-loader": "^8.3.0", "babel-loader": "^8.3.0",
"babel-plugin-transform-remove-console": "^6.9.4", "babel-plugin-transform-remove-console": "^6.9.4",
@@ -132,19 +129,21 @@
"jsdom": "^20.0.3", "jsdom": "^20.0.3",
"lint-staged": ">=13", "lint-staged": ">=13",
"msw": "^0.49.0", "msw": "^0.49.0",
"msw-storybook-addon": "^1.6.3",
"postcss": "^8.4.19", "postcss": "^8.4.19",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.2.0", "prettier-plugin-organize-imports": "^3.2.0",
"prettier-plugin-tailwindcss": "^0.2.0", "prettier-plugin-tailwindcss": "^0.2.0",
"react-date-fns-hooks": "^0.9.4", "react-date-fns-hooks": "^0.9.4",
"require-from-string": "^2.0.2", "require-from-string": "^2.0.2",
"storybook-addon-next-router": "^4.0.1",
"tailwindcss": "^3.1.2", "tailwindcss": "^3.1.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsconfig-paths-webpack-plugin": "^4.0.0", "tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"vite": "^3.2.4", "vite": "^4.0.2",
"vite-tsconfig-paths": "^3.6.0", "vite-tsconfig-paths": "^4.0.3",
"vitest": "^0.25.2", "vitest": "^0.27.0",
"webpack": "^5.75.0" "webpack": "^5.75.0"
}, },
"browserslist": { "browserslist": {
@@ -158,5 +157,8 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"msw": {
"workerDirectory": "public"
} }
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,303 @@
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker (0.49.0).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: true,
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
const accept = request.headers.get('accept') || ''
// Bypass server-sent events.
if (accept.includes('text/event-stream')) {
return
}
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = Math.random().toString(16).slice(2)
event.respondWith(
handleRequest(event, requestId).catch((error) => {
if (error.name === 'NetworkError') {
console.warn(
'[MSW] Successfully emulated a network error for the "%s %s" request.',
request.method,
request.url,
)
return
}
// At this point, any exception indicates an issue with the original request/response.
console.error(
`\
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
request.method,
request.url,
`${error.name}: ${error.message}`,
)
}),
)
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const clonedResponse = response.clone()
sendToClient(client, {
type: 'RESPONSE',
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body:
clonedResponse.body === null ? null : await clonedResponse.text(),
headers: Object.fromEntries(clonedResponse.headers.entries()),
redirected: clonedResponse.redirected,
},
})
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
const clonedRequest = request.clone()
function passthrough() {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const headers = Object.fromEntries(clonedRequest.headers.entries())
// Remove MSW-specific request headers so the bypassed requests
// comply with the server's CORS preflight check.
// Operate with the headers as an object because request "Headers"
// are immutable.
delete headers['x-msw-bypass']
return fetch(clonedRequest, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Bypass requests with the explicit bypass header.
// Such requests can be issued by "ctx.fetch()".
if (request.headers.get('x-msw-bypass') === 'true') {
return passthrough()
}
// Notify the client that a request has been intercepted.
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.text(),
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
})
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'MOCK_NOT_FOUND': {
return passthrough()
}
case 'NETWORK_ERROR': {
const { name, message } = clientMessage.data
const networkError = new Error(message)
networkError.name = name
// Rejecting a "respondWith" promise emulates a network error.
throw networkError
}
}
return passthrough()
}
function sendToClient(client, message) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(message, [channel.port2])
})
}
function sleep(timeMs) {
return new Promise((resolve) => {
setTimeout(resolve, timeMs)
})
}
async function respondWithMock(response) {
await sleep(response.delay)
return new Response(response.body, response)
}

View File

@@ -15,7 +15,7 @@ import {
useInsertFeatureFlagMutation, useInsertFeatureFlagMutation,
useUpdateApplicationMutation, useUpdateApplicationMutation,
} from '@/utils/__generated__/graphql'; } from '@/utils/__generated__/graphql';
import { useUserEmail } from '@nhost/react'; import { useUserEmail } from '@nhost/nextjs';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
/** /**

View File

@@ -11,7 +11,7 @@ import Text from '@/ui/v2/Text';
import { discordAnnounce } from '@/utils/discordAnnounce'; import { discordAnnounce } from '@/utils/discordAnnounce';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';
import { updateOwnCache } from '@/utils/updateOwnCache'; import { updateOwnCache } from '@/utils/updateOwnCache';
import { useUserData } from '@nhost/react'; import { useUserData } from '@nhost/nextjs';
import Image from 'next/image'; import Image from 'next/image';
import { useState } from 'react'; import { useState } from 'react';
import { RemoveApplicationModal } from './RemoveApplicationModal'; import { RemoveApplicationModal } from './RemoveApplicationModal';

View File

@@ -5,7 +5,7 @@ import { Modal } from '@/ui/Modal';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { Dropdown } from '@/ui/v2/Dropdown'; import { Dropdown } from '@/ui/v2/Dropdown';
import Text from '@/ui/v2/Text'; import Text from '@/ui/v2/Text';
import { useUserData } from '@nhost/react'; import { useUserData } from '@nhost/nextjs';
import Image from 'next/image'; import Image from 'next/image';
import { useState } from 'react'; import { useState } from 'react';
import ApplicationInfo from './ApplicationInfo'; import ApplicationInfo from './ApplicationInfo';

View File

@@ -1,119 +0,0 @@
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { discordAnnounce } from '@/utils/discordAnnounce';
import { inputErrorMessages } from '@/utils/getErrorMessage';
import { slugifyString } from '@/utils/helpers';
import { triggerToast } from '@/utils/toast';
import { useUpdateWorkspaceMutation } from '@/utils/__generated__/graphql';
import router from 'next/router';
import type { ChangeEvent } from 'react';
import React, { useState } from 'react';
type ChangeWorkspaceNameProps = {
close: VoidFunction;
};
export default function ChangeWorkspaceName({
close,
}: ChangeWorkspaceNameProps) {
const { currentWorkspace } = useCurrentWorkspaceAndApplication();
const [newWorkspaceName, setNewWorkspaceName] = useState(
currentWorkspace.name,
);
const [workspaceError, setWorkspaceError] = useState<string>('');
const [updateWorkspace, { loading: mutationLoading, error: mutationError }] =
useUpdateWorkspaceMutation({
refetchQueries: [],
});
function handleChange(event: ChangeEvent<HTMLInputElement>) {
inputErrorMessages(
event.target.value,
setNewWorkspaceName,
setWorkspaceError,
'Workspace',
);
}
async function handleSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
e.preventDefault();
const name = newWorkspaceName;
const slug = slugifyString(name);
if (slug.length < 4 || slug.length > 32) {
setWorkspaceError('Slug should be within 4 and 32 characters.');
return;
}
try {
await updateWorkspace({
variables: {
id: currentWorkspace.id,
workspace: {
name,
slug,
},
},
});
close();
triggerToast('Workspace name changed');
} catch (error) {
await discordAnnounce(
`Error trying to remove workspace: ${currentWorkspace.id} - ${error.message}`,
);
}
await router.push(slug);
}
return (
<div className="w-modal px-6 py-6 text-left">
<div className="flex flex-col">
<Text variant="h3" component="h2">
Change Workspace Name
</Text>
<form onSubmit={handleSubmit}>
<div className="mt-4 grid grid-flow-row gap-2">
<Input
id="workspaceName"
label="New Workspace Name"
onChange={handleChange}
value={newWorkspaceName}
placeholder="New workspace name"
fullWidth
autoFocus
autoComplete="off"
helperText={`https://app.nhost.io/${slugifyString(
newWorkspaceName || '',
)}`}
/>
{workspaceError && <Alert severity="error">{workspaceError}</Alert>}
{mutationError && (
<Alert severity="error">{mutationError.toString()}</Alert>
)}
</div>
<div className="mt-6 grid grid-flow-row gap-2">
<Button
type="submit"
disabled={mutationLoading || !!workspaceError}
>
Save Changes
</Button>
<Button variant="outlined" color="secondary" onClick={close}>
Close
</Button>
</div>
</form>
</div>
</div>
);
}

View File

@@ -32,9 +32,12 @@ export default function ConnectGithubModal({ close }: ConnectGithubModalProps) {
useState<ConnectGithubModalState>('CONNECTING'); useState<ConnectGithubModalState>('CONNECTING');
const [selectedRepoId, setSelectedRepoId] = useState<string | null>(null); const [selectedRepoId, setSelectedRepoId] = useState<string | null>(null);
const { data, loading, error } = useGetGithubRepositoriesQuery({ const { data, loading, error, startPolling } =
pollInterval: 2000, useGetGithubRepositoriesQuery();
});
useEffect(() => {
startPolling(2000);
}, [startPolling]);
const handleSelectAnotherRepository = () => { const handleSelectAnotherRepository = () => {
setSelectedRepoId(null); setSelectedRepoId(null);

View File

@@ -1,12 +1,16 @@
import { ConnectionDetail } from '@/components/applications/ConnectionDetail'; import { ConnectionDetail } from '@/components/applications/ConnectionDetail';
import { LoadingScreen } from '@/components/common/LoadingScreen'; import { LoadingScreen } from '@/components/common/LoadingScreen';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication'; import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon'; import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
import Link from '@/ui/v2/Link'; import Link from '@/ui/v2/Link';
import Text from '@/ui/v2/Text'; import Text from '@/ui/v2/Text';
import generateAppServiceUrl, {
defaultLocalBackendSlugs,
defaultRemoteBackendSlugs,
} from '@/utils/common/generateAppServiceUrl';
import { LOCAL_HASURA_URL } from '@/utils/env'; import { LOCAL_HASURA_URL } from '@/utils/env';
import { generateRemoteAppUrl } from '@/utils/helpers';
import Image from 'next/image'; import Image from 'next/image';
interface HasuraDataProps { interface HasuraDataProps {
@@ -15,6 +19,7 @@ interface HasuraDataProps {
export function HasuraData({ close }: HasuraDataProps) { export function HasuraData({ close }: HasuraDataProps) {
const { currentApplication } = useCurrentWorkspaceAndApplication(); const { currentApplication } = useCurrentWorkspaceAndApplication();
const isPlatform = useIsPlatform();
if ( if (
!currentApplication?.subdomain || !currentApplication?.subdomain ||
@@ -24,9 +29,15 @@ export function HasuraData({ close }: HasuraDataProps) {
} }
const hasuraUrl = const hasuraUrl =
process.env.NEXT_PUBLIC_ENV === 'dev' process.env.NEXT_PUBLIC_ENV === 'dev' || !isPlatform
? LOCAL_HASURA_URL ? `${LOCAL_HASURA_URL}/console`
: generateRemoteAppUrl(currentApplication.subdomain); : generateAppServiceUrl(
currentApplication?.subdomain,
currentApplication?.region.awsName,
'hasura',
defaultLocalBackendSlugs,
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
);
return ( return (
<div className="mx-auto w-full max-w-md px-6 py-4 text-left"> <div className="mx-auto w-full max-w-md px-6 py-4 text-left">
@@ -60,7 +71,7 @@ export function HasuraData({ close }: HasuraDataProps) {
<div className="mt-6 grid grid-flow-row gap-2"> <div className="mt-6 grid grid-flow-row gap-2">
<Link <Link
href={`${hasuraUrl}/console`} href={hasuraUrl}
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
className="grid grid-flow-col items-center justify-center gap-1 rounded-[4px] bg-btn p-2 text-sm+ font-medium text-white hover:ring-2 motion-safe:transition-all" className="grid grid-flow-col items-center justify-center gap-1 rounded-[4px] bg-btn p-2 text-sm+ font-medium text-white hover:ring-2 motion-safe:transition-all"

View File

@@ -13,14 +13,17 @@ export function FunctionsLogsTerminalPage({ functionName }: any) {
const { currentApplication } = useCurrentWorkspaceAndApplication(); const { currentApplication } = useCurrentWorkspaceAndApplication();
const [normalizedFunctionData, setNormalizedFunctionData] = useState(null); const [normalizedFunctionData, setNormalizedFunctionData] = useState(null);
const { data } = useGetFunctionLogQuery({ const { data, startPolling } = useGetFunctionLogQuery({
variables: { variables: {
subdomain: currentApplication.subdomain, subdomain: currentApplication.subdomain,
functionPaths: [functionName?.split('/').slice(1, 3).join('/')], functionPaths: [functionName?.split('/').slice(1, 3).join('/')],
}, },
pollInterval: 3000,
}); });
useEffect(() => {
startPolling(3000);
}, [startPolling]);
useEffect(() => { useEffect(() => {
if (!data || data.getFunctionLogs.length === 0) { if (!data || data.getFunctionLogs.length === 0) {
return; return;

View File

@@ -2,7 +2,7 @@ import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLCl
import ActivityIndicator from '@/ui/v2/ActivityIndicator'; import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Option from '@/ui/v2/Option'; import Option from '@/ui/v2/Option';
import Select from '@/ui/v2/Select'; import Select from '@/ui/v2/Select';
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql'; import type { RemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import { useRemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql'; import { useRemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import { DEFAULT_ROLES } from './utils'; import { DEFAULT_ROLES } from './utils';
@@ -57,7 +57,7 @@ export function UserSelect({ onUserChange, ...props }: UserSelectProps) {
return; return;
} }
const user: RemoteAppGetUsersQuery['users'][number] = users.find( const user: RemoteAppGetUsersCustomQuery['users'][0] = users.find(
({ id }) => id === userId, ({ id }) => id === userId,
); );

View File

@@ -37,20 +37,29 @@ function ControlledAutocomplete(
}: ControlledAutocompleteProps<AutocompleteOption>, }: ControlledAutocompleteProps<AutocompleteOption>,
ref: ForwardedRef<HTMLInputElement>, ref: ForwardedRef<HTMLInputElement>,
) { ) {
const { setValue } = useFormContext(); const form = useFormContext();
const { field } = useController({ const { field } = useController({
...controllerProps, ...(controllerProps || {}),
name: controllerProps?.name || name || '', name: controllerProps?.name || name || '',
control: controllerProps?.control || control, control: controllerProps?.control || control,
}); });
if (!form) {
throw new Error('ControlledAutocomplete must be used in a FormContext.');
}
const { setValue } = form || {};
return ( return (
<Autocomplete <Autocomplete
inputValue={typeof field.value === 'string' ? field.value : undefined}
{...props} {...props}
{...field} {...field}
ref={mergeRefs([field.ref, ref])} ref={mergeRefs([field.ref, ref])}
onChange={(event, options, reason, details) => { onChange={(event, options, reason, details) => {
setValue(controllerProps?.name || name, options); setValue?.(controllerProps?.name || name, options, {
shouldDirty: true,
});
if (props.onChange) { if (props.onChange) {
props.onChange(event, options, reason, details); props.onChange(event, options, reason, details);

View File

@@ -42,13 +42,16 @@ function ControlledSwitch(
{...props} {...props}
{...field} {...field}
ref={mergeRefs([field.ref, ref])} ref={mergeRefs([field.ref, ref])}
onChange={(e) => { onChange={(event) => {
setValue(controllerProps?.name || name, e.target.checked, { setValue(controllerProps?.name || name, event.target.checked, {
shouldDirty: true, shouldDirty: true,
}); });
if (props.onChange) {
props.onChange(event);
}
}} }}
checked={field.value || false} checked={field.value || false}
{...props}
/> />
); );
} }

View File

@@ -2,11 +2,11 @@ import DataGridBody from '@/components/common/DataGridBody';
import DataGridFrame from '@/components/common/DataGridFrame'; import DataGridFrame from '@/components/common/DataGridFrame';
import type { DataGridHeaderProps } from '@/components/common/DataGridHeader'; import type { DataGridHeaderProps } from '@/components/common/DataGridHeader';
import DataGridHeader from '@/components/common/DataGridHeader'; import DataGridHeader from '@/components/common/DataGridHeader';
import DataBrowserEmptyState from '@/components/data-browser/DataBrowserEmptyState'; import DataBrowserEmptyState from '@/components/dataBrowser/DataBrowserEmptyState';
import { DataGridProvider } from '@/context/DataGridContext'; import { DataGridProvider } from '@/context/DataGridContext';
import type { UseDataGridOptions } from '@/hooks/useDataGrid'; import type { UseDataGridOptions } from '@/hooks/useDataGrid';
import useDataGrid from '@/hooks/useDataGrid'; import useDataGrid from '@/hooks/useDataGrid';
import type { DataBrowserGridColumn } from '@/types/data-browser'; import type { DataBrowserGridColumn } from '@/types/dataBrowser';
import ActivityIndicator from '@/ui/v2/ActivityIndicator'; import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import type { ForwardedRef } from 'react'; import type { ForwardedRef } from 'react';
import { forwardRef, useEffect, useRef } from 'react'; import { forwardRef, useEffect, useRef } from 'react';

View File

@@ -1,7 +1,7 @@
import type { DataGridProps } from '@/components/common/DataGrid'; import type { DataGridProps } from '@/components/common/DataGrid';
import DataGridCell from '@/components/common/DataGridCell'; import DataGridCell from '@/components/common/DataGridCell';
import useDataGridConfig from '@/hooks/useDataGridConfig'; import useDataGridConfig from '@/hooks/useDataGridConfig';
import type { DataBrowserGridColumn } from '@/types/data-browser'; import type { DataBrowserGridColumn } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import PlusIcon from '@/ui/v2/icons/PlusIcon'; import PlusIcon from '@/ui/v2/icons/PlusIcon';
import type { DetailedHTMLProps, HTMLProps, KeyboardEvent } from 'react'; import type { DetailedHTMLProps, HTMLProps, KeyboardEvent } from 'react';
@@ -35,15 +35,15 @@ function InsertPlaceholderTableRow({
}: InsertPlaceholderTableRowProps) { }: InsertPlaceholderTableRowProps) {
return ( return (
<div <div
className="h-12 border-r-1 border-b-1 border-gray-200 bg-white" className="h-12 bg-white border-gray-200 border-r-1 border-b-1"
{...props} {...props}
> >
<Button <Button
onClick={onInsertRow} onClick={onInsertRow}
variant="borderless" variant="borderless"
color="secondary" color="secondary"
className="h-full w-full justify-start rounded-none px-2 py-3 text-xs font-normal hover:shadow-none focus:shadow-none focus:outline-none" className="justify-start w-full h-full px-2 py-3 text-xs font-normal rounded-none hover:shadow-none focus:shadow-none focus:outline-none"
startIcon={<PlusIcon className="h-4 w-4 text-greyscaleGrey" />} startIcon={<PlusIcon className="w-4 h-4 text-greyscaleGrey" />}
> >
Insert New Row Insert New Row
</Button> </Button>
@@ -181,7 +181,7 @@ export default function DataGridBody<T extends object>({
return ( return (
<div {...getTableBodyProps()} ref={bodyRef} {...props}> <div {...getTableBodyProps()} ref={bodyRef} {...props}>
{rows.length === 0 && !loading && ( {rows.length === 0 && !loading && (
<div className="flex flex-nowrap pr-5"> <div className="flex pr-5 flex-nowrap">
{onInsertRow ? ( {onInsertRow ? (
<InsertPlaceholderTableRow <InsertPlaceholderTableRow
style={{ style={{
@@ -279,7 +279,7 @@ export default function DataGridBody<T extends object>({
})} })}
{allowInsertColumn && ( {allowInsertColumn && (
<div className="h-12 w-25 border-r-1 border-b-1 border-gray-200 bg-white" /> <div className="h-12 bg-white border-gray-200 w-25 border-r-1 border-b-1" />
)} )}
</div> </div>

View File

@@ -3,7 +3,7 @@ import type {
ColumnType, ColumnType,
DataBrowserGridCell, DataBrowserGridCell,
DataBrowserGridCellProps, DataBrowserGridCellProps,
} from '@/types/data-browser'; } from '@/types/dataBrowser';
import Tooltip, { useTooltip } from '@/ui/v2/Tooltip'; import Tooltip, { useTooltip } from '@/ui/v2/Tooltip';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';
import type { import type {

View File

@@ -1,6 +1,6 @@
import type { DataGridProps } from '@/components/common/DataGrid'; import type { DataGridProps } from '@/components/common/DataGrid';
import useDataGridConfig from '@/hooks/useDataGridConfig'; import useDataGridConfig from '@/hooks/useDataGridConfig';
import type { DataBrowserGridColumn } from '@/types/data-browser'; import type { DataBrowserGridColumn } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import Divider from '@/ui/v2/Divider'; import Divider from '@/ui/v2/Divider';
import { Dropdown } from '@/ui/v2/Dropdown'; import { Dropdown } from '@/ui/v2/Dropdown';

View File

@@ -6,19 +6,25 @@ import { createContext } from 'react';
* Available dialog types. * Available dialog types.
*/ */
export type DialogType = export type DialogType =
| 'EDIT_WORKSPACE_NAME'
| 'CREATE_RECORD' | 'CREATE_RECORD'
| 'CREATE_COLUMN' | 'CREATE_COLUMN'
| 'EDIT_COLUMN' | 'EDIT_COLUMN'
| 'CREATE_TABLE' | 'CREATE_TABLE'
| 'EDIT_TABLE' | 'EDIT_TABLE'
| 'EDIT_PERMISSIONS'
| 'CREATE_FOREIGN_KEY' | 'CREATE_FOREIGN_KEY'
| 'EDIT_FOREIGN_KEY' | 'EDIT_FOREIGN_KEY'
| 'CREATE_ROLE' | 'CREATE_ROLE'
| 'EDIT_ROLE' | 'EDIT_ROLE'
| 'CREATE_USER'
| 'CREATE_PERMISSION_VARIABLE' | 'CREATE_PERMISSION_VARIABLE'
| 'EDIT_PERMISSION_VARIABLE' | 'EDIT_PERMISSION_VARIABLE'
| 'CREATE_ENVIRONMENT_VARIABLE' | 'CREATE_ENVIRONMENT_VARIABLE'
| 'EDIT_ENVIRONMENT_VARIABLE'; | 'EDIT_ENVIRONMENT_VARIABLE'
| 'EDIT_USER'
| 'EDIT_USER_PASSWORD'
| 'EDIT_JWT_SECRET';
export interface DialogConfig<TPayload = unknown> { export interface DialogConfig<TPayload = unknown> {
/** /**
@@ -62,6 +68,16 @@ export interface DialogContextProps {
* Call this function to close the active drawer. * Call this function to close the active drawer.
*/ */
closeDrawer: VoidFunction; closeDrawer: VoidFunction;
/**
* Call this function to check if the form is dirty and close the active dialog
* if the form is pristine.
*/
closeDialogWithDirtyGuard: VoidFunction;
/**
* Call this function to check if the form is dirty and close the active drawer
* if the form is pristine.
*/
closeDrawerWithDirtyGuard: VoidFunction;
/** /**
* Call this function to close the active alert dialog. * Call this function to close the active alert dialog.
*/ */
@@ -73,6 +89,10 @@ export interface DialogContextProps {
isDirty: boolean, isDirty: boolean,
location?: 'drawer' | 'dialog', location?: 'drawer' | 'dialog',
) => void; ) => void;
/**
* Call this function to open a dirty confirmation dialog.
*/
openDirtyConfirmation: (config?: Partial<DialogConfig<string>>) => void;
} }
export default createContext<DialogContextProps>({ export default createContext<DialogContextProps>({
@@ -81,6 +101,9 @@ export default createContext<DialogContextProps>({
openAlertDialog: () => {}, openAlertDialog: () => {},
closeDialog: () => {}, closeDialog: () => {},
closeDrawer: () => {}, closeDrawer: () => {},
closeDialogWithDirtyGuard: () => {},
closeDrawerWithDirtyGuard: () => {},
closeAlertDialog: () => {}, closeAlertDialog: () => {},
onDirtyStateChange: () => {}, onDirtyStateChange: () => {},
openDirtyConfirmation: () => {},
}); });

View File

@@ -1,24 +1,37 @@
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary'; import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import CreateForeignKeyForm from '@/components/data-browser/CreateForeignKeyForm'; import CreateForeignKeyForm from '@/components/dataBrowser/CreateForeignKeyForm';
import EditForeignKeyForm from '@/components/data-browser/EditForeignKeyForm'; import EditForeignKeyForm from '@/components/dataBrowser/EditForeignKeyForm';
import EditWorkspaceNameForm from '@/components/home/EditWorkspaceNameForm';
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm'; import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm'; import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
import EditJwtSecretForm from '@/components/settings/environmentVariables/EditJwtSecretForm';
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm'; import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm'; import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm'; import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
import EditRoleForm from '@/components/settings/roles/EditRoleForm'; import EditRoleForm from '@/components/settings/roles/EditRoleForm';
import CreateUserForm from '@/components/users/CreateUserForm';
import EditUserForm from '@/components/users/EditUserForm';
import EditUserPasswordForm from '@/components/users/EditUserPasswordForm';
import ActivityIndicator from '@/ui/v2/ActivityIndicator'; import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import AlertDialog from '@/ui/v2/AlertDialog'; import AlertDialog from '@/ui/v2/AlertDialog';
import { BaseDialog } from '@/ui/v2/Dialog'; import { BaseDialog } from '@/ui/v2/Dialog';
import Drawer from '@/ui/v2/Drawer'; import Drawer from '@/ui/v2/Drawer';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import type { import type {
BaseSyntheticEvent, BaseSyntheticEvent,
DetailedHTMLProps, DetailedHTMLProps,
HTMLProps, HTMLProps,
PropsWithChildren, PropsWithChildren,
} from 'react'; } from 'react';
import { useCallback, useMemo, useReducer, useRef, useState } from 'react'; import {
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import type { DialogConfig, DialogType } from './DialogContext'; import type { DialogConfig, DialogType } from './DialogContext';
import DialogContext from './DialogContext'; import DialogContext from './DialogContext';
@@ -49,31 +62,38 @@ function LoadingComponent({
} }
const CreateRecordForm = dynamic( const CreateRecordForm = dynamic(
() => import('@/components/data-browser/CreateRecordForm'), () => import('@/components/dataBrowser/CreateRecordForm'),
{ ssr: false, loading: () => LoadingComponent() }, { ssr: false, loading: () => LoadingComponent() },
); );
const CreateColumnForm = dynamic( const CreateColumnForm = dynamic(
() => import('@/components/data-browser/CreateColumnForm'), () => import('@/components/dataBrowser/CreateColumnForm'),
{ ssr: false, loading: () => LoadingComponent() }, { ssr: false, loading: () => LoadingComponent() },
); );
const EditColumnForm = dynamic( const EditColumnForm = dynamic(
() => import('@/components/data-browser/EditColumnForm'), () => import('@/components/dataBrowser/EditColumnForm'),
{ ssr: false, loading: () => LoadingComponent() }, { ssr: false, loading: () => LoadingComponent() },
); );
const CreateTableForm = dynamic( const CreateTableForm = dynamic(
() => import('@/components/data-browser/CreateTableForm'), () => import('@/components/dataBrowser/CreateTableForm'),
{ ssr: false, loading: () => LoadingComponent() }, { ssr: false, loading: () => LoadingComponent() },
); );
const EditTableForm = dynamic( const EditTableForm = dynamic(
() => import('@/components/data-browser/EditTableForm'), () => import('@/components/dataBrowser/EditTableForm'),
{ ssr: false, loading: () => LoadingComponent() },
);
const EditPermissionsForm = dynamic(
() => import('@/components/dataBrowser/EditPermissionsForm'),
{ ssr: false, loading: () => LoadingComponent() }, { ssr: false, loading: () => LoadingComponent() },
); );
function DialogProvider({ children }: PropsWithChildren<unknown>) { function DialogProvider({ children }: PropsWithChildren<unknown>) {
const router = useRouter();
const [ const [
{ {
open: dialogOpen, open: dialogOpen,
@@ -161,42 +181,52 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
alertDialogDispatch({ type: 'CLEAR_ALERT_CONTENT' }); alertDialogDispatch({ type: 'CLEAR_ALERT_CONTENT' });
} }
function openDirtyConfirmation(config?: Partial<DialogConfig<string>>) { const openDirtyConfirmation = useCallback(
const { props, ...restConfig } = config || {}; (config?: Partial<DialogConfig<string>>) => {
const { props, ...restConfig } = config || {};
openAlertDialog({
...config,
title: 'Unsaved changes',
payload:
'You have unsaved local changes. Are you sure you want to discard them?',
props: {
...props,
primaryButtonText: 'Discard',
primaryButtonColor: 'error',
},
...restConfig,
});
}
function closeDrawerWithDirtyGuard(event?: BaseSyntheticEvent) {
if (isDrawerDirty.current && event?.type !== 'submit') {
setShowDirtyConfirmation(true); setShowDirtyConfirmation(true);
openDirtyConfirmation({ props: { onPrimaryAction: closeDrawer } }); openAlertDialog({
return; ...config,
} title: 'Unsaved changes',
payload:
'You have unsaved local changes. Are you sure you want to discard them?',
props: {
...props,
primaryButtonText: 'Discard',
primaryButtonColor: 'error',
},
...restConfig,
});
},
[],
);
closeDrawer(); const closeDrawerWithDirtyGuard = useCallback(
} (event?: BaseSyntheticEvent) => {
if (isDrawerDirty.current && event?.type !== 'submit') {
setShowDirtyConfirmation(true);
openDirtyConfirmation({ props: { onPrimaryAction: closeDrawer } });
return;
}
function closeDialogWithDirtyGuard(event?: BaseSyntheticEvent) { closeDrawer();
if (isDialogDirty.current && event?.type !== 'submit') { },
setShowDirtyConfirmation(true); [closeDrawer, openDirtyConfirmation],
openDirtyConfirmation({ props: { onPrimaryAction: closeDialog } }); );
return;
}
closeDialog(); const closeDialogWithDirtyGuard = useCallback(
} (event?: BaseSyntheticEvent) => {
if (isDialogDirty.current && event?.type !== 'submit') {
setShowDirtyConfirmation(true);
openDirtyConfirmation({ props: { onPrimaryAction: closeDialog } });
return;
}
closeDialog();
},
[closeDialog, openDirtyConfirmation],
);
// We are coupling this logic with the location of the dialog content which is // We are coupling this logic with the location of the dialog content which is
// not ideal. We shoule figure out a better logic for tracking the dirty // not ideal. We shoule figure out a better logic for tracking the dirty
@@ -223,10 +253,22 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
openAlertDialog, openAlertDialog,
closeDialog, closeDialog,
closeDrawer, closeDrawer,
closeDialogWithDirtyGuard,
closeDrawerWithDirtyGuard,
closeAlertDialog, closeAlertDialog,
onDirtyStateChange, onDirtyStateChange,
openDirtyConfirmation,
}), }),
[closeDialog, closeDrawer, onDirtyStateChange, openDialog, openDrawer], [
closeDialog,
closeDialogWithDirtyGuard,
closeDrawer,
closeDrawerWithDirtyGuard,
onDirtyStateChange,
openDialog,
openDirtyConfirmation,
openDrawer,
],
); );
const sharedDialogProps = { const sharedDialogProps = {
@@ -248,6 +290,32 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
onCancel: closeDrawerWithDirtyGuard, onCancel: closeDrawerWithDirtyGuard,
}; };
useEffect(() => {
function handleCloseDrawerAndDialog() {
if (isDrawerDirty.current || isDialogDirty.current) {
openDirtyConfirmation({
props: {
onPrimaryAction: () => {
closeDialog();
closeDrawer();
},
},
});
throw new Error('Unsaved changes');
}
closeDrawer();
closeDialog();
}
router?.events?.on?.('routeChangeStart', handleCloseDrawerAndDialog);
return () => {
router?.events?.off?.('routeChangeStart', handleCloseDrawerAndDialog);
};
}, [closeDialog, closeDrawer, openDirtyConfirmation, router.events]);
return ( return (
<DialogContext.Provider value={contextValue}> <DialogContext.Provider value={contextValue}>
<AlertDialog <AlertDialog
@@ -299,6 +367,10 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
<RetryableErrorBoundary <RetryableErrorBoundary
errorMessageProps={{ className: 'pt-0 pb-5 px-6' }} errorMessageProps={{ className: 'pt-0 pb-5 px-6' }}
> >
{activeDialogType === 'EDIT_WORKSPACE_NAME' && (
<EditWorkspaceNameForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_FOREIGN_KEY' && ( {activeDialogType === 'CREATE_FOREIGN_KEY' && (
<CreateForeignKeyForm {...sharedDialogProps} /> <CreateForeignKeyForm {...sharedDialogProps} />
)} )}
@@ -315,6 +387,10 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
<EditRoleForm {...sharedDialogProps} /> <EditRoleForm {...sharedDialogProps} />
)} )}
{activeDialogType === 'CREATE_USER' && (
<CreateUserForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_PERMISSION_VARIABLE' && ( {activeDialogType === 'CREATE_PERMISSION_VARIABLE' && (
<CreatePermissionVariableForm {...sharedDialogProps} /> <CreatePermissionVariableForm {...sharedDialogProps} />
)} )}
@@ -330,17 +406,34 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
{activeDialogType === 'EDIT_ENVIRONMENT_VARIABLE' && ( {activeDialogType === 'EDIT_ENVIRONMENT_VARIABLE' && (
<EditEnvironmentVariableForm {...sharedDialogProps} /> <EditEnvironmentVariableForm {...sharedDialogProps} />
)} )}
{activeDialogType === 'EDIT_USER_PASSWORD' && (
<EditUserPasswordForm
{...sharedDialogProps}
user={sharedDialogProps?.user}
/>
)}
{activeDialogType === 'EDIT_JWT_SECRET' && (
<EditJwtSecretForm {...sharedDialogProps} />
)}
</RetryableErrorBoundary> </RetryableErrorBoundary>
</BaseDialog> </BaseDialog>
<Drawer <Drawer
anchor="right"
{...drawerProps} {...drawerProps}
title={drawerTitle} title={drawerTitle}
open={drawerOpen} open={drawerOpen}
onClose={closeDrawerWithDirtyGuard} onClose={closeDrawerWithDirtyGuard}
SlideProps={{ onExited: clearDrawerContent, unmountOnExit: false }} SlideProps={{ onExited: clearDrawerContent, unmountOnExit: false }}
anchor="right" PaperProps={{
PaperProps={{ className: 'max-w-2.5xl w-full' }} ...drawerProps?.PaperProps,
className: twMerge(
'max-w-2.5xl w-full',
drawerProps?.PaperProps?.className,
),
}}
> >
<RetryableErrorBoundary> <RetryableErrorBoundary>
{activeDrawerType === 'CREATE_RECORD' && ( {activeDrawerType === 'CREATE_RECORD' && (
@@ -375,6 +468,19 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
schema={drawerPayload?.schema} schema={drawerPayload?.schema}
/> />
)} )}
{activeDrawerType === 'EDIT_PERMISSIONS' && (
<EditPermissionsForm
{...sharedDrawerProps}
disabled={drawerPayload?.disabled}
schema={drawerPayload?.schema}
table={drawerPayload?.table}
/>
)}
{activeDrawerType === 'EDIT_USER' && (
<EditUserForm {...sharedDrawerProps} {...drawerPayload} />
)}
</RetryableErrorBoundary> </RetryableErrorBoundary>
</Drawer> </Drawer>

View File

@@ -0,0 +1,12 @@
import InlineCode from '@/components/common/InlineCode';
import type { PropsWithChildren } from 'react';
export default function HighlightedText({
children,
}: PropsWithChildren<unknown>) {
return (
<InlineCode className="text-greyscaleDark bg-primary-light font-display text-sm">
{children}
</InlineCode>
);
}

View File

@@ -0,0 +1 @@
export { default } from './HighlightedText';

View File

@@ -8,7 +8,7 @@ function InlineCode({ className, children, ...props }: InlineCodeProps) {
return ( return (
<code <code
className={twMerge( className={twMerge(
'inline-grid h-full max-h-[18px] max-w-xs items-center truncate rounded-sm bg-gray-100 px-1 font-mono text-[11px] text-gray-600', 'inline-grid max-w-xs items-center truncate rounded-sm bg-gray-100 px-1 font-mono text-[11px] text-greyscaleMedium',
className, className,
)} )}
{...props} {...props}

View File

@@ -0,0 +1,138 @@
import type { ButtonProps } from '@/ui/v2/Button';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import ChevronLeftIcon from '@/ui/v2/icons/ChevronLeftIcon';
import ChevronRightIcon from '@/ui/v2/icons/ChevronRightIcon';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { twMerge } from 'tailwind-merge';
export type PaginationProps = DetailedHTMLProps<
HTMLProps<HTMLDivElement>,
HTMLDivElement
> & {
/**
* Total number of pages.
*/
totalNrOfPages: number;
/**
* Number of total elements per page.
*/
elementsPerPage?: number;
/**
* Total number of elements.
*/
totalNrOfElements: number;
/**
* Current page number.
*/
currentPageNumber: number;
/**
* Function to be called when navigating to the previous page.
*/
onPrevPageClick: VoidFunction;
/**
* Function to be called when navigating to the next page.
*/
onNextPageClick: VoidFunction;
/**
* Function to be called when a new page number is submitted.
*/
onPageChange: (page: number) => void;
/**
* Props for component slots.
*/
slotProps?: {
/**
* Props to be passed to the next button component.
*/
nextButton?: Partial<ButtonProps>;
/**
* Props to be passed to the previous button component.
*/
prevButton?: Partial<ButtonProps>;
};
};
export default function Pagination({
className,
totalNrOfPages,
currentPageNumber,
onPrevPageClick,
onNextPageClick,
slotProps,
elementsPerPage,
onPageChange,
totalNrOfElements,
...props
}: PaginationProps) {
return (
<div
className={twMerge('grid grid-flow-col items-center gap-2', className)}
{...props}
>
<div className="grid justify-start grid-flow-col gap-2">
<Button
variant="outlined"
color="secondary"
className="block text-xs"
disabled={currentPageNumber === 1}
aria-label="Previous page"
onClick={onPrevPageClick}
>
<ChevronLeftIcon className="w-4 h-4" />
Back
</Button>
<div className="grid items-center grid-cols-3 gap-1 text-center grid-col !text-greyscaleGreyDark">
<Text className="text-xs align-middle ">Page</Text>
<Input
value={currentPageNumber}
onChange={(e) => {
const page = parseInt(e.target.value, 10);
if (page > 0 && page <= totalNrOfPages) {
onPageChange(page);
}
}}
disabled={totalNrOfPages === 1}
color="secondary"
slotProps={{
inputRoot: {
className: 'w-4 h-2.5 text-center !text-[11.5px]',
},
}}
/>
<Text className="self-center text-xs align-middle text-greyscaleGreyDark">
of {totalNrOfPages}
</Text>
</div>
<Button
variant="outlined"
color="secondary"
className="text-xs"
aria-label="Next page"
disabled={currentPageNumber === totalNrOfPages}
onClick={onNextPageClick}
{...slotProps?.nextButton}
>
Next
<ChevronRightIcon className="w-4 h-4" />
</Button>
</div>
<div className="flex flex-row items-center justify-end text-center gap-x-1">
<Text className="text-xs text-greyscaleGreyDark">
{currentPageNumber === 1 && currentPageNumber}
{currentPageNumber === 2 && elementsPerPage + currentPageNumber - 1}
{currentPageNumber > 2 &&
(currentPageNumber - 1) * elementsPerPage + 1}{' '}
-{' '}
{totalNrOfElements < currentPageNumber * elementsPerPage
? totalNrOfElements
: currentPageNumber * elementsPerPage}{' '}
of {totalNrOfElements} users
</Text>
</div>
</div>
);
}

View File

@@ -0,0 +1,3 @@
export * from './Pagination';
export { default } from './Pagination';

View File

@@ -0,0 +1,48 @@
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import type { PropsWithoutRef } from 'react';
import type { ReadOnlyToggleProps } from './ReadOnlyToggle';
import ReadOnlyToggle from './ReadOnlyToggle';
export default {
title: 'Common Components / ReadOnlyToggle',
component: ReadOnlyToggle,
argTypes: {
checked: {
options: [null, true, false],
control: { type: 'radio' },
},
},
} as ComponentMeta<typeof ReadOnlyToggle>;
const Template: ComponentStory<typeof ReadOnlyToggle> = function Template(
args: PropsWithoutRef<ReadOnlyToggleProps>,
) {
return <ReadOnlyToggle {...args} />;
};
export const Null = Template.bind({});
Null.args = {
checked: null,
};
export const True = Template.bind({});
True.args = {
checked: true,
};
export const False = Template.bind({});
False.args = {
checked: false,
};
export const CustomClasses = Template.bind({});
CustomClasses.args = {
checked: true,
className: '!bg-red',
slotProps: {
label: {
className: '!text-sm !text-white',
},
},
};

View File

@@ -1,39 +1,79 @@
import type { ForwardedRef } from 'react'; import type { TextProps } from '@/ui/v2/Text';
import Text from '@/ui/v2/Text';
import type { DetailedHTMLProps, ForwardedRef, HTMLProps } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
const ReadOnlyToggle = forwardRef( export interface ReadOnlyToggleProps
( extends DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement> {
{ checked }: { checked: boolean | null }, /**
ref: ForwardedRef<HTMLSpanElement>, * Determines whether the toggle is checked or not.
) => ( */
checked?: boolean | null;
/**
* Props passed to specific component slots.
*/
slotProps?: {
/**
* Props passed to the root `<span />` element.
*/
root?: DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>;
/**
* Props passed to the label.
*/
label?: TextProps;
};
}
function ReadOnlyToggle(
{ checked, className, slotProps = {}, ...props }: ReadOnlyToggleProps,
ref: ForwardedRef<HTMLSpanElement>,
) {
return (
<span <span
className="inline-grid h-full w-full grid-flow-col items-center justify-start gap-1.5" {...props}
{...(slotProps?.root || {})}
className={twMerge(
'inline-grid h-full w-full grid-flow-col items-center justify-start gap-1.5',
slotProps?.root?.className,
className,
)}
ref={ref} ref={ref}
> >
<span <span
className={twMerge( className={twMerge(
'box-border inline-grid h-3 w-5 items-center rounded-full px-0.5', 'box-border inline-grid h-3 w-5 items-center rounded-full px-0.5',
checked === true && 'justify-end bg-greyscaleDark', checked === true &&
'border-1 border-transparent justify-end bg-greyscaleDark',
checked === false && 'border-1 border-greyscaleDark', checked === false && 'border-1 border-greyscaleDark',
checked === null && 'border-1 border-greyscaleDark', checked === null && 'border-1 border-greyscaleDark',
)} )}
> >
<span <span
className={twMerge( className={twMerge(
'inline rounded-full', 'inline-block rounded-full',
checked === true && 'h-2 w-2 bg-white', checked === true && 'h-2 w-2 bg-white',
checked === false && 'h-2 w-2 bg-greyscaleDark', checked === false && 'h-2 w-2 bg-greyscaleDark',
checked === null && 'h-px w-2 justify-self-center bg-greyscaleDark', checked === null &&
'h-px my-px w-2 justify-self-center bg-greyscaleDark',
)} )}
/> />
</span> </span>
<span className="truncate text-xs font-normal">{String(checked)}</span> <Text
{...(slotProps?.label || {})}
component="span"
className={twMerge(
'truncate !text-xs font-normal',
slotProps?.label?.className,
)}
>
{String(checked)}
</Text>
</span> </span>
), );
); }
ReadOnlyToggle.displayName = 'NhostReadOnlyToggle'; ReadOnlyToggle.displayName = 'NhostReadOnlyToggle';
export default ReadOnlyToggle; export default forwardRef(ReadOnlyToggle);

View File

@@ -1,13 +1,11 @@
import { ChangePasswordModal } from '@/components/applications/ChangePasswordModal'; import { ChangePasswordModal } from '@/components/applications/ChangePasswordModal';
import { useWorkspaceContext } from '@/context/workspace-context';
import { useUserDataContext } from '@/context/workspace1-context';
import { Avatar } from '@/ui/Avatar'; import { Avatar } from '@/ui/Avatar';
import { Modal } from '@/ui/Modal'; import { Modal } from '@/ui/Modal';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { Dropdown, useDropdown } from '@/ui/v2/Dropdown'; import { Dropdown, useDropdown } from '@/ui/v2/Dropdown';
import Text from '@/ui/v2/Text'; import Text from '@/ui/v2/Text';
import { emptyWorkspace } from '@/utils/helpers';
import { nhost } from '@/utils/nhost'; import { nhost } from '@/utils/nhost';
import { useApolloClient } from '@apollo/client';
import { useUserData } from '@nhost/nextjs'; import { useUserData } from '@nhost/nextjs';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -22,9 +20,8 @@ function AccountMenuContent({
}: AccountMenuContentProps) { }: AccountMenuContentProps) {
const user = useUserData(); const user = useUserData();
const router = useRouter(); const router = useRouter();
const client = useApolloClient();
const [clicked, setClicked] = useState(false); const [clicked, setClicked] = useState(false);
const { setWorkspaceContext } = useWorkspaceContext();
const { setUserContext } = useUserDataContext();
const { handleClose } = useDropdown(); const { handleClose } = useDropdown();
return ( return (
@@ -34,10 +31,9 @@ function AccountMenuContent({
color="secondary" color="secondary"
className="absolute top-6 right-4 grid grid-flow-col items-center gap-1 self-start font-medium" className="absolute top-6 right-4 grid grid-flow-col items-center gap-1 self-start font-medium"
onClick={async () => { onClick={async () => {
setWorkspaceContext(emptyWorkspace());
setUserContext({ workspaces: [] });
nhost.auth.signOut();
router.push('/signin'); router.push('/signin');
await nhost.auth.signOut();
await client.resetStore();
}} }}
aria-label="Sign Out" aria-label="Sign Out"
> >

View File

@@ -3,7 +3,7 @@ import ControlledCheckbox from '@/components/common/ControlledCheckbox';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form'; import Form from '@/components/common/Form';
import InlineCode from '@/components/common/InlineCode'; import InlineCode from '@/components/common/InlineCode';
import type { ColumnType, DatabaseColumn } from '@/types/data-browser'; import type { ColumnType, DatabaseColumn } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input'; import Input from '@/ui/v2/Input';
import { OptionBase } from '@/ui/v2/Option'; import { OptionBase } from '@/ui/v2/Option';
@@ -118,6 +118,7 @@ export default function BaseColumnForm({
variant="inline" variant="inline"
className="col-span-8 py-3" className="col-span-8 py-3"
autoFocus autoFocus
autoComplete="off"
/> />
<ControlledAutocomplete <ControlledAutocomplete
@@ -272,6 +273,7 @@ export default function BaseColumnForm({
error={Boolean(errors.comment)} error={Boolean(errors.comment)}
variant="inline" variant="inline"
className="col-span-8 py-3" className="col-span-8 py-3"
autoComplete="off"
/> />
</section> </section>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import type { BaseForeignKeyFormValues } from '@/components/data-browser/BaseForeignKeyForm'; import type { BaseForeignKeyFormValues } from '@/components/dataBrowser/BaseForeignKeyForm';
import type { DatabaseColumn } from '@/types/data-browser'; import type { DatabaseColumn } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon'; import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon';
import LinkIcon from '@/ui/v2/icons/LinkIcon'; import LinkIcon from '@/ui/v2/icons/LinkIcon';

View File

@@ -2,7 +2,7 @@ import ControlledSelect from '@/components/common/ControlledSelect';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form'; import Form from '@/components/common/Form';
import useDatabaseQuery from '@/hooks/dataBrowser/useDatabaseQuery'; import useDatabaseQuery from '@/hooks/dataBrowser/useDatabaseQuery';
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/data-browser'; import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import Divider from '@/ui/v2/Divider'; import Divider from '@/ui/v2/Divider';
import Option from '@/ui/v2/Option'; import Option from '@/ui/v2/Option';

View File

@@ -1,6 +1,6 @@
import type { ControlledSelectProps } from '@/components/common/ControlledSelect'; import type { ControlledSelectProps } from '@/components/common/ControlledSelect';
import ControlledSelect from '@/components/common/ControlledSelect'; import ControlledSelect from '@/components/common/ControlledSelect';
import type { NormalizedQueryDataRow } from '@/types/data-browser'; import type { NormalizedQueryDataRow } from '@/types/dataBrowser';
import Option from '@/ui/v2/Option'; import Option from '@/ui/v2/Option';
import type { ForwardedRef, PropsWithoutRef } from 'react'; import type { ForwardedRef, PropsWithoutRef } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';

View File

@@ -1,5 +1,5 @@
import ControlledSelect from '@/components/common/ControlledSelect'; import ControlledSelect from '@/components/common/ControlledSelect';
import type { NormalizedQueryDataRow } from '@/types/data-browser'; import type { NormalizedQueryDataRow } from '@/types/dataBrowser';
import Option from '@/ui/v2/Option'; import Option from '@/ui/v2/Option';
import { useFormContext, useFormState, useWatch } from 'react-hook-form'; import { useFormContext, useFormState, useWatch } from 'react-hook-form';
import type { BaseForeignKeyFormValues } from './BaseForeignKeyForm'; import type { BaseForeignKeyFormValues } from './BaseForeignKeyForm';

View File

@@ -1,10 +1,10 @@
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form'; import Form from '@/components/common/Form';
import DatabaseRecordInputGroup from '@/components/data-browser/DatabaseRecordInputGroup'; import DatabaseRecordInputGroup from '@/components/dataBrowser/DatabaseRecordInputGroup';
import type { import type {
ColumnInsertOptions, ColumnInsertOptions,
DataBrowserGridColumn, DataBrowserGridColumn,
} from '@/types/data-browser'; } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';

View File

@@ -1,7 +1,7 @@
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form'; import Form from '@/components/common/Form';
import { baseColumnValidationSchema } from '@/components/data-browser/BaseColumnForm'; import { baseColumnValidationSchema } from '@/components/dataBrowser/BaseColumnForm';
import type { DatabaseTable, ForeignKeyRelation } from '@/types/data-browser'; import type { DatabaseTable, ForeignKeyRelation } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input'; import Input from '@/ui/v2/Input';
import { useEffect } from 'react'; import { useEffect } from 'react';
@@ -88,6 +88,7 @@ function NameInput() {
error={Boolean(errors.name)} error={Boolean(errors.name)}
variant="inline" variant="inline"
className="col-span-8 py-3" className="col-span-8 py-3"
autoComplete="off"
autoFocus autoFocus
/> />
); );

View File

@@ -1,7 +1,7 @@
import ControlledAutocomplete from '@/components/common/ControlledAutocomplete'; import ControlledAutocomplete from '@/components/common/ControlledAutocomplete';
import ControlledCheckbox from '@/components/common/ControlledCheckbox'; import ControlledCheckbox from '@/components/common/ControlledCheckbox';
import InlineCode from '@/components/common/InlineCode'; import InlineCode from '@/components/common/InlineCode';
import type { ColumnType, ForeignKeyRelation } from '@/types/data-browser'; import type { ColumnType, ForeignKeyRelation } from '@/types/dataBrowser';
import type { ButtonProps } from '@/ui/v2/Button'; import type { ButtonProps } from '@/ui/v2/Button';
import type { CheckboxProps } from '@/ui/v2/Checkbox'; import type { CheckboxProps } from '@/ui/v2/Checkbox';
import IconButton from '@/ui/v2/IconButton'; import IconButton from '@/ui/v2/IconButton';
@@ -70,6 +70,7 @@ function NameInput({ index }: FieldArrayInputProps) {
} }
}, },
})} })}
autoComplete="off"
aria-label="Name" aria-label="Name"
placeholder="Enter name" placeholder="Enter name"
hideEmptyHelperText hideEmptyHelperText

View File

@@ -82,7 +82,7 @@ export default function ColumnEditorTable() {
startIcon={<PlusIcon />} startIcon={<PlusIcon />}
size="small" size="small"
> >
Add column Add Column
</Button> </Button>
</div> </div>
</> </>

View File

@@ -1,4 +1,4 @@
import type { ForeignKeyRelation } from '@/types/data-browser'; import type { ForeignKeyRelation } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon'; import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon';
import LinkIcon from '@/ui/v2/icons/LinkIcon'; import LinkIcon from '@/ui/v2/icons/LinkIcon';

View File

@@ -1,6 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import type { BaseForeignKeyFormValues } from '@/components/data-browser/BaseForeignKeyForm'; import type { BaseForeignKeyFormValues } from '@/components/dataBrowser/BaseForeignKeyForm';
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/data-browser'; import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import PlusIcon from '@/ui/v2/icons/PlusIcon'; import PlusIcon from '@/ui/v2/icons/PlusIcon';
import InputLabel from '@/ui/v2/InputLabel'; import InputLabel from '@/ui/v2/InputLabel';

View File

@@ -1,5 +1,5 @@
import ControlledSelect from '@/components/common/ControlledSelect'; import ControlledSelect from '@/components/common/ControlledSelect';
import type { ColumnType, DatabaseColumn } from '@/types/data-browser'; import type { ColumnType, DatabaseColumn } from '@/types/dataBrowser';
import Option from '@/ui/v2/Option'; import Option from '@/ui/v2/Option';
import { identityTypes } from '@/utils/dataBrowser/postgresqlConstants'; import { identityTypes } from '@/utils/dataBrowser/postgresqlConstants';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@@ -1,5 +1,5 @@
import ControlledSelect from '@/components/common/ControlledSelect'; import ControlledSelect from '@/components/common/ControlledSelect';
import type { DatabaseColumn } from '@/types/data-browser'; import type { DatabaseColumn } from '@/types/dataBrowser';
import Option from '@/ui/v2/Option'; import Option from '@/ui/v2/Option';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useFormState, useWatch } from 'react-hook-form'; import { useFormState, useWatch } from 'react-hook-form';

View File

@@ -0,0 +1,129 @@
import Form from '@/components/common/Form';
import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text';
import hasuraMetadataQuery from '@/utils/msw/mocks/rest/hasuraMetadataQuery';
import tableQuery from '@/utils/msw/mocks/rest/tableQuery';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type { ColumnAutocompleteProps } from './ColumnAutocomplete';
import ColumnAutocomplete from './ColumnAutocomplete';
export default {
title: 'Data Browser / ColumnAutocomplete',
component: ColumnAutocomplete,
parameters: {
docs: {
source: {
type: 'code',
},
},
},
} as ComponentMeta<typeof ColumnAutocomplete>;
const defaultParameters = {
nextRouter: {
path: '/[workspaceSlug]/[appSlug]/database/browser/[dataSourceSlug]/[schemaSlug]/[tableSlug]',
asPath: '/workspace/app/database/browser/default/public/users',
query: {
workspaceSlug: 'workspace',
appSlug: 'app',
dataSourceSlug: 'default',
schemaSlug: 'public',
tableSlug: 'books',
},
},
msw: {
handlers: [tableQuery, hasuraMetadataQuery],
},
};
const Template: ComponentStory<typeof ColumnAutocomplete> = function Template(
args: ColumnAutocompleteProps,
) {
const [submittedValues, setSubmittedValues] = useState<string>('');
const form = useForm<{ firstReference: string; secondReference: string }>({
defaultValues: {
firstReference: null,
secondReference: null,
},
});
function handleSubmit(values: {
firstReference: string;
secondReference: string;
}) {
setSubmittedValues(JSON.stringify(values, null, 2));
}
return (
<div className="grid grid-flow-row gap-2">
<FormProvider {...form}>
<Form onSubmit={handleSubmit} className="grid grid-flow-row gap-2">
<ColumnAutocomplete
{...args}
name="firstReference"
label="First Reference"
onChange={(_event, newValue) =>
form.setValue('firstReference', newValue.value, {
shouldDirty: true,
})
}
onInitialized={(newValue) => {
form.setValue('firstReference', newValue.value, {
shouldDirty: true,
});
}}
/>
<ColumnAutocomplete
{...args}
name="secondReference"
label="Second Reference"
onChange={(_event, newValue) =>
form.setValue('secondReference', newValue.value, {
shouldDirty: true,
})
}
onInitialized={(newValue) => {
form.setValue('secondReference', newValue.value, {
shouldDirty: true,
});
}}
/>
<Button type="submit" className="justify-self-start">
Submit
</Button>
</Form>
</FormProvider>
<Text component="pre" className="!font-mono !text-gray-700">
{submittedValues || 'The form has not been submitted yet.'}
</Text>
</div>
);
};
export const Basic = Template.bind({});
Basic.args = {
schema: 'public',
table: 'books',
};
Basic.parameters = defaultParameters;
export const DefaultValue = Template.bind({});
DefaultValue.args = {
schema: 'public',
table: 'books',
value: 'author.id',
};
DefaultValue.parameters = defaultParameters;
export const DisabledRelationships = Template.bind({});
DisabledRelationships.args = {
schema: 'public',
table: 'books',
disableRelationships: true,
};
DisabledRelationships.parameters = defaultParameters;

View File

@@ -0,0 +1,33 @@
import customClaimsQuery from '@/utils/msw/mocks/graphql/customClaimsQuery';
import hasuraMetadataQuery from '@/utils/msw/mocks/rest/hasuraMetadataQuery';
import tableQuery from '@/utils/msw/mocks/rest/tableQuery';
import { render, screen } from '@/utils/testUtils';
import { setupServer } from 'msw/node';
import { test, vi } from 'vitest';
import ColumnAutocomplete from './ColumnAutocomplete';
const server = setupServer(tableQuery, hasuraMetadataQuery, customClaimsQuery);
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }));
afterEach(() => server.resetHandlers());
afterAll(() => {
server.close();
vi.restoreAllMocks();
});
test('should render a combobox', () => {
render(
<ColumnAutocomplete
schema="public"
table="books"
label="Column Autocomplete"
/>,
);
expect(
screen.getByRole('combobox', { name: /column autocomplete/i }),
).toBeInTheDocument();
});
// Note: Network requests don't go through in tests, so we can't test the
// autocomplete functionality for now.

View File

@@ -0,0 +1,406 @@
import InlineCode from '@/components/common/InlineCode';
import useMetadataQuery from '@/hooks/dataBrowser/useMetadataQuery';
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
import { AutocompletePopper } from '@/ui/v2/Autocomplete';
import IconButton from '@/ui/v2/IconButton';
import ArrowLeftIcon from '@/ui/v2/icons/ArrowLeftIcon';
import type { InputProps } from '@/ui/v2/Input';
import Input from '@/ui/v2/Input';
import List from '@/ui/v2/List';
import { OptionBase } from '@/ui/v2/Option';
import { OptionGroupBase } from '@/ui/v2/OptionGroup';
import Text from '@/ui/v2/Text';
import getTruncatedText from '@/utils/common/getTruncatedText';
import type { AutocompleteGroupedOption } from '@mui/base/AutocompleteUnstyled';
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
import { autocompleteClasses } from '@mui/material/Autocomplete';
import type {
ForwardedRef,
HTMLAttributes,
PropsWithoutRef,
SyntheticEvent,
} from 'react';
import { forwardRef, useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import type { UseAsyncValueOptions } from './useAsyncValue';
import useAsyncValue from './useAsyncValue';
import type { UseColumnGroupsOptions } from './useColumnGroups';
import useColumnGroups from './useColumnGroups';
export interface ColumnAutocompleteProps
extends Omit<PropsWithoutRef<InputProps>, 'onChange'> {
/**
* Schema where the `table` is located.
*/
schema: string;
/**
* Table to get the columns from.
*/
table: string;
/**
* Function to be called when the value changes.
*/
onChange?: (
event: SyntheticEvent,
value: {
value: string;
columnMetadata?: Record<string, any>;
disableReset?: boolean;
},
) => void;
/**
* Function to be called when the input is asynchronously initialized.
*/
onInitialized?: UseAsyncValueOptions['onInitialized'];
/**
* Class name to be applied to the root element.
*/
rootClassName?: string;
/**
* Determines if the autocomplete should allow relationships.
*/
disableRelationships?: UseColumnGroupsOptions['disableRelationships'];
}
function renderGroup(params: AutocompleteRenderGroupParams) {
return (
<li key={params.key}>
<OptionGroupBase>{params.group}</OptionGroupBase>
<List>{params.children}</List>
</li>
);
}
function renderOption(
option: AutocompleteOption<string>,
optionProps: HTMLAttributes<HTMLLIElement>,
) {
return (
<OptionBase
{...optionProps}
className="grid grid-flow-col items-baseline justify-start justify-items-start gap-1.5"
>
<span>{option.label}</span>
{option.group === 'columns' && (
<InlineCode>{option.metadata?.type || option.value}</InlineCode>
)}
</OptionBase>
);
}
function ColumnAutocomplete(
{
rootClassName,
schema: defaultSchema,
table: defaultTable,
value: externalValue,
disableRelationships,
onChange,
onInitialized,
...props
}: ColumnAutocompleteProps,
ref: ForwardedRef<HTMLInputElement>,
) {
const [open, setOpen] = useState(false);
const [activeRelationship, setActiveRelationship] = useState<{
schema: string;
table: string;
name: string;
}>();
const selectedSchema = activeRelationship?.schema || defaultSchema;
const selectedTable = activeRelationship?.table || defaultTable;
const {
data: tableData,
status: tableStatus,
error: tableError,
isFetching: isTableFetching,
} = useTableQuery([`default.${selectedSchema}.${selectedTable}`], {
schema: selectedSchema,
table: selectedTable,
preventRowFetching: true,
queryOptions: { refetchOnWindowFocus: false },
});
const {
data: metadata,
status: metadataStatus,
error: metadataError,
isFetching: isMetadataFetching,
} = useMetadataQuery([`default.metadata`], {
queryOptions: { refetchOnWindowFocus: false },
});
const {
initialized,
inputValue,
setInputValue,
selectedColumn,
setSelectedColumn,
selectedRelationships,
setSelectedRelationships,
relationshipDotNotation,
activeRelationship: asyncActiveRelationship,
} = useAsyncValue({
selectedSchema,
selectedTable,
initialValue: externalValue as string,
isTableLoading: tableStatus === 'loading' || isTableFetching,
isMetadataLoading: metadataStatus === 'loading' || isMetadataFetching,
tableData,
metadata,
onInitialized,
});
useEffect(() => {
setActiveRelationship(asyncActiveRelationship);
}, [asyncActiveRelationship]);
function isOptionEqualToValue(
option: AutocompleteOption,
value: NonNullable<string | AutocompleteOption>,
) {
if (!value) {
return false;
}
if (typeof value === 'string') {
return option.value === value;
}
return option.value === value.value && option.custom === value.custom;
}
function handleChange(
event: SyntheticEvent,
value: NonNullable<string | AutocompleteOption>,
) {
if (typeof value === 'string' || Array.isArray(value) || !value) {
return;
}
if ('group' in value && value.group === 'columns') {
setSelectedColumn(value);
setOpen(false);
setInputValue(value.value);
onChange?.(event, {
value:
selectedRelationships.length > 0
? [relationshipDotNotation, value.value].join('.')
: value.value,
columnMetadata: value.metadata,
});
return;
}
setInputValue('');
setSelectedColumn(null);
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
value.metadata?.target,
]);
}
const options = useColumnGroups({
selectedSchema,
selectedTable,
tableData,
metadata,
disableRelationships,
});
const {
popupOpen,
anchorEl,
setAnchorEl,
getRootProps,
getInputLabelProps,
getInputProps,
getListboxProps,
getOptionProps,
groupedOptions,
} = useAutocomplete({
open,
inputValue,
options,
id: props?.name,
openOnFocus: !props.disabled,
disableCloseOnSelect: true,
value: selectedColumn,
onClose: () => setOpen(false),
groupBy: (option) => option.group,
isOptionEqualToValue,
onChange: handleChange,
});
return (
<>
<div {...getRootProps()} className={rootClassName}>
<Input
{...props}
ref={ref}
fullWidth
slotProps={{
...(props.slotProps || {}),
label: getInputLabelProps(),
input: { ...(props.slotProps?.input || {}), ref: setAnchorEl },
inputRoot: {
...getInputProps(),
className: twMerge(
Boolean(selectedColumn) || Boolean(relationshipDotNotation)
? '!pl-0'
: null,
props.slotProps?.inputRoot?.className,
),
},
}}
onFocus={() => {
if (props.disabled) {
return;
}
setOpen(true);
}}
onClick={() => {
if (props.disabled) {
return;
}
setOpen(true);
}}
error={Boolean(tableError || metadataError) || props.error}
helperText={
String(tableError || metadataError || '') || props.helperText
}
onChange={(event) => setInputValue(event.target.value)}
value={inputValue}
startAdornment={
selectedColumn || relationshipDotNotation ? (
<Text
className={twMerge(
'!ml-2 lg:max-w-[200px] flex-shrink-0 truncate',
props.disabled && 'text-greyscaleGrey',
)}
>
<span className="text-greyscaleGrey">{defaultTable}</span>.
{relationshipDotNotation && (
<>
<span className="hidden lg:inline">
{getTruncatedText(relationshipDotNotation, 15, 'end')}.
</span>
<span className="inline lg:hidden">
{getTruncatedText(relationshipDotNotation, 35, 'end')}.
</span>
</>
)}
</Text>
) : null
}
endAdornment={
tableStatus === 'loading' ||
metadataStatus === 'loading' ||
!initialized ? (
<ActivityIndicator className="mr-2" delay={500} />
) : null
}
/>
</div>
<AutocompletePopper
onMouseDown={(event) => event.preventDefault()}
modifiers={[{ name: 'offset', options: { offset: [0, 10] } }]}
placement="bottom-start"
open={popupOpen}
anchorEl={anchorEl}
style={{ width: anchorEl?.parentElement?.clientWidth }}
>
<div className={autocompleteClasses.paper}>
<div className="px-3 py-2.5 border-b-1 border-greyscale-100 grid grid-flow-col gap-2 justify-start items-center">
{selectedRelationships.length > 0 && (
<IconButton
variant="borderless"
color="secondary"
onClick={(event) => {
event.stopPropagation();
setInputValue('');
setSelectedColumn(null);
setSelectedRelationships((activeRelationships) =>
activeRelationships.slice(0, -1),
);
}}
>
<ArrowLeftIcon className="w-4 h-4" />
</IconButton>
)}
<Text className="truncate direction-rtl text-left">
<span className="!text-greyscaleMedium">{defaultTable}</span>
{relationshipDotNotation && (
<>
<span className="hidden lg:inline">
.{getTruncatedText(relationshipDotNotation, 20, 'start')}
</span>
<span className="inline lg:hidden">
.{relationshipDotNotation}
</span>
</>
)}
</Text>
</div>
{(tableStatus === 'loading' ||
metadataStatus === 'loading' ||
!initialized) && (
<div className="p-2">
<ActivityIndicator label="Loading..." />
</div>
)}
{groupedOptions.length > 0 && (
<List
{...getListboxProps()}
className={autocompleteClasses.listbox}
>
{(
groupedOptions as AutocompleteGroupedOption<
typeof options[number]
>[]
).map((optionGroup) =>
renderGroup({
key: `${optionGroup.key}`,
group: optionGroup.group,
children: optionGroup.options.map((option, index) =>
renderOption(
option,
getOptionProps({
option,
index: optionGroup.index + index,
}),
),
),
}),
)}
</List>
)}
{groupedOptions.length === 0 && Boolean(anchorEl) && (
<Text className={autocompleteClasses.noOptions}>No options</Text>
)}
</div>
</AutocompletePopper>
</>
);
}
export default forwardRef(ColumnAutocomplete);

View File

@@ -0,0 +1,2 @@
export * from './ColumnAutocomplete';
export { default } from './ColumnAutocomplete';

View File

@@ -0,0 +1,302 @@
import type { FetchMetadataReturnType } from '@/hooks/dataBrowser/useMetadataQuery';
import type { FetchTableReturnType } from '@/hooks/dataBrowser/useTableQuery';
import type { HasuraMetadataTable } from '@/types/dataBrowser';
import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
import { useEffect, useState } from 'react';
export interface UseAsyncValueOptions {
/**
* Selected schema to be used to determine the initial value.
*/
selectedSchema?: string;
/**
* Selected table to be used to determine the initial value.
*/
selectedTable?: string;
/**
* Initial value to be used before the async value is loaded.
*/
initialValue?: string;
/**
* Determines whether or not the table data is loading.
*/
isTableLoading?: boolean;
/**
* Determines whether or not the metadata is loading.
*/
isMetadataLoading?: boolean;
/**
* Table data to be used to determine the initial value.
*/
tableData?: FetchTableReturnType;
/**
* Metadata to be used to determine the initial value.
*/
metadata?: FetchMetadataReturnType;
/**
* Function to be called when the input is asynchronously initialized.
*/
onInitialized?: (value: {
value: string;
columnMetadata?: Record<string, any>;
}) => void;
}
export default function useAsyncValue({
selectedSchema,
selectedTable,
initialValue,
isTableLoading,
isMetadataLoading,
tableData,
metadata,
onInitialized,
}: UseAsyncValueOptions) {
const currentTablePath = `${selectedSchema}.${selectedTable}`;
const [inputValue, setInputValue] = useState('');
const [initialized, setInitialized] = useState(false);
// We might not going to have the most up-to-date table data because the
// relationship is loaded asynchronously, so we need to make sure we don't
// look for the column in a stale table when initializing
const [asyncTablePath, setAsyncTablePath] = useState(currentTablePath);
const [remainingColumnPath, setRemainingColumnPath] = useState(
initialValue?.split('.') || [],
);
const [selectedRelationships, setSelectedRelationships] = useState<
{ schema: string; table: string; name: string }[]
>([]);
const relationshipDotNotation = selectedRelationships
.map((relationship) => relationship.name)
.join('.');
const [selectedColumn, setSelectedColumn] =
useState<AutocompleteOption>(null);
const activeRelationship =
selectedRelationships[selectedRelationships.length - 1];
useEffect(() => {
if (remainingColumnPath?.length > 0 || initialized) {
return;
}
setInitialized(true);
if (!selectedColumn) {
return;
}
onInitialized?.({
value:
selectedRelationships.length > 0
? [relationshipDotNotation, selectedColumn.value].join('.')
: selectedColumn.value,
columnMetadata: selectedColumn.metadata,
});
}, [
initialized,
onInitialized,
relationshipDotNotation,
remainingColumnPath?.length,
selectedColumn,
selectedRelationships.length,
]);
useEffect(() => {
if (
remainingColumnPath?.length !== 1 ||
isTableLoading ||
!tableData?.columns ||
asyncTablePath !== currentTablePath
) {
return;
}
const [activeColumn] = remainingColumnPath;
// If there is a single column in the path, it means that we can look for it
// in the table columns
if (
!tableData?.columns.some((column) => column.column_name === activeColumn)
) {
setRemainingColumnPath([]);
return;
}
setSelectedColumn({
value: activeColumn,
label: activeColumn,
group: 'columns',
metadata: tableData.columns.find(
(column) => column.column_name === activeColumn,
),
});
setRemainingColumnPath((columnPath) => columnPath.slice(1));
setInputValue(activeColumn);
}, [
remainingColumnPath,
isTableLoading,
tableData?.columns,
asyncTablePath,
currentTablePath,
]);
useEffect(() => {
if (
remainingColumnPath.length < 2 ||
isTableLoading ||
isMetadataLoading ||
!tableData?.columns ||
asyncTablePath !== currentTablePath
) {
return;
}
const metadataMap = metadata.tables.reduce(
(map, metadataTable) =>
map.set(
`${metadataTable.table.schema}.${metadataTable.table.name}`,
metadataTable,
),
new Map<string, HasuraMetadataTable>(),
);
const [nextPath] = remainingColumnPath.slice(
0,
remainingColumnPath.length - 1,
);
const tableMetadata = metadataMap.get(`${selectedSchema}.${selectedTable}`);
const currentRelationship = [
...(tableMetadata?.object_relationships || []),
...(tableMetadata?.array_relationships || []),
].find(({ name }) => name === nextPath);
if (!currentRelationship) {
setRemainingColumnPath([]);
return;
}
const {
foreign_key_constraint_on: metadataConstraint,
manual_configuration: metadataManualConfiguration,
} = currentRelationship.using || {};
if (metadataManualConfiguration) {
setAsyncTablePath(
`${metadataManualConfiguration.remote_table.schema}.${metadataManualConfiguration.remote_table.name}`,
);
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
{
schema: metadataManualConfiguration.remote_table.schema || 'public',
table: metadataManualConfiguration.remote_table.name,
name: nextPath,
},
]);
setRemainingColumnPath((columnPath) => columnPath.slice(1));
return;
}
// In some cases the metadata already contains the schema and table name
if (metadataConstraint && typeof metadataConstraint !== 'string') {
setAsyncTablePath(
`${metadataConstraint.table.schema || 'public'}.${
metadataConstraint.table.name
}`,
);
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
{
schema: metadataConstraint.table.schema || 'public',
table: metadataConstraint.table.name,
name: nextPath,
},
]);
setRemainingColumnPath((columnPath) => columnPath.slice(1));
return;
}
const foreignKeyRelation = tableData?.foreignKeyRelations?.find(
({ columnName }) => {
const normalizedColumnName = columnName.replace(/"/g, '');
const { foreign_key_constraint_on, manual_configuration } =
currentRelationship.using || {};
if (!foreign_key_constraint_on && !manual_configuration) {
return false;
}
if (manual_configuration) {
return Object.keys(manual_configuration.column_mapping).includes(
normalizedColumnName,
);
}
if (typeof foreign_key_constraint_on === 'string') {
return foreign_key_constraint_on === normalizedColumnName;
}
return foreign_key_constraint_on.column === normalizedColumnName;
},
);
if (!foreignKeyRelation) {
setRemainingColumnPath([]);
return;
}
const normalizedSchema = foreignKeyRelation.referencedSchema?.replace(
/(\\"|")/g,
'',
);
const normalizedTable = foreignKeyRelation.referencedTable?.replace(
/(\\"|")/g,
'',
);
setAsyncTablePath(`${normalizedSchema || 'public'}.${normalizedTable}`);
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
{
schema: normalizedSchema || 'public',
table: normalizedTable,
name: nextPath,
},
]);
setRemainingColumnPath((columnPath) => columnPath.slice(1));
}, [
currentTablePath,
asyncTablePath,
selectedSchema,
selectedTable,
metadata?.tables,
tableData?.columns,
tableData?.foreignKeyRelations,
remainingColumnPath,
isTableLoading,
isMetadataLoading,
]);
return {
initialized,
inputValue,
setInputValue,
activeRelationship,
selectedRelationships: initialized ? selectedRelationships : [],
selectedColumn: initialized ? selectedColumn : null,
setSelectedRelationships,
setSelectedColumn,
relationshipDotNotation:
initialized && selectedRelationships?.length > 0
? relationshipDotNotation
: '',
};
}

View File

@@ -0,0 +1,124 @@
import type { FetchMetadataReturnType } from '@/hooks/dataBrowser/useMetadataQuery';
import type { FetchTableReturnType } from '@/hooks/dataBrowser/useTableQuery';
import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
export interface UseColumnGroupsOptions {
/**
* Selected schema to be used to determines the column groups.
*/
selectedSchema?: string;
/**
* Selected table to be used to determine the column groups.
*/
selectedTable?: string;
/**
* Table data to be used to determine the column groups.
*/
tableData?: FetchTableReturnType;
/**
* Metadata to be used to determine the column groups.
*/
metadata?: FetchMetadataReturnType;
/**
* Determines whether or not to disable column groups.
*/
disableRelationships?: boolean;
}
export default function useColumnGroups({
selectedTable,
selectedSchema,
tableData,
metadata,
disableRelationships,
}: UseColumnGroupsOptions) {
const { columns, foreignKeyRelations } = tableData || {};
const columnTargetMap = foreignKeyRelations?.reduce(
(map, currentRelation) =>
map.set(currentRelation.columnName, {
schema: currentRelation.referencedSchema || 'public',
table: currentRelation.referencedTable,
}),
new Map<string, { schema: string; table: string }>(),
);
const columnOptions: AutocompleteOption[] =
columns?.map((column) => ({
label: column.column_name,
value: column.column_name,
group: 'columns',
metadata: column,
})) || [];
if (disableRelationships) {
return columnOptions;
}
const { object_relationships, array_relationships } =
metadata?.tables?.find(
({ table: metadataTable }) =>
metadataTable.name === selectedTable &&
metadataTable.schema === selectedSchema,
) || {};
const objectAndArrayRelationships = [
...(object_relationships || []),
...(array_relationships || []),
].reduce((relationships, currentRelationship) => {
const { foreign_key_constraint_on, manual_configuration } =
currentRelationship?.using || {};
if (manual_configuration) {
return [
...relationships,
...Object.keys(manual_configuration.column_mapping).map((column) => ({
schema: manual_configuration.remote_table?.schema || 'public',
table: manual_configuration.remote_table?.name,
column,
name: currentRelationship.name,
})),
];
}
if (typeof foreign_key_constraint_on === 'string') {
return [
...relationships,
{
schema: selectedSchema,
table: selectedTable,
column: foreign_key_constraint_on,
name: currentRelationship.name,
},
];
}
return [
...relationships,
{
schema: foreign_key_constraint_on.table.schema,
table: foreign_key_constraint_on.table.name,
column: foreign_key_constraint_on.column,
name: currentRelationship.name,
},
];
}, [] as { schema: string; table: string; column: string; name: string }[]);
return [
...columnOptions,
...objectAndArrayRelationships.map((relationship) => ({
label: relationship.name,
value: relationship.name,
group: 'relationships',
metadata: {
target: {
schema: relationship.schema,
table: relationship.table,
column: relationship.column,
...(columnTargetMap?.get(relationship.column) || {}),
name: relationship.name,
},
},
})),
];
}

View File

@@ -1,10 +1,10 @@
import type { import type {
BaseColumnFormProps, BaseColumnFormProps,
BaseColumnFormValues, BaseColumnFormValues,
} from '@/components/data-browser/BaseColumnForm'; } from '@/components/dataBrowser/BaseColumnForm';
import BaseColumnForm, { import BaseColumnForm, {
baseColumnValidationSchema, baseColumnValidationSchema,
} from '@/components/data-browser/BaseColumnForm'; } from '@/components/dataBrowser/BaseColumnForm';
import useCreateColumnMutation from '@/hooks/dataBrowser/useCreateColumnMutation'; import useCreateColumnMutation from '@/hooks/dataBrowser/useCreateColumnMutation';
import useTrackForeignKeyRelationsMutation from '@/hooks/dataBrowser/useTrackForeignKeyRelationsMutation'; import useTrackForeignKeyRelationsMutation from '@/hooks/dataBrowser/useTrackForeignKeyRelationsMutation';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';

View File

@@ -1,11 +1,11 @@
import type { import type {
BaseForeignKeyFormProps, BaseForeignKeyFormProps,
BaseForeignKeyFormValues, BaseForeignKeyFormValues,
} from '@/components/data-browser/BaseForeignKeyForm'; } from '@/components/dataBrowser/BaseForeignKeyForm';
import { import {
BaseForeignKeyForm, BaseForeignKeyForm,
baseForeignKeyValidationSchema, baseForeignKeyValidationSchema,
} from '@/components/data-browser/BaseForeignKeyForm'; } from '@/components/dataBrowser/BaseForeignKeyForm';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';

View File

@@ -1,7 +1,7 @@
import type { BaseRecordFormProps } from '@/components/data-browser/BaseRecordForm'; import type { BaseRecordFormProps } from '@/components/dataBrowser/BaseRecordForm';
import BaseRecordForm from '@/components/data-browser/BaseRecordForm'; import BaseRecordForm from '@/components/dataBrowser/BaseRecordForm';
import useCreateRecordMutation from '@/hooks/dataBrowser/useCreateRecordMutation'; import useCreateRecordMutation from '@/hooks/dataBrowser/useCreateRecordMutation';
import type { ColumnInsertOptions } from '@/types/data-browser'; import type { ColumnInsertOptions } from '@/types/dataBrowser';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { createDynamicValidationSchema } from '@/utils/dataBrowser/validationSchemaHelpers'; import { createDynamicValidationSchema } from '@/utils/dataBrowser/validationSchemaHelpers';

View File

@@ -1,14 +1,14 @@
import type { import type {
BaseTableFormProps, BaseTableFormProps,
BaseTableFormValues, BaseTableFormValues,
} from '@/components/data-browser/BaseTableForm'; } from '@/components/dataBrowser/BaseTableForm';
import BaseTableForm, { import BaseTableForm, {
baseTableValidationSchema, baseTableValidationSchema,
} from '@/components/data-browser/BaseTableForm'; } from '@/components/dataBrowser/BaseTableForm';
import useCreateTableMutation from '@/hooks/dataBrowser/useCreateTableMutation'; import useCreateTableMutation from '@/hooks/dataBrowser/useCreateTableMutation';
import useTrackForeignKeyRelationMutation from '@/hooks/dataBrowser/useTrackForeignKeyRelationsMutation'; import useTrackForeignKeyRelationMutation from '@/hooks/dataBrowser/useTrackForeignKeyRelationsMutation';
import useTrackTableMutation from '@/hooks/dataBrowser/useTrackTableMutation'; import useTrackTableMutation from '@/hooks/dataBrowser/useTrackTableMutation';
import type { DatabaseTable } from '@/types/data-browser'; import type { DatabaseTable } from '@/types/dataBrowser';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';

View File

@@ -6,8 +6,8 @@ import DataGridNumericCell from '@/components/common/DataGridNumericCell';
import DataGridTextCell from '@/components/common/DataGridTextCell'; import DataGridTextCell from '@/components/common/DataGridTextCell';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import InlineCode from '@/components/common/InlineCode'; import InlineCode from '@/components/common/InlineCode';
import DataBrowserEmptyState from '@/components/data-browser/DataBrowserEmptyState'; import DataBrowserEmptyState from '@/components/dataBrowser/DataBrowserEmptyState';
import DataBrowserGridControls from '@/components/data-browser/DataBrowserGridControls'; import DataBrowserGridControls from '@/components/dataBrowser/DataBrowserGridControls';
import useDeleteColumnWithToastMutation from '@/hooks/dataBrowser/useDeleteColumnMutation/useDeleteColumnWithToastMutation'; import useDeleteColumnWithToastMutation from '@/hooks/dataBrowser/useDeleteColumnMutation/useDeleteColumnWithToastMutation';
import useTableQuery from '@/hooks/dataBrowser/useTableQuery'; import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
import type { UpdateRecordVariables } from '@/hooks/dataBrowser/useUpdateRecordMutation'; import type { UpdateRecordVariables } from '@/hooks/dataBrowser/useUpdateRecordMutation';
@@ -17,7 +17,7 @@ import useTablePath from '@/hooks/useTablePath';
import type { import type {
DataBrowserGridColumn, DataBrowserGridColumn,
NormalizedQueryDataRow, NormalizedQueryDataRow,
} from '@/types/data-browser'; } from '@/types/dataBrowser';
import KeyIcon from '@/ui/v2/icons/KeyIcon'; import KeyIcon from '@/ui/v2/icons/KeyIcon';
import normalizeDefaultValue from '@/utils/dataBrowser/normalizeDefaultValue'; import normalizeDefaultValue from '@/utils/dataBrowser/normalizeDefaultValue';
import { import {
@@ -348,7 +348,7 @@ export default function DataBrowserGrid({
description={ description={
<span> <span>
Schema{' '} Schema{' '}
<InlineCode className="max-h-[32px] bg-gray-200 bg-opacity-80 px-1.5 text-sm"> <InlineCode className="bg-gray-200 bg-opacity-80 px-1.5 text-sm">
{metadata.schema || schemaSlug} {metadata.schema || schemaSlug}
</InlineCode>{' '} </InlineCode>{' '}
does not exist. does not exist.
@@ -365,7 +365,7 @@ export default function DataBrowserGrid({
description={ description={
<span> <span>
Table{' '} Table{' '}
<InlineCode className="max-h-[32px] bg-gray-200 bg-opacity-80 px-1.5 text-sm"> <InlineCode className="bg-gray-200 bg-opacity-80 px-1.5 text-sm">
{metadata.schema || schemaSlug}.{metadata.table || tableSlug} {metadata.schema || schemaSlug}.{metadata.table || tableSlug}
</InlineCode>{' '} </InlineCode>{' '}
does not exist. does not exist.

View File

@@ -4,7 +4,7 @@ import { useDialog } from '@/components/common/DialogProvider';
import useDeleteRecordMutation from '@/hooks/dataBrowser/useDeleteRecordMutation'; import useDeleteRecordMutation from '@/hooks/dataBrowser/useDeleteRecordMutation';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication'; import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import useDataGridConfig from '@/hooks/useDataGridConfig'; import useDataGridConfig from '@/hooks/useDataGridConfig';
import type { DataBrowserGridColumn } from '@/types/data-browser'; import type { DataBrowserGridColumn } from '@/types/dataBrowser';
import Chip from '@/ui/Chip'; import Chip from '@/ui/Chip';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { Dropdown } from '@/ui/v2/Dropdown'; import { Dropdown } from '@/ui/v2/Dropdown';

View File

@@ -1,5 +1,5 @@
import type { DataBrowserSidebarProps } from '@/components/data-browser/DataBrowserSidebar'; import type { DataBrowserSidebarProps } from '@/components/dataBrowser/DataBrowserSidebar';
import DataBrowserSidebar from '@/components/data-browser/DataBrowserSidebar'; import DataBrowserSidebar from '@/components/dataBrowser/DataBrowserSidebar';
import type { ProjectLayoutProps } from '@/components/layout/ProjectLayout'; import type { ProjectLayoutProps } from '@/components/layout/ProjectLayout';
import ProjectLayout from '@/components/layout/ProjectLayout'; import ProjectLayout from '@/components/layout/ProjectLayout';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import InlineCode from '@/components/common/InlineCode';
import NavLink from '@/components/common/NavLink'; import NavLink from '@/components/common/NavLink';
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary'; import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import useIsPlatform from '@/hooks/common/useIsPlatform'; import useIsPlatform from '@/hooks/common/useIsPlatform';
@@ -8,6 +9,7 @@ import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAn
import FloatingActionButton from '@/ui/FloatingActionButton'; import FloatingActionButton from '@/ui/FloatingActionButton';
import ActivityIndicator from '@/ui/v2/ActivityIndicator'; import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import Chip from '@/ui/v2/Chip';
import Divider from '@/ui/v2/Divider'; import Divider from '@/ui/v2/Divider';
import { Dropdown } from '@/ui/v2/Dropdown'; import { Dropdown } from '@/ui/v2/Dropdown';
import IconButton from '@/ui/v2/IconButton'; import IconButton from '@/ui/v2/IconButton';
@@ -17,6 +19,7 @@ import LockIcon from '@/ui/v2/icons/LockIcon';
import PencilIcon from '@/ui/v2/icons/PencilIcon'; import PencilIcon from '@/ui/v2/icons/PencilIcon';
import PlusIcon from '@/ui/v2/icons/PlusIcon'; import PlusIcon from '@/ui/v2/icons/PlusIcon';
import TrashIcon from '@/ui/v2/icons/TrashIcon'; import TrashIcon from '@/ui/v2/icons/TrashIcon';
import UsersIcon from '@/ui/v2/icons/UsersIcon';
import Link from '@/ui/v2/Link'; import Link from '@/ui/v2/Link';
import List from '@/ui/v2/List'; import List from '@/ui/v2/List';
import { ListItem } from '@/ui/v2/ListItem'; import { ListItem } from '@/ui/v2/ListItem';
@@ -194,6 +197,40 @@ function DataBrowserSidebarContent({
}); });
} }
function handleEditPermissionClick(
schema: string,
table: string,
disabled?: boolean,
) {
openDrawer('EDIT_PERMISSIONS', {
title: (
<span className="inline-grid grid-flow-col gap-2 items-center">
Permissions
<InlineCode className="!text-sm+ font-normal text-greyscaleMedium">
{table}
</InlineCode>
<Chip label="Preview" size="small" color="info" component="span" />
</span>
),
props: {
PaperProps: {
className: 'lg:w-[65%] lg:max-w-7xl',
},
},
payload: {
onSubmit: async () => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${schema}.${table}`,
]);
await refetch();
},
disabled,
schema,
table,
},
});
}
return ( return (
<div className="grid gap-1"> <div className="grid gap-1">
{schemas && schemas.length > 0 && ( {schemas && schemas.length > 0 && (
@@ -318,9 +355,7 @@ function DataBrowserSidebarContent({
<Dropdown.Trigger <Dropdown.Trigger
asChild asChild
hideChevron hideChevron
disabled={ disabled={tablePath === removableTable}
tablePath === removableTable || isGitHubConnected
}
> >
<IconButton <IconButton
variant="borderless" variant="borderless"
@@ -329,7 +364,6 @@ function DataBrowserSidebarContent({
!isSelected && !isSelected &&
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100', 'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100',
)} )}
disabled={isGitHubConnected}
> >
<DotsHorizontalIcon /> <DotsHorizontalIcon />
</IconButton> </IconButton>
@@ -339,44 +373,84 @@ function DataBrowserSidebarContent({
menu menu
PaperProps={{ className: 'w-52' }} PaperProps={{ className: 'w-52' }}
> >
<Dropdown.Item {isGitHubConnected ? (
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium" <Dropdown.Item
onClick={() => className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
openDrawer('EDIT_TABLE', { onClick={() =>
title: 'Edit Table', handleEditPermissionClick(
payload: { table.table_schema,
onSubmit: async () => { table.table_name,
await queryClient.refetchQueries([ true,
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`, )
]); }
await refetch(); >
}, <UsersIcon className="h-4 w-4 text-gray-700" />
schema: table.table_schema,
table,
},
})
}
>
<PencilIcon className="h-4 w-4 text-gray-700" />
<span>Edit Table</span> <span>View Permissions</span>
</Dropdown.Item> </Dropdown.Item>
) : (
[
<Dropdown.Item
key="edit-table"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
openDrawer('EDIT_TABLE', {
title: 'Edit Table',
payload: {
onSubmit: async () => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
]);
await refetch();
},
schema: table.table_schema,
table,
},
})
}
>
<PencilIcon className="h-4 w-4 text-gray-700" />
<Divider component="li" /> <span>Edit Table</span>
</Dropdown.Item>,
<Divider
key="edit-table-separator"
component="li"
/>,
<Dropdown.Item
key="edit-permissions"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
handleEditPermissionClick(
table.table_schema,
table.table_name,
)
}
>
<UsersIcon className="h-4 w-4 text-gray-700" />
<Dropdown.Item <span>Edit Permissions</span>
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium text-red" </Dropdown.Item>,
onClick={() => <Divider
handleDeleteTableClick( key="edit-permissions-separator"
table.table_schema, component="li"
table.table_name, />,
) <Dropdown.Item
} key="delete-table"
> className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium text-red"
<TrashIcon className="h-4 w-4 text-red" /> onClick={() =>
handleDeleteTableClick(
table.table_schema,
table.table_name,
)
}
>
<TrashIcon className="h-4 w-4 text-red" />
<span>Delete Table</span> <span>Delete Table</span>
</Dropdown.Item> </Dropdown.Item>,
]
)}
</Dropdown.Content> </Dropdown.Content>
</Dropdown.Root> </Dropdown.Root>
) )

View File

@@ -1,6 +1,6 @@
import InlineCode from '@/components/common/InlineCode'; import InlineCode from '@/components/common/InlineCode';
import ReadOnlyToggle from '@/components/common/ReadOnlyToggle'; import ReadOnlyToggle from '@/components/common/ReadOnlyToggle';
import type { DataBrowserGridColumn } from '@/types/data-browser'; import type { DataBrowserGridColumn } from '@/types/dataBrowser';
import KeyIcon from '@/ui/v2/icons/KeyIcon'; import KeyIcon from '@/ui/v2/icons/KeyIcon';
import Input from '@/ui/v2/Input'; import Input from '@/ui/v2/Input';
import Option from '@/ui/v2/Option'; import Option from '@/ui/v2/Option';
@@ -127,7 +127,7 @@ export default function DatabaseRecordInputGroup({
<span>{columnId}</span> <span>{columnId}</span>
</span> </span>
<InlineCode> <InlineCode className="h-[18px]">
{specificType} {specificType}
{maxLength ? `(${maxLength})` : null} {maxLength ? `(${maxLength})` : null}
</InlineCode> </InlineCode>
@@ -204,7 +204,7 @@ export default function DatabaseRecordInputGroup({
multiline={isMultiline} multiline={isMultiline}
rows={5} rows={5}
autoFocus={index === 0 && autoFocusFirstInput} autoFocus={index === 0 && autoFocusFirstInput}
componentsProps={{ slotProps={{
label: commonLabelProps, label: commonLabelProps,
inputRoot: { step: 1 }, inputRoot: { step: 1 },
}} }}

View File

@@ -1,13 +1,13 @@
import type { import type {
BaseColumnFormProps, BaseColumnFormProps,
BaseColumnFormValues, BaseColumnFormValues,
} from '@/components/data-browser/BaseColumnForm'; } from '@/components/dataBrowser/BaseColumnForm';
import BaseColumnForm, { import BaseColumnForm, {
baseColumnValidationSchema, baseColumnValidationSchema,
} from '@/components/data-browser/BaseColumnForm'; } from '@/components/dataBrowser/BaseColumnForm';
import useTrackForeignKeyRelationMutation from '@/hooks/dataBrowser/useTrackForeignKeyRelationsMutation'; import useTrackForeignKeyRelationMutation from '@/hooks/dataBrowser/useTrackForeignKeyRelationsMutation';
import useUpdateColumnMutation from '@/hooks/dataBrowser/useUpdateColumnMutation'; import useUpdateColumnMutation from '@/hooks/dataBrowser/useUpdateColumnMutation';
import type { DataBrowserGridColumn } from '@/types/data-browser'; import type { DataBrowserGridColumn } from '@/types/dataBrowser';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
import type { AutocompleteOption } from '@/ui/v2/Autocomplete'; import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';

View File

@@ -1,12 +1,12 @@
import type { import type {
BaseForeignKeyFormProps, BaseForeignKeyFormProps,
BaseForeignKeyFormValues, BaseForeignKeyFormValues,
} from '@/components/data-browser/BaseForeignKeyForm'; } from '@/components/dataBrowser/BaseForeignKeyForm';
import { import {
BaseForeignKeyForm, BaseForeignKeyForm,
baseForeignKeyValidationSchema, baseForeignKeyValidationSchema,
} from '@/components/data-browser/BaseForeignKeyForm'; } from '@/components/dataBrowser/BaseForeignKeyForm';
import type { ForeignKeyRelation } from '@/types/data-browser'; import type { ForeignKeyRelation } from '@/types/dataBrowser';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';

View File

@@ -0,0 +1,348 @@
import { useDialog } from '@/components/common/DialogProvider';
import useMetadataQuery from '@/hooks/dataBrowser/useMetadataQuery';
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type {
DatabaseAccessLevel,
DatabaseAction,
HasuraMetadataPermission,
} from '@/types/dataBrowser';
import { Alert } from '@/ui/Alert';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Button from '@/ui/v2/Button';
import FullPermissionIcon from '@/ui/v2/icons/FullPermissionIcon';
import NoPermissionIcon from '@/ui/v2/icons/NoPermissionIcon';
import PartialPermissionIcon from '@/ui/v2/icons/PartialPermissionIcon';
import Link from '@/ui/v2/Link';
import Table from '@/ui/v2/Table';
import TableBody from '@/ui/v2/TableBody';
import TableCell from '@/ui/v2/TableCell';
import TableContainer from '@/ui/v2/TableContainer';
import TableHead from '@/ui/v2/TableHead';
import TableRow from '@/ui/v2/TableRow';
import Text from '@/ui/v2/Text';
import { useGetRemoteAppRolesQuery } from '@/utils/__generated__/graphql';
import NavLink from 'next/link';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';
import RolePermissionEditorForm from './RolePermissionEditorForm';
import RolePermissionsRow from './RolePermissionsRow';
export interface EditPermissionsFormProps {
/**
* Determines whether the form is disabled or not.
*/
disabled?: boolean;
/**
* The schema that is being edited.
*/
schema: string;
/**
* The table that is being edited.
*/
table: string;
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
}
export default function EditPermissionsForm({
disabled,
schema,
table,
onCancel,
}: EditPermissionsFormProps) {
const [role, setRole] = useState<string>();
const [action, setAction] = useState<DatabaseAction>();
const { closeDrawerWithDirtyGuard } = useDialog();
const { currentWorkspace, currentApplication } =
useCurrentWorkspaceAndApplication();
const client = useRemoteApplicationGQLClient();
const {
data: rolesData,
loading: rolesLoading,
error: rolesError,
} = useGetRemoteAppRolesQuery({ client });
const {
data: tableData,
status: tableStatus,
error: tableError,
} = useTableQuery([`default.${schema}.${table}`], { schema, table });
const {
data: metadata,
status: metadataStatus,
error: metadataError,
} = useMetadataQuery([`default.metadata`]);
if (tableStatus === 'loading') {
return (
<div className="p-6">
<ActivityIndicator label="Loading table..." />
</div>
);
}
if (tableError) {
throw tableError;
}
if (metadataStatus === 'loading') {
return (
<div className="p-6">
<ActivityIndicator label="Loading table metadata..." />
</div>
);
}
if (metadataError) {
throw metadataError;
}
if (rolesLoading) {
return (
<div className="p-6">
<ActivityIndicator label="Loading available roles..." />
</div>
);
}
if (rolesError) {
throw rolesError;
}
const availableRoles = [
'public',
...(rolesData?.authRoles?.map(({ role: authRole }) => authRole) || []),
];
const metadataForTable = metadata?.tables?.find(
({ table: currentTable }) =>
currentTable.name === table && currentTable.schema === schema,
);
const availableColumns =
tableData?.columns.map((column) => column.column_name) || [];
function handleSubmit() {
setRole(undefined);
setAction(undefined);
}
function handleCancel() {
setRole(undefined);
setAction(undefined);
}
function getAccessLevel(
permission?: HasuraMetadataPermission['permission'],
): DatabaseAccessLevel {
if (
!permission ||
(!permission?.check && !permission && permission?.columns?.length === 0)
) {
return 'none';
}
const sortedTableColumns = [...availableColumns].sort();
const isAllColumnSelected =
sortedTableColumns.length === permission?.columns?.length &&
[...(permission?.columns || [])]
.sort()
.every(
(permissionColumn, index) =>
permissionColumn === sortedTableColumns[index],
);
if (
Object.keys(permission?.check || {}).length === 0 &&
Object.keys(permission?.filter || {}).length === 0 &&
isAllColumnSelected
) {
return 'full';
}
return 'partial';
}
if (role && action) {
const permissionsForAction = {
insert: metadataForTable?.insert_permissions,
select: metadataForTable?.select_permissions,
update: metadataForTable?.update_permissions,
delete: metadataForTable?.delete_permissions,
};
return (
<RolePermissionEditorForm
disabled={disabled}
schema={schema}
table={table}
role={role}
action={action}
onSubmit={handleSubmit}
onCancel={handleCancel}
permission={
permissionsForAction[action]?.find(
({ role: currentRole }) => currentRole === role,
)?.permission
}
/>
);
}
return (
<div className="flex flex-auto flex-col content-between overflow-hidden border-t-1 border-gray-200 bg-[#fafafa]">
<div className="flex-auto">
<section className="grid grid-flow-row gap-6 content-start overflow-y-auto p-6 bg-white border-b-1 border-gray-200">
<div className="grid grid-flow-row gap-2">
<Text component="h2" className="!font-bold">
Roles & Actions overview
</Text>
<Text>
Rules for each role and action can be set by clicking on the
corresponding cell.
</Text>
</div>
<div className="grid grid-flow-col gap-4 items-center justify-start">
<Text
variant="subtitle2"
className="!text-greyscaleDark grid items-center grid-flow-col gap-1"
>
full access <FullPermissionIcon />
</Text>
<Text
variant="subtitle2"
className="!text-greyscaleDark grid items-center grid-flow-col gap-1"
>
partial access <PartialPermissionIcon />
</Text>
<Text
variant="subtitle2"
className="!text-greyscaleDark grid items-center grid-flow-col gap-1"
>
no access <NoPermissionIcon />
</Text>
</div>
<TableContainer>
<Table>
<TableHead className="block">
<TableRow className="grid grid-cols-5 items-center">
<TableCell className="border-b-0 p-2">Role</TableCell>
<TableCell className="border-b-0 p-2 text-center">
Insert
</TableCell>
<TableCell className="border-b-0 p-2 text-center">
Select
</TableCell>
<TableCell className="border-b-0 p-2 text-center">
Update
</TableCell>
<TableCell className="border-b-0 p-2 text-center">
Delete
</TableCell>
</TableRow>
</TableHead>
<TableBody className="rounded-sm+ block border-1">
<RolePermissionsRow
name="admin"
disabled
accessLevels={{
insert: 'full',
select: 'full',
update: 'full',
delete: 'full',
}}
/>
{availableRoles.map((currentRole, index) => {
const insertPermissions =
metadataForTable?.insert_permissions?.find(
({ role: permissionRole }) =>
permissionRole === currentRole,
);
const selectPermissions =
metadataForTable?.select_permissions?.find(
({ role: permissionRole }) =>
permissionRole === currentRole,
);
const updatePermissions =
metadataForTable?.update_permissions?.find(
({ role: permissionRole }) =>
permissionRole === currentRole,
);
const deletePermissions =
metadataForTable?.delete_permissions?.find(
({ role: permissionRole }) =>
permissionRole === currentRole,
);
return (
<RolePermissionsRow
name={currentRole}
key={currentRole}
className={twMerge(
index === availableRoles.length - 1 && 'border-b-0',
)}
onActionSelect={(selectedAction) => {
setRole(currentRole);
setAction(selectedAction);
}}
accessLevels={{
insert: getAccessLevel(insertPermissions?.permission),
select: getAccessLevel(selectPermissions?.permission),
update: getAccessLevel(updatePermissions?.permission),
delete: getAccessLevel(deletePermissions?.permission),
}}
/>
);
})}
</TableBody>
</Table>
</TableContainer>
<Alert className="text-left">
Please go to the{' '}
<NavLink
href={`/${currentWorkspace.slug}/${currentApplication.slug}/settings/roles-and-permissions`}
passHref
>
<Link
href="settings/roles-and-permissions"
underline="hover"
onClick={closeDrawerWithDirtyGuard}
>
Settings page
</Link>
</NavLink>{' '}
to add and delete roles.
</Alert>
</section>
</div>
<div className="grid flex-shrink-0 grid-flow-col justify-between gap-3 border-t-1 border-gray-200 p-2 bg-white">
<Button variant="borderless" color="secondary" onClick={onCancel}>
Cancel
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,425 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import HighlightedText from '@/components/common/HighlightedText';
import useManagePermissionMutation from '@/hooks/dataBrowser/useManagePermissionMutation';
import type {
DatabaseAction,
HasuraMetadataPermission,
RuleGroup,
} from '@/types/dataBrowser';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text';
import convertToHasuraPermissions from '@/utils/dataBrowser/convertToHasuraPermissions';
import convertToRuleGroup from '@/utils/dataBrowser/convertToRuleGroup';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import { yupResolver } from '@hookform/resolvers/yup';
import { useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import AggregationQuerySection from './sections/AggregationQuerySection';
import BackendOnlySection from './sections/BackendOnlySection';
import ColumnPermissionsSection from './sections/ColumnPermissionsSection';
import type { ColumnPreset } from './sections/ColumnPresetsSection';
import ColumnPresetsSection from './sections/ColumnPresetsSection';
import PermissionSettingsSection from './sections/PermissionSettingsSection';
import RootFieldPermissionsSection from './sections/RootFieldPermissionsSection';
import RowPermissionsSection from './sections/RowPermissionsSection';
import validationSchemas from './validationSchemas';
export interface RolePermissionEditorFormValues {
/**
* The permission filter to be applied for the role.
*/
filter: Record<string, any> | {};
/**
* The allowed columns to CRUD for the role.
*/
columns?: string[];
/**
* The number of rows to be returned for the role.
*/
limit?: number;
/**
* Whether the role is allowed to perform aggregations.
*/
allowAggregations?: boolean;
/**
* Whether the role is allowed to have access to special fields.
*/
enableRootFieldCustomization?: boolean;
/**
* The allowed root fields in queries and mutations for the role.
*/
queryRootFields?: string[];
/**
* The allowed root fields in subscriptions for the role.
*/
subscriptionRootFields?: string[];
/**
* Column presets for the role.
*/
columnPresets?: ColumnPreset[];
/**
* Whether the mutation should be restricted to trusted backends.
*/
backendOnly?: boolean;
}
export interface RolePermissionEditorFormProps {
/**
* Determines whether or not the form is disabled.
*/
disabled?: boolean;
/**
* The schema that is being edited.
*/
schema: string;
/**
* The table that is being edited.
*/
table: string;
/**
* The role that is being edited.
*/
role: string;
/**
* The action that is being edited.
*/
action: DatabaseAction;
/**
* Function to be called when the form is submitted.
*/
onSubmit: VoidFunction;
/**
* Function to be called when the editing is cancelled.
*/
onCancel: VoidFunction;
/**
* The existing permissions for the role and action.
*/
permission?: HasuraMetadataPermission['permission'];
}
function getDefaultRuleGroup(
action: DatabaseAction,
permission: HasuraMetadataPermission['permission'],
): RuleGroup | {} {
if (!permission) {
return null;
}
if (action === 'insert') {
return convertToRuleGroup(permission.check);
}
return convertToRuleGroup(permission.filter);
}
function getColumnPresets(data: Record<string, any>): ColumnPreset[] {
if (!data || Object.keys(data).length === 0) {
return [{ column: '', value: '' }];
}
return Object.keys(data).map((key) => ({
column: key,
value: data[key],
}));
}
function convertToColumnPresetObject(
columnPresets: ColumnPreset[],
): Record<string, any> {
if (columnPresets?.length === 0) {
return null;
}
const returnValue = columnPresets.reduce((data, { column, value }) => {
if (column) {
return { ...data, [column]: value };
}
return data;
}, {});
if (Object.keys(returnValue).length === 0) {
return null;
}
return returnValue;
}
export default function RolePermissionEditorForm({
schema,
table,
role,
action,
onSubmit,
onCancel,
permission,
disabled,
}: RolePermissionEditorFormProps) {
const queryClient = useQueryClient();
const {
mutateAsync: managePermission,
error,
reset: resetError,
isLoading,
} = useManagePermissionMutation({
schema,
table,
mutationOptions: {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['default.metadata'] });
},
},
});
const form = useForm<RolePermissionEditorFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
filter: getDefaultRuleGroup(action, permission),
columns: permission?.columns || [],
limit: permission?.limit || null,
allowAggregations: permission?.allow_aggregations || false,
enableRootFieldCustomization:
permission?.query_root_fields?.length > 0 ||
permission?.subscription_root_fields?.length > 0,
queryRootFields: permission?.query_root_fields || [],
subscriptionRootFields: permission?.subscription_root_fields || [],
columnPresets: getColumnPresets(permission?.set || {}),
backendOnly: permission?.backend_only || false,
},
resolver: yupResolver(validationSchemas[action]),
});
const {
formState: { dirtyFields, isSubmitting },
} = form;
const { onDirtyStateChange, openDirtyConfirmation, openAlertDialog } =
useDialog();
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'drawer');
}, [isDirty, onDirtyStateChange]);
async function handleSubmit(values: RolePermissionEditorFormValues) {
const managePermissionPromise = managePermission({
role,
action,
mode: permission ? 'update' : 'insert',
originalPermission: permission,
permission: {
set: convertToColumnPresetObject(values.columnPresets),
columns: values.columns,
limit: values.limit,
allow_aggregations: values.allowAggregations,
query_root_fields: values.queryRootFields,
subscription_root_fields: values.subscriptionRootFields,
filter:
action !== 'insert'
? convertToHasuraPermissions(values.filter as RuleGroup)
: permission?.filter,
check:
action === 'insert'
? convertToHasuraPermissions(values.filter as RuleGroup)
: permission?.check,
backend_only: values.backendOnly,
},
});
await toast.promise(
managePermissionPromise,
{
loading: 'Saving permission...',
success: 'Permission has been saved successfully.',
error: 'An error occurred while saving the permission.',
},
toastStyleProps,
);
onDirtyStateChange(false, 'drawer');
onSubmit?.();
}
function handleCancelClick() {
if (isDirty) {
openDirtyConfirmation({
props: {
onPrimaryAction: () => {
onDirtyStateChange(false, 'drawer');
onCancel?.();
},
},
});
return;
}
onCancel?.();
}
async function handleDelete() {
const deletePermissionPromise = managePermission({
role,
action,
originalPermission: permission,
mode: 'delete',
});
await toast.promise(
deletePermissionPromise,
{
loading: 'Deleting permission...',
success: 'Permission has been deleted successfully.',
error: 'An error occurred while deleting the permission.',
},
toastStyleProps,
);
onDirtyStateChange(false, 'drawer');
onSubmit?.();
}
function handleDeleteClick() {
openAlertDialog({
title: 'Delete permissions',
payload: (
<span>
Are you sure you want to delete the{' '}
<HighlightedText>{action}</HighlightedText> permissions of{' '}
<HighlightedText>{role}</HighlightedText>?
</span>
),
props: {
primaryButtonText: 'Delete',
primaryButtonColor: 'error',
onPrimaryAction: handleDelete,
},
});
}
return (
<FormProvider {...form}>
{error && error instanceof Error && (
<div className="-mt-3 mb-4 px-6">
<Alert
severity="error"
className="grid grid-flow-col items-center justify-between px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {error.message}
</span>
<Button
variant="borderless"
color="secondary"
className="p-1"
onClick={resetError}
>
Clear
</Button>
</Alert>
</div>
)}
<Form
onSubmit={handleSubmit}
className="flex flex-auto flex-col content-between overflow-hidden border-t-1 border-gray-200 bg-[#fafafa]"
>
<div className="grid grid-flow-row gap-6 content-start flex-auto py-4 overflow-auto">
<PermissionSettingsSection
title="Selected role & action"
className="justify-between grid-flow-col"
>
<div className="grid grid-flow-col gap-4">
<Text>
Role: <HighlightedText>{role}</HighlightedText>
</Text>
<Text>
Action: <HighlightedText>{action}</HighlightedText>
</Text>
</div>
<Button variant="borderless" onClick={handleCancelClick}>
Change
</Button>
</PermissionSettingsSection>
<RowPermissionsSection
disabled={disabled}
role={role}
action={action}
schema={schema}
table={table}
/>
{action !== 'delete' && (
<ColumnPermissionsSection
disabled={disabled}
role={role}
action={action}
schema={schema}
table={table}
/>
)}
{action === 'select' && (
<>
<AggregationQuerySection role={role} disabled={disabled} />
<RootFieldPermissionsSection disabled={disabled} />
</>
)}
{(action === 'insert' || action === 'update') && (
<ColumnPresetsSection
schema={schema}
table={table}
disabled={disabled}
/>
)}
{action !== 'select' && <BackendOnlySection disabled={disabled} />}
</div>
<div className="grid flex-shrink-0 sm:grid-flow-col sm:justify-between gap-2 border-t-1 border-gray-200 p-2 bg-white">
<Button
variant="borderless"
color="secondary"
onClick={handleCancelClick}
tabIndex={isDirty ? -1 : 0}
>
Cancel
</Button>
{!disabled && (
<div className="grid grid-flow-row sm:grid-flow-col gap-2">
{Boolean(permission) && (
<Button
variant="outlined"
color="error"
onClick={handleDeleteClick}
disabled={isLoading}
>
Delete Permissions
</Button>
)}
<Button
loading={isSubmitting}
disabled={isSubmitting}
type="submit"
className="justify-self-end"
>
Save
</Button>
</div>
)}
</div>
</Form>
</FormProvider>
);
}

View File

@@ -0,0 +1,176 @@
import type { DatabaseAccessLevel, DatabaseAction } from '@/types/dataBrowser';
import IconButton from '@/ui/v2/IconButton';
import FullPermissionIcon from '@/ui/v2/icons/FullPermissionIcon';
import NoPermissionIcon from '@/ui/v2/icons/NoPermissionIcon';
import PartialPermissionIcon from '@/ui/v2/icons/PartialPermissionIcon';
import type { TableCellProps } from '@/ui/v2/TableCell';
import TableCell from '@/ui/v2/TableCell';
import type { TableRowProps } from '@/ui/v2/TableRow';
import TableRow from '@/ui/v2/TableRow';
import { twMerge } from 'tailwind-merge';
export interface RolePermissionsProps extends TableRowProps {
/**
* Role name.
*/
name: string;
/**
* Determines whether or not the actions are disabled.
*/
disabled?: boolean;
/**
* Access types for specific operations.
*/
accessLevels?: Record<DatabaseAction, DatabaseAccessLevel>;
/**
* Function to be called when the user wants to open the settings for an
* operation.
*/
onActionSelect?: (action: DatabaseAction) => void;
/**
* Props passed to individual component slots.
*/
slotProps?: {
/**
* Props passed to every cell in the table row.
*/
cell?: Partial<TableCellProps>;
};
}
function AccessLevelIcon({ level }: { level: DatabaseAccessLevel }) {
if (level === 'none') {
return <NoPermissionIcon />;
}
if (level === 'partial') {
return <PartialPermissionIcon />;
}
return <FullPermissionIcon />;
}
export default function RolePermissions({
name,
disabled,
accessLevels = {
insert: 'none',
select: 'none',
update: 'none',
delete: 'none',
},
onActionSelect,
slotProps,
className,
...props
}: RolePermissionsProps) {
const cellProps = slotProps?.cell || {};
return (
<TableRow
className={twMerge(
'grid grid-cols-5 items-center justify-items-stretch border-b-1',
className,
)}
{...props}
>
<TableCell
{...cellProps}
className={twMerge(
'block p-2 border-0 truncate border-r-1',
cellProps.className,
)}
>
{name}
</TableCell>
<TableCell
{...cellProps}
className={twMerge(
'inline-grid items-center p-0 border-0 text-center w-full h-full border-r-1',
disabled && 'justify-center',
cellProps.className,
)}
>
{disabled ? (
<AccessLevelIcon level={accessLevels.insert} />
) : (
<IconButton
variant="borderless"
color="secondary"
className="w-full h-full rounded-none"
onClick={() => onActionSelect('insert')}
>
<AccessLevelIcon level={accessLevels.insert} />
</IconButton>
)}
</TableCell>
<TableCell
{...cellProps}
className={twMerge(
'inline-grid items-center p-0 border-0 text-center w-full h-full border-r-1',
disabled && 'justify-center',
cellProps.className,
)}
>
{disabled ? (
<AccessLevelIcon level={accessLevels.select} />
) : (
<IconButton
variant="borderless"
color="secondary"
className="w-full h-full rounded-none"
onClick={() => onActionSelect('select')}
>
<AccessLevelIcon level={accessLevels.select} />
</IconButton>
)}
</TableCell>
<TableCell
{...cellProps}
className={twMerge(
'inline-grid items-center p-0 border-0 text-center w-full h-full border-r-1',
disabled && 'justify-center',
cellProps.className,
)}
>
{disabled ? (
<AccessLevelIcon level={accessLevels.update} />
) : (
<IconButton
variant="borderless"
color="secondary"
className="w-full h-full rounded-none"
onClick={() => onActionSelect('update')}
>
<AccessLevelIcon level={accessLevels.update} />
</IconButton>
)}
</TableCell>
<TableCell
{...cellProps}
className={twMerge(
'inline-grid items-center p-0 border-0 text-center w-full h-full',
disabled && 'justify-center',
cellProps.className,
)}
>
{disabled ? (
<AccessLevelIcon level={accessLevels.delete} />
) : (
<IconButton
variant="borderless"
color="secondary"
className="w-full h-full rounded-none"
onClick={() => onActionSelect('delete')}
>
<AccessLevelIcon level={accessLevels.delete} />
</IconButton>
)}
</TableCell>
</TableRow>
);
}

View File

@@ -0,0 +1,2 @@
export * from './EditPermissionsForm';
export { default } from './EditPermissionsForm';

View File

@@ -0,0 +1,64 @@
import ControlledSwitch from '@/components/common/ControlledSwitch';
import HighlightedText from '@/components/common/HighlightedText';
import type { RolePermissionEditorFormValues } from '@/components/dataBrowser/EditPermissionsForm/RolePermissionEditorForm';
import Text from '@/ui/v2/Text';
import { useFormContext } from 'react-hook-form';
import PermissionSettingsSection from './PermissionSettingsSection';
export interface AggregationQuerySectionProps {
/**
* The role that is being edited.
*/
role: string;
/**
* Determines whether or not the section is disabled.
*/
disabled?: boolean;
}
export default function AggregationQuerySection({
role,
disabled,
}: AggregationQuerySectionProps) {
const { setValue, getValues } =
useFormContext<RolePermissionEditorFormValues>();
return (
<PermissionSettingsSection title="Aggregation queries permissions">
<Text variant="subtitle1">
Allow queries with aggregate functions like sum, count, avg, max, min,
etc.
</Text>
<ControlledSwitch
disabled={disabled}
name="allowAggregations"
label={
<Text variant="subtitle1" component="span">
Allow <HighlightedText>{role}</HighlightedText> to make aggregation
queries
</Text>
}
onChange={(event) => {
if (event.target.checked) {
return;
}
setValue(
'queryRootFields',
getValues('queryRootFields')?.filter(
(field) => field !== 'select_aggregate',
) || [],
);
setValue(
'subscriptionRootFields',
getValues('subscriptionRootFields')?.filter(
(field) => field !== 'select_aggregate',
) || [],
);
}}
/>
</PermissionSettingsSection>
);
}

Some files were not shown because too many files have changed in this diff Show More