Compare commits

..

9 Commits

Author SHA1 Message Date
github-actions[bot]
b0e848d353 chore: update versions (#3066)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@2.11.0

### Minor Changes

-   cea3ef5: Feat: add org and project placeholders

## @nhost/docs@2.24.0

### Minor Changes

-   a99f034: chore: fix function name

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-11 12:57:46 +01:00
Nuno Pato
cea3ef5c8a feat: dashboard: add org and project static placeholder routes (#3069)
### **PR Type**
enhancement


___

### **Description**
- Introduced new components `SelectOrg` and `SelectOrgAndProject` for
selecting organizations and projects, respectively.
- Implemented filtering functionality for both organizations and
projects.
- Integrated loading indicators and error boundaries for better user
experience.
- Added navigation logic to handle routing to selected organization and
project pages.
- Updated redirect logic to accommodate new routes for organizations and
projects.
- Added new pages and index for organization and project selection.
- Documented changes in a changeset file.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>8
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>SelectOrg.tsx</strong><dd><code>Add
SelectOrganizationAndProject component with filtering and
</code><br><code>navigation</code></dd></summary>
<hr>

dashboard/src/components/common/SelectOrg/SelectOrg.tsx

<li>Added a new component <code>SelectOrganizationAndProject</code> for
selecting <br>organizations.<br> <li> Implemented filtering
functionality for organizations.<br> <li> Integrated loading indicator
and error boundary.<br> <li> Added navigation logic to organization
pages.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-3d9046053de6cf89a71b2c8843435afbade4eacff8f38f57bd9dd40e81fc5ba0">+144/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export SelectOrg
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/components/common/SelectOrg/index.ts

- Exported `SelectOrg` component from `SelectOrg.tsx`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-13c3f09c9b9992210b030c77a795c895b7b5672a603fd6577547272f1b4292c3">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>SelectOrgAndProject.tsx</strong><dd><code>Add
SelectOrganizationAndProject component with filtering and
</code><br><code>navigation</code></dd></summary>
<hr>


dashboard/src/components/common/SelectOrgAndProject/SelectOrgAndProject.tsx

<li>Added a new component <code>SelectOrganizationAndProject</code> for
selecting <br>projects.<br> <li> Implemented filtering functionality for
projects.<br> <li> Integrated loading indicator and error boundary.<br>
<li> Added navigation logic to project pages.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-7d86c6e5bc51696bf1aa421c920e01a1447699456c37b025bdc407050c7b5613">+146/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export SelectOrgAndProject
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/components/common/SelectOrgAndProject/index.ts

<li>Exported <code>SelectOrgAndProject</code> component from
<code>SelectOrgAndProject.tsx</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-8b07aa6bcfe4996a7c46923856e713ecf3156fe6c2720b28efcadebd7fb1496f">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useNotFoundRedirect.ts</strong><dd><code>Update
redirect logic for new routes</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/projects/common/hooks/useNotFoundRedirect/useNotFoundRedirect.ts

<li>Updated redirect logic to include new organization and project
routes.<br> <br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-837279cf43199053bca09913f62c4af019063a2e8dc7bfb7643ec54b7cecd29d">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>[...slug].tsx</strong><dd><code>Add organization
selection page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/pages/orgs/_/[...slug].tsx

- Added a new page for selecting organizations using `SelectOrg`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-3993ec4184ca06532310b26dcf40fb3fb5b79c78621fbb8c83b15b145331b3e6">+15/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>[...slug].tsx</strong><dd><code>Add project selection
page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/orgs/_/projects/_/[...slug].tsx

<li>Added a new page for selecting projects using
<code>SelectOrgAndProject</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-e4e0c0ae58b0bb766af6983e44171470d085d9b15079450d788ffe0ab34440ae">+15/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Add index page for project
selection</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/orgs/_/projects/_/index.tsx

<li>Added a new index page for project selection using
<br><code>SelectOrgAndProject</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-f1903b9c41e81033add23bed91df48b3e2c485802187b160a87d2d6e2caef507">+14/-0</a>&nbsp;
&nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>thin-pants-battle.md</strong><dd><code>Document org and
project placeholder feature</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/thin-pants-battle.md

- Documented the addition of organization and project placeholders.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-74b67093a68ccc2b180504af0ce16b3404f16de81bd5200d15e066bda7345038">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-11 19:41:38 +08:00
Hassan Ben Jobrane
a05db74bb6 chore: remove refs toNEXT_PUBLIC_NHOST_BACKEND_URL env var (#3064)
### **PR Type**
enhancement, documentation


___

### **Description**
- Removed the `NEXT_PUBLIC_NHOST_BACKEND_URL` environment variable from
multiple configuration files, including Storybook, CI, and dashboard
workflows.
- Updated the README file to remove the reference to the
`NEXT_PUBLIC_NHOST_BACKEND_URL` environment variable.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>main.js</strong><dd><code>Remove unused environment
variable from Storybook configuration</code></dd></summary>
<hr>

dashboard/.storybook/main.js

<li>Removed <code>NEXT_PUBLIC_NHOST_BACKEND_URL</code> from environment
configuration.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-0fca1613b8f07aaeab7e635b990a910ff90eddb8a4a84f79f16d9dce3ab90aeb">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>ci.yaml</strong><dd><code>Remove unused environment
variable from CI workflow</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.github/workflows/ci.yaml

<li>Removed <code>NEXT_PUBLIC_NHOST_BACKEND_URL</code> from CI
environment variables.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>dashboard.yaml</strong><dd><code>Remove unused
environment variable from dashboard workflow</code></dd></summary>
<hr>

.github/workflows/dashboard.yaml

<li>Removed <code>NEXT_PUBLIC_NHOST_BACKEND_URL</code> from dashboard
workflow <br>environment variables.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-1b7fed7873af85166ff8fc5089ed9507b4ae6b40cccd993849feca4b42a37c4a">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>README.md</strong><dd><code>Update README to remove
unused environment variable</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/README.md

<li>Removed documentation reference to
<code>NEXT_PUBLIC_NHOST_BACKEND_URL</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-c15729e6c35a283a4b0eda60a991303b6c36c03903ba42dbf832bb8d0daa1a1a">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-09 14:14:57 +01:00
David Barroso
73f3d69776 chore (ci): remove unnecessary/unsafe permissions to gen_ai_review (#3067)
### **PR Type**
enhancement


___

### **Description**
- Removed the 'contents: write' permission from the GitHub Actions
workflow to enhance security by limiting permissions to only those
necessary for the job.
- The change ensures that the workflow runs with minimal permissions,
reducing potential security risks.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>gen_ai_review.yaml</strong><dd><code>Remove unnecessary
permissions from GitHub Actions workflow</code></dd></summary>
<hr>

.github/workflows/gen_ai_review.yaml

<li>Removed 'contents: write' permission from the workflow.<br> <li>
Ensured only necessary permissions are retained for the job.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3067/files#diff-d1e4c772e0acb5ce4891df2dd94ba58ffaf6393e8f75493ec7e10cbce1c4992c">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-09 14:13:30 +01:00
Nuno Pato
a99f034bd4 chore: fix docs (#3063)
### **PR Type**
documentation, enhancement


___

### **Description**
- Corrected the function name from `refreshToken` to `refreshSession` in
both TypeScript code and documentation.
- Updated code examples and documentation to reflect the correct
function usage.
- Added a changeset to document the changes made to the function name.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>hasura-auth-client.ts</strong><dd><code>Correct
function name in TypeScript code examples</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/hasura-auth-client.ts

<li>Corrected function name from <code>refreshToken</code> to
<code>refreshSession</code> in code <br>examples.<br> <li> Updated
documentation comments to reflect the function name change.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3063/files#diff-0dbc30932ed723b7fd458066893f29f2f77658436c84adf42613813ea042c992">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>late-shrimps-taste.md</strong><dd><code>Add changeset
for function name correction</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/late-shrimps-taste.md

<li>Added a changeset file for documenting the function name
correction.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3063/files#diff-9405aff0ebd3f8ea80c4142f72c9fdfede73f701869ccaf6d05e27c10f2f5eee">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>refresh-session.mdx</strong><dd><code>Update function
name in documentation examples</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/reference/javascript/auth/refresh-session.mdx

<li>Updated function name from <code>refreshToken</code> to
<code>refreshSession</code> in <br>documentation examples.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3063/files#diff-a738ad2b44e508034c9df030d5538c17596a205d75dae3f03cb76690ca8d0d50">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-09 20:30:46 +08:00
github-actions[bot]
3b37af06a0 chore: update versions (#3056)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@2.10.0

### Minor Changes

-   86ecf27: feat: add support for additional metrics in overview
- 21708be: feat: dashboard: add support for storage buckets to AI
assistants

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-05 16:18:38 +01:00
Hassan Ben Jobrane
86ecf27b23 feat(dashboard): add support for additional metrics in overview (#3052)
### **User description**
resolves https://github.com/nhost/nhost/issues/3017


___

### **PR Type**
Enhancement, Other


___

### **Description**
- Introduced new metrics in the dashboard overview, including monthly
and daily active users, total users, and storage.
- Implemented a new GraphQL query `GetUserProjectMetrics` to fetch
user-related metrics.
- Updated the `OverviewMetrics` component to display the newly added
metrics.
- Enhanced the GraphQL schema with new fields and queries to support the
additional metrics.
- Added a changeset to document the new feature in the dashboard.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>OverviewMetrics.tsx</strong><dd><code>Add support for
additional user and storage metrics in overview</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/overview/components/OverviewMetrics/OverviewMetrics.tsx

<li>Added new metrics for monthly and daily active users, total users,
and <br>storage.<br> <li> Integrated
<code>useGetUserProjectMetricsQuery</code> for fetching user-related
<br>metrics.<br> <li> Updated metrics card elements to display new
metrics.<br> <li> Removed redundant data checks and improved error
handling.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3052/files#diff-de881837e53f594075bb725282b02e92c2cb281f8f6a438fdbaa2e3254907fd1">+90/-17</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL types and
queries for user metrics</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Added new GraphQL query types for user project metrics.<br> <li>
Introduced <code>automaticDeploys</code> field in the <code>Apps</code>
type.<br> <li> Updated generated types to include new fields and
queries.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3052/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+87/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>getUserProjectMetrics.gql</strong><dd><code>Add GraphQL
query for user project metrics</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/gql/organizations/getUserProjectMetrics.gql

- Created new GraphQL query for fetching user project metrics.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3052/files#diff-902523302d8b32d218ef665a252dec5b9cbcf5fbab0cbb32845c441b01eaa28e">+28/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>khaki-pets-argue.md</strong><dd><code>Add changeset for
dashboard metrics feature</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/khaki-pets-argue.md

- Added changeset for new feature in dashboard.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3052/files#diff-51eb36ca77dae90a865c185e0d528fcaa4b836f3a0469550513f68003bf11c9a">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information

---------

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-12-05 16:14:15 +01:00
Hassan Ben Jobrane
1b5dc5e7f5 fix: update prettier version to fix changeset changelog (#3059)
### **PR Type**
enhancement, dependencies


___

### **Description**
- Updated the `prettier` dependency in `package.json` from version
`^2.8.8` to `^3.3.3` to address issues with changeset changelog
formatting.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update Prettier version
in package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

package.json

<li>Updated the <code>prettier</code> dependency version from
<code>^2.8.8</code> to <code>^3.3.3</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3059/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-05 15:44:24 +01:00
Nuno Pato
21708be3d2 feat: dashboard: add file stores to assistants (#2809)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-12-05 20:26:37 +08:00
52 changed files with 23564 additions and 628 deletions

View File

@@ -18,7 +18,6 @@ env:
TURBO_TEAM: nhost
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
NHOST_TEST_DASHBOARD_URL: ${{ vars.NHOST_TEST_DASHBOARD_URL }}
NHOST_TEST_WORKSPACE_NAME: ${{ vars.NHOST_TEST_WORKSPACE_NAME }}
NHOST_TEST_PROJECT_NAME: ${{ vars.NHOST_TEST_PROJECT_NAME }}

View File

@@ -8,7 +8,6 @@ env:
TURBO_TEAM: nhost
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
jobs:
build:

View File

@@ -12,7 +12,6 @@ jobs:
permissions:
issues: write
pull-requests: write
contents: write
name: Run pr agent on every pull request, respond to user comments
steps:
- name: PR Agent action step

View File

@@ -42,7 +42,6 @@ module.exports = {
env: (config) => ({
...config,
NEXT_PUBLIC_ENV: 'dev',
NEXT_PUBLIC_NHOST_BACKEND_URL: 'http://localhost:1337',
NEXT_PUBLIC_NHOST_PLATFORM: 'false',
}),
};

View File

@@ -1,5 +1,18 @@
# @nhost/dashboard
## 2.11.0
### Minor Changes
- cea3ef5: Feat: add org and project placeholders
## 2.10.0
### Minor Changes
- 86ecf27: feat: add support for additional metrics in overview
- 21708be: feat: dashboard: add support for storage buckets to AI assistants
## 1.30.0
### Minor Changes

View File

@@ -100,7 +100,6 @@ pnpm storybook --port 6007
| Name | Description |
| --------------------------------------- | ------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_NHOST_BACKEND_URL` | Backend URL. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |
| `NEXT_PUBLIC_STRIPE_PK` | Stripe public key. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |
| `NEXT_PUBLIC_GITHUB_APP_INSTALL_URL` | URL of the GitHub application. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |
| `NEXT_PUBLIC_ANALYTICS_WRITE_KEY` | Analytics key. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "2.9.0",
"version": "2.11.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -0,0 +1,137 @@
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { } from '@/utils/__generated__/graphql';
import { Divider } from '@mui/material';
import debounce from 'lodash.debounce';
import Image from 'next/image';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
import { Fragment, useEffect, useMemo, useState } from 'react';
export default function SelectOrganizationAndProject() {
const { orgs, loading } = useOrgs();
const router = useRouter();
const organizations = orgs.map((org) => ({
name: org.name,
value: `/orgs/${org.slug}`,
}));
const [filter, setFilter] = useState('');
const handleFilterChange = useMemo(
() =>
debounce((event: ChangeEvent<HTMLInputElement>) => {
setFilter(event.target.value);
}, 200),
[],
);
useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]);
const goToOrgPage = async (org: {
name: string;
value: string;
}) => {
const { slug } = router.query;
await router.push({
pathname: `${org.value}/${
(() => {
if (!slug) {
return '';
}
return Array.isArray(slug) ? slug.join('/') : slug;
})()
}`,
});
};
const orgsToDisplay = filter
? organizations.filter((org) =>
org.name.toLowerCase().includes(filter.toLowerCase()),
)
: organizations;
if (loading) {
return (
<div className="flex w-full justify-center">
<ActivityIndicator
delay={500}
label="Loading organizations..."
/>
</div>
);
}
return (
<div className="flex flex-col items-start w-full h-full px-5 py-4 mx-auto bg-background">
<div className="mx-auto flex h-full w-full max-w-[760px] flex-col gap-4 py-6 sm:py-14">
<Text variant="h2" component="h1" className="">
Select an Organization
</Text>
<div>
<div className="mb-2 flex w-full">
<Input
placeholder="Search..."
onChange={handleFilterChange}
fullWidth
autoFocus
/>
</div>
<RetryableErrorBoundary>
{orgsToDisplay.length === 0 ? (
<Box className="h-import py-2">
<Text variant="subtitle2">No results found.</Text>
</Box>
) : (
<List className="h-import overflow-y-auto">
{orgsToDisplay.map((org, index) => (
<Fragment key={org.value}>
<ListItem.Root
className="grid grid-flow-col justify-start gap-2 py-2.5"
secondaryAction={
<Button
variant="borderless"
color="primary"
onClick={() => goToOrgPage(org)}
>
Select
</Button>
}
>
<ListItem.Avatar>
<span className="inline-block h-6 w-6 overflow-hidden rounded-md">
<Image
src="/logos/new.svg"
alt="Nhost Logo"
width={24}
height={24}
/>
</span>
</ListItem.Avatar>
<ListItem.Text
primary={org.name}
secondary={`${org.name} / ${org.name}`}
/>
</ListItem.Root>
{index < orgs.length - 1 && <Divider component="li" />}
</Fragment>
))}
</List>
)}
</RetryableErrorBoundary>
</div>
</div>
</div>
);
}

View File

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

View File

@@ -0,0 +1,141 @@
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { } from '@/utils/__generated__/graphql';
import { Divider } from '@mui/material';
import debounce from 'lodash.debounce';
import Image from 'next/image';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
import { Fragment, useEffect, useMemo, useState } from 'react';
export default function SelectOrganizationAndProject() {
const { orgs, loading } = useOrgs();
const router = useRouter();
const projects = orgs.flatMap((org) =>
org.apps.map((app) => ({
organizationName: org.name,
projectName: app.name,
value: `/orgs/${org.slug}/projects/${app.subdomain}`,
})),
);
const [filter, setFilter] = useState('');
const handleFilterChange = useMemo(
() =>
debounce((event: ChangeEvent<HTMLInputElement>) => {
setFilter(event.target.value);
}, 200),
[],
);
useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]);
const goToProjectPage = async (project: {
organizationName: string;
projectName: string;
value: string;
}) => {
const { slug } = router.query;
await router.push({
pathname: `${project.value}/${
(() => {
if (!slug) {
return '';
}
return Array.isArray(slug) ? slug.join('/') : slug;
})()
}`,
});
};
const projectsToDisplay = filter
? projects.filter((project) =>
project.projectName.toLowerCase().includes(filter.toLowerCase()),
)
: projects;
if (loading) {
return (
<div className="flex w-full justify-center">
<ActivityIndicator
delay={500}
label="Loading organizations and projects..."
/>
</div>
);
}
return (
<div className="flex flex-col items-start w-full h-full px-5 py-4 mx-auto bg-background">
<div className="mx-auto flex h-full w-full max-w-[760px] flex-col gap-4 py-6 sm:py-14">
<Text variant="h2" component="h1" className="">
Select a Project
</Text>
<div>
<div className="mb-2 flex w-full">
<Input
placeholder="Search..."
onChange={handleFilterChange}
fullWidth
autoFocus
/>
</div>
<RetryableErrorBoundary>
{projectsToDisplay.length === 0 ? (
<Box className="h-import py-2">
<Text variant="subtitle2">No results found.</Text>
</Box>
) : (
<List className="h-import overflow-y-auto">
{projectsToDisplay.map((project, index) => (
<Fragment key={project.value}>
<ListItem.Root
className="grid grid-flow-col justify-start gap-2 py-2.5"
secondaryAction={
<Button
variant="borderless"
color="primary"
onClick={() => goToProjectPage(project)}
>
Select
</Button>
}
>
<ListItem.Avatar>
<span className="inline-block h-6 w-6 overflow-hidden rounded-md">
<Image
src="/logos/new.svg"
alt="Nhost Logo"
width={24}
height={24}
/>
</span>
</ListItem.Avatar>
<ListItem.Text
primary={project.projectName}
secondary={`${project.organizationName} / ${project.projectName}`}
/>
</ListItem.Root>
{index < projects.length - 1 && <Divider component="li" />}
</Fragment>
))}
</List>
)}
</RetryableErrorBoundary>
</div>
</div>
</div>
);
}

View File

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

View File

@@ -20,8 +20,7 @@ interface AINavLinkProps extends ListItemButtonProps {
*/
href: string;
/**
* Determines whether or not the link should be active if it's href exactly
* matches the current route.
* Determines whether or not the link should be active if href matches the current route.
*
* @default true
*/
@@ -87,7 +86,7 @@ export default function AISidebar({ className, ...props }: AISidebarProps) {
<>
<Backdrop
open={expanded}
className="absolute top-0 left-0 bottom-0 right-0 z-[34] md:hidden"
className="absolute bottom-0 left-0 right-0 top-0 z-[34] md:hidden"
role="button"
tabIndex={-1}
onClick={() => setExpanded(false)}
@@ -104,7 +103,7 @@ export default function AISidebar({ className, ...props }: AISidebarProps) {
<Box
component="aside"
className={twMerge(
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pt-2 pb-17 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pb-17 pt-2 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
className,
)}
@@ -119,6 +118,7 @@ export default function AISidebar({ className, ...props }: AISidebarProps) {
>
Auto-Embeddings
</AINavLink>
<AINavLink href="/assistants" exact={false} onClick={handleSelect}>
Assistants
</AINavLink>

View File

@@ -0,0 +1,26 @@
import type { IconProps } from '@/components/ui/v2/icons';
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
function FileStoresIcon(props: IconProps) {
return (
<SvgIcon
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
aria-label="FileStores Icon"
{...props}
>
<path d="M12 22v-9" />
<path d="M15.17 2.21a1.67 1.67 0 0 1 1.63 0L21 4.57a1.93 1.93 0 0 1 0 3.36L8.82 14.79a1.655 1.655 0 0 1-1.64 0L3 12.43a1.93 1.93 0 0 1 0-3.36z" />
<path d="M20 13v3.87a2.06 2.06 0 0 1-1.11 1.83l-6 3.08a1.93 1.93 0 0 1-1.78 0l-6-3.08A2.06 2.06 0 0 1 4 16.87V13" />
<path d="M21 12.43a1.93 1.93 0 0 0 0-3.36L8.83 2.2a1.64 1.64 0 0 0-1.63 0L3 4.57a1.93 1.93 0 0 0 0 3.36l12.18 6.86a1.636 1.636 0 0 0 1.63 0z" />
</SvgIcon>
);
}
FileStoresIcon.displayName = 'FileStoresIcon';
export default FileStoresIcon;

View File

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

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Form } from '@/components/form/Form';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
@@ -10,14 +11,14 @@ import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { GraphqlDataSourcesFormSection } from '@/features/ai/AssistantForm/components/GraphqlDataSourcesFormSection';
import { WebhooksDataSourcesFormSection } from '@/features/ai/AssistantForm/components/WebhooksDataSourcesFormSection';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
import { useAdminApolloClient } from '@/features/orgs/projects/hooks/useAdminApolloClient'
import type { DialogFormProps } from '@/types/common';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { removeTypename, type DeepRequired } from '@/utils/helpers';
import {
useInsertAssistantMutation,
useUpdateAssistantMutation,
} from '@/utils/__generated__/graphite.graphql';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { removeTypename, type DeepRequired } from '@/utils/helpers';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@@ -28,6 +29,7 @@ export const validationSchema = Yup.object({
description: Yup.string(),
instructions: Yup.string().required('The instructions are required'),
model: Yup.string().required('The model is required'),
fileStore: Yup.string().label('File Store'),
graphql: Yup.array().of(
Yup.object().shape({
name: Yup.string().required(),
@@ -64,14 +66,14 @@ export type AssistantFormValues = Yup.InferType<typeof validationSchema>;
export interface AssistantFormProps extends DialogFormProps {
/**
* To use in conjunction with initialData to allow for updating the autoEmbeddingsConfiguration
* To use in conjunction with initialData to allow for updating the Assistant Configuration
*/
assistantId?: string;
/**
* if there is initialData then it's an update operation
*/
initialData?: AssistantFormValues;
initialData?: AssistantFormValues
/**
* Function to be called when the operation is cancelled.
@@ -114,26 +116,26 @@ export default function AssistantForm({
} = form;
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
const createOrUpdateAutoEmbeddings = async (
values: DeepRequired<AssistantFormValues> & { assistantID: string },
const createOrUpdateAssistant = async (
values: DeepRequired<AssistantFormValues> & {
assistantID: string;
},
) => {
// remove any __typename from the form values
const payload = removeTypename(values);
if (values.webhooks.length === 0) {
if (values.webhooks?.length === 0) {
delete payload.webhooks;
}
if (values.graphql.length === 0) {
if (values.graphql?.length === 0) {
delete payload.graphql;
}
// remove assistantId because the update mutation fails otherwise
delete payload.assistantID;
// If the assistantId is set then we do an update
@@ -158,11 +160,13 @@ export default function AssistantForm({
};
const handleSubmit = async (
values: DeepRequired<AssistantFormValues> & { assistantID: string },
values: DeepRequired<AssistantFormValues> & {
assistantID: string;
},
) => {
await execPromiseWithErrorToast(
async () => {
await createOrUpdateAutoEmbeddings(values);
await createOrUpdateAssistant(values);
onSubmit?.();
},
{
@@ -282,6 +286,7 @@ export default function AssistantForm({
autoComplete="off"
autoFocus
/>
<GraphqlDataSourcesFormSection />
<WebhooksDataSourcesFormSection />
</div>

View File

@@ -15,12 +15,12 @@ import { type Assistant } from 'pages/[workspaceSlug]/[appSlug]/ai/assistants';
interface AssistantsListProps {
/**
* The run services fetched from entering the users page.
* The list of assistants.
*/
assistants: Assistant[];
/**
* Function to be called after a submitting the form for either creating or updating a service.
* Function to be called after a submitting the form for either creating or updating an assistant.
*
* @example onDelete={() => refetch()}
*/

View File

@@ -128,6 +128,9 @@ export default function AISidebar({ className, ...props }: AISidebarProps) {
<AINavLink href="/assistants" exact={false} onClick={handleSelect}>
Assistants
</AINavLink>
<AINavLink href="/file-stores" exact={false} onClick={handleSelect}>
File Stores
</AINavLink>
</List>
</nav>
</Box>

View File

@@ -14,21 +14,27 @@ import { WebhooksDataSourcesFormSection } from '@/features/orgs/projects/ai/Assi
import { useAdminApolloClient } from '@/features/orgs/projects/hooks/useAdminApolloClient';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import type { DialogFormProps } from '@/types/common';
import { removeTypename, type DeepRequired } from '@/utils/helpers';
import {
useInsertAssistantMutation,
useUpdateAssistantMutation,
} from '@/utils/__generated__/graphite.graphql';
import { removeTypename, type DeepRequired } from '@/utils/helpers';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Option } from '@/components/ui/v2/Option';
import { useIsFileStoreSupported } from '@/features/orgs/projects/common/hooks/useIsFileStoreSupported';
import { type GraphiteFileStore } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/ai/file-stores';
export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'),
description: Yup.string(),
instructions: Yup.string().required('The instructions are required'),
model: Yup.string().required('The model is required'),
fileStore: Yup.string().label('File Store'),
graphql: Yup.array().of(
Yup.object().shape({
name: Yup.string().required(),
@@ -65,14 +71,17 @@ export type AssistantFormValues = Yup.InferType<typeof validationSchema>;
export interface AssistantFormProps extends DialogFormProps {
/**
* To use in conjunction with initialData to allow for updating the autoEmbeddingsConfiguration
* To use in conjunction with initialData to allow for updating the Assistant Configuration
*/
assistantId?: string;
/**
* if there is initialData then it's an update operation
*/
initialData?: AssistantFormValues;
initialData?: AssistantFormValues & {
fileStores?: string[];
};
fileStores?: GraphiteFileStore[];
/**
* Function to be called when the operation is cancelled.
@@ -87,6 +96,7 @@ export interface AssistantFormProps extends DialogFormProps {
export default function AssistantForm({
assistantId,
initialData,
fileStores,
onSubmit,
onCancel,
location,
@@ -103,8 +113,27 @@ export default function AssistantForm({
client: adminClient,
});
const isFileStoreSupported = useIsFileStoreSupported();
const fileStoresOptions = fileStores
? fileStores.map((fileStore: GraphiteFileStore) => ({
label: fileStore.name,
value: fileStore.name,
id: fileStore.id,
}))
: [];
const assistantFileStore = initialData?.fileStores
? fileStores?.find((fileStore: GraphiteFileStore) =>
fileStore.id === initialData?.fileStores[0]
)
: null;
const formDefaultValues = { ...initialData, fileStores: [] };
formDefaultValues.fileStore = assistantFileStore ? assistantFileStore.id : '';
const form = useForm<AssistantFormValues>({
defaultValues: initialData,
defaultValues: formDefaultValues,
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
@@ -120,22 +149,32 @@ export default function AssistantForm({
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
const createOrUpdateAutoEmbeddings = async (
values: DeepRequired<AssistantFormValues> & { assistantID: string },
const createOrUpdateAssistant = async (
values: DeepRequired<AssistantFormValues> & {
assistantID: string;
},
) => {
// remove any __typename from the form values
const payload = removeTypename(values);
if (values.webhooks.length === 0) {
if (values.webhooks?.length === 0) {
delete payload.webhooks;
}
if (values.graphql.length === 0) {
if (values.graphql?.length === 0) {
delete payload.graphql;
}
if (isFileStoreSupported && values.fileStore) {
payload.fileStores = [values.fileStore];
}
if (!isFileStoreSupported) {
delete payload.fileStores;
}
// remove assistantId because the update mutation fails otherwise
delete payload.assistantID;
delete payload.fileStore;
// If the assistantId is set then we do an update
if (assistantId) {
@@ -152,7 +191,7 @@ export default function AssistantForm({
await insertAssistantMutation({
variables: {
data: {
...values,
...payload,
},
},
});
@@ -163,7 +202,7 @@ export default function AssistantForm({
) => {
await execPromiseWithErrorToast(
async () => {
await createOrUpdateAutoEmbeddings(values);
await createOrUpdateAssistant(values);
onSubmit?.();
},
{
@@ -175,6 +214,10 @@ export default function AssistantForm({
);
};
const fileStoreTooltip = isFileStoreSupported
? "If specified, all text documents in this file store will be available to the assistant."
: "Please upgrade Graphite to its latest version in order to use file stores.";
return (
<FormProvider {...form}>
<Form
@@ -285,6 +328,36 @@ export default function AssistantForm({
/>
<GraphqlDataSourcesFormSection />
<WebhooksDataSourcesFormSection />
<ControlledSelect
slotProps={{
popper: { disablePortal: false, className: 'z-[10000]' },
}}
id="fileStore"
name="fileStore"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>File Store</Text>
<Tooltip title={fileStoreTooltip}>
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
</Box>
}
fullWidth
error={!!errors?.model?.message}
helperText={errors?.model?.message}
disabled={!isFileStoreSupported}
>
<Option value="" />
{fileStoresOptions.map((fileStore) => (
<Option key={fileStore.id} value={fileStore.id}>
{fileStore.label}
</Option>
))}
</ControlledSelect>
</div>
<Box className="flex flex-row justify-between w-full p-4 border-t rounded">

View File

@@ -11,16 +11,22 @@ import { Text } from '@/components/ui/v2/Text';
import { AssistantForm } from '@/features/orgs/projects/ai/AssistantForm';
import { DeleteAssistantModal } from '@/features/orgs/projects/ai/DeleteAssistantModal';
import { type Assistant } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/ai/assistants';
import { type GraphiteFileStore } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/ai/file-stores';
import { copy } from '@/utils/copy';
interface AssistantsListProps {
/**
* The run services fetched from entering the users page.
* The list of assistants
*/
assistants: Assistant[];
/**
* Function to be called after a submitting the form for either creating or updating a service.
* The list of file stores
*/
fileStores: GraphiteFileStore[];
/**
* Function to be called after a submitting the form for either creating or updating an assistant.
*
* @example onDelete={() => refetch()}
*/
@@ -35,6 +41,7 @@ interface AssistantsListProps {
export default function AssistantsList({
assistants,
fileStores,
onCreateOrUpdate,
onDelete,
}: AssistantsListProps) {
@@ -49,6 +56,7 @@ export default function AssistantsList({
initialData={{
...assistant,
}}
fileStores={fileStores}
onSubmit={() => onCreateOrUpdate()}
/>
),

View File

@@ -0,0 +1,100 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox';
import { Text } from '@/components/ui/v2/Text';
import { useAdminApolloClient } from '@/features/orgs/projects/hooks/useAdminApolloClient';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { type GraphiteFileStore } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/ai/file-stores';
import { useDeleteFileStoreMutation } from '@/utils/__generated__/graphite.graphql';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';
export interface DeleteFileStoreModalProps {
fileStore: GraphiteFileStore;
onDelete?: () => Promise<any>;
close: () => void;
}
export default function DeleteFileStoreModal({
fileStore,
onDelete,
close,
}: DeleteFileStoreModalProps) {
const [remove, setRemove] = useState(false);
const [loading, setLoading] = useState(false);
const { adminClient } = useAdminApolloClient();
const [deleteFileStoreMutation] = useDeleteFileStoreMutation({
client: adminClient,
});
const deleteFileStore = async () => {
await deleteFileStoreMutation({
variables: {
id: fileStore.id,
},
});
await onDelete?.();
close();
};
async function handleClick() {
setLoading(true);
await execPromiseWithErrorToast(deleteFileStore, {
loadingMessage: 'Deleting the file store...',
successMessage: 'The file store has been deleted successfully.',
errorMessage:
'An error occurred while deleting the file store. Please try again.',
});
}
return (
<Box className={twMerge('w-full rounded-lg p-6 text-left')}>
{' '}
<div className="grid grid-flow-row gap-1">
{' '}
<Text variant="h3" component="h2">
{' '}
Delete File Store {fileStore?.name}{' '}
</Text>{' '}
<Text variant="subtitle2">
{' '}
Are you sure you want to delete this File Store?{' '}
</Text>{' '}
<Text
variant="subtitle2"
className="font-bold"
sx={{ color: (theme) => `${theme.palette.error.main} !important` }}
>
This cannot be undone.
</Text>
<Box className="my-4">
<Checkbox
id="accept-1"
label={`I'm sure I want to delete ${fileStore?.name}`}
className="py-2"
checked={remove}
onChange={(_event, checked) => setRemove(checked)}
aria-label="Confirm Delete File Store"
/>
</Box>
<div className="grid grid-flow-row gap-2">
<Button
color="error"
onClick={handleClick}
disabled={!remove}
loading={loading}
>
Delete File Store
</Button>
<Button variant="outlined" color="secondary" onClick={close}>
Cancel
</Button>
</div>
</div>
</Box>
);
}

View File

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

View File

@@ -0,0 +1,217 @@
import { useDialog } from '@/components/common/DialogProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { ArrowsClockwise } from '@/components/ui/v2/icons/ArrowsClockwise';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useAdminApolloClient } from '@/features/orgs/projects/hooks/useAdminApolloClient'
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
import type { DialogFormProps } from '@/types/common';
import {
useInsertFileStoreMutation,
useUpdateFileStoreMutation,
} from '@/utils/__generated__/graphite.graphql';
import { useGetBucketsQuery } from '@/utils/__generated__/graphql';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { removeTypename, type DeepRequired } from '@/utils/helpers';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
export const validationSchema = Yup.object({
name: Yup.string().required('The name is required'),
buckets: Yup.array()
.of(
Yup.object({
label: Yup.string(),
value: Yup.string(),
}),
)
.label('Buckets')
.required('At least one bucket is required'),
});
export type FileStoreFormValues = Yup.InferType<typeof validationSchema>;
export interface FileStoreFormProps extends DialogFormProps {
id?: string;
initialData?: Omit<FileStoreFormValues, 'buckets'> & { buckets: string[] };
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
onCancel?: VoidFunction;
}
export default function FileStoreForm({
id,
initialData,
onSubmit,
onCancel,
location,
}: FileStoreFormProps) {
const { onDirtyStateChange } = useDialog();
const { adminClient } = useAdminApolloClient();
const [insertFileStore] = useInsertFileStoreMutation({
client: adminClient,
});
const [updateFileStore] = useUpdateFileStoreMutation({
client: adminClient,
});
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
const { data: buckets } = useGetBucketsQuery({
client: remoteProjectGQLClient,
});
const bucketOptions = buckets
? buckets.buckets.map((bucket) => ({
label: bucket.id,
value: bucket.id,
}))
: [];
const formDefaultValues = { ...initialData, buckets: [] };
formDefaultValues.buckets = initialData?.buckets
? initialData.buckets.map((bucket) => ({
label: bucket,
value: bucket,
}))
: [];
const form = useForm<FileStoreFormValues>({
defaultValues: formDefaultValues,
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
const {
register,
formState: { errors, isSubmitting, dirtyFields },
} = form;
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
const createOrUpdateFileStore = async (
values: DeepRequired<FileStoreFormValues> & { id: string },
) => {
const payload = removeTypename(values);
delete payload.id;
delete payload.vectorStoreID;
if (id) {
await updateFileStore({
variables: {
id,
object: { ...payload, buckets: values.buckets.map((b) => b.value) },
},
});
return;
}
await insertFileStore({
variables: {
object: { ...values, buckets: values.buckets.map((b) => b.value) },
},
});
};
const handleSubmit = async (
values: DeepRequired<FileStoreFormValues> & { id: string },
) => {
await execPromiseWithErrorToast(
async () => {
await createOrUpdateFileStore(values);
onSubmit?.();
},
{
loadingMessage: 'Creating File Store...',
successMessage: 'The File Store has been created successfully.',
errorMessage:
'An error occurred while creating the File Store. Please try again.',
},
);
};
return (
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden border-t"
>
<div className="flex flex-1 flex-col space-y-4 overflow-auto p-4">
<Input
{...register('name')}
id="name"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Name</Text>
<Tooltip title="Name of the file store">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
</Box>
}
placeholder=""
hideEmptyHelperText
error={!!errors.name}
helperText={errors?.name?.message}
fullWidth
autoComplete="off"
autoFocus
/>
<ControlledAutocomplete
id="buckets"
name="buckets"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Buckets</Text>
<Tooltip title="One or more buckets from storage from which documents can be used by Assistants">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
</Box>
}
fullWidth
multiple
aria-label="Buckets"
error={!!errors.buckets}
options={bucketOptions}
helperText={errors?.buckets?.message}
/>
</div>
<Box className="flex w-full flex-row justify-between rounded border-t p-4">
<Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
<Button
type="submit"
disabled={isSubmitting}
startIcon={id ? <ArrowsClockwise /> : <PlusIcon />}
>
{id ? 'Update' : 'Create'}
</Button>
</Box>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,157 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Box } from '@/components/ui/v2/Box';
import { Divider } from '@/components/ui/v2/Divider';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import { IconButton } from '@/components/ui/v2/IconButton';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { DotsHorizontalIcon } from '@/components/ui/v2/icons/DotsHorizontalIcon';
import { FileStoresIcon } from '@/components/ui/v2/icons/FileStoresIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
import { Text } from '@/components/ui/v2/Text';
import { DeleteFileStoreModal } from '@/features/orgs/projects/ai/DeleteFileStoreModal';
import { FileStoreForm } from '@/features/orgs/projects/ai/FileStoreForm';
import { type GraphiteFileStore } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/ai/file-stores';
import { copy } from '@/utils/copy';
interface FileStoresListProps {
/**
* List of File Stores to be displayed.
*/
fileStores: GraphiteFileStore[];
/**
* Function to be called after a submitting the form for either creating or updating a File Store.
*
* @example onDelete={() => refetch()}
*/
onCreateOrUpdate?: () => Promise<any>;
/**
* Function to be called after a successful delete action.
*
*/
onDelete?: () => Promise<any>;
}
export default function FileStoresList({
fileStores,
onCreateOrUpdate,
onDelete,
}: FileStoresListProps) {
const { openDrawer, openDialog, closeDialog } = useDialog();
const viewFileStore = async (fileStore: GraphiteFileStore) => {
openDrawer({
title: fileStore.name,
component: (
<FileStoreForm
id={fileStore.id}
initialData={{ ...fileStore }}
onSubmit={() => onCreateOrUpdate()}
/>
),
});
};
const deleteFileStore = async (fileStore: GraphiteFileStore) => {
openDialog({
component: (
<DeleteFileStoreModal
fileStore={fileStore}
close={closeDialog}
onDelete={onDelete}
/>
),
});
};
return (
<Box className="flex flex-col">
{fileStores.map((fileStore) => (
<Box
key={fileStore.id}
className="flex h-[64px] w-full cursor-pointer items-center justify-between space-x-4 border-b-1 px-4 py-2 transition-colors"
sx={{
[`&:hover`]: {
backgroundColor: 'action.hover',
},
}}
>
<Box
onClick={() => viewFileStore(fileStore)}
className="flex w-full flex-row justify-between"
sx={{ backgroundColor: 'transparent' }}
>
<div className="flex flex-1 flex-row items-center space-x-4">
<FileStoresIcon className="h-5 w-5" />
<div className="flex flex-col">
<Text variant="h4" className="font-semibold">
{fileStore?.name ?? 'unset'}
</Text>
<div className="hidden flex-row items-center space-x-2 md:flex">
<Text variant="subtitle1" className="font-mono text-xs">
{fileStore.id}
</Text>
<IconButton
variant="borderless"
color="secondary"
onClick={(event) => {
copy(fileStore.id, 'File Store Id');
event.stopPropagation();
}}
aria-label="Service Id"
>
<CopyIcon className="h-4 w-4" />
</IconButton>
</div>
</div>
</div>
</Box>
<Dropdown.Root>
<Dropdown.Trigger
asChild
hideChevron
onClick={(event) => event.stopPropagation()}
>
<IconButton
variant="borderless"
color="secondary"
aria-label="More options"
onClick={(event) => event.stopPropagation()}
>
<DotsHorizontalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content
menu
PaperProps={{ className: 'w-auto' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<Dropdown.Item
onClick={() => viewFileStore(fileStore)}
className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
>
<UserIcon className="h-4 w-4" />
<Text className="font-medium">View {fileStore?.name}</Text>
</Dropdown.Item>
<Divider component="li" />
<Dropdown.Item
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
sx={{ color: 'error.main' }}
onClick={() => deleteFileStore(fileStore)}
>
<TrashIcon className="h-4 w-4" />
<Text className="font-medium" color="error">
Delete {fileStore?.name}
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
</Box>
))}
</Box>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useGetConfiguredVersionsQuery } from '@/utils/__generated__/graphql';
import { useEffect, useState } from 'react';
function compareSemver(v1: string, v2: string): number {
const parse = (v: string) => v.split('.').map(Number);
const [a, b] = [parse(v1), parse(v2)];
for (let i = 0; i < 3; i += 1) {
if (a[i] > b[i]) { return 1; }
if (a[i] < b[i]) { return -1; }
}
return 0;
}
const MIN_VERSION_WITH_FILE_STORE_SUPPORT = '0.6.2';
export default function useIsFileStoreSupported() {
const [isFileStoreSupported, setIsFileStoreSupported] = useState<boolean | null>(null);
const { project } = useProject();
const localMimirClient = useLocalMimirClient();
const isPlatform = useIsPlatform();
const { data, loading, error } = useGetConfiguredVersionsQuery({
variables: { appId: project?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
useEffect(() => {
if (!loading && data?.config?.ai?.version) {
setIsFileStoreSupported(compareSemver(data.config.ai.version, MIN_VERSION_WITH_FILE_STORE_SUPPORT) >= 0);
}
}, [data, loading]);
return {
isFileStoreSupported,
version: data?.config?.ai?.version,
loading,
error,
};
}

View File

@@ -1,54 +1,132 @@
import { Text } from '@/components/ui/v2/Text';
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import type { MetricsCardProps } from '@/features/orgs/projects/overview/components/MetricsCard';
import { MetricsCard } from '@/features/orgs/projects/overview/components/MetricsCard';
import { prettifyNumber } from '@/utils/prettifyNumber';
import { prettifySize } from '@/utils/prettifySize';
import { useGetProjectMetricsQuery } from '@/utils/__generated__/graphql';
import {
useGetProjectMetricsQuery,
useGetProjectRequestsMetricQuery,
useGetUserProjectMetricsQuery,
} from '@/utils/__generated__/graphql';
import { twMerge } from 'tailwind-merge';
import { prettifySize } from '@/utils/prettifySize';
import { formatISO, startOfDay, startOfMonth, subMinutes } from 'date-fns';
const now = new Date();
export default function OverviewMetrics() {
const { project } = useProject();
const { data, loading, error } = useGetProjectMetricsQuery({
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
const {
data: {
allUsers: { aggregate: { count: allUsers = 0 } = {} } = {},
dailyActiveUsers: {
aggregate: { count: dailyActiveUsers = 0 } = {},
} = {},
monthlyActiveUsers: {
aggregate: { count: monthlyActiveUsers = 0 } = {},
} = {},
filesAggregate: {
aggregate: { sum: { size: totalStorage = 0 } = {} } = {},
} = {},
} = {},
} = useGetUserProjectMetricsQuery({
client: remoteProjectGQLClient,
variables: {
appId: project?.id,
startOfMonth: startOfMonth(new Date()),
today: startOfDay(new Date()),
},
skip: !project,
});
const {
data: {
totalRequests: { value: totalRequestsInLastFiveMinutes = 0 } = {},
} = {},
} = useGetProjectRequestsMetricQuery({
variables: {
appId: project.id,
from: formatISO(subMinutes(new Date(), 6)), // 6 mns earlier
to: formatISO(subMinutes(new Date(), 1)), // 1 mn earlier
},
skip: !project,
pollInterval: 1000 * 60 * 5, // Poll every 5 minutes
});
const {
data: {
functionsDuration: { value: functionsDuration = 0 } = {},
totalRequests: { value: totalRequests = 0 } = {},
postgresVolumeUsage: { value: postgresVolumeUsage = 0 } = {},
egressVolume: { value: egressVolume = 0 } = {},
} = {},
loading,
error,
} = useGetProjectMetricsQuery({
variables: {
appId: project.id,
subdomain: project?.subdomain,
from: new Date(now.getFullYear(), now.getMonth(), 1),
},
skip: !project?.id,
skip: !project,
});
const cardElements: MetricsCardProps[] = [
{
label: 'CPU Usage Seconds',
tooltip: 'Total time the service has used the CPUs',
value: prettifyNumber(data?.cpuSecondsUsage?.value || 0),
label: 'Daily Active Users',
tooltip: 'Unique users active today',
value: prettifyNumber(dailyActiveUsers),
},
{
label: 'Monthly Active Users',
tooltip: 'Unique users active this month',
value: prettifyNumber(monthlyActiveUsers),
},
{
label: 'All Users',
tooltip: 'Total registered users',
value: prettifyNumber(allUsers),
},
{
label: 'RPS',
tooltip: 'Requests Per Second (RPS) measured in the last 5 minutes',
value: prettifyNumber(totalRequestsInLastFiveMinutes / 300, {
numberOfDecimals: 2,
}),
},
{
label: 'Total Requests',
tooltip:
'Total amount of requests your services have received excluding functions',
value: prettifyNumber(data?.totalRequests?.value || 0, {
numberOfDecimals: data?.totalRequests?.value > 1000 ? 2 : 0,
tooltip: 'Total service requests this month so far (excluding functions)',
value: prettifyNumber(totalRequests || 0, {
numberOfDecimals: totalRequests > 1000 ? 2 : 0,
}),
},
{
label: 'Function Invocations',
tooltip: 'Number of times your functions have been called',
value: prettifyNumber(data?.functionInvocations?.value || 0, {
numberOfDecimals: 0,
}),
label: 'Egress',
tooltip: 'Total outgoing data transfer this month so far',
value: prettifySize(egressVolume),
},
{
label: 'Logs',
tooltip: 'Amount of logs stored',
value: prettifySize(data?.logsVolume?.value || 0),
label: 'Functions Duration',
tooltip: 'Total Functions execution this month so far',
value: prettifyNumber(functionsDuration),
},
{
label: 'Storage',
tooltip: 'Total size of stored files in the storage service',
value: prettifySize(totalStorage || 0),
},
{
label: 'Postgres Volume Usage',
tooltip: 'Used storage in the Postgres database',
value: prettifySize(postgresVolumeUsage),
},
];
if (!data && error) {
if (error) {
throw error;
}

View File

@@ -29,6 +29,8 @@ export default function useNotFoundRedirect() {
router.pathname === '/account' ||
router.pathname === '/support/ticket' ||
router.pathname === '/run-one-click-install' ||
router.pathname.includes('/orgs/_') ||
router.pathname.includes('/orgs/_/projects/_') ||
orgSlug ||
(orgSlug && appSubdomain) ||
// If we are on a valid workspace and project, we don't want to redirect to 404

View File

@@ -0,0 +1,9 @@
query GetProjectRequestsMetric(
$appId: String!
$from: Timestamp
$to: Timestamp
) {
totalRequests: getTotalRequests(appID: $appId, from: $from, to: $to) {
value
}
}

View File

@@ -1,4 +1,4 @@
query getAssistants {
query getAssistants($isFileStoresSupported: Boolean!) {
graphite {
assistants {
assistantID
@@ -28,6 +28,7 @@ query getAssistants {
required
}
}
fileStores @include(if: $isFileStoresSupported)
}
}
}

View File

@@ -0,0 +1,5 @@
mutation deleteFileStore($id: uuid!) {
graphite {
deleteFileStore(id: $id)
}
}

View File

@@ -0,0 +1,10 @@
query getGraphiteFileStores {
graphite {
fileStores {
id
name
vectorStoreID
buckets
}
}
}

View File

@@ -0,0 +1,7 @@
mutation insertFileStore($object: graphiteFileStoreInput!) {
graphite {
insertFileStore(object: $object) {
id
}
}
}

View File

@@ -0,0 +1,7 @@
mutation updateFileStore($id: uuid!, $object: graphiteFileStoreInput!) {
graphite {
updateFileStore(id: $id, object: $object) {
name
}
}
}

View File

@@ -0,0 +1,28 @@
query GetUserProjectMetrics($startOfMonth: timestamptz!, $today: timestamptz!) {
monthlyActiveUsers: usersAggregate(
where: { lastSeen: { _gte: $startOfMonth, _lte: $today } }
) {
aggregate {
count
}
}
dailyActiveUsers: usersAggregate(where: { lastSeen: { _gte: $today } }) {
aggregate {
count
}
}
allUsers: usersAggregate {
aggregate {
count
}
}
filesAggregate {
aggregate {
count
sum {
size
}
}
}
}

View File

@@ -17,7 +17,7 @@ import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGrap
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
useGetAssistantsQuery,
type GetAssistantsQuery,
type GetAssistantsQuery
} from '@/utils/__generated__/graphite.graphql';
import { useMemo, type ReactElement } from 'react';
@@ -29,21 +29,29 @@ export type Assistant = Omit<
export default function AssistantsPage() {
const { openDrawer } = useDialog();
const isPlatform = useIsPlatform();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const { adminClient } = useAdminApolloClient();
const { isGraphiteEnabled } = useIsGraphiteEnabled();
const { data, loading, refetch } = useGetAssistantsQuery({
const {
data,
loading,
refetch,
} = useGetAssistantsQuery({
client: adminClient,
});
const assistants = useMemo(() => data?.graphite?.assistants || [], [data]);
const assistants = useMemo(
() => data?.graphite?.assistants || [],
[data],
);
const openCreateAssistantForm = () => {
openDrawer({
title: 'Create a new Assistant',
component: <AssistantForm onSubmit={refetch} />,
component: (
<AssistantForm onSubmit={refetch} />
),
});
};
@@ -97,7 +105,11 @@ export default function AssistantsPage() {
);
}
if (data?.graphite?.assistants.length === 0 && !loading) {
if (loading) {
return <Box className="p-4">Loading...</Box>;
}
if (assistants.length === 0) {
return (
<Box
className="w-full p-6"
@@ -141,13 +153,11 @@ export default function AssistantsPage() {
New
</Button>
</Box>
<div>
<AssistantsList
assistants={assistants}
onDelete={() => refetch()}
onCreateOrUpdate={() => refetch()}
/>
</div>
<AssistantsList
assistants={assistants}
onDelete={() => refetch()}
onCreateOrUpdate={() => refetch()}
/>
</Box>
);
}

View File

@@ -12,6 +12,7 @@ import { Text } from '@/components/ui/v2/Text';
import {
useGetAssistantsQuery,
useGetGraphiteFileStoresQuery,
type GetAssistantsQuery,
} from '@/utils/__generated__/graphite.graphql';
import { useMemo, type ReactElement } from 'react';
@@ -21,6 +22,7 @@ import { AISidebar } from '@/features/orgs/layout/AISidebar';
import { ProjectLayout } from '@/features/orgs/layout/ProjectLayout';
import { AssistantForm } from '@/features/orgs/projects/ai/AssistantForm';
import { AssistantsList } from '@/features/orgs/projects/ai/AssistantsList';
import { useIsFileStoreSupported } from '@/features/orgs/projects/common/hooks/useIsFileStoreSupported';
import { useIsGraphiteEnabled } from '@/features/orgs/projects/common/hooks/useIsGraphiteEnabled';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useAdminApolloClient } from '@/features/orgs/projects/hooks/useAdminApolloClient';
@@ -43,24 +45,46 @@ export default function AssistantsPage() {
const { isGraphiteEnabled, loading: loadingGraphite } =
useIsGraphiteEnabled();
const {
data,
loading: loadingAssistants,
refetch,
} = useGetAssistantsQuery({
client: adminClient,
});
const { isFileStoreSupported, loading: fileStoreLoading } =
useIsFileStoreSupported();
const assistants = useMemo(() => data?.graphite?.assistants || [], [data]);
const {
data: assistantsData,
loading: assistantsLoading,
refetch: assistantsRefetch,
} = useGetAssistantsQuery({
client: adminClient,
variables: {
isFileStoresSupported: isFileStoreSupported ?? false,
},
skip: isFileStoreSupported === null || fileStoreLoading,
});
const { data: fileStoresData } = useGetGraphiteFileStoresQuery({
client: adminClient,
});
const assistants = useMemo(
() => assistantsData?.graphite?.assistants || [],
[assistantsData],
);
const fileStores = useMemo(
() => fileStoresData?.graphite?.fileStores || [],
[fileStoresData],
);
const openCreateAssistantForm = () => {
openDrawer({
title: 'Create a new Assistant',
component: <AssistantForm onSubmit={refetch} />,
component: (
<AssistantForm
onSubmit={assistantsRefetch}
fileStores={isFileStoreSupported ? fileStores : undefined}
/>
),
});
};
if (loadingOrg || loadingProject || loadingGraphite || loadingAssistants) {
if (loadingOrg || loadingProject || loadingGraphite || assistantsLoading) {
return (
<Box className="flex items-center justify-center w-full h-full">
<ActivityIndicator
@@ -114,7 +138,7 @@ export default function AssistantsPage() {
);
}
if (data?.graphite?.assistants.length === 0 && !loadingAssistants) {
if (assistants.length === 0 && !assistantsLoading) {
return (
<Box
className="w-full p-6"
@@ -161,8 +185,9 @@ export default function AssistantsPage() {
<div>
<AssistantsList
assistants={assistants}
onDelete={() => refetch()}
onCreateOrUpdate={() => refetch()}
fileStores={isFileStoreSupported ? fileStores : undefined}
onDelete={() => assistantsRefetch()}
onCreateOrUpdate={() => assistantsRefetch()}
/>
</div>
</Box>

View File

@@ -12,14 +12,13 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { AISidebar } from '@/features/orgs/layout/AISidebar';
// import AILayout from '@/features/orgs/layout/AILayout/AILayout';
import { ProjectLayout } from '@/features/orgs/layout/ProjectLayout';
import { AutoEmbeddingsForm } from '@/features/orgs/projects/ai/AutoEmbeddingsForm';
import { AutoEmbeddingsList } from '@/features/orgs/projects/ai/AutoEmbeddingsList';
import { useIsGraphiteEnabled } from '@/features/orgs/projects/common/hooks/useIsGraphiteEnabled';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useAdminApolloClient } from '@/features/orgs/projects/hooks/useAdminApolloClient';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import {
useGetGraphiteAutoEmbeddingsConfigurationsQuery,
@@ -36,9 +35,11 @@ export type AutoEmbeddingsConfiguration = Omit<
export default function AutoEmbeddingsPage() {
const limit = useRef(25);
const router = useRouter();
const { openDrawer } = useDialog();
const isPlatform = useIsPlatform();
const { currentOrg: org } = useOrgs();
const { org } = useCurrentOrg();
const { project } = useProject();
const { adminClient } = useAdminApolloClient();
@@ -128,7 +129,7 @@ export default function AutoEmbeddingsPage() {
);
}
if (data?.graphiteAutoEmbeddingsConfigurations.length === 0 && !loading) {
if (autoEmbeddingsConfigurations.length === 0 && !loading) {
return (
<Box
className="w-full p-6"

View File

@@ -0,0 +1,192 @@
import { useDialog } from '@/components/common/DialogProvider';
import { UpgradeToProBanner } from '@/components/common/UpgradeToProBanner';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { FileStoresIcon } from '@/components/ui/v2/icons/FileStoresIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { ProjectLayout } from '@/features/orgs/layout/ProjectLayout';
import { FileStoreForm } from '@/features/orgs/projects/ai/FileStoreForm';
import { FileStoresList } from '@/features/orgs/projects/ai/FileStoresList';
import { useIsFileStoreSupported } from '@/features/orgs/projects/common/hooks/useIsFileStoreSupported';
import { useIsGraphiteEnabled } from '@/features/orgs/projects/common/hooks/useIsGraphiteEnabled';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useAdminApolloClient } from '@/features/orgs/projects/hooks/useAdminApolloClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import {
useGetGraphiteFileStoresQuery,
type GetGraphiteFileStoresQuery
} from '@/utils/__generated__/graphite.graphql';
import { useMemo, type ReactElement } from 'react';
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { AISidebar } from '@/features/orgs/layout/AISidebar';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
export type GraphiteFileStore = Omit<
GetGraphiteFileStoresQuery['graphite']['fileStores'][0],
'__typename'
>;
export default function FileStoresPage() {
const { openDrawer } = useDialog();
const isPlatform = useIsPlatform();
const { org, loading: loadingOrg } = useCurrentOrg();
const { project, loading: loadingProject } = useProject();
const { adminClient } = useAdminApolloClient();
const { isGraphiteEnabled } = useIsGraphiteEnabled();
const { isFileStoreSupported } = useIsFileStoreSupported();
const { data, loading, refetch } = useGetGraphiteFileStoresQuery({
client: adminClient,
});
const fileStores = useMemo(() => data?.graphite.fileStores || [], [data]);
const openCreateFileStoreForm = () => {
openDrawer({
title: 'Create a new File Store',
component: <FileStoreForm onSubmit={refetch} />,
});
};
if (loadingOrg || loadingProject || loading) {
return (
<Box className="flex items-center justify-center w-full h-full">
<ActivityIndicator
delay={1000}
label="Loading File Stores..."
className="justify-center"
/>
</Box>
);
}
if (isPlatform && org?.plan?.isFree) {
return (
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
<UpgradeToProBanner
title="Upgrade to Nhost Pro."
description={
<Text>
Graphite is an addon to the Pro plan. To unlock it, please upgrade
to Pro first.
</Text>
}
/>
</Box>
);
}
if (
(isPlatform &&
!org?.plan?.isFree &&
!project.config?.ai) ||
!isGraphiteEnabled
) {
return (
<Box
className="w-full p-4"
sx={{ backgroundColor: 'background.default' }}
>
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
<Text className="grid grid-flow-row justify-items-start gap-0.5">
<Text component="span">
To enable graphite, configure the service first in{' '}
<Link
href={`/orgs/${org?.slug}/projects/${project?.subdomain}/settings/ai`}
rel="noopener noreferrer"
underline="hover"
>
AI Settings
</Link>
.
</Text>
</Text>
</Alert>
</Box>
);
}
if (fileStores.length === 0 && !loading) {
return (
<Box
className="w-full p-6"
sx={{ backgroundColor: 'background.default' }}
>
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
<FileStoresIcon className="h-10 w-10" />
<div className="flex flex-col space-y-1">
<Text className="text-center font-medium" variant="h3">
No File Stores are configured
</Text>
<Text variant="subtitle1" className="text-center">
File Stores are used to share storage documents with your
AI assistants.
</Text>
{!isFileStoreSupported && (
<Box className="px-4 pb-4">
<Alert className="mt-2 text-left">
Please upgrade Graphite to its latest version in order to use
file stores.
</Alert>
</Box>
)}
</div>
<div className="flex flex-row place-content-between rounded-lg">
<Button
variant="contained"
color="primary"
className="w-full"
onClick={openCreateFileStoreForm}
startIcon={<PlusIcon className="h-4 w-4" />}
disabled={!isFileStoreSupported}
>
Add a new File Store
</Button>
</div>
</Box>
</Box>
);
}
return (
<Box className="flex flex-col w-full overflow-hidden">
<Box className="flex flex-row place-content-end border-b-1 p-4">
<Button
variant="contained"
color="primary"
onClick={openCreateFileStoreForm}
startIcon={<PlusIcon className="h-4 w-4" />}
>
New
</Button>
</Box>
<div>
<FileStoresList
fileStores={fileStores}
onDelete={() => refetch()}
onCreateOrUpdate={() => refetch()}
/>
</div>
</Box>
);
}
FileStoresPage.getLayout = function getLayout(page: ReactElement) {
return (
<ProjectLayout
mainContainerProps={{ className: 'flex flex-row w-full h-full' }}
>
<AISidebar className="w-full max-w-sidebar" />
<RetryableErrorBoundary>{page}</RetryableErrorBoundary>
</ProjectLayout>
);
};

View File

@@ -0,0 +1,15 @@
import { SelectOrg } from '@/components/common/SelectOrg';
import { AuthenticatedLayout } from '@/components/layout/AuthenticatedLayout';
import { } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
export default function SelectOrganization() {
return <SelectOrg />
}
SelectOrganization.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout title="Select an Organization">{page}</AuthenticatedLayout>
);
};

View File

@@ -0,0 +1,15 @@
import { AuthenticatedLayout } from '@/components/layout/AuthenticatedLayout';
import { } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
import { SelectOrgAndProject } from '@/components/common/SelectOrgAndProject';
export default function OrganizationAndProject() {
return <SelectOrgAndProject />
}
OrganizationAndProject.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout title="Select a Project">{page}</AuthenticatedLayout>
);
};

View File

@@ -0,0 +1,14 @@
import { SelectOrgAndProject } from '@/components/common/SelectOrgAndProject';
import { AuthenticatedLayout } from '@/components/layout/AuthenticatedLayout';
import { } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
export default function SelectOrganizationAndProject() {
return <SelectOrgAndProject />
}
SelectOrganizationAndProject.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout title="Select a Project">{page}</AuthenticatedLayout>
);
};

View File

@@ -290,10 +290,10 @@ function TicketPage() {
<ControlledAutocomplete
id="services"
name="services"
label="services"
label="Services"
fullWidth
multiple
aria-label="Enabled APIs"
aria-label="Services"
options={[
'Dashboard',
'Database',

File diff suppressed because it is too large Load Diff

View File

@@ -4341,6 +4341,7 @@ export type Apps = {
appStates: Array<AppStateHistory>;
/** An aggregate relationship */
appStates_aggregate: AppStateHistory_Aggregate;
automaticDeploys: Scalars['Boolean'];
/** An array relationship */
backups: Array<Backups>;
/** An aggregate relationship */
@@ -4619,6 +4620,7 @@ export type Apps_Bool_Exp = {
_or?: InputMaybe<Array<Apps_Bool_Exp>>;
appStates?: InputMaybe<AppStateHistory_Bool_Exp>;
appStates_aggregate?: InputMaybe<AppStateHistory_Aggregate_Bool_Exp>;
automaticDeploys?: InputMaybe<Boolean_Comparison_Exp>;
backups?: InputMaybe<Backups_Bool_Exp>;
backups_aggregate?: InputMaybe<Backups_Aggregate_Bool_Exp>;
billingDedicatedCompute?: InputMaybe<Billing_Dedicated_Compute_Bool_Exp>;
@@ -4696,6 +4698,7 @@ export type Apps_Inc_Input = {
/** input type for inserting data into table "apps" */
export type Apps_Insert_Input = {
appStates?: InputMaybe<AppStateHistory_Arr_Rel_Insert_Input>;
automaticDeploys?: InputMaybe<Scalars['Boolean']>;
backups?: InputMaybe<Backups_Arr_Rel_Insert_Input>;
billingDedicatedCompute?: InputMaybe<Billing_Dedicated_Compute_Obj_Rel_Insert_Input>;
billingSubscriptions?: InputMaybe<Billing_Subscriptions_Obj_Rel_Insert_Input>;
@@ -4862,6 +4865,7 @@ export type Apps_On_Conflict = {
/** Ordering options when selecting data from "apps". */
export type Apps_Order_By = {
appStates_aggregate?: InputMaybe<AppStateHistory_Aggregate_Order_By>;
automaticDeploys?: InputMaybe<Order_By>;
backups_aggregate?: InputMaybe<Backups_Aggregate_Order_By>;
billingDedicatedCompute?: InputMaybe<Billing_Dedicated_Compute_Order_By>;
billingSubscriptions?: InputMaybe<Billing_Subscriptions_Order_By>;
@@ -4913,6 +4917,8 @@ export type Apps_Prepend_Input = {
/** select columns of table "apps" */
export enum Apps_Select_Column {
/** column name */
AutomaticDeploys = 'automaticDeploys',
/** column name */
CreatedAt = 'createdAt',
/** column name */
@@ -4965,6 +4971,8 @@ export enum Apps_Select_Column {
/** select "apps_aggregate_bool_exp_bool_and_arguments_columns" columns of table "apps" */
export enum Apps_Select_Column_Apps_Aggregate_Bool_Exp_Bool_And_Arguments_Columns {
/** column name */
AutomaticDeploys = 'automaticDeploys',
/** column name */
IsLocked = 'isLocked',
/** column name */
@@ -4973,6 +4981,8 @@ export enum Apps_Select_Column_Apps_Aggregate_Bool_Exp_Bool_And_Arguments_Column
/** select "apps_aggregate_bool_exp_bool_or_arguments_columns" columns of table "apps" */
export enum Apps_Select_Column_Apps_Aggregate_Bool_Exp_Bool_Or_Arguments_Columns {
/** column name */
AutomaticDeploys = 'automaticDeploys',
/** column name */
IsLocked = 'isLocked',
/** column name */
@@ -4981,6 +4991,7 @@ export enum Apps_Select_Column_Apps_Aggregate_Bool_Exp_Bool_Or_Arguments_Columns
/** input type for updating data in table "apps" */
export type Apps_Set_Input = {
automaticDeploys?: InputMaybe<Scalars['Boolean']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
creatorUserId?: InputMaybe<Scalars['uuid']>;
currentState?: InputMaybe<Scalars['Int']>;
@@ -5055,6 +5066,7 @@ export type Apps_Stream_Cursor_Input = {
/** Initial value of the column from where the streaming should start */
export type Apps_Stream_Cursor_Value_Input = {
automaticDeploys?: InputMaybe<Scalars['Boolean']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
creatorUserId?: InputMaybe<Scalars['uuid']>;
currentState?: InputMaybe<Scalars['Int']>;
@@ -5096,6 +5108,8 @@ export type Apps_Sum_Order_By = {
/** update columns of table "apps" */
export enum Apps_Update_Column {
/** column name */
AutomaticDeploys = 'automaticDeploys',
/** column name */
CreatedAt = 'createdAt',
/** column name */
@@ -27411,6 +27425,15 @@ export type GetProjectMetricsQueryVariables = Exact<{
export type GetProjectMetricsQuery = { __typename?: 'query_root', logsVolume: { __typename?: 'Metrics', value: any }, cpuSecondsUsage: { __typename?: 'Metrics', value: any }, functionInvocations: { __typename?: 'Metrics', value: any }, functionsDuration: { __typename?: 'Metrics', value: any }, postgresVolumeCapacity: { __typename?: 'Metrics', value: any }, postgresVolumeUsage: { __typename?: 'Metrics', value: any }, totalRequests: { __typename?: 'Metrics', value: any }, egressVolume: { __typename?: 'Metrics', value: any } };
export type GetProjectRequestsMetricQueryVariables = Exact<{
appId: Scalars['String'];
from?: InputMaybe<Scalars['Timestamp']>;
to?: InputMaybe<Scalars['Timestamp']>;
}>;
export type GetProjectRequestsMetricQuery = { __typename?: 'query_root', totalRequests: { __typename?: 'Metrics', value: any } };
export type GetProjectServicesHealthQueryVariables = Exact<{
appId: Scalars['String'];
}>;
@@ -27853,6 +27876,14 @@ export type GetProjectsQueryVariables = Exact<{
export type GetProjectsQuery = { __typename?: 'query_root', apps: Array<{ __typename?: 'apps', id: any, name: string, slug: string, createdAt: any, subdomain: string, region: { __typename?: 'regions', id: any, name: string }, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }> }> };
export type GetUserProjectMetricsQueryVariables = Exact<{
startOfMonth: Scalars['timestamptz'];
today: Scalars['timestamptz'];
}>;
export type GetUserProjectMetricsQuery = { __typename?: 'query_root', monthlyActiveUsers: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null }, dailyActiveUsers: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null }, allUsers: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null }, filesAggregate: { __typename?: 'files_aggregate', aggregate?: { __typename?: 'files_aggregate_fields', count: number, sum?: { __typename?: 'files_sum_fields', size?: number | null } | null } | null } };
export type InsertOrgApplicationMutationVariables = Exact<{
app: Apps_Insert_Input;
}>;
@@ -29936,6 +29967,46 @@ export type GetProjectMetricsQueryResult = Apollo.QueryResult<GetProjectMetricsQ
export function refetchGetProjectMetricsQuery(variables: GetProjectMetricsQueryVariables) {
return { query: GetProjectMetricsDocument, variables: variables }
}
export const GetProjectRequestsMetricDocument = gql`
query GetProjectRequestsMetric($appId: String!, $from: Timestamp, $to: Timestamp) {
totalRequests: getTotalRequests(appID: $appId, from: $from, to: $to) {
value
}
}
`;
/**
* __useGetProjectRequestsMetricQuery__
*
* To run a query within a React component, call `useGetProjectRequestsMetricQuery` and pass it any options that fit your needs.
* When your component renders, `useGetProjectRequestsMetricQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetProjectRequestsMetricQuery({
* variables: {
* appId: // value for 'appId'
* from: // value for 'from'
* to: // value for 'to'
* },
* });
*/
export function useGetProjectRequestsMetricQuery(baseOptions: Apollo.QueryHookOptions<GetProjectRequestsMetricQuery, GetProjectRequestsMetricQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetProjectRequestsMetricQuery, GetProjectRequestsMetricQueryVariables>(GetProjectRequestsMetricDocument, options);
}
export function useGetProjectRequestsMetricLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetProjectRequestsMetricQuery, GetProjectRequestsMetricQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetProjectRequestsMetricQuery, GetProjectRequestsMetricQueryVariables>(GetProjectRequestsMetricDocument, options);
}
export type GetProjectRequestsMetricQueryHookResult = ReturnType<typeof useGetProjectRequestsMetricQuery>;
export type GetProjectRequestsMetricLazyQueryHookResult = ReturnType<typeof useGetProjectRequestsMetricLazyQuery>;
export type GetProjectRequestsMetricQueryResult = Apollo.QueryResult<GetProjectRequestsMetricQuery, GetProjectRequestsMetricQueryVariables>;
export function refetchGetProjectRequestsMetricQuery(variables: GetProjectRequestsMetricQueryVariables) {
return { query: GetProjectRequestsMetricDocument, variables: variables }
}
export const GetProjectServicesHealthDocument = gql`
query getProjectServicesHealth($appId: String!) {
getProjectStatus(appID: $appId) {
@@ -32534,6 +32605,67 @@ export type GetProjectsQueryResult = Apollo.QueryResult<GetProjectsQuery, GetPro
export function refetchGetProjectsQuery(variables: GetProjectsQueryVariables) {
return { query: GetProjectsDocument, variables: variables }
}
export const GetUserProjectMetricsDocument = gql`
query GetUserProjectMetrics($startOfMonth: timestamptz!, $today: timestamptz!) {
monthlyActiveUsers: usersAggregate(
where: {lastSeen: {_gte: $startOfMonth, _lte: $today}}
) {
aggregate {
count
}
}
dailyActiveUsers: usersAggregate(where: {lastSeen: {_gte: $today}}) {
aggregate {
count
}
}
allUsers: usersAggregate {
aggregate {
count
}
}
filesAggregate {
aggregate {
count
sum {
size
}
}
}
}
`;
/**
* __useGetUserProjectMetricsQuery__
*
* To run a query within a React component, call `useGetUserProjectMetricsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetUserProjectMetricsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetUserProjectMetricsQuery({
* variables: {
* startOfMonth: // value for 'startOfMonth'
* today: // value for 'today'
* },
* });
*/
export function useGetUserProjectMetricsQuery(baseOptions: Apollo.QueryHookOptions<GetUserProjectMetricsQuery, GetUserProjectMetricsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetUserProjectMetricsQuery, GetUserProjectMetricsQueryVariables>(GetUserProjectMetricsDocument, options);
}
export function useGetUserProjectMetricsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetUserProjectMetricsQuery, GetUserProjectMetricsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetUserProjectMetricsQuery, GetUserProjectMetricsQueryVariables>(GetUserProjectMetricsDocument, options);
}
export type GetUserProjectMetricsQueryHookResult = ReturnType<typeof useGetUserProjectMetricsQuery>;
export type GetUserProjectMetricsLazyQueryHookResult = ReturnType<typeof useGetUserProjectMetricsLazyQuery>;
export type GetUserProjectMetricsQueryResult = Apollo.QueryResult<GetUserProjectMetricsQuery, GetUserProjectMetricsQueryVariables>;
export function refetchGetUserProjectMetricsQuery(variables: GetUserProjectMetricsQueryVariables) {
return { query: GetUserProjectMetricsDocument, variables: variables }
}
export const InsertOrgApplicationDocument = gql`
mutation insertOrgApplication($app: apps_insert_input!) {
insertApp(object: $app) {

View File

@@ -1,5 +1,11 @@
# @nhost/docs
## 2.24.0
### Minor Changes
- a99f034: chore: fix function name
## 2.23.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/docs",
"version": "2.23.0",
"version": "2.24.0",
"private": true,
"scripts": {
"start": "mintlify dev"

View File

@@ -9,10 +9,10 @@ Note: The Nhost client automatically refreshes the session when the user is auth
```ts
// Refresh the session with the the current internal refresh token.
nhost.auth.refreshToken()
nhost.auth.refreshSession()
// Refresh the session with an external refresh token.
nhost.auth.refreshToken(refreshToken)
nhost.auth.refreshSession(refreshToken)
```
## Parameters

View File

@@ -79,7 +79,7 @@
"eslint-plugin-vue": "^9.26.0",
"husky": "^8.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"prettier": "^3.3.3",
"turbo": "1.11.3",
"typedoc": "^0.22.18",
"typescript": "4.9.5",

View File

@@ -804,10 +804,10 @@ export class HasuraAuthClient {
* @example
* ```ts
* // Refresh the session with the the current internal refresh token.
* nhost.auth.refreshToken();
* nhost.auth.refreshSession();
*
* // Refresh the session with an external refresh token.
* nhost.auth.refreshToken(refreshToken);
* nhost.auth.refreshSession(refreshToken);
* ```
*
* @docs https://docs.nhost.io/reference/javascript/auth/refresh-session

285
pnpm-lock.yaml generated
View File

@@ -146,8 +146,8 @@ importers:
specifier: ^4.1.5
version: 4.1.5
prettier:
specifier: ^2.8.8
version: 2.8.8
specifier: ^3.3.3
version: 3.3.3
turbo:
specifier: 1.11.3
version: 1.11.3
@@ -1123,7 +1123,7 @@ importers:
devDependencies:
'@nhost/nhost-js':
specifier: ^3.1.5
version: 3.2.0(graphql@16.8.1)
version: 3.2.1(graphql@16.8.1)
'@playwright/test':
specifier: ^1.41.0
version: 1.47.0
@@ -2146,10 +2146,10 @@ importers:
version: 9.7.0(react@18.2.0)
'@nhost/react':
specifier: ^3.5.4
version: 3.7.0(@types/react@18.3.4)(react-dom@18.2.0)(react@18.2.0)
version: link:../../../packages/react
'@nhost/react-apollo':
specifier: ^12.0.4
version: 12.0.6(@apollo/client@3.11.4)(@nhost/react@3.7.0)(react-dom@18.2.0)(react@18.2.0)
version: 12.0.6(@apollo/client@3.11.4)(@nhost/react@packages+react)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-dialog':
specifier: ^1.1.1
version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.2.0)(react@18.2.0)
@@ -2230,7 +2230,7 @@ importers:
version: 6.26.1(react-dom@18.2.0)(react@18.2.0)
react-scripts:
specifier: 5.0.1
version: 5.0.1(react@18.2.0)(typescript@4.9.5)
version: 5.0.1(@babel/plugin-syntax-flow@7.24.7)(@babel/plugin-transform-react-jsx@7.25.2)(react@18.2.0)(typescript@4.9.5)
react-syntax-highlighter:
specifier: ^15.5.0
version: 15.5.0(react@18.2.0)
@@ -2261,7 +2261,7 @@ importers:
version: 8.4.47
tailwindcss:
specifier: ^3.4.10
version: 3.4.12
version: 3.4.12(ts-node@10.9.2)
templates/react-native: {}
@@ -2566,7 +2566,7 @@ packages:
/@apollo/client@3.11.4(@types/react@18.3.4)(graphql-ws@5.16.0)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-bmgYKkULpym8wt8aXlAZ1heaYo0skLJ5ru0qJ+JCRoo03Pe+yIDbBCnqlDw6Mjj76hFkDw3HwFMgZC2Hxp30Mg==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
graphql-ws: ^5.5.5
react: 18.2.0
react-dom: 18.2.0
@@ -2605,7 +2605,7 @@ packages:
/@apollo/client@3.11.4(@types/react@18.3.4)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-bmgYKkULpym8wt8aXlAZ1heaYo0skLJ5ru0qJ+JCRoo03Pe+yIDbBCnqlDw6Mjj76hFkDw3HwFMgZC2Hxp30Mg==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
graphql-ws: ^5.5.5
react: 18.2.0
react-dom: 18.2.0
@@ -2620,11 +2620,11 @@ packages:
subscriptions-transport-ws:
optional: true
dependencies:
'@graphql-typed-document-node/core': 3.2.0
'@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1)
'@wry/caches': 1.0.1
'@wry/equality': 0.5.7
'@wry/trie': 0.5.0
graphql-tag: 2.12.6
graphql-tag: 2.12.6(graphql@16.8.1)
hoist-non-react-statics: 3.3.2
optimism: 0.18.0
prop-types: 15.8.1
@@ -2643,7 +2643,7 @@ packages:
/@apollo/client@3.11.4(graphql-ws@5.16.0)(graphql@16.8.1):
resolution: {integrity: sha512-bmgYKkULpym8wt8aXlAZ1heaYo0skLJ5ru0qJ+JCRoo03Pe+yIDbBCnqlDw6Mjj76hFkDw3HwFMgZC2Hxp30Mg==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
graphql-ws: ^5.5.5
react: 18.2.0
react-dom: 18.2.0
@@ -2681,7 +2681,7 @@ packages:
resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==}
hasBin: true
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@babel/core': 7.25.2
'@babel/generator': 7.25.5
@@ -4636,7 +4636,7 @@ packages:
cosmiconfig-typescript-loader: 1.0.9(@types/node@16.18.106)(typescript@4.9.5)
cross-spawn: 7.0.6
lodash: 4.17.21
react-scripts: 5.0.1(react@18.2.0)(typescript@4.9.5)
react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.7)(@babel/plugin-transform-react-jsx@7.25.2)(react@18.2.0)(typescript@4.9.5)
semver: 7.6.3
webpack-merge: 5.10.0
transitivePeerDependencies:
@@ -6215,7 +6215,7 @@ packages:
/@graphql-codegen/plugin-helpers@4.2.0(graphql@16.8.1):
resolution: {integrity: sha512-THFTCfg+46PXlXobYJ/OoCX6pzjI+9woQqCjdyKtgoI0tn3Xq2HUUCiidndxUpEYVrXb5pRiRXb7b/ZbMQqD0A==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
change-case-all: 1.0.15
@@ -6464,7 +6464,7 @@ packages:
/@graphql-codegen/visitor-plugin-common@3.1.1(encoding@0.1.13)(graphql@16.8.1):
resolution: {integrity: sha512-uAfp+zu/009R3HUAuTK2AamR1bxIltM6rrYYI6EXSmkM3rFtFsLTuJhjUDj98HcUCszJZrADppz8KKLGRUVlNg==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@graphql-codegen/plugin-helpers': 4.2.0(graphql@16.8.1)
'@graphql-tools/optimize': 1.4.0(graphql@16.8.1)
@@ -6823,7 +6823,7 @@ packages:
resolution: {integrity: sha512-TmkzFTFVieHnqu9mPTF6RxAQltaprpDQnM5HMTPSyMLXnJGMTvdWejV0yORKj7DW1YSi791/sUnKf8HytepBFQ==}
engines: {node: '>=16.0.0'}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@graphql-tools/utils': 10.5.4(graphql@16.8.1)
graphql: 16.8.1
@@ -6833,7 +6833,7 @@ packages:
/@graphql-tools/optimize@1.4.0(graphql@16.8.1):
resolution: {integrity: sha512-dJs/2XvZp+wgHH8T5J2TqptT9/6uVzIYvA6uFACha+ufvdMBedkfR4b4GbT8jAKLRARiqRTxy3dctnwkTM2tdw==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
graphql: 16.8.1
tslib: 2.7.0
@@ -6914,7 +6914,7 @@ packages:
/@graphql-tools/relay-operation-optimizer@6.5.18(encoding@0.1.13)(graphql@16.8.1):
resolution: {integrity: sha512-mc5VPyTeV+LwiM+DNvoDQfPqwQYhPV/cl5jOBjTgSniyaq8/86aODfMkrE2OduhQ5E00hqrkuL2Fdrgk0w1QJg==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@ardatan/relay-compiler': 12.0.0(encoding@0.1.13)(graphql@16.8.1)
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
@@ -6944,7 +6944,7 @@ packages:
resolution: {integrity: sha512-EIJgPRGzpvDFEjVp+RF1zNNYIC36BYuIeZ514jFoJnI6IdxyVyIRDLx/ykgMdaa1pKQerpfdqDnsF4JnZoDHSQ==}
engines: {node: '>=16.0.0'}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@graphql-tools/merge': 9.0.6(graphql@16.8.1)
'@graphql-tools/utils': 10.5.4(graphql@16.8.1)
@@ -6956,7 +6956,7 @@ packages:
/@graphql-tools/schema@9.0.19(graphql@16.8.1):
resolution: {integrity: sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@graphql-tools/merge': 8.4.2(graphql@16.8.1)
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
@@ -7035,7 +7035,7 @@ packages:
/@graphql-tools/utils@8.13.1(graphql@16.8.1):
resolution: {integrity: sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
graphql: 16.8.1
tslib: 2.7.0
@@ -7043,7 +7043,7 @@ packages:
/@graphql-tools/utils@9.2.1(graphql@16.8.1):
resolution: {integrity: sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1)
graphql: 16.8.1
@@ -7071,12 +7071,6 @@ packages:
graphql: 16.8.1
dev: true
/@graphql-typed-document-node/core@3.2.0:
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
peerDependencies:
graphql: '>=16.8.1'
dev: false
/@graphql-typed-document-node/core@3.2.0(graphql@16.8.1):
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
peerDependencies:
@@ -8970,19 +8964,6 @@ packages:
jwt-decode: 4.0.0
dev: false
/@nhost/graphql-js@0.3.0:
resolution: {integrity: sha512-CVYq6wx0VbaYdpUBmfNO/6mZatHB5+YBCqFjWyxhpN1nzHCHEO6rgdL7j0qk31OFE6XAX0z7AQZSXg1Pn63GUw==}
peerDependencies:
graphql: '>=16.8.1'
dependencies:
'@graphql-typed-document-node/core': 3.2.0
base-64: 1.0.0
isomorphic-unfetch: 3.1.0
jwt-decode: 4.0.0
transitivePeerDependencies:
- encoding
dev: false
/@nhost/graphql-js@0.3.0(graphql@16.8.1):
resolution: {integrity: sha512-CVYq6wx0VbaYdpUBmfNO/6mZatHB5+YBCqFjWyxhpN1nzHCHEO6rgdL7j0qk31OFE6XAX0z7AQZSXg1Pn63GUw==}
peerDependencies:
@@ -8997,8 +8978,8 @@ packages:
- encoding
dev: true
/@nhost/hasura-auth-js@2.7.0:
resolution: {integrity: sha512-hxmPO26nsA7/e5FQUo6VNHMbY2JCxEQXwuj400PvCg87sbGXoEM5MbKD93zv2X3kVtdlQG4/K3y6AE9rqrmxqQ==}
/@nhost/hasura-auth-js@2.8.0:
resolution: {integrity: sha512-0IMLe+xK+F/cp7t5x5FDKyxoI8jz11P2Z4UgIeIJlCk1vxXh+y8bSrSBXbImAywf+SySPOFBFUM3DbKoWOBs0g==}
dependencies:
'@simplewebauthn/browser': 9.0.1
fetch-ponyfill: 7.1.0
@@ -9007,6 +8988,7 @@ packages:
xstate: 4.38.3
transitivePeerDependencies:
- encoding
dev: true
/@nhost/hasura-storage-js@2.5.1:
resolution: {integrity: sha512-I3rOSa095lcR9BUmNw7dOoXLPWL39WOcrb0paUBFX4h3ltR92ILEHTZ38hN6bZSv157ZdqkIFNL/M2G45SSf7g==}
@@ -9017,27 +8999,15 @@ packages:
xstate: 4.38.3
transitivePeerDependencies:
- encoding
dev: true
/@nhost/nhost-js@3.2.0:
resolution: {integrity: sha512-Oj5T+c8Ni9MHi25MiCD5AnN5juz45BMNNqUv/MLNjt9sUzEzldQd0iodBqu0+9Nj14dKU9dqKhEhPArUYTvCCQ==}
peerDependencies:
graphql: 16.8.1
dependencies:
'@nhost/graphql-js': 0.3.0
'@nhost/hasura-auth-js': 2.7.0
'@nhost/hasura-storage-js': 2.5.1
isomorphic-unfetch: 3.1.0
transitivePeerDependencies:
- encoding
dev: false
/@nhost/nhost-js@3.2.0(graphql@16.8.1):
resolution: {integrity: sha512-Oj5T+c8Ni9MHi25MiCD5AnN5juz45BMNNqUv/MLNjt9sUzEzldQd0iodBqu0+9Nj14dKU9dqKhEhPArUYTvCCQ==}
/@nhost/nhost-js@3.2.1(graphql@16.8.1):
resolution: {integrity: sha512-MfTIJXatm+J27SL8UPNVjckH6dR9dY3zLmJKnnI3XpBRYpkhrhmnn4W9JvxjlOywRdTE0WWhgSUEuN1VherdTw==}
peerDependencies:
graphql: '>=16.8.1'
dependencies:
'@nhost/graphql-js': 0.3.0(graphql@16.8.1)
'@nhost/hasura-auth-js': 2.7.0
'@nhost/hasura-auth-js': 2.8.0
'@nhost/hasura-storage-js': 2.5.1
graphql: 16.8.1
isomorphic-unfetch: 3.1.0
@@ -9045,30 +9015,12 @@ packages:
- encoding
dev: true
/@nhost/react-apollo@12.0.6(@apollo/client@3.11.4)(@nhost/react@3.7.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-6Q4uN7PvC6UqS4YPKbjv/q/9FMP4SECdEcZFrfaKfJrcWyoAA5MRwJeQwDnD3uhx+npEUNgTbBxezXHjYH3AYw==}
peerDependencies:
'@apollo/client': ^3.7.10
'@nhost/react': 3.5.6
graphql: '>=16.8.1'
react: 18.2.0
react-dom: 18.2.0
dependencies:
'@apollo/client': 3.11.4(@types/react@18.3.4)(react-dom@18.2.0)(react@18.2.0)
'@nhost/apollo': 7.1.6(@apollo/client@3.11.4)
'@nhost/react': 3.7.0(@types/react@18.3.4)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@nhost/nhost-js'
dev: false
/@nhost/react-apollo@12.0.6(@apollo/client@3.11.4)(@nhost/react@packages+react)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-6Q4uN7PvC6UqS4YPKbjv/q/9FMP4SECdEcZFrfaKfJrcWyoAA5MRwJeQwDnD3uhx+npEUNgTbBxezXHjYH3AYw==}
peerDependencies:
'@apollo/client': ^3.7.10
'@nhost/react': 3.5.6
graphql: '>=16.8.1'
graphql: 16.8.1
react: 18.2.0
react-dom: 18.2.0
dependencies:
@@ -9081,25 +9033,6 @@ packages:
- '@nhost/nhost-js'
dev: false
/@nhost/react@3.7.0(@types/react@18.3.4)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Clln1GnxbwnGANpmuyEs5N6kpnpvUfBRS4cjGOMj1MpqfoTprWcTW8pD+9pKQzAdbD7x7K4s+lb2ywEu4PLoAA==}
peerDependencies:
react: 18.2.0
react-dom: 18.2.0
dependencies:
'@nhost/nhost-js': 3.2.0
'@xstate/react': 3.2.2(@types/react@18.3.4)(react@18.2.0)(xstate@4.38.3)
jwt-decode: 4.0.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
xstate: 4.38.3
transitivePeerDependencies:
- '@types/react'
- '@xstate/fsm'
- encoding
- graphql
dev: false
/@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1:
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
dependencies:
@@ -9454,7 +9387,7 @@ packages:
/@pothos/core@3.41.2(graphql@16.8.1):
resolution: {integrity: sha512-iR1gqd93IyD/snTW47HwKSsRCrvnJaYwjVNcUG8BztZPqMxyJKPAnjPHAgu1XB82KEdysrNqIUnXqnzZIs08QA==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
graphql: 16.8.1
dev: false
@@ -14882,7 +14815,7 @@ packages:
resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: '>=4.3.9'
vite: '>=4.5.1'
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2)
@@ -14898,7 +14831,7 @@ packages:
resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: '>=4.3.9'
vite: '>=4.5.1'
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2)
@@ -14914,7 +14847,7 @@ packages:
resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: '>=4.3.9'
vite: '>=4.5.1'
vue: ^3.2.25
dependencies:
vite: 5.4.6(@types/node@16.18.106)(sass@1.32.0)
@@ -15036,7 +14969,7 @@ packages:
peerDependencies:
'@apollo/client': ^3.4.13
'@vue/composition-api': ^1.0.0
graphql: '>=16.8.1'
graphql: 16.8.1
vue: ^2.6.0 || ^3.1.0
peerDependenciesMeta:
'@vue/composition-api':
@@ -15057,7 +14990,7 @@ packages:
peerDependencies:
'@apollo/client': ^3.4.13
'@vue/composition-api': ^1.0.0
graphql: '>=16.8.1'
graphql: 16.8.1
vue: ^2.6.0 || ^3.1.0
peerDependenciesMeta:
'@vue/composition-api':
@@ -19222,42 +19155,6 @@ packages:
- eslint-import-resolver-webpack
- jest
- supports-color
dev: true
/eslint-config-react-app@7.0.1(eslint@8.57.0)(jest@27.5.1)(typescript@4.9.5):
resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==}
engines: {node: '>=14.0.0'}
peerDependencies:
eslint: ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@babel/core': 7.25.2
'@babel/eslint-parser': 7.25.1(@babel/core@7.25.2)(eslint@8.57.0)
'@rushstack/eslint-patch': 1.10.4
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@4.9.5)
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@4.9.5)
babel-preset-react-app: 10.0.1
confusing-browser-globals: 1.0.11
eslint: 8.57.0
eslint-plugin-flowtype: 8.0.3(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)
eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.57.0)(jest@27.5.1)(typescript@4.9.5)
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
eslint-plugin-react: 7.35.0(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
eslint-plugin-testing-library: 5.11.1(eslint@8.57.0)(typescript@4.9.5)
typescript: 4.9.5
transitivePeerDependencies:
- '@babel/plugin-syntax-flow'
- '@babel/plugin-transform-react-jsx'
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- jest
- supports-color
dev: false
/eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
@@ -19284,7 +19181,6 @@ packages:
tsconfig-paths: 3.15.0
transitivePeerDependencies:
- supports-color
dev: true
/eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.48.0):
resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==}
@@ -19374,36 +19270,6 @@ packages:
eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.29.1)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
dev: true
/eslint-module-utils@2.8.2(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
resolution: {integrity: sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint:
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@4.9.5)
debug: 3.2.7
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
dev: false
/eslint-module-utils@2.8.2(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.48.0):
resolution: {integrity: sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==}
@@ -19521,20 +19387,6 @@ packages:
eslint: 8.57.0
lodash: 4.17.21
string-natural-compare: 3.0.1
dev: true
/eslint-plugin-flowtype@8.0.3(eslint@8.57.0):
resolution: {integrity: sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@babel/plugin-syntax-flow': ^7.14.5
'@babel/plugin-transform-react-jsx': ^7.14.9
eslint: ^8.1.0
dependencies:
eslint: 8.57.0
lodash: 4.17.21
string-natural-compare: 3.0.1
dev: false
/eslint-plugin-ft-flow@2.0.3(@babel/eslint-parser@7.25.1)(eslint@8.57.0):
resolution: {integrity: sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==}
@@ -19588,42 +19440,6 @@ packages:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0)(eslint@8.57.0):
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@4.9.5)
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2
debug: 3.2.7
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.2(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
minimatch: 3.1.2
object.fromentries: 2.0.8
object.groupby: 1.0.3
object.values: 1.2.0
semver: 6.3.1
tsconfig-paths: 3.15.0
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: false
/eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.48.0):
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
@@ -21719,7 +21535,7 @@ packages:
/graphql-request@6.1.0(encoding@0.1.13)(graphql@16.8.1):
resolution: {integrity: sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1)
cross-fetch: 3.1.8(encoding@0.1.13)
@@ -21737,15 +21553,6 @@ packages:
tslib: 2.7.0
dev: false
/graphql-tag@2.12.6:
resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==}
engines: {node: '>=10'}
peerDependencies:
graphql: '>=16.8.1'
dependencies:
tslib: 2.7.0
dev: false
/graphql-tag@2.12.6(graphql@16.8.1):
resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==}
engines: {node: '>=10'}
@@ -21759,7 +21566,7 @@ packages:
resolution: {integrity: sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==}
engines: {node: '>=10'}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
dependencies:
graphql: 16.8.1
@@ -28377,6 +28184,7 @@ packages:
lilconfig: 3.1.2
postcss: 8.4.47
yaml: 2.5.0
dev: true
/postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2):
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
@@ -30034,7 +29842,7 @@ packages:
react: 18.2.0
dev: false
/react-scripts@5.0.1(react@18.2.0)(typescript@4.9.5):
/react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7)(@babel/plugin-transform-react-jsx@7.25.2)(react@18.2.0)(typescript@4.9.5):
resolution: {integrity: sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==}
engines: {node: '>=14.0.0'}
hasBin: true
@@ -30061,7 +29869,7 @@ packages:
dotenv: 10.0.0
dotenv-expand: 5.1.0
eslint: 8.57.0
eslint-config-react-app: 7.0.1(eslint@8.57.0)(jest@27.5.1)(typescript@4.9.5)
eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7)(@babel/plugin-transform-react-jsx@7.25.2)(eslint@8.57.0)(jest@27.5.1)(typescript@4.9.5)
eslint-webpack-plugin: 3.2.0(eslint@8.57.0)(webpack@5.94.0)
file-loader: 6.2.0(webpack@5.94.0)
fs-extra: 10.1.0
@@ -30087,7 +29895,7 @@ packages:
semver: 7.6.3
source-map-loader: 3.0.2(webpack@5.94.0)
style-loader: 3.3.4(webpack@5.94.0)
tailwindcss: 3.4.12
tailwindcss: 3.4.12(ts-node@10.9.2)
terser-webpack-plugin: 5.3.10(webpack@5.94.0)
typescript: 4.9.5
webpack: 5.94.0
@@ -32536,7 +32344,7 @@ packages:
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
dependencies:
tailwindcss: 3.4.12
tailwindcss: 3.4.12(ts-node@10.9.2)
dev: false
/tailwindcss@3.3.3:
@@ -32599,6 +32407,7 @@ packages:
sucrase: 3.35.0
transitivePeerDependencies:
- ts-node
dev: true
/tailwindcss@3.4.12(ts-node@10.9.2):
resolution: {integrity: sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==}
@@ -34005,7 +33814,7 @@ packages:
/urql@3.0.4(graphql@16.8.1)(react@18.2.0):
resolution: {integrity: sha512-okmQKQ9pF4t8O8iCC5gH9acqfFji5lkhW3nLBjx8WKDd2zZG7PXoUpUK19VQEMK87L6VFBOO/XZ52MMKFEI0AA==}
peerDependencies:
graphql: '>=16.8.1'
graphql: 16.8.1
react: 18.2.0
dependencies:
'@urql/core': 3.2.2(graphql@16.8.1)
@@ -34377,7 +34186,7 @@ packages:
/vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@5.4.6):
resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==}
peerDependencies:
vite: '>=4.3.9'
vite: '>=4.5.1'
peerDependenciesMeta:
vite:
optional: true