Compare commits
35 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19818e2b59 | ||
|
|
b3eeec82ef | ||
|
|
34ff254696 | ||
|
|
1c4806bf51 | ||
|
|
2fb82ec97d | ||
|
|
0c994a9651 | ||
|
|
4713cecfc2 | ||
|
|
f79eebadbf | ||
|
|
ac174b5e51 | ||
|
|
dc9ddfc9ae | ||
|
|
3bdd9f570c | ||
|
|
94477be998 | ||
|
|
568577e8ca | ||
|
|
e93b06ab8f | ||
|
|
c75bf46ba1 | ||
|
|
63a1fd09b5 | ||
|
|
630d44ad6e | ||
|
|
d7db521974 | ||
|
|
90e4053f0a | ||
|
|
8e9d5d1b38 | ||
|
|
43c86fef14 | ||
|
|
6b97340cf4 | ||
|
|
1605756362 | ||
|
|
6437544384 | ||
|
|
b4dcd1996d | ||
|
|
7fb73dbb1b | ||
|
|
a66b11d245 | ||
|
|
912ed76c64 | ||
|
|
b47c0d1af7 | ||
|
|
b97ab2be2f | ||
|
|
f12cb666ff | ||
|
|
c3b2b1cd02 | ||
|
|
c0b71102d4 | ||
|
|
5f68ae95c4 | ||
|
|
2d1b7bb292 |
19
.github/workflows/ci.yaml
vendored
19
.github/workflows/ci.yaml
vendored
@@ -128,12 +128,27 @@ jobs:
|
||||
- name: Install Nhost CLI
|
||||
if: hashFiles(format('{0}/nhost/config.yaml', matrix.package.path)) != ''
|
||||
uses: ./.github/actions/nhost-cli
|
||||
- name: Fetch Dashboard Preview URL
|
||||
id: fetch-dashboard-preview-url
|
||||
uses: zentered/vercel-preview-url@v1.1.9
|
||||
if: github.ref_name != 'main'
|
||||
env:
|
||||
VERCEL_TOKEN: ${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
|
||||
GITHUB_REF: ${{ github.ref_name }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
with:
|
||||
vercel_team_id: ${{ secrets.DASHBOARD_VERCEL_TEAM_ID }}
|
||||
vercel_project_id: ${{ secrets.DASHBOARD_STAGING_VERCEL_PROJECT_ID }}
|
||||
vercel_state: BUILDING,READY,INITIALIZING
|
||||
- name: Set Dashboard Preview URL
|
||||
if: steps.fetch-dashboard-preview-url.outputs.preview_url != ''
|
||||
run: echo "NHOST_TEST_DASHBOARD_URL=https://${{ steps.fetch-dashboard-preview-url.outputs.preview_url }}" >> $GITHUB_ENV
|
||||
# * Run the `ci` script of the current package of the matrix. Dependencies build is cached by Turborepo
|
||||
- name: Run e2e test
|
||||
- name: Run e2e tests
|
||||
run: pnpm --filter="${{ matrix.package.name }}" run e2e
|
||||
- id: file-name
|
||||
if: ${{ failure() }}
|
||||
name: Tranform package name into a valid file name
|
||||
name: Transform package name into a valid file name
|
||||
run: |
|
||||
PACKAGE_FILE_NAME=$(echo "${{ matrix.package.name }}" | sed 's/@//g; s/\//-/g')
|
||||
echo "fileName=$PACKAGE_FILE_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -51,7 +51,7 @@ export const decorators = [
|
||||
(Story) => (
|
||||
<NhostApolloProvider
|
||||
fetchPolicy="cache-first"
|
||||
graphqlUrl="http://localhost:1337/v1/graphql"
|
||||
graphqlUrl="https://local.graphql.nhost.run/v1"
|
||||
>
|
||||
<Story />
|
||||
</NhostApolloProvider>
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 0.13.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e93b06ab: fix(dashboard): remove left margin from workspace list on mobile
|
||||
- 1c4806bf: chore(deps): bump `sharp` to 0.32.0
|
||||
- @nhost/react-apollo@5.0.14
|
||||
- @nhost/nextjs@1.13.18
|
||||
|
||||
## 0.13.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 912ed76c: chore(dashboard): bump `@apollo/client` to 3.7.10
|
||||
- Updated dependencies [912ed76c]
|
||||
- @nhost/react-apollo@5.0.13
|
||||
|
||||
## 0.13.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
TEST_DASHBOARD_URL,
|
||||
TEST_PROJECT_NAME,
|
||||
TEST_PROJECT_SLUG,
|
||||
TEST_WORKSPACE_SLUG,
|
||||
} from './env';
|
||||
} from '@/e2e/env';
|
||||
import { openProject } from '@/e2e/utils';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
@@ -14,20 +14,21 @@ test.describe.configure({ mode: 'serial' });
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
|
||||
await page.goto(TEST_DASHBOARD_URL);
|
||||
await page.getByRole('link', { name: TEST_PROJECT_NAME }).click();
|
||||
await page.waitForURL(
|
||||
`${TEST_DASHBOARD_URL}/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}`,
|
||||
);
|
||||
await page.goto('/');
|
||||
|
||||
await openProject({
|
||||
page,
|
||||
projectName: TEST_PROJECT_NAME,
|
||||
workspaceSlug: TEST_WORKSPACE_SLUG,
|
||||
projectSlug: TEST_PROJECT_SLUG,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: /main navigation/i })
|
||||
.getByRole('link', { name: /auth/i })
|
||||
.click();
|
||||
|
||||
await page.waitForURL(
|
||||
`${TEST_DASHBOARD_URL}/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`,
|
||||
);
|
||||
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
276
dashboard/e2e/database/create-table.test.ts
Normal file
276
dashboard/e2e/database/create-table.test.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import {
|
||||
TEST_PROJECT_NAME,
|
||||
TEST_PROJECT_SLUG,
|
||||
TEST_WORKSPACE_SLUG,
|
||||
} from '@/e2e/env';
|
||||
import { openProject, prepareTable } from '@/e2e/utils';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
await page.goto('/');
|
||||
|
||||
await openProject({
|
||||
page,
|
||||
projectName: TEST_PROJECT_NAME,
|
||||
workspaceSlug: TEST_WORKSPACE_SLUG,
|
||||
projectSlug: TEST_PROJECT_SLUG,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: /main navigation/i })
|
||||
.getByRole('link', { name: /database/i })
|
||||
.click();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should create a simple table', async () => {
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const tableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'title', type: 'text' },
|
||||
],
|
||||
});
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: tableName, exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should create a table with unique constraints', async () => {
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const tableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'title', type: 'text', unique: true },
|
||||
{ name: 'isbn', type: 'text', unique: true },
|
||||
],
|
||||
});
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: tableName, exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should create a table with nullable columns', async () => {
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const tableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'title', type: 'text', nullable: true },
|
||||
{ name: 'description', type: 'text', nullable: true },
|
||||
],
|
||||
});
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: tableName, exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should create a table with an identity column', async () => {
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const tableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'int4' },
|
||||
{ name: 'title', type: 'text', nullable: true },
|
||||
{ name: 'description', type: 'text', nullable: true },
|
||||
],
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /identity/i }).click();
|
||||
await page.getByRole('option', { name: /id/i }).click();
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: tableName, exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should create table with foreign key constraint', async () => {
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const firstTableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: firstTableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'name', type: 'text' },
|
||||
],
|
||||
});
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const secondTableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: secondTableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'author_id', type: 'uuid' },
|
||||
],
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||
|
||||
// select column in current table
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.first()
|
||||
.click();
|
||||
await page.getByRole('option', { name: /author_id/i }).click();
|
||||
|
||||
// select reference schema
|
||||
await page.getByRole('button', { name: /schema/i }).click();
|
||||
await page.getByRole('option', { name: /public/i }).click();
|
||||
|
||||
// select reference table
|
||||
await page.getByRole('button', { name: /table/i }).click();
|
||||
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||
|
||||
// select reference column
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.nth(1)
|
||||
.click();
|
||||
await page.getByRole('option', { name: /id/i }).click();
|
||||
|
||||
await page.getByRole('button', { name: /add/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(`public.${firstTableName}.id`, { exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: secondTableName, exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should not be able to create a table with a name that already exists', async () => {
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const tableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'name', type: 'text' },
|
||||
],
|
||||
});
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'author_id', type: 'uuid' },
|
||||
],
|
||||
});
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(/error: a table with this name already exists/i),
|
||||
).toBeVisible();
|
||||
});
|
||||
191
dashboard/e2e/database/delete-table.test.ts
Normal file
191
dashboard/e2e/database/delete-table.test.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
TEST_PROJECT_NAME,
|
||||
TEST_PROJECT_SLUG,
|
||||
TEST_WORKSPACE_SLUG,
|
||||
} from '@/e2e/env';
|
||||
import { openProject, prepareTable } from '@/e2e/utils';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
await page.goto('/');
|
||||
|
||||
await openProject({
|
||||
page,
|
||||
projectName: TEST_PROJECT_NAME,
|
||||
workspaceSlug: TEST_WORKSPACE_SLUG,
|
||||
projectSlug: TEST_PROJECT_SLUG,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: /main navigation/i })
|
||||
.getByRole('link', { name: /database/i })
|
||||
.click();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should delete a table', async () => {
|
||||
const tableName = faker.random.word().toLowerCase();
|
||||
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'title', type: 'text' },
|
||||
],
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
|
||||
);
|
||||
|
||||
const tableLink = page.getByRole('link', {
|
||||
name: tableName,
|
||||
exact: true,
|
||||
});
|
||||
|
||||
await tableLink.hover();
|
||||
await page
|
||||
.getByRole('listitem')
|
||||
.filter({ hasText: tableName })
|
||||
.getByRole('button')
|
||||
.click();
|
||||
|
||||
await page.getByRole('menuitem', { name: /delete table/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /delete table/i }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: /delete/i }).click();
|
||||
|
||||
// navigate to next URL
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/**`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: tableName, exact: true }),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should not be able to delete a table if other tables have foreign keys referencing it', async () => {
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const firstTableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: firstTableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'name', type: 'text' },
|
||||
],
|
||||
});
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: /new table/i }).click();
|
||||
await expect(page.getByText(/create a new table/i)).toBeVisible();
|
||||
|
||||
const secondTableName = faker.random.word().toLowerCase();
|
||||
|
||||
await prepareTable({
|
||||
page,
|
||||
name: secondTableName,
|
||||
primaryKey: 'id',
|
||||
columns: [
|
||||
{ name: 'id', type: 'uuid', defaultValue: 'gen_random_uuid()' },
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'author_id', type: 'uuid' },
|
||||
],
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||
|
||||
// select column in current table
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.first()
|
||||
.click();
|
||||
await page.getByRole('option', { name: /author_id/i }).click();
|
||||
|
||||
// select reference schema
|
||||
await page.getByRole('button', { name: /schema/i }).click();
|
||||
await page.getByRole('option', { name: /public/i }).click();
|
||||
|
||||
// select reference table
|
||||
await page.getByRole('button', { name: /table/i }).click();
|
||||
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||
|
||||
// select reference column
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.nth(1)
|
||||
.click();
|
||||
await page.getByRole('option', { name: /id/i }).click();
|
||||
|
||||
await page.getByRole('button', { name: /add/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(`public.${firstTableName}.id`, { exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
// create table
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await page.waitForURL(
|
||||
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: secondTableName, exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
// try to delete the first table that is referenced by the second table
|
||||
const tableLink = page.getByRole('link', {
|
||||
name: firstTableName,
|
||||
exact: true,
|
||||
});
|
||||
|
||||
await tableLink.hover();
|
||||
await page
|
||||
.getByRole('listitem')
|
||||
.filter({ hasText: firstTableName })
|
||||
.getByRole('button')
|
||||
.click();
|
||||
|
||||
await page.getByRole('menuitem', { name: /delete table/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /delete table/i }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: /delete/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(
|
||||
/constraint [a-zA-Z_]+ on table [a-zA-Z_]+ depends on table [a-zA-Z_]+/i,
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
@@ -1,23 +1,25 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
TEST_DASHBOARD_URL,
|
||||
TEST_PROJECT_NAME,
|
||||
TEST_PROJECT_SLUG,
|
||||
TEST_WORKSPACE_NAME,
|
||||
TEST_WORKSPACE_SLUG,
|
||||
} from './env';
|
||||
} from '@/e2e/env';
|
||||
import { openProject } from '@/e2e/utils';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
|
||||
await page.goto(TEST_DASHBOARD_URL);
|
||||
await page.getByRole('link', { name: TEST_PROJECT_NAME }).click();
|
||||
await page.waitForURL(
|
||||
`${TEST_DASHBOARD_URL}/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}`,
|
||||
);
|
||||
await page.goto('/');
|
||||
await openProject({
|
||||
page,
|
||||
projectName: TEST_PROJECT_NAME,
|
||||
workspaceSlug: TEST_WORKSPACE_SLUG,
|
||||
projectSlug: TEST_PROJECT_SLUG,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
113
dashboard/e2e/utils.ts
Normal file
113
dashboard/e2e/utils.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Open a project by navigating to the project's overview page.
|
||||
*
|
||||
* @param page - The Playwright page object.
|
||||
* @param workspaceSlug - The slug of the workspace that contains the project.
|
||||
* @param projectSlug - The slug of the project to open.
|
||||
* @param projectName - The name of the project to open.
|
||||
* @returns A promise that resolves when the project is opened.
|
||||
*/
|
||||
export async function openProject({
|
||||
page,
|
||||
projectName,
|
||||
workspaceSlug,
|
||||
projectSlug,
|
||||
}: {
|
||||
page: Page;
|
||||
workspaceSlug: string;
|
||||
projectSlug: string;
|
||||
projectName: string;
|
||||
}) {
|
||||
await page.getByRole('link', { name: projectName }).click();
|
||||
await page.waitForURL(`/${workspaceSlug}/${projectSlug}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a table by filling out the form.
|
||||
*
|
||||
* @param page - The Playwright page object.
|
||||
* @param name - The name of the table to create.
|
||||
* @param columns - The columns to create in the table.
|
||||
* @returns A promise that resolves when the table is prepared.
|
||||
*/
|
||||
export async function prepareTable({
|
||||
page,
|
||||
name: tableName,
|
||||
primaryKey,
|
||||
columns,
|
||||
}: {
|
||||
page: Page;
|
||||
name: string;
|
||||
primaryKey: string;
|
||||
columns: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
nullable?: boolean;
|
||||
unique?: boolean;
|
||||
defaultValue?: string;
|
||||
}>;
|
||||
}) {
|
||||
if (!columns.some(({ name }) => name === primaryKey)) {
|
||||
throw new Error('Primary key must be one of the columns.');
|
||||
}
|
||||
|
||||
await page.getByRole('textbox', { name: /name/i }).first().fill(tableName);
|
||||
|
||||
await Promise.all(
|
||||
columns.map(
|
||||
async (
|
||||
{ name: columnName, type, nullable, unique, defaultValue },
|
||||
index,
|
||||
) => {
|
||||
// set name
|
||||
await page.getByPlaceholder(/name/i).nth(index).fill(columnName);
|
||||
|
||||
// set type
|
||||
await page
|
||||
.getByRole('combobox', { name: /type/i })
|
||||
.nth(index)
|
||||
.fill(type);
|
||||
await page.getByRole('option', { name: type }).first().click();
|
||||
|
||||
// optionally set default value
|
||||
if (defaultValue) {
|
||||
await page
|
||||
.getByRole('combobox', { name: /default value/i })
|
||||
.first()
|
||||
.fill(defaultValue);
|
||||
await page
|
||||
.getByRole('option', { name: defaultValue })
|
||||
.first()
|
||||
.click();
|
||||
}
|
||||
|
||||
// optionally check unique
|
||||
if (unique) {
|
||||
await page
|
||||
.getByRole('checkbox', { name: /unique/i })
|
||||
.nth(index)
|
||||
.check();
|
||||
}
|
||||
|
||||
// optionally check nullable
|
||||
if (nullable) {
|
||||
await page
|
||||
.getByRole('checkbox', { name: /nullable/i })
|
||||
.nth(index)
|
||||
.check();
|
||||
}
|
||||
|
||||
// add new column if not last
|
||||
if (index < columns.length - 1) {
|
||||
await page.getByRole('button', { name: /add column/i }).click();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// select the first column as primary key
|
||||
await page.getByRole('button', { name: /primary key/i }).click();
|
||||
await page.getByRole('option', { name: primaryKey, exact: true }).click();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
schema:
|
||||
- http://localhost:1337/v1/graphql:
|
||||
- https://local.graphql.nhost.run/v1:
|
||||
headers:
|
||||
x-hasura-admin-secret: nhost-admin-secret
|
||||
generates:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "0.13.8",
|
||||
"version": "0.13.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -18,7 +18,7 @@
|
||||
"e2e": "npx playwright@1.31.2 install --with-deps && playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.3",
|
||||
"@apollo/client": "^3.7.10",
|
||||
"@codemirror/language": "^6.3.0",
|
||||
"@emotion/cache": "^11.10.5",
|
||||
"@emotion/react": "^11.10.5",
|
||||
@@ -71,7 +71,7 @@
|
||||
"react-merge-refs": "^1.1.0",
|
||||
"react-syntax-highlighter": "^15.4.5",
|
||||
"react-table": "^7.8.0",
|
||||
"sharp": "^0.31.2",
|
||||
"sharp": "^0.32.0",
|
||||
"slugify": "^1.6.5",
|
||||
"stripe": "^10.17.0",
|
||||
"tailwind-merge": "^1.8.0",
|
||||
@@ -82,6 +82,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.2",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "^3.0.0",
|
||||
"@graphql-codegen/typescript": "^3.0.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.1",
|
||||
|
||||
@@ -12,11 +12,8 @@ export default defineConfig({
|
||||
timeout: 5000,
|
||||
},
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: 'html',
|
||||
globalSetup: require.resolve('./global-setup'),
|
||||
@@ -24,41 +21,12 @@ export default defineConfig({
|
||||
actionTimeout: 0,
|
||||
trace: 'on-first-retry',
|
||||
storageState: 'storageState.json',
|
||||
baseURL: process.env.NHOST_TEST_DASHBOARD_URL,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import Link from 'next/link';
|
||||
|
||||
export default function Sidebar() {
|
||||
return (
|
||||
<div className="grid grid-flow-row gap-8 mt-2 ml-10 w-full md:grid md:w-workspaceSidebar content-start">
|
||||
<div className="mt-2 grid w-full grid-flow-row content-start gap-8 md:ml-10 md:grid md:w-workspaceSidebar">
|
||||
<WorkspaceSection />
|
||||
<Resources />
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ import { UserDataProvider } from '@/context/workspace1-context';
|
||||
import type { Project } from '@/types/application';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import type { Workspace } from '@/types/workspace';
|
||||
import nhostGraphQLLink from '@/utils/msw/mocks/graphql/nhostGraphQLLink';
|
||||
import { render, screen, waitForElementToBeRemoved } from '@/utils/testUtils';
|
||||
import { graphql, rest } from 'msw';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { afterAll, beforeAll, vi } from 'vitest';
|
||||
import OverviewDeployments from '.';
|
||||
@@ -73,13 +74,11 @@ const mockWorkspace: Workspace = {
|
||||
applications: [mockApplication],
|
||||
};
|
||||
|
||||
const mockGraphqlLink = graphql.link('http://localhost:1337/v1/graphql');
|
||||
|
||||
const server = setupServer(
|
||||
rest.get('http://localhost:1337/v1/graphql', (req, res, ctx) =>
|
||||
rest.get('https://local.graphql.nhost.run/v1', (_req, res, ctx) =>
|
||||
res(ctx.status(200)),
|
||||
),
|
||||
mockGraphqlLink.operation(async (req, res, ctx) =>
|
||||
nhostGraphQLLink.operation(async (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.data({
|
||||
deployments: [],
|
||||
@@ -143,7 +142,7 @@ test('should render an empty state when GitHub is connected, but there are no de
|
||||
|
||||
test('should render a list of deployments', async () => {
|
||||
server.use(
|
||||
mockGraphqlLink.operation(async (req, res, ctx) => {
|
||||
nhostGraphQLLink.operation(async (req, res, ctx) => {
|
||||
const requestPayload = await req.json();
|
||||
|
||||
if (requestPayload.operationName === 'ScheduledOrPendingDeploymentsSub') {
|
||||
@@ -193,7 +192,7 @@ test('should render a list of deployments', async () => {
|
||||
|
||||
test('should disable redeployments if a deployment is already in progress', async () => {
|
||||
server.use(
|
||||
mockGraphqlLink.operation(async (req, res, ctx) => {
|
||||
nhostGraphQLLink.operation(async (req, res, ctx) => {
|
||||
const requestPayload = await req.json();
|
||||
|
||||
if (requestPayload.operationName === 'ScheduledOrPendingDeploymentsSub') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { graphql } from 'msw';
|
||||
|
||||
const nhostGraphQLLink = graphql.link('http://localhost:1337/v1/graphql');
|
||||
const nhostGraphQLLink = graphql.link('https://local.graphql.nhost.run/v1');
|
||||
|
||||
export default nhostGraphQLLink;
|
||||
|
||||
@@ -79,7 +79,7 @@ function Providers({ children }: PropsWithChildren<{}>) {
|
||||
<NhostApolloProvider
|
||||
nhost={nhost}
|
||||
link={createHttpLink({
|
||||
uri: 'http://localhost:1337/v1/graphql',
|
||||
uri: 'https://local.graphql.nhost.run/v1',
|
||||
})}
|
||||
>
|
||||
<WorkspaceProvider>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"baseUrl": "./src",
|
||||
"useUnknownInCatchVariables": false,
|
||||
"paths": {
|
||||
"@/e2e/*": ["../e2e/*"],
|
||||
"@/components/*": ["components/*"],
|
||||
"@/hooks/*": ["hooks/*"],
|
||||
"@/utils/*": ["utils/*"],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"jsx": "react-jsx",
|
||||
"types": ["vitest/globals"],
|
||||
"paths": {
|
||||
"@/e2e/*": ["../e2e/*"],
|
||||
"@/components/*": ["components/*"],
|
||||
"@/hooks/*": ["hooks/*"],
|
||||
"@/utils/*": ["utils/*"],
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^4.9.1",
|
||||
"@docusaurus/core": "2.3.1",
|
||||
"@docusaurus/plugin-sitemap": "2.3.1",
|
||||
"@docusaurus/preset-classic": "2.3.1",
|
||||
"@docusaurus/core": "2.4.0",
|
||||
"@docusaurus/plugin-sitemap": "2.4.0",
|
||||
"@docusaurus/preset-classic": "2.4.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-plugin-image-zoom": "^0.1.1",
|
||||
@@ -30,7 +30,7 @@
|
||||
"unist-util-visit": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.3.1",
|
||||
"@docusaurus/module-type-aliases": "2.4.0",
|
||||
"@tsconfig/docusaurus": "^1.0.6",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/apollo
|
||||
|
||||
## 5.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@2.1.2
|
||||
|
||||
## 5.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 912ed76c: fix(apollo): retry subscriptions on error
|
||||
|
||||
## 5.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "5.1.1",
|
||||
"version": "5.1.3",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -59,15 +59,15 @@
|
||||
"verify:fix": "run-p prettier:fix lint:fix"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nhost/nhost-js": "workspace:*",
|
||||
"@apollo/client": "^3.6.2"
|
||||
"@apollo/client": "^3.7.10",
|
||||
"@nhost/nhost-js": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"graphql": "16.6.0",
|
||||
"graphql-ws": "^5.10.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nhost/nhost-js": "workspace:*",
|
||||
"@apollo/client": "^3.7.3"
|
||||
"@apollo/client": "^3.7.10",
|
||||
"@nhost/nhost-js": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
ApolloClient,
|
||||
ApolloClientOptions,
|
||||
createHttpLink,
|
||||
from,
|
||||
InMemoryCache,
|
||||
@@ -38,16 +37,19 @@ export const createApolloClient = ({
|
||||
connectToDevTools = isBrowser && process.env.NODE_ENV === 'development',
|
||||
onError,
|
||||
link: customLink
|
||||
}: NhostApolloClientOptions): ApolloClient<any> => {
|
||||
let backendUrl = graphqlUrl || nhost?.graphql.getUrl()
|
||||
}: NhostApolloClientOptions) => {
|
||||
const backendUrl = graphqlUrl || nhost?.graphql.httpUrl
|
||||
|
||||
if (!backendUrl) {
|
||||
throw Error("Can't initialize the Apollo Client: no backend Url has been provided")
|
||||
}
|
||||
|
||||
const uri = backendUrl
|
||||
const interpreter = nhost?.auth.client.interpreter
|
||||
|
||||
let token: string | null = null
|
||||
|
||||
const getAuthHeaders = () => {
|
||||
function getAuthHeaders() {
|
||||
// add headers
|
||||
const resHeaders = {
|
||||
...headers,
|
||||
@@ -66,33 +68,28 @@ export const createApolloClient = ({
|
||||
return resHeaders
|
||||
}
|
||||
|
||||
const uri = backendUrl
|
||||
|
||||
const wsClient =
|
||||
isBrowser &&
|
||||
createRestartableClient({
|
||||
url: uri.startsWith('https') ? uri.replace(/^https/, 'wss') : uri.replace(/^http/, 'ws'),
|
||||
connectionParams: () => ({
|
||||
headers: {
|
||||
...headers,
|
||||
...getAuthHeaders()
|
||||
}
|
||||
const wsClient = isBrowser
|
||||
? createRestartableClient({
|
||||
url: uri.startsWith('https') ? uri.replace(/^https/, 'wss') : uri.replace(/^http/, 'ws'),
|
||||
shouldRetry: () => true,
|
||||
retryAttempts: 10,
|
||||
connectionParams: () => ({
|
||||
headers: {
|
||||
...headers,
|
||||
...getAuthHeaders()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
const wsLink = wsClient && new GraphQLWsLink(wsClient)
|
||||
: null
|
||||
|
||||
const httpLink = setContext((_, { headers }) => {
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
...getAuthHeaders()
|
||||
}
|
||||
const wsLink = wsClient ? new GraphQLWsLink(wsClient) : null
|
||||
|
||||
const httpLink = setContext((_, { headers }) => ({
|
||||
headers: {
|
||||
...headers,
|
||||
...getAuthHeaders()
|
||||
}
|
||||
}).concat(
|
||||
createHttpLink({
|
||||
uri
|
||||
})
|
||||
)
|
||||
})).concat(createHttpLink({ uri }))
|
||||
|
||||
const link = wsLink
|
||||
? split(
|
||||
@@ -112,7 +109,7 @@ export const createApolloClient = ({
|
||||
)
|
||||
: httpLink
|
||||
|
||||
const apolloClientOptions: ApolloClientOptions<any> = {
|
||||
const client = new ApolloClient({
|
||||
cache: cache || new InMemoryCache(),
|
||||
ssrMode: !isBrowser,
|
||||
defaultOptions: {
|
||||
@@ -120,34 +117,35 @@ export const createApolloClient = ({
|
||||
fetchPolicy
|
||||
}
|
||||
},
|
||||
connectToDevTools
|
||||
}
|
||||
|
||||
// add link
|
||||
if (customLink) {
|
||||
apolloClientOptions.link = from([customLink])
|
||||
} else {
|
||||
apolloClientOptions.link = typeof onError === 'function' ? from([onError, link]) : from([link])
|
||||
}
|
||||
|
||||
const client = new ApolloClient(apolloClientOptions)
|
||||
connectToDevTools,
|
||||
link: customLink
|
||||
? from([customLink])
|
||||
: from(typeof onError === 'function' ? [onError, link] : [link])
|
||||
})
|
||||
|
||||
interpreter?.onTransition(async (state, event) => {
|
||||
if (['SIGNOUT', 'SIGNED_IN', 'TOKEN_CHANGED'].includes(event.type)) {
|
||||
const newToken = state.context.accessToken.value
|
||||
token = newToken
|
||||
if (event.type === 'SIGNOUT') {
|
||||
token = null
|
||||
|
||||
try {
|
||||
await client.resetStore()
|
||||
} catch (error) {
|
||||
console.error('Error resetting Apollo client cache')
|
||||
console.error(error)
|
||||
}
|
||||
} else {
|
||||
if (isBrowser && wsClient && wsClient.started()) {
|
||||
wsClient.restart()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// update token
|
||||
token = state.context.accessToken.value
|
||||
|
||||
if (!isBrowser) {
|
||||
return
|
||||
}
|
||||
|
||||
wsClient?.restart()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Client, ClientOptions, createClient } from 'graphql-ws'
|
||||
|
||||
export interface RestartableClient extends Client {
|
||||
restart(): void
|
||||
started(): boolean
|
||||
}
|
||||
|
||||
export function createRestartableClient(options: ClientOptions): RestartableClient {
|
||||
@@ -11,18 +10,41 @@ export function createRestartableClient(options: ClientOptions): RestartableClie
|
||||
let restart = () => {
|
||||
restartRequested = true
|
||||
}
|
||||
let _started = false
|
||||
const started = () => _started
|
||||
let socket: WebSocket
|
||||
let timedOut: NodeJS.Timeout
|
||||
|
||||
const client = createClient({
|
||||
...options,
|
||||
on: {
|
||||
...options.on,
|
||||
connected: () => {
|
||||
_started = true
|
||||
error: (error) => {
|
||||
console.error(error)
|
||||
options.on?.error?.(error)
|
||||
|
||||
restart()
|
||||
},
|
||||
ping: (received) => {
|
||||
if (!received /* sent */) {
|
||||
timedOut = setTimeout(() => {
|
||||
// a close event `4499: Terminated` is issued to the current WebSocket and an
|
||||
// artificial `{ code: 4499, reason: 'Terminated', wasClean: false }` close-event-like
|
||||
// object is immediately emitted without waiting for the one coming from `WebSocket.onclose`
|
||||
//
|
||||
// calling terminate is not considered fatal and a connection retry will occur as expected
|
||||
//
|
||||
// see: https://github.com/enisdenjo/graphql-ws/discussions/290
|
||||
client.terminate()
|
||||
restart()
|
||||
}, 5_000)
|
||||
}
|
||||
},
|
||||
pong: (received) => {
|
||||
if (received) {
|
||||
clearTimeout(timedOut)
|
||||
}
|
||||
},
|
||||
opened: (originalSocket) => {
|
||||
const socket = originalSocket as WebSocket
|
||||
socket = originalSocket as WebSocket
|
||||
options.on?.opened?.(socket)
|
||||
|
||||
restart = () => {
|
||||
@@ -47,7 +69,6 @@ export function createRestartableClient(options: ClientOptions): RestartableClie
|
||||
|
||||
return {
|
||||
...client,
|
||||
restart: () => restart(),
|
||||
started
|
||||
restart: () => restart()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 5.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@5.1.3
|
||||
- @nhost/react@2.0.12
|
||||
|
||||
## 5.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 912ed76c: fix(apollo): retry subscriptions on error
|
||||
- Updated dependencies [912ed76c]
|
||||
- @nhost/apollo@5.1.2
|
||||
|
||||
## 5.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "5.0.12",
|
||||
"version": "5.0.14",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -63,14 +63,14 @@
|
||||
"@nhost/apollo": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apollo/client": "^3.6.2",
|
||||
"@apollo/client": "^3.7.10",
|
||||
"@nhost/react": "workspace:*",
|
||||
"graphql": "^16.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/client": "^3.7.1",
|
||||
"@apollo/client": "^3.7.10",
|
||||
"@nhost/react": "workspace:*",
|
||||
"@types/react": "^18.0.25",
|
||||
"graphql": "16.6.0",
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
} from '@apollo/client'
|
||||
import { useAuthenticated } from '@nhost/react'
|
||||
|
||||
export function useAuthQuery<TData = any, TVariables = OperationVariables>(
|
||||
export function useAuthQuery<
|
||||
TData = any,
|
||||
TVariables extends OperationVariables = OperationVariables
|
||||
>(
|
||||
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
|
||||
options?: QueryHookOptions<TData, TVariables>
|
||||
) {
|
||||
@@ -18,7 +21,10 @@ export function useAuthQuery<TData = any, TVariables = OperationVariables>(
|
||||
return useQuery(query, newOptions)
|
||||
}
|
||||
|
||||
export function useAuthSubscription<TData = any, TVariables = OperationVariables>(
|
||||
export function useAuthSubscription<
|
||||
TData = any,
|
||||
TVariables extends OperationVariables = OperationVariables
|
||||
>(
|
||||
subscription: DocumentNode | TypedDocumentNode<TData, TVariables>,
|
||||
options?: SubscriptionHookOptions<TData, TVariables>
|
||||
) {
|
||||
|
||||
@@ -22,9 +22,7 @@ export const NhostApolloProvider: React.FC<PropsWithChildren<NhostApolloClientOp
|
||||
if (!client) {
|
||||
setClient(createApolloClient(options))
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [client, options])
|
||||
|
||||
return <ApolloProvider client={client || mockApolloClient}>{children}</ApolloProvider>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/react-urql
|
||||
|
||||
## 2.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@2.0.12
|
||||
|
||||
## 2.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-urql",
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"description": "Nhost React URQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/hasura-storage-js
|
||||
|
||||
## 2.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 43c86fef: chore: improve presignedUrl test
|
||||
|
||||
## 2.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/hasura-storage-js",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"description": "Hasura-storage client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -66,6 +66,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nhost/docgen": "workspace:*",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"pixelmatch": "^5.3.0",
|
||||
"start-server-and-test": "^1.15.2",
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import fs from 'fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { storage } from './utils/helpers'
|
||||
import FormData from 'form-data'
|
||||
import fs from 'fs'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { storage } from './utils/helpers'
|
||||
|
||||
describe('test get presigned url of file', () => {
|
||||
it('should be able to get presigned url of file', async () => {
|
||||
const fd = new FormData()
|
||||
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
|
||||
const formData = new FormData()
|
||||
formData.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
|
||||
|
||||
const { fileMetadata } = await storage.upload({
|
||||
formData: fd
|
||||
})
|
||||
const { fileMetadata } = await storage.upload({ formData })
|
||||
|
||||
const { error } = await storage.getPresignedUrl({
|
||||
const { presignedUrl, error } = await storage.getPresignedUrl({
|
||||
fileId: fileMetadata?.id as string
|
||||
})
|
||||
|
||||
expect(presignedUrl).not.toBeNull()
|
||||
expect(error).toBeNull()
|
||||
|
||||
const imageResponse = await fetch(presignedUrl!.url)
|
||||
|
||||
expect(imageResponse.ok).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should fail to get presigned url of file that does not exist', async () => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/nextjs
|
||||
|
||||
## 1.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@2.0.12
|
||||
|
||||
## 1.13.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nextjs",
|
||||
"version": "1.13.17",
|
||||
"version": "1.13.18",
|
||||
"description": "Nhost NextJS library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/nhost-js
|
||||
|
||||
## 2.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [43c86fef]
|
||||
- @nhost/hasura-storage-js@2.0.5
|
||||
|
||||
## 2.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nhost-js",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.2",
|
||||
"description": "Nhost JavaScript SDK",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/react
|
||||
|
||||
## 2.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@2.1.2
|
||||
|
||||
## 2.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react",
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"description": "Nhost React library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useAuthenticationStatus } from '../useAuthenticationStatus'
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { NhostProvider, SignedOut } from "@nhost/react";
|
||||
* import { NhostProvider, SignedIn } from "@nhost/react";
|
||||
* import { nhost } from '@/utils/nhost';
|
||||
*
|
||||
* function Page() {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/sync-versions
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4713cecf: chore(deps): bump `@pnpm/find-workspace-dir` to v6
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@nhost/sync-versions",
|
||||
"description": "Sync the versions of Nhost services in each of the packages of a pnpm workspace",
|
||||
"private": true,
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -33,7 +33,7 @@
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/find-workspace-dir": "^5.0.0",
|
||||
"@pnpm/find-workspace-dir": "^6.0.0",
|
||||
"glob": "^9.0.0",
|
||||
"object-path": "^0.11.8",
|
||||
"yaml": "^2.1.1"
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/vue
|
||||
|
||||
## 1.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@2.1.2
|
||||
|
||||
## 1.13.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/vue",
|
||||
"version": "1.13.17",
|
||||
"version": "1.13.18",
|
||||
"description": "Nhost Vue library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
642
pnpm-lock.yaml
generated
642
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user