Compare commits
10 Commits
@nhost/das
...
@nhost/nho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf347341d4 | ||
|
|
d6c670a78b | ||
|
|
197e406209 | ||
|
|
5fc78964dc | ||
|
|
5880f0cd17 | ||
|
|
b2b17d71aa | ||
|
|
0785a41070 | ||
|
|
c6c8569088 | ||
|
|
c38f60740e | ||
|
|
a37a430b9f |
@@ -1,5 +1,13 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 0.11.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a37a430b: fix(dashboard): don't break UI when deployments are unavailable
|
||||||
|
- @nhost/react-apollo@4.13.4
|
||||||
|
- @nhost/nextjs@1.13.4
|
||||||
|
|
||||||
## 0.11.9
|
## 0.11.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "0.11.9",
|
"version": "0.11.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jsdom": "^21.0.0",
|
"jsdom": "^21.0.0",
|
||||||
"lint-staged": ">=13",
|
"lint-staged": ">=13",
|
||||||
"msw": "^0.49.0",
|
"msw": "^1.0.1",
|
||||||
"msw-storybook-addon": "^1.6.3",
|
"msw-storybook-addon": "^1.6.3",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.19",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
|||||||
@@ -111,9 +111,9 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { deployments } = deploymentPageData || {};
|
const { deployments } = deploymentPageData || { deployments: [] };
|
||||||
const { deployments: scheduledOrPendingDeployments } =
|
const { deployments: scheduledOrPendingDeployments } =
|
||||||
scheduledOrPendingDeploymentsData || {};
|
scheduledOrPendingDeploymentsData || { deployments: [] };
|
||||||
|
|
||||||
const latestDeployment = latestDeploymentData?.deployments[0];
|
const latestDeployment = latestDeploymentData?.deployments[0];
|
||||||
const latestLiveDeployment = latestLiveDeploymentData?.deployments[0];
|
const latestLiveDeployment = latestLiveDeploymentData?.deployments[0];
|
||||||
@@ -135,7 +135,7 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
deployment={deployment}
|
deployment={deployment}
|
||||||
isLive={liveDeploymentId === deployment.id}
|
isLive={liveDeploymentId === deployment.id}
|
||||||
showRedeploy={latestDeployment.id === deployment.id}
|
showRedeploy={latestDeployment.id === deployment.id}
|
||||||
disableRedeploy={scheduledOrPendingDeployments.length > 0}
|
disableRedeploy={scheduledOrPendingDeployments?.length > 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{index !== deployments.length - 1 && <Divider component="li" />}
|
{index !== deployments.length - 1 && <Divider component="li" />}
|
||||||
@@ -143,7 +143,7 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
<div className="mt-8 flex w-full justify-center">
|
<div className="mt-8 flex w-full justify-center">
|
||||||
<div className="grid grid-flow-col gap-2 items-center">
|
<div className="grid grid-flow-col items-center gap-2">
|
||||||
<NextPrevPageLink
|
<NextPrevPageLink
|
||||||
direction="prev"
|
direction="prev"
|
||||||
prevAllowed={page !== 1}
|
prevAllowed={page !== 1}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
import { features } from '@/components/overview/features';
|
import { features } from '@/components/overview/features';
|
||||||
import { frameworks } from '@/components/overview/frameworks';
|
import { frameworks } from '@/components/overview/frameworks';
|
||||||
@@ -55,7 +56,9 @@ export default function ApplicationLive() {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 gap-12 pt-3 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-12 pt-3 lg:grid-cols-3">
|
||||||
<div className="order-2 grid grid-flow-row gap-12 lg:order-1 lg:col-span-2">
|
<div className="order-2 grid grid-flow-row gap-12 lg:order-1 lg:col-span-2">
|
||||||
<OverviewDeployments />
|
<RetryableErrorBoundary>
|
||||||
|
<OverviewDeployments />
|
||||||
|
</RetryableErrorBoundary>
|
||||||
|
|
||||||
<OverviewDocumentation
|
<OverviewDocumentation
|
||||||
title="Pick your favorite framework and start learning"
|
title="Pick your favorite framework and start learning"
|
||||||
|
|||||||
@@ -58,9 +58,10 @@ export default function DeploymentListItem({
|
|||||||
return (
|
return (
|
||||||
<ListItem.Root>
|
<ListItem.Root>
|
||||||
<ListItem.Button
|
<ListItem.Button
|
||||||
className="grid grid-flow-col items-center justify-between gap-2 px-2 py-2"
|
className="grid grid-flow-col items-center justify-between gap-2 rounded-none px-2 py-2"
|
||||||
component={NavLink}
|
component={NavLink}
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
||||||
|
aria-label={commitMessage || 'No commit message'}
|
||||||
>
|
>
|
||||||
<div className="flex cursor-pointer flex-row items-center justify-center space-x-2 self-center">
|
<div className="flex cursor-pointer flex-row items-center justify-center space-x-2 self-center">
|
||||||
<ListItem.Avatar>
|
<ListItem.Avatar>
|
||||||
@@ -83,10 +84,14 @@ export default function DeploymentListItem({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-flow-col gap-2 items-center">
|
<div className="grid grid-flow-col items-center gap-2">
|
||||||
{showRedeploy && (
|
{showRedeploy && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Deployments cannot be re-triggered when a deployment is in progress."
|
title={
|
||||||
|
!disableRedeploy && !loading
|
||||||
|
? 'Deployments cannot be re-triggered when a deployment is in progress.'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
hasDisabledChildren={disableRedeploy || loading}
|
hasDisabledChildren={disableRedeploy || loading}
|
||||||
disableHoverListener={!disableRedeploy}
|
disableHoverListener={!disableRedeploy}
|
||||||
>
|
>
|
||||||
@@ -123,9 +128,10 @@ export default function DeploymentListItem({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
startIcon={
|
startIcon={
|
||||||
<ArrowCounterclockwiseIcon className={twMerge('w-4 h-4')} />
|
<ArrowCounterclockwiseIcon className={twMerge('h-4 w-4')} />
|
||||||
}
|
}
|
||||||
className="rounded-full py-1 px-2 text-xs"
|
className="rounded-full py-1 px-2 text-xs"
|
||||||
|
aria-label="Redeploy"
|
||||||
>
|
>
|
||||||
Redeploy
|
Redeploy
|
||||||
</Button>
|
</Button>
|
||||||
@@ -133,7 +139,7 @@ export default function DeploymentListItem({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isLive && (
|
{isLive && (
|
||||||
<div className="w-12 flex justify-end">
|
<div className="flex w-12 justify-end">
|
||||||
<Chip size="small" color="success" label="Live" />
|
<Chip size="small" color="success" label="Live" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import { UserDataProvider } from '@/context/workspace1-context';
|
||||||
|
import type { Application } from '@/types/application';
|
||||||
|
import { ApplicationStatus } from '@/types/application';
|
||||||
|
import type { Workspace } from '@/types/workspace';
|
||||||
|
import { render, screen, waitForElementToBeRemoved } from '@/utils/testUtils';
|
||||||
|
import { graphql, rest } from 'msw';
|
||||||
|
import { setupServer } from 'msw/node';
|
||||||
|
import { afterAll, beforeAll, vi } from 'vitest';
|
||||||
|
import OverviewDeployments from '.';
|
||||||
|
|
||||||
|
vi.mock('next/router', () => ({
|
||||||
|
useRouter: vi.fn().mockReturnValue({
|
||||||
|
basePath: '',
|
||||||
|
pathname: '/test-workspace/test-application',
|
||||||
|
route: '/[workspaceSlug]/[appSlug]',
|
||||||
|
asPath: '/test-workspace/test-application',
|
||||||
|
isLocaleDomain: false,
|
||||||
|
isReady: true,
|
||||||
|
isPreview: false,
|
||||||
|
query: {
|
||||||
|
workspaceSlug: 'test-workspace',
|
||||||
|
appSlug: 'test-application',
|
||||||
|
},
|
||||||
|
push: vi.fn(),
|
||||||
|
replace: vi.fn(),
|
||||||
|
reload: vi.fn(),
|
||||||
|
back: vi.fn(),
|
||||||
|
prefetch: vi.fn(),
|
||||||
|
beforePopState: vi.fn(),
|
||||||
|
events: {
|
||||||
|
on: vi.fn(),
|
||||||
|
off: vi.fn(),
|
||||||
|
emit: vi.fn(),
|
||||||
|
},
|
||||||
|
isFallback: false,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockApplication: Application = {
|
||||||
|
id: '1',
|
||||||
|
name: 'Test Application',
|
||||||
|
slug: 'test-application',
|
||||||
|
appStates: [],
|
||||||
|
hasuraGraphqlAdminSecret: 'nhost-admin-secret',
|
||||||
|
subdomain: '',
|
||||||
|
isProvisioned: true,
|
||||||
|
region: {
|
||||||
|
awsName: 'us-east-1',
|
||||||
|
city: 'New York',
|
||||||
|
countryCode: 'US',
|
||||||
|
id: '1',
|
||||||
|
},
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
deployments: [],
|
||||||
|
desiredState: ApplicationStatus.Live,
|
||||||
|
featureFlags: [],
|
||||||
|
providersUpdated: true,
|
||||||
|
githubRepository: { fullName: 'test/git-project' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockWorkspace: Workspace = {
|
||||||
|
id: '1',
|
||||||
|
name: 'Test Workspace',
|
||||||
|
slug: 'test-workspace',
|
||||||
|
members: [],
|
||||||
|
applications: [mockApplication],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockGraphqlLink = graphql.link('http://localhost:1337/v1/graphql');
|
||||||
|
|
||||||
|
const server = setupServer(
|
||||||
|
rest.get('http://localhost:1337/v1/graphql', (req, res, ctx) =>
|
||||||
|
res(ctx.status(200)),
|
||||||
|
),
|
||||||
|
mockGraphqlLink.operation(async (req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.data({
|
||||||
|
deployments: [],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
|
||||||
|
process.env.NEXT_PUBLIC_ENV = 'production';
|
||||||
|
server.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => server.resetHandlers());
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render an empty state when GitHub is not connected', () => {
|
||||||
|
render(
|
||||||
|
<UserDataProvider
|
||||||
|
initialWorkspaces={[
|
||||||
|
{
|
||||||
|
...mockWorkspace,
|
||||||
|
applications: [{ ...mockApplication, githubRepository: null }],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<OverviewDeployments />
|
||||||
|
</UserDataProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/no deployments/i)).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: /connect to github/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render an empty state when GitHub is connected, but there are no deployments', async () => {
|
||||||
|
render(
|
||||||
|
<UserDataProvider initialWorkspaces={[mockWorkspace]}>
|
||||||
|
<OverviewDeployments />
|
||||||
|
</UserDataProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/^deployments$/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: /view all/i })).toBeInTheDocument();
|
||||||
|
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'));
|
||||||
|
|
||||||
|
expect(screen.getByText(/no deployments/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/test\/git-project/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: /edit/i })).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'/test-workspace/test-application/settings/git',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render a list of deployments', async () => {
|
||||||
|
server.use(
|
||||||
|
mockGraphqlLink.operation(async (req, res, ctx) => {
|
||||||
|
const requestPayload = await req.json();
|
||||||
|
|
||||||
|
if (requestPayload.operationName === 'ScheduledOrPendingDeploymentsSub') {
|
||||||
|
return res(ctx.data({ deployments: [] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res(
|
||||||
|
ctx.data({
|
||||||
|
deployments: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
commitSHA: 'abc123',
|
||||||
|
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
|
||||||
|
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
|
||||||
|
deploymentStatus: 'DEPLOYED',
|
||||||
|
commitUserName: 'test.user',
|
||||||
|
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
|
||||||
|
commitMessage: 'Test commit message',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<UserDataProvider initialWorkspaces={[mockWorkspace]}>
|
||||||
|
<OverviewDeployments />
|
||||||
|
</UserDataProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'));
|
||||||
|
|
||||||
|
expect(screen.getByText(/test commit message/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/avatar/i)).toHaveStyle(
|
||||||
|
'background-image: url(http://images.example.com/avatar.png)',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole('link', {
|
||||||
|
name: /test commit message/i,
|
||||||
|
}),
|
||||||
|
).toHaveAttribute('href', '/test-workspace/test-application/deployments/1');
|
||||||
|
expect(screen.getByText(/5m 0s/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/live/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /redeploy/i })).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should disable redeployments if a deployment is already in progress', async () => {
|
||||||
|
server.use(
|
||||||
|
mockGraphqlLink.operation(async (req, res, ctx) => {
|
||||||
|
const requestPayload = await req.json();
|
||||||
|
|
||||||
|
if (requestPayload.operationName === 'ScheduledOrPendingDeploymentsSub') {
|
||||||
|
return res(
|
||||||
|
ctx.data({
|
||||||
|
deployments: [
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
commitSHA: 'abc234',
|
||||||
|
deploymentStartedAt: '2021-08-02T00:00:00.000Z',
|
||||||
|
deploymentEndedAt: null,
|
||||||
|
deploymentStatus: 'PENDING',
|
||||||
|
commitUserName: 'test.user',
|
||||||
|
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
|
||||||
|
commitMessage: 'Test commit message',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res(
|
||||||
|
ctx.data({
|
||||||
|
deployments: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
commitSHA: 'abc123',
|
||||||
|
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
|
||||||
|
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
|
||||||
|
deploymentStatus: 'DEPLOYED',
|
||||||
|
commitUserName: 'test.user',
|
||||||
|
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
|
||||||
|
commitMessage: 'Test commit message',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<UserDataProvider initialWorkspaces={[mockWorkspace]}>
|
||||||
|
<OverviewDeployments />
|
||||||
|
</UserDataProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'));
|
||||||
|
expect(screen.getByRole('button', { name: /redeploy/i })).toBeDisabled();
|
||||||
|
});
|
||||||
@@ -6,6 +6,7 @@ import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
|||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Divider from '@/ui/v2/Divider';
|
import Divider from '@/ui/v2/Divider';
|
||||||
|
import ChevronRightIcon from '@/ui/v2/icons/ChevronRightIcon';
|
||||||
import RocketIcon from '@/ui/v2/icons/RocketIcon';
|
import RocketIcon from '@/ui/v2/icons/RocketIcon';
|
||||||
import List from '@/ui/v2/List';
|
import List from '@/ui/v2/List';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
useGetDeploymentsSubSubscription,
|
useGetDeploymentsSubSubscription,
|
||||||
useScheduledOrPendingDeploymentsSubSubscription,
|
useScheduledOrPendingDeploymentsSubSubscription,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
|
||||||
import NavLink from 'next/link';
|
import NavLink from 'next/link';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
|
|
||||||
@@ -22,16 +22,16 @@ function OverviewDeploymentsTopBar() {
|
|||||||
const { currentWorkspace, currentApplication } =
|
const { currentWorkspace, currentApplication } =
|
||||||
useCurrentWorkspaceAndApplication();
|
useCurrentWorkspaceAndApplication();
|
||||||
|
|
||||||
const { githubRepository } = currentApplication;
|
const { githubRepository } = currentApplication || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-flow-col gap-2 items-center place-content-between pb-4">
|
<div className="grid grid-flow-col place-content-between items-center gap-2 pb-4">
|
||||||
<Text variant="h3" className="font-medium">
|
<Text variant="h3" className="font-medium">
|
||||||
Deployments
|
Deployments
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments`}
|
href={`/${currentWorkspace?.slug}/${currentApplication?.slug}/deployments`}
|
||||||
passHref
|
passHref
|
||||||
>
|
>
|
||||||
<Button variant="borderless" disabled={!githubRepository}>
|
<Button variant="borderless" disabled={!githubRepository}>
|
||||||
@@ -43,20 +43,12 @@ function OverviewDeploymentsTopBar() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OverviewDeploymentsProps {
|
function OverviewDeploymentList() {
|
||||||
projectId: string;
|
|
||||||
githubRepository: { fullName: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
function OverviewDeployments({
|
|
||||||
projectId,
|
|
||||||
githubRepository,
|
|
||||||
}: OverviewDeploymentsProps) {
|
|
||||||
const { currentWorkspace, currentApplication } =
|
const { currentWorkspace, currentApplication } =
|
||||||
useCurrentWorkspaceAndApplication();
|
useCurrentWorkspaceAndApplication();
|
||||||
const { data, loading } = useGetDeploymentsSubSubscription({
|
const { data, loading } = useGetDeploymentsSubSubscription({
|
||||||
variables: {
|
variables: {
|
||||||
id: projectId,
|
id: currentApplication?.id,
|
||||||
limit: 5,
|
limit: 5,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
@@ -67,23 +59,23 @@ function OverviewDeployments({
|
|||||||
loading: scheduledOrPendingDeploymentsLoading,
|
loading: scheduledOrPendingDeploymentsLoading,
|
||||||
} = useScheduledOrPendingDeploymentsSubSubscription({
|
} = useScheduledOrPendingDeploymentsSubSubscription({
|
||||||
variables: {
|
variables: {
|
||||||
appId: projectId,
|
appId: currentApplication?.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading || scheduledOrPendingDeploymentsLoading) {
|
if (loading || scheduledOrPendingDeploymentsLoading) {
|
||||||
return (
|
return (
|
||||||
<Box className="h-[323px] p-2 border-1 rounded-lg">
|
<Box className="h-[323px] rounded-lg border-1 p-2">
|
||||||
<ActivityIndicator label="Loading deployments..." />
|
<ActivityIndicator label="Loading deployments..." />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { deployments } = data;
|
const { deployments } = data || { deployments: [] };
|
||||||
|
|
||||||
if (deployments.length === 0) {
|
if (!deployments?.length) {
|
||||||
return (
|
return (
|
||||||
<Box className="grid grid-flow-row gap-5 items-center justify-items-center rounded-lg py-12 px-48 shadow-sm border-1">
|
<Box className="grid grid-flow-row items-center justify-items-center gap-5 overflow-hidden rounded-lg border-1 py-12 px-48 shadow-sm">
|
||||||
<RocketIcon
|
<RocketIcon
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
className="h-10 w-10"
|
className="h-10 w-10"
|
||||||
@@ -100,16 +92,16 @@ function OverviewDeployments({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
className="mt-6 flex flex-row place-content-between rounded-lg py-2 px-2 max-w-sm w-full"
|
className="mt-6 flex w-full max-w-sm flex-row place-content-between rounded-lg py-2 px-2"
|
||||||
sx={{ backgroundColor: 'grey.200' }}
|
sx={{ backgroundColor: 'grey.200' }}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
className="grid grid-flow-col gap-1.5 ml-2"
|
className="ml-2 grid grid-flow-col gap-1.5"
|
||||||
sx={{ backgroundColor: 'transparent' }}
|
sx={{ backgroundColor: 'transparent' }}
|
||||||
>
|
>
|
||||||
<GithubIcon className="h-4 w-4 self-center" />
|
<GithubIcon className="h-4 w-4 self-center" />
|
||||||
<Text variant="body1" className="self-center font-normal">
|
<Text variant="body1" className="self-center font-normal">
|
||||||
{githubRepository.fullName}
|
{currentApplication?.githubRepository?.fullName}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -128,20 +120,20 @@ function OverviewDeployments({
|
|||||||
|
|
||||||
const liveDeploymentId = getLastLiveDeployment(deployments);
|
const liveDeploymentId = getLastLiveDeployment(deployments);
|
||||||
const { deployments: scheduledOrPendingDeployments } =
|
const { deployments: scheduledOrPendingDeployments } =
|
||||||
scheduledOrPendingDeploymentsData;
|
scheduledOrPendingDeploymentsData || { deployments: [] };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
className="rounded-x-lg flex flex-col rounded-lg"
|
className="rounded-x-lg flex flex-col overflow-hidden rounded-lg"
|
||||||
sx={{ borderColor: 'grey.300', borderWidth: 1 }}
|
sx={{ borderColor: 'grey.300', borderWidth: 1 }}
|
||||||
>
|
>
|
||||||
{deployments.map((deployment, index) => (
|
{deployments?.map((deployment, index) => (
|
||||||
<Fragment key={deployment.id}>
|
<Fragment key={deployment.id}>
|
||||||
<DeploymentListItem
|
<DeploymentListItem
|
||||||
deployment={deployment}
|
deployment={deployment}
|
||||||
isLive={deployment.id === liveDeploymentId}
|
isLive={deployment.id === liveDeploymentId}
|
||||||
showRedeploy={index === 0}
|
showRedeploy={index === 0}
|
||||||
disableRedeploy={scheduledOrPendingDeployments.length > 0}
|
disableRedeploy={scheduledOrPendingDeployments?.length > 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{index !== deployments.length - 1 && <Divider component="li" />}
|
{index !== deployments.length - 1 && <Divider component="li" />}
|
||||||
@@ -151,21 +143,18 @@ function OverviewDeployments({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OverviewDeploymentsPage() {
|
export default function OverviewDeployments() {
|
||||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||||
const { openGitHubModal } = useGitHubModal();
|
const { openGitHubModal } = useGitHubModal();
|
||||||
|
|
||||||
const { githubRepository } = currentApplication;
|
const { githubRepository } = currentApplication || {};
|
||||||
|
|
||||||
// GitHub repo connected. Show deployments
|
// GitHub repo connected. Show deployments
|
||||||
if (githubRepository) {
|
if (githubRepository) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<OverviewDeploymentsTopBar />
|
<OverviewDeploymentsTopBar />
|
||||||
<OverviewDeployments
|
<OverviewDeploymentList />
|
||||||
projectId={currentApplication.id}
|
|
||||||
githubRepository={githubRepository}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -175,7 +164,7 @@ export default function OverviewDeploymentsPage() {
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<OverviewDeploymentsTopBar />
|
<OverviewDeploymentsTopBar />
|
||||||
|
|
||||||
<Box className="grid grid-flow-row gap-5 items-center justify-items-center rounded-lg py-12 px-48 shadow-sm border-1">
|
<Box className="grid grid-flow-row items-center justify-items-center gap-5 rounded-lg border-1 py-12 px-48 shadow-sm">
|
||||||
<RocketIcon strokeWidth={1} className="h-10 w-10" />
|
<RocketIcon strokeWidth={1} className="h-10 w-10" />
|
||||||
|
|
||||||
<div className="grid grid-flow-row gap-1">
|
<div className="grid grid-flow-row gap-1">
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export function Avatar({
|
|||||||
<Box
|
<Box
|
||||||
style={Object.assign(style, { backgroundImage: `url(${avatarUrl})` })}
|
style={Object.assign(style, { backgroundImage: `url(${avatarUrl})` })}
|
||||||
className={classes}
|
className={classes}
|
||||||
|
aria-label="Avatar"
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ function ActivityIndicator({
|
|||||||
// We are rendering a span instead of null in order to keep the layout
|
// We are rendering a span instead of null in order to keep the layout
|
||||||
// intact in certain cases (e.g. when elements have a "space-between"
|
// intact in certain cases (e.g. when elements have a "space-between"
|
||||||
// position).
|
// position).
|
||||||
return <span />;
|
return (
|
||||||
|
<span role="progressbar" aria-label="Activity indicator placeholder" />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -25,10 +25,25 @@ export const UserDataContext = createContext<UserDataContent>({
|
|||||||
setUserContext: () => {},
|
setUserContext: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function UserDataProvider({ children }: PropsWithChildren<unknown>) {
|
export interface UserDataProviderProps {
|
||||||
|
/**
|
||||||
|
* Initial workspaces to be used in the context.
|
||||||
|
*/
|
||||||
|
initialWorkspaces?: Workspace[];
|
||||||
|
/**
|
||||||
|
* Initial metadata to be used in the context.
|
||||||
|
*/
|
||||||
|
initialMetadata?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UserDataProvider({
|
||||||
|
children,
|
||||||
|
initialWorkspaces,
|
||||||
|
initialMetadata,
|
||||||
|
}: PropsWithChildren<UserDataProviderProps>) {
|
||||||
const [userContext, setUserContext] = useState({
|
const [userContext, setUserContext] = useState({
|
||||||
workspaces: [],
|
workspaces: initialWorkspaces || [],
|
||||||
metadata: {},
|
metadata: initialMetadata || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ test('should throw an error if permission object is incorrectly provided', async
|
|||||||
action: 'select',
|
action: 'select',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
mode: 'update',
|
mode: 'update',
|
||||||
|
resourceVersion: 1,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrowError(
|
).rejects.toThrowError(
|
||||||
new Error(
|
new Error(
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import slugify from 'slugify';
|
|||||||
import { LOCAL_BACKEND_URL } from './env';
|
import { LOCAL_BACKEND_URL } from './env';
|
||||||
import type { DeploymentRowFragment } from './__generated__/graphql';
|
import type { DeploymentRowFragment } from './__generated__/graphql';
|
||||||
|
|
||||||
export function getLastLiveDeployment(deployments: DeploymentRowFragment[]) {
|
export function getLastLiveDeployment(deployments?: DeploymentRowFragment[]) {
|
||||||
|
if (!deployments) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
deployments.find((deployment) => deployment.deploymentStatus === 'DEPLOYED')
|
deployments.find((deployment) => deployment.deploymentStatus === 'DEPLOYED')
|
||||||
?.id || ''
|
?.id || ''
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ManagedUIContext } from '@/context/UIContext';
|
|||||||
import { WorkspaceProvider } from '@/context/workspace-context';
|
import { WorkspaceProvider } from '@/context/workspace-context';
|
||||||
import { UserDataProvider } from '@/context/workspace1-context';
|
import { UserDataProvider } from '@/context/workspace1-context';
|
||||||
import createTheme from '@/ui/v2/createTheme';
|
import createTheme from '@/ui/v2/createTheme';
|
||||||
|
import { createHttpLink } from '@apollo/client';
|
||||||
import { CacheProvider } from '@emotion/react';
|
import { CacheProvider } from '@emotion/react';
|
||||||
import { ThemeProvider } from '@mui/material/styles';
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
import { NhostProvider } from '@nhost/nextjs';
|
import { NhostProvider } from '@nhost/nextjs';
|
||||||
@@ -33,7 +34,7 @@ const queryClient = new QueryClient({
|
|||||||
|
|
||||||
global.fetch = fetch;
|
global.fetch = fetch;
|
||||||
|
|
||||||
const mockRouter: NextRouter = {
|
export const mockRouter: NextRouter = {
|
||||||
basePath: '',
|
basePath: '',
|
||||||
pathname: '/',
|
pathname: '/',
|
||||||
route: '/',
|
route: '/',
|
||||||
@@ -65,7 +66,12 @@ function Providers({ children }: PropsWithChildren<{}>) {
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<CacheProvider value={emotionCache}>
|
<CacheProvider value={emotionCache}>
|
||||||
<NhostProvider nhost={nhost}>
|
<NhostProvider nhost={nhost}>
|
||||||
<NhostApolloProvider nhost={nhost}>
|
<NhostApolloProvider
|
||||||
|
nhost={nhost}
|
||||||
|
link={createHttpLink({
|
||||||
|
uri: 'http://localhost:1337/v1/graphql',
|
||||||
|
})}
|
||||||
|
>
|
||||||
<WorkspaceProvider>
|
<WorkspaceProvider>
|
||||||
<UserDataProvider>
|
<UserDataProvider>
|
||||||
<ManagedUIContext>
|
<ManagedUIContext>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/apollo
|
# @nhost/apollo
|
||||||
|
|
||||||
|
## 4.13.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- c38f6074: chore(react-apollo): allow `link` in configuration options
|
||||||
|
- @nhost/nhost-js@1.13.3
|
||||||
|
|
||||||
## 4.13.2
|
## 4.13.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/apollo",
|
"name": "@nhost/apollo",
|
||||||
"version": "4.13.2",
|
"version": "4.13.3",
|
||||||
"description": "Nhost Apollo Client library",
|
"description": "Nhost Apollo Client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -68,6 +68,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nhost/nhost-js": "workspace:*",
|
"@nhost/nhost-js": "workspace:*",
|
||||||
"@apollo/client": "^3.7.1"
|
"@apollo/client": "^3.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ export type NhostApolloClientOptions = {
|
|||||||
connectToDevTools?: boolean
|
connectToDevTools?: boolean
|
||||||
cache?: InMemoryCache
|
cache?: InMemoryCache
|
||||||
onError?: RequestHandler
|
onError?: RequestHandler
|
||||||
|
link?: ApolloClient<any>['link']
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createApolloClient = ({
|
export const createApolloClient = ({
|
||||||
@@ -35,7 +36,8 @@ export const createApolloClient = ({
|
|||||||
fetchPolicy,
|
fetchPolicy,
|
||||||
cache = new InMemoryCache(),
|
cache = new InMemoryCache(),
|
||||||
connectToDevTools = isBrowser && process.env.NODE_ENV === 'development',
|
connectToDevTools = isBrowser && process.env.NODE_ENV === 'development',
|
||||||
onError
|
onError,
|
||||||
|
link: customLink
|
||||||
}: NhostApolloClientOptions): ApolloClient<any> => {
|
}: NhostApolloClientOptions): ApolloClient<any> => {
|
||||||
let backendUrl = graphqlUrl || nhost?.graphql.getUrl()
|
let backendUrl = graphqlUrl || nhost?.graphql.getUrl()
|
||||||
if (!backendUrl) {
|
if (!backendUrl) {
|
||||||
@@ -122,7 +124,11 @@ export const createApolloClient = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add link
|
// add link
|
||||||
apolloClientOptions.link = typeof onError === 'function' ? from([onError, link]) : from([link])
|
if (customLink) {
|
||||||
|
apolloClientOptions.link = from([customLink])
|
||||||
|
} else {
|
||||||
|
apolloClientOptions.link = typeof onError === 'function' ? from([onError, link]) : from([link])
|
||||||
|
}
|
||||||
|
|
||||||
const client = new ApolloClient(apolloClientOptions)
|
const client = new ApolloClient(apolloClientOptions)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @nhost/react-apollo
|
# @nhost/react-apollo
|
||||||
|
|
||||||
|
## 4.13.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [c38f6074]
|
||||||
|
- @nhost/apollo@4.13.3
|
||||||
|
- @nhost/react@1.13.4
|
||||||
|
|
||||||
## 4.13.3
|
## 4.13.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-apollo",
|
"name": "@nhost/react-apollo",
|
||||||
"version": "4.13.3",
|
"version": "4.13.4",
|
||||||
"description": "Nhost React Apollo client",
|
"description": "Nhost React Apollo client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/react-urql
|
# @nhost/react-urql
|
||||||
|
|
||||||
|
## 1.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@1.13.4
|
||||||
|
|
||||||
## 1.0.3
|
## 1.0.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-urql",
|
"name": "@nhost/react-urql",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"description": "Nhost React URQL client",
|
"description": "Nhost React URQL client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/hasura-auth-js
|
# @nhost/hasura-auth-js
|
||||||
|
|
||||||
|
## 1.12.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5880f0cd: chore(hasura-auth-js): bump `msw` version to `1.0.1`
|
||||||
|
|
||||||
## 1.12.2
|
## 1.12.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/hasura-auth-js",
|
"name": "@nhost/hasura-auth-js",
|
||||||
"version": "1.12.2",
|
"version": "1.12.3",
|
||||||
"description": "Hasura-auth client",
|
"description": "Hasura-auth client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"@types/js-cookie": "^3.0.2",
|
"@types/js-cookie": "^3.0.2",
|
||||||
"cheerio": "1.0.0-rc.12",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"mailhog": "^4.16.0",
|
"mailhog": "^4.16.0",
|
||||||
"msw": "^0.47.4",
|
"msw": "^1.0.1",
|
||||||
"start-server-and-test": "^1.15.2"
|
"start-server-and-test": "^1.15.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/nextjs
|
# @nhost/nextjs
|
||||||
|
|
||||||
|
## 1.13.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@1.13.4
|
||||||
|
|
||||||
## 1.13.3
|
## 1.13.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nextjs",
|
"name": "@nhost/nextjs",
|
||||||
"version": "1.13.3",
|
"version": "1.13.4",
|
||||||
"description": "Nhost NextJS library",
|
"description": "Nhost NextJS library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/nhost-js
|
# @nhost/nhost-js
|
||||||
|
|
||||||
|
## 1.13.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [5880f0cd]
|
||||||
|
- @nhost/hasura-auth-js@1.12.3
|
||||||
|
|
||||||
## 1.13.2
|
## 1.13.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nhost-js",
|
"name": "@nhost/nhost-js",
|
||||||
"version": "1.13.2",
|
"version": "1.13.3",
|
||||||
"description": "Nhost JavaScript SDK",
|
"description": "Nhost JavaScript SDK",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/react
|
# @nhost/react
|
||||||
|
|
||||||
|
## 1.13.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@1.13.3
|
||||||
|
|
||||||
## 1.13.3
|
## 1.13.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react",
|
"name": "@nhost/react",
|
||||||
"version": "1.13.3",
|
"version": "1.13.4",
|
||||||
"description": "Nhost React library",
|
"description": "Nhost React library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/vue
|
# @nhost/vue
|
||||||
|
|
||||||
|
## 1.13.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@1.13.3
|
||||||
|
|
||||||
## 1.13.3
|
## 1.13.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/vue",
|
"name": "@nhost/vue",
|
||||||
"version": "1.13.3",
|
"version": "1.13.4",
|
||||||
"description": "Nhost Vue library",
|
"description": "Nhost Vue library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
893
pnpm-lock.yaml
generated
893
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user