Compare commits

..

35 Commits

Author SHA1 Message Date
Szilárd Dóró
19818e2b59 Merge pull request #1777 from nhost/changeset-release/main
chore: update versions
2023-03-27 12:03:16 +02:00
github-actions[bot]
b3eeec82ef chore: update versions 2023-03-27 09:38:55 +00:00
Szilárd Dóró
34ff254696 Merge pull request #1782 from nhost/renovate/sharp-0.x
fix(deps): update dependency sharp to ^0.32.0
2023-03-27 11:37:33 +02:00
Szilárd Dóró
1c4806bf51 chore: add changeset 2023-03-27 11:17:41 +02:00
renovate[bot]
2fb82ec97d fix(deps): update dependency sharp to ^0.32.0 2023-03-27 07:50:45 +00:00
Szilárd Dóró
0c994a9651 Merge pull request #1779 from nhost/renovate/pnpm-find-workspace-dir-6.x
fix(deps): update dependency @pnpm/find-workspace-dir to v6
2023-03-27 09:48:14 +02:00
Szilárd Dóró
4713cecfc2 chore: add changeset 2023-03-27 09:26:44 +02:00
renovate[bot]
f79eebadbf fix(deps): update dependency @pnpm/find-workspace-dir to v6 2023-03-24 21:30:22 +00:00
Szilárd Dóró
ac174b5e51 Merge pull request #1780 from nhost/chore/vercel-preview-fetcher 2023-03-24 17:07:43 +01:00
Szilárd Dóró
dc9ddfc9ae chore(ci): adjust preview fetcher 2023-03-24 16:30:29 +01:00
Szilárd Dóró
3bdd9f570c Merge pull request #1773 from nhost/chore/dashboard-delete-table-tests
chore(dashboard): tests for table deletion
2023-03-24 15:52:25 +01:00
Szilárd Dóró
94477be998 Merge pull request #1778 from nhost/chore/fetch-preview-url
chore: use dynamic test URL
2023-03-24 15:51:47 +01:00
Szilárd Dóró
568577e8ca Merge pull request #1774 from nhost/renovate/docusaurus-monorepo
fix(deps): update docusaurus monorepo to v2.4.0
2023-03-24 15:38:23 +01:00
Szilárd Dóró
e93b06ab8f chore: add changeset 2023-03-24 15:37:08 +01:00
Szilárd Dóró
c75bf46ba1 fix: fetch valid previews only 2023-03-24 15:24:36 +01:00
Szilárd Dóró
63a1fd09b5 fix: use correct Vercel token 2023-03-24 15:09:42 +01:00
Szilárd Dóró
630d44ad6e fix: use staging project ID 2023-03-24 14:55:26 +01:00
Szilárd Dóró
d7db521974 chore: use dynamic test URL 2023-03-24 14:16:05 +01:00
renovate[bot]
90e4053f0a fix(deps): update docusaurus monorepo to v2.4.0 2023-03-24 09:57:19 +00:00
Szilárd Dóró
8e9d5d1b38 Merge pull request #1775 from nhost/fix/storage-sdk-tests
chore(hasura-storage-js): improve presignedUrl test
2023-03-24 10:54:51 +01:00
Szilárd Dóró
43c86fef14 chore(hasura-storage-js): improve presignedUrl test 2023-03-24 10:25:18 +01:00
Szilárd Dóró
6b97340cf4 fix: remove test.only call 2023-03-23 16:14:49 +01:00
Szilárd Dóró
1605756362 chore: add tests for table deletion 2023-03-23 16:05:21 +01:00
Szilárd Dóró
6437544384 Merge pull request #1771 from nhost/changeset-release/main
chore: update versions
2023-03-23 14:20:16 +01:00
github-actions[bot]
b4dcd1996d chore: update versions 2023-03-23 13:01:48 +00:00
Szilárd Dóró
7fb73dbb1b Merge pull request #1770 from nhost/fix/subscription-errors
fix(apollo): retry subscriptions on error
2023-03-23 14:00:11 +01:00
Szilárd Dóró
a66b11d245 Merge pull request #1769 from st3phan/patch-1
Fix import in docs for SignedIn component
2023-03-23 13:23:35 +01:00
Szilárd Dóró
912ed76c64 fix: potential subscription fix 2023-03-23 12:30:14 +01:00
Szilárd Dóró
b47c0d1af7 Merge pull request #1765 from nhost/chore/dashboard-db-tests
chore(dashboard): tests for table creation
2023-03-23 09:36:27 +01:00
Stephan van Opstal
b97ab2be2f Fix import in docs 2023-03-22 21:46:58 +01:00
Szilárd Dóró
f12cb666ff fix: remove test.only call 2023-03-22 15:42:05 +01:00
Szilárd Dóró
c3b2b1cd02 chore: add remaining table creation tests 2023-03-22 15:40:39 +01:00
Szilárd Dóró
c0b71102d4 chore: add foreign key constraint test 2023-03-22 15:32:18 +01:00
Szilárd Dóró
5f68ae95c4 chore: add extra database UI tests 2023-03-22 15:22:49 +01:00
Szilárd Dóró
2d1b7bb292 chore: restructure tests, add basic table creation test 2023-03-22 14:57:33 +01:00
43 changed files with 1162 additions and 486 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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 () => {

View 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();
});

View 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();
});

View File

@@ -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
View 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();
}

View File

@@ -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:

View File

@@ -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",

View File

@@ -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' },
// },
],
});

View File

@@ -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 />

View File

@@ -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') {

View File

@@ -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;

View File

@@ -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>

View File

@@ -19,6 +19,7 @@
"baseUrl": "./src",
"useUnknownInCatchVariables": false,
"paths": {
"@/e2e/*": ["../e2e/*"],
"@/components/*": ["components/*"],
"@/hooks/*": ["hooks/*"],
"@/utils/*": ["utils/*"],

View File

@@ -4,6 +4,7 @@
"jsx": "react-jsx",
"types": ["vitest/globals"],
"paths": {
"@/e2e/*": ["../e2e/*"],
"@/components/*": ["components/*"],
"@/hooks/*": ["hooks/*"],
"@/utils/*": ["utils/*"],

View File

@@ -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"
},

View File

@@ -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

View File

@@ -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:*"
}
}

View File

@@ -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()
}
})

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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>
) {

View File

@@ -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>
}

View File

@@ -1,5 +1,11 @@
# @nhost/react-urql
## 2.0.12
### Patch Changes
- @nhost/react@2.0.12
## 2.0.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-urql",
"version": "2.0.11",
"version": "2.0.12",
"description": "Nhost React URQL client",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/hasura-storage-js
## 2.0.5
### Patch Changes
- 43c86fef: chore: improve presignedUrl test
## 2.0.4
### Patch Changes

View File

@@ -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",

View File

@@ -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 () => {

View File

@@ -1,5 +1,11 @@
# @nhost/nextjs
## 1.13.18
### Patch Changes
- @nhost/react@2.0.12
## 1.13.17
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nextjs",
"version": "1.13.17",
"version": "1.13.18",
"description": "Nhost NextJS library",
"license": "MIT",
"keywords": [

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nhost-js",
"version": "2.1.1",
"version": "2.1.2",
"description": "Nhost JavaScript SDK",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/react
## 2.0.12
### Patch Changes
- @nhost/nhost-js@2.1.2
## 2.0.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react",
"version": "2.0.11",
"version": "2.0.12",
"description": "Nhost React library",
"license": "MIT",
"keywords": [

View File

@@ -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() {

View File

@@ -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

View File

@@ -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"

View File

@@ -1,5 +1,11 @@
# @nhost/vue
## 1.13.18
### Patch Changes
- @nhost/nhost-js@2.1.2
## 1.13.17
### Patch Changes

View File

@@ -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

File diff suppressed because it is too large Load Diff