Compare commits
10 Commits
@nhost/nho
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1bea1294d | ||
|
|
8af2f6e9dd | ||
|
|
e3d0b96917 | ||
|
|
36c3519cf8 | ||
|
|
9b52e9bf13 | ||
|
|
07c8d90053 | ||
|
|
a2621e40a4 | ||
|
|
61120a137a | ||
|
|
faea8feb2e | ||
|
|
552e31a4f0 |
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 0.9.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 36c3519c: feat(dashboard): retrigger deployments
|
||||||
|
|
||||||
## 0.9.5
|
## 0.9.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "0.9.5",
|
"version": "0.9.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
import type { DeploymentRowFragment } from '@/generated/graphql';
|
import DeploymentListItem from '@/components/deployments/DeploymentListItem';
|
||||||
import { useGetDeploymentsSubSubscription } from '@/generated/graphql';
|
import {
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
useGetDeploymentsSubSubscription,
|
||||||
import { Avatar } from '@/ui/Avatar';
|
useScheduledOrPendingDeploymentsSubSubscription,
|
||||||
import DelayedLoading from '@/ui/DelayedLoading';
|
} from '@/generated/graphql';
|
||||||
import Status, { StatusEnum } from '@/ui/Status';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
import List from '@/ui/v2/List';
|
||||||
import { StatusCircle } from '@/ui/StatusCircle';
|
|
||||||
import { getLastLiveDeployment } from '@/utils/helpers';
|
import { getLastLiveDeployment } from '@/utils/helpers';
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid';
|
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid';
|
||||||
import {
|
|
||||||
differenceInSeconds,
|
|
||||||
formatDistanceToNowStrict,
|
|
||||||
parseISO,
|
|
||||||
} from 'date-fns';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
type AppDeploymentsProps = {
|
type AppDeploymentsProps = {
|
||||||
appId: string;
|
appId: string;
|
||||||
@@ -66,146 +59,8 @@ function NextPrevPageLink(props: NextPrevPageLinkProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppDeploymentDurationProps = {
|
|
||||||
startedAt: string;
|
|
||||||
endedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AppDeploymentDuration({
|
|
||||||
startedAt,
|
|
||||||
endedAt,
|
|
||||||
}: AppDeploymentDurationProps) {
|
|
||||||
const [currentTime, setCurrentTime] = useState(new Date());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let interval: NodeJS.Timeout;
|
|
||||||
|
|
||||||
if (!endedAt) {
|
|
||||||
interval = setInterval(() => {
|
|
||||||
setCurrentTime(new Date());
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
}, [endedAt]);
|
|
||||||
|
|
||||||
const totalDurationInSeconds = differenceInSeconds(
|
|
||||||
endedAt ? parseISO(endedAt) : currentTime,
|
|
||||||
parseISO(startedAt),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (totalDurationInSeconds > 1200) {
|
|
||||||
return <div>20+m</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const durationMins = Math.floor(totalDurationInSeconds / 60);
|
|
||||||
const durationSecs = totalDurationInSeconds % 60;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontVariantNumeric: 'tabular-nums',
|
|
||||||
}}
|
|
||||||
className="self-center font-display text-sm+ text-greyscaleDark"
|
|
||||||
>
|
|
||||||
{durationMins}m {durationSecs}s
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppDeploymentRowProps = {
|
|
||||||
deployment: DeploymentRowFragment;
|
|
||||||
isDeploymentLive: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AppDeploymentRow({
|
|
||||||
deployment,
|
|
||||||
isDeploymentLive,
|
|
||||||
}: AppDeploymentRowProps) {
|
|
||||||
const { currentWorkspace, currentApplication } =
|
|
||||||
useCurrentWorkspaceAndApplication();
|
|
||||||
|
|
||||||
const { commitMessage } = deployment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row items-center px-2 py-4">
|
|
||||||
<div className="mr-2 flex items-center justify-center">
|
|
||||||
<Avatar
|
|
||||||
name={deployment.commitUserName}
|
|
||||||
avatarUrl={deployment.commitUserAvatarUrl}
|
|
||||||
className="h-8 w-8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx-4 w-full">
|
|
||||||
<Link
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
passHref
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
>
|
|
||||||
<div className="max-w-md truncate text-sm+ font-normal text-greyscaleDark">
|
|
||||||
{commitMessage?.trim() || (
|
|
||||||
<span className="pr-1 font-normal italic">
|
|
||||||
No commit message
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm+ text-greyscaleGrey">
|
|
||||||
{formatDistanceToNowStrict(
|
|
||||||
parseISO(deployment.deploymentStartedAt),
|
|
||||||
{
|
|
||||||
addSuffix: true,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row">
|
|
||||||
{isDeploymentLive && (
|
|
||||||
<div className="flex self-center align-middle">
|
|
||||||
<Status status={StatusEnum.Live}>Live</Status>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="w-28 self-center text-right font-mono text-sm- font-medium">
|
|
||||||
<a
|
|
||||||
className="font-mono font-medium text-greyscaleDark"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
href={`https://github.com/${currentApplication.githubRepository?.fullName}/commit/${deployment.commitSHA}`}
|
|
||||||
>
|
|
||||||
{deployment.commitSHA.substring(0, 7)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="mx-4 w-28 text-right">
|
|
||||||
<AppDeploymentDuration
|
|
||||||
startedAt={deployment.deploymentStartedAt}
|
|
||||||
endedAt={deployment.deploymentEndedAt}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx-3 self-center">
|
|
||||||
<StatusCircle
|
|
||||||
status={deployment.deploymentStatus as DeploymentStatus}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="self-center">
|
|
||||||
<Link
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
passHref
|
|
||||||
>
|
|
||||||
<ChevronRightIcon className="ml-2 h-4 w-4 cursor-pointer self-center" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AppDeployments(props: AppDeploymentsProps) {
|
export default function AppDeployments(props: AppDeploymentsProps) {
|
||||||
const { appId } = props;
|
const { appId } = props;
|
||||||
const [idOfLiveDeployment, setIdOfLiveDeployment] = useState('');
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -216,8 +71,6 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
const limit = 10;
|
const limit = 10;
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
// @TODO: Should query for all deployments, then subscribe to new ones.
|
|
||||||
|
|
||||||
const { data, loading, error } = useGetDeploymentsSubSubscription({
|
const { data, loading, error } = useGetDeploymentsSubSubscription({
|
||||||
variables: {
|
variables: {
|
||||||
id: appId,
|
id: appId,
|
||||||
@@ -226,26 +79,36 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
if (!data) {
|
data: scheduledOrPendingDeploymentsData,
|
||||||
return;
|
loading: scheduledOrPendingDeploymentsLoading,
|
||||||
}
|
} = useScheduledOrPendingDeploymentsSubSubscription({
|
||||||
|
variables: {
|
||||||
|
appId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (page === 1) {
|
if (loading || scheduledOrPendingDeploymentsLoading) {
|
||||||
setIdOfLiveDeployment(getLastLiveDeployment(data.deployments));
|
return (
|
||||||
}
|
<ActivityIndicator
|
||||||
}, [data, idOfLiveDeployment, loading, page]);
|
delay={500}
|
||||||
|
className="mt-12"
|
||||||
if (loading) {
|
label="Loading deployments..."
|
||||||
return <DelayedLoading delay={500} className="mt-12" />;
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nrOfDeployments = data.deployments.length;
|
const { deployments } = data || {};
|
||||||
|
const { deployments: scheduledOrPendingDeployments } =
|
||||||
|
scheduledOrPendingDeploymentsData || {};
|
||||||
|
|
||||||
|
const nrOfDeployments = deployments?.length || 0;
|
||||||
const nextAllowed = !(nrOfDeployments < limit);
|
const nextAllowed = !(nrOfDeployments < limit);
|
||||||
|
const liveDeploymentId = getLastLiveDeployment(deployments);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
@@ -253,15 +116,24 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
<p className="text-sm text-greyscaleGrey">No deployments yet.</p>
|
<p className="text-sm text-greyscaleGrey">No deployments yet.</p>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 divide-y-1 border-t border-b">
|
<List className="mt-3 divide-y-1 border-t border-b">
|
||||||
{data.deployments.map((deployment) => (
|
{deployments.map((deployment, index) => (
|
||||||
<AppDeploymentRow
|
<DeploymentListItem
|
||||||
deployment={deployment}
|
|
||||||
key={deployment.id}
|
key={deployment.id}
|
||||||
isDeploymentLive={idOfLiveDeployment === deployment.id}
|
deployment={deployment}
|
||||||
|
isLive={liveDeploymentId === deployment.id}
|
||||||
|
showRedeploy={
|
||||||
|
scheduledOrPendingDeployments.length > 0
|
||||||
|
? scheduledOrPendingDeployments.some(
|
||||||
|
(scheduledOrPendingDeployment) =>
|
||||||
|
scheduledOrPendingDeployment.id === deployment.id,
|
||||||
|
)
|
||||||
|
: index === 0
|
||||||
|
}
|
||||||
|
disableRedeploy={scheduledOrPendingDeployments.length > 0}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</List>
|
||||||
<div className="mt-8 flex w-full justify-center">
|
<div className="mt-8 flex w-full justify-center">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<NextPrevPageLink
|
<NextPrevPageLink
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ export function Repo({ repo, setSelectedRepoId }: RepoProps) {
|
|||||||
const [updateApp, { loading, error }] = useUpdateAppMutation({
|
const [updateApp, { loading, error }] = useUpdateAppMutation({
|
||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
refetchGetAppByWorkspaceAndNameQuery({
|
refetchGetAppByWorkspaceAndNameQuery({
|
||||||
workspace: currentWorkspace.slug,
|
workspace: currentWorkspace?.slug,
|
||||||
slug: currentApplication.slug,
|
slug: currentApplication?.slug,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { githubRepository } = currentApplication;
|
const { githubRepository } = currentApplication || {};
|
||||||
|
|
||||||
const isThisRepositoryAlreadyConnected =
|
const isThisRepositoryAlreadyConnected =
|
||||||
githubRepository?.fullName && githubRepository.fullName === repo.fullName;
|
githubRepository?.fullName && githubRepository.fullName === repo.fullName;
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { differenceInSeconds, parseISO } from 'date-fns';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export interface AppDeploymentDurationProps {
|
||||||
|
/**
|
||||||
|
* Start date of the deployment.
|
||||||
|
*/
|
||||||
|
startedAt: string;
|
||||||
|
/**
|
||||||
|
* End date of the deployment.
|
||||||
|
*/
|
||||||
|
endedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppDeploymentDuration({
|
||||||
|
startedAt,
|
||||||
|
endedAt,
|
||||||
|
}: AppDeploymentDurationProps) {
|
||||||
|
const [currentTime, setCurrentTime] = useState(new Date());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let interval: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (!endedAt) {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
setCurrentTime(new Date());
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [endedAt]);
|
||||||
|
|
||||||
|
const totalDurationInSeconds = differenceInSeconds(
|
||||||
|
endedAt ? parseISO(endedAt) : currentTime,
|
||||||
|
parseISO(startedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (totalDurationInSeconds > 1200) {
|
||||||
|
return <div>20+m</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationMins = Math.floor(totalDurationInSeconds / 60);
|
||||||
|
const durationSecs = totalDurationInSeconds % 60;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ fontVariantNumeric: 'tabular-nums' }}
|
||||||
|
className="self-center font-display text-sm+ text-greyscaleDark"
|
||||||
|
>
|
||||||
|
{durationMins}m {durationSecs}s
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './AppDeploymentDuration';
|
||||||
|
export { default } from './AppDeploymentDuration';
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import NavLink from '@/components/common/NavLink';
|
||||||
|
import AppDeploymentDuration from '@/components/deployments/AppDeploymentDuration';
|
||||||
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
|
import { Avatar } from '@/ui/Avatar';
|
||||||
|
import Status, { StatusEnum } from '@/ui/Status';
|
||||||
|
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
||||||
|
import { StatusCircle } from '@/ui/StatusCircle';
|
||||||
|
import Button from '@/ui/v2/Button';
|
||||||
|
import ArrowCounterclockwiseIcon from '@/ui/v2/icons/ArrowCounterclockwiseIcon';
|
||||||
|
import ChevronRightIcon from '@/ui/v2/icons/ChevronRightIcon';
|
||||||
|
import { ListItem } from '@/ui/v2/ListItem';
|
||||||
|
import Tooltip from '@/ui/v2/Tooltip';
|
||||||
|
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
|
import type { DeploymentRowFragment } from '@/utils/__generated__/graphql';
|
||||||
|
import { useInsertDeploymentMutation } from '@/utils/__generated__/graphql';
|
||||||
|
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export interface DeploymentListItemProps {
|
||||||
|
/**
|
||||||
|
* Deployment data.
|
||||||
|
*/
|
||||||
|
deployment: DeploymentRowFragment;
|
||||||
|
/**
|
||||||
|
* Determines whether or not the deployment is live.
|
||||||
|
*/
|
||||||
|
isLive?: boolean;
|
||||||
|
/**
|
||||||
|
* Determines whether or not the redeploy button should be shown for the
|
||||||
|
* deployment.
|
||||||
|
*/
|
||||||
|
showRedeploy?: boolean;
|
||||||
|
/**
|
||||||
|
* Determines whether or not the redeploy button is disabled.
|
||||||
|
*/
|
||||||
|
disableRedeploy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeploymentListItem({
|
||||||
|
deployment,
|
||||||
|
isLive,
|
||||||
|
showRedeploy,
|
||||||
|
disableRedeploy,
|
||||||
|
}: DeploymentListItemProps) {
|
||||||
|
const { currentWorkspace, currentApplication } =
|
||||||
|
useCurrentWorkspaceAndApplication();
|
||||||
|
|
||||||
|
const showTime =
|
||||||
|
!['SCHEDULED', 'PENDING'].includes(deployment.deploymentStatus) &&
|
||||||
|
deployment.deploymentStartedAt;
|
||||||
|
|
||||||
|
const relativeDateOfDeployment = showTime
|
||||||
|
? formatDistanceToNowStrict(parseISO(deployment.deploymentStartedAt), {
|
||||||
|
addSuffix: true,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const [insertDeployment, { loading }] = useInsertDeploymentMutation();
|
||||||
|
const { commitMessage } = deployment;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem.Root>
|
||||||
|
<ListItem.Button
|
||||||
|
className="grid grid-flow-col items-center justify-between gap-2 px-2 py-2"
|
||||||
|
component={NavLink}
|
||||||
|
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
||||||
|
>
|
||||||
|
<div className="flex cursor-pointer flex-row items-center justify-center space-x-2 self-center">
|
||||||
|
<ListItem.Avatar>
|
||||||
|
<Avatar
|
||||||
|
name={deployment.commitUserName}
|
||||||
|
avatarUrl={deployment.commitUserAvatarUrl}
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
/>
|
||||||
|
</ListItem.Avatar>
|
||||||
|
|
||||||
|
<ListItem.Text
|
||||||
|
primary={
|
||||||
|
commitMessage?.trim() || (
|
||||||
|
<span className="truncate pr-1 font-normal italic">
|
||||||
|
No commit message
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
secondary={relativeDateOfDeployment}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-flow-col gap-2 items-center">
|
||||||
|
{showRedeploy && (
|
||||||
|
<Tooltip
|
||||||
|
title="An active deployment cannot be re-triggered"
|
||||||
|
hasDisabledChildren={disableRedeploy || loading}
|
||||||
|
disableHoverListener={!disableRedeploy}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disableRedeploy || loading}
|
||||||
|
size="small"
|
||||||
|
color="secondary"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={async (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const insertDeploymentPromise = insertDeployment({
|
||||||
|
variables: {
|
||||||
|
object: {
|
||||||
|
appId: currentApplication?.id,
|
||||||
|
commitMessage: deployment.commitMessage,
|
||||||
|
commitSHA: deployment.commitSHA,
|
||||||
|
commitUserAvatarUrl: deployment.commitUserAvatarUrl,
|
||||||
|
commitUserName: deployment.commitUserName,
|
||||||
|
deploymentStatus: 'SCHEDULED',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await toast.promise(
|
||||||
|
insertDeploymentPromise,
|
||||||
|
{
|
||||||
|
loading: 'Scheduling deployment...',
|
||||||
|
success: 'Deployment has been scheduled successfully.',
|
||||||
|
error: 'An error occurred when scheduling deployment.',
|
||||||
|
},
|
||||||
|
toastStyleProps,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
startIcon={
|
||||||
|
<ArrowCounterclockwiseIcon
|
||||||
|
className={twMerge(
|
||||||
|
'w-4 h-4',
|
||||||
|
disableRedeploy && 'animate-spin-reverse',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
className="rounded-full py-1 px-2 text-xs"
|
||||||
|
>
|
||||||
|
{disableRedeploy ? 'Redeploying...' : 'Redeploy'}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLive && (
|
||||||
|
<div className="w-12 flex justify-end">
|
||||||
|
<Status status={StatusEnum.Live}>Live</Status>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="w-16 text-right font-mono text-sm- font-medium">
|
||||||
|
{deployment.commitSHA.substring(0, 7)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showTime && (
|
||||||
|
<div className="w-[80px] text-right font-mono text-sm- font-medium">
|
||||||
|
<AppDeploymentDuration
|
||||||
|
startedAt={deployment.deploymentStartedAt}
|
||||||
|
endedAt={deployment.deploymentEndedAt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<StatusCircle
|
||||||
|
status={deployment.deploymentStatus as DeploymentStatus}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChevronRightIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</ListItem.Button>
|
||||||
|
</ListItem.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './DeploymentListItem';
|
||||||
|
export { default } from './DeploymentListItem';
|
||||||
@@ -1,25 +1,21 @@
|
|||||||
import { AppDeploymentDuration } from '@/components/applications/AppDeployments';
|
|
||||||
import { EditRepositorySettings } from '@/components/applications/github/EditRepositorySettings';
|
import { EditRepositorySettings } from '@/components/applications/github/EditRepositorySettings';
|
||||||
import useGitHubModal from '@/components/applications/github/useGitHubModal';
|
import useGitHubModal from '@/components/applications/github/useGitHubModal';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import NavLink from '@/components/common/NavLink';
|
import NavLink from '@/components/common/NavLink';
|
||||||
|
import DeploymentListItem from '@/components/deployments/DeploymentListItem';
|
||||||
import GithubIcon from '@/components/icons/GithubIcon';
|
import GithubIcon from '@/components/icons/GithubIcon';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import { Avatar } from '@/ui/Avatar';
|
|
||||||
import Status, { StatusEnum } from '@/ui/Status';
|
|
||||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
|
||||||
import { StatusCircle } from '@/ui/StatusCircle';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import RocketIcon from '@/ui/v2/icons/RocketIcon';
|
import RocketIcon from '@/ui/v2/icons/RocketIcon';
|
||||||
import type { ListItemRootProps } from '@/ui/v2/ListItem';
|
import List from '@/ui/v2/List';
|
||||||
import { ListItem } from '@/ui/v2/ListItem';
|
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { getLastLiveDeployment } from '@/utils/helpers';
|
import { getLastLiveDeployment } from '@/utils/helpers';
|
||||||
import type { DeploymentRowFragment } from '@/utils/__generated__/graphql';
|
import {
|
||||||
import { useGetDeploymentsSubSubscription } from '@/utils/__generated__/graphql';
|
useGetDeploymentsSubSubscription,
|
||||||
|
useScheduledOrPendingDeploymentsSubSubscription,
|
||||||
|
} from '@/utils/__generated__/graphql';
|
||||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
import { ChevronRightIcon } from '@heroicons/react/solid';
|
||||||
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
function OverviewDeploymentsTopBar() {
|
function OverviewDeploymentsTopBar() {
|
||||||
@@ -54,92 +50,6 @@ function OverviewDeploymentsTopBar() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OverviewDeployProps extends ListItemRootProps {
|
|
||||||
/**
|
|
||||||
* Deployment metadata to display.
|
|
||||||
*/
|
|
||||||
deployment: DeploymentRowFragment;
|
|
||||||
/**
|
|
||||||
* Determines to show a status badge showing the live status of a deployment reflecting the latest state of the application.
|
|
||||||
*/
|
|
||||||
isDeploymentLive: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function OverviewDeployment({
|
|
||||||
deployment,
|
|
||||||
isDeploymentLive,
|
|
||||||
className,
|
|
||||||
}: OverviewDeployProps) {
|
|
||||||
const { currentWorkspace, currentApplication } =
|
|
||||||
useCurrentWorkspaceAndApplication();
|
|
||||||
|
|
||||||
const relativeDateOfDeployment = formatDistanceToNowStrict(
|
|
||||||
parseISO(deployment.deploymentStartedAt),
|
|
||||||
{
|
|
||||||
addSuffix: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { commitMessage } = deployment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListItem.Root className={twMerge('grid grid-flow-row', className)}>
|
|
||||||
<ListItem.Button
|
|
||||||
className="grid grid-flow-col items-center justify-between gap-2 px-2 py-2"
|
|
||||||
component={NavLink}
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
>
|
|
||||||
<div className="flex cursor-pointer flex-row items-center justify-center space-x-2 self-center">
|
|
||||||
<div>
|
|
||||||
<Avatar
|
|
||||||
name={deployment.commitUserName}
|
|
||||||
avatarUrl={deployment.commitUserAvatarUrl}
|
|
||||||
className="h-8 w-8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-flow-row truncate text-sm+ font-medium">
|
|
||||||
<Text className="inline cursor-pointer truncate font-medium leading-snug text-greyscaleDark">
|
|
||||||
{commitMessage?.trim() || (
|
|
||||||
<span className="truncate pr-1 font-normal italic">
|
|
||||||
No commit message
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-sm font-normal leading-[1.375rem] text-greyscaleGrey">
|
|
||||||
{relativeDateOfDeployment}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-flow-col items-center self-center">
|
|
||||||
{isDeploymentLive && (
|
|
||||||
<div className="flex self-center align-middle">
|
|
||||||
<Status status={StatusEnum.Live}>Live</Status>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="w-20 self-center text-right align-middle font-mono text-sm- font-medium">
|
|
||||||
{deployment.commitSHA.substring(0, 7)}
|
|
||||||
</div>
|
|
||||||
<div className="w-20 self-center text-right align-middle font-mono text-sm-">
|
|
||||||
<AppDeploymentDuration
|
|
||||||
startedAt={deployment.deploymentStartedAt}
|
|
||||||
endedAt={deployment.deploymentEndedAt}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx-3 self-center">
|
|
||||||
<StatusCircle
|
|
||||||
status={deployment.deploymentStatus as DeploymentStatus}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="self-center">
|
|
||||||
<ChevronRightIcon className="ml-2 h-4 w-4 cursor-pointer self-center" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ListItem.Button>
|
|
||||||
</ListItem.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OverviewDeploymentsProps {
|
interface OverviewDeploymentsProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
githubRepository: { fullName: string };
|
githubRepository: { fullName: string };
|
||||||
@@ -159,9 +69,18 @@ function OverviewDeployments({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) {
|
const {
|
||||||
|
data: scheduledOrPendingDeploymentsData,
|
||||||
|
loading: scheduledOrPendingDeploymentsLoading,
|
||||||
|
} = useScheduledOrPendingDeploymentsSubSubscription({
|
||||||
|
variables: {
|
||||||
|
appId: projectId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading || scheduledOrPendingDeploymentsLoading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '240px' }}>
|
<div className="h-60">
|
||||||
<ActivityIndicator label="Loading deployments..." />
|
<ActivityIndicator label="Loading deployments..." />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -218,22 +137,29 @@ function OverviewDeployments({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLastLiveDeploymentId = getLastLiveDeployment(deployments);
|
const liveDeploymentId = getLastLiveDeployment(deployments);
|
||||||
|
const { deployments: scheduledOrPendingDeployments } =
|
||||||
|
scheduledOrPendingDeploymentsData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-x-lg flex flex-col divide-y-1 divide-gray-200 rounded-lg border border-veryLightGray">
|
<List className="rounded-x-lg flex flex-col divide-y-1 divide-gray-200 rounded-lg border border-veryLightGray">
|
||||||
{deployments.map((deployment) => {
|
{deployments.map((deployment, index) => (
|
||||||
const isDeploymentLive = deployment.id === getLastLiveDeploymentId;
|
<DeploymentListItem
|
||||||
|
key={deployment.id}
|
||||||
return (
|
deployment={deployment}
|
||||||
<OverviewDeployment
|
isLive={deployment.id === liveDeploymentId}
|
||||||
key={deployment.id}
|
showRedeploy={
|
||||||
deployment={deployment}
|
scheduledOrPendingDeployments.length > 0
|
||||||
isDeploymentLive={isDeploymentLive}
|
? scheduledOrPendingDeployments.some(
|
||||||
/>
|
(scheduledOrPendingDeployment) =>
|
||||||
);
|
scheduledOrPendingDeployment.id === deployment.id,
|
||||||
})}
|
)
|
||||||
</div>
|
: index === 0
|
||||||
|
}
|
||||||
|
disableRedeploy={scheduledOrPendingDeployments.length > 0}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
dashboard/src/components/ui/v2/ListItem/ListItemAvatar.tsx
Normal file
17
dashboard/src/components/ui/v2/ListItem/ListItemAvatar.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { ListItemAvatarProps as MaterialListItemAvatarProps } from '@mui/material/ListItemAvatar';
|
||||||
|
import MaterialListItemAvatar from '@mui/material/ListItemAvatar';
|
||||||
|
|
||||||
|
export interface ListItemAvatarProps extends MaterialListItemAvatarProps {}
|
||||||
|
|
||||||
|
const StyledListItemAvatar = styled(MaterialListItemAvatar)({
|
||||||
|
minWidth: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
function ListItemAvatar({ children, ...props }: ListItemAvatarProps) {
|
||||||
|
return <StyledListItemAvatar {...props}>{children}</StyledListItemAvatar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListItemAvatar.displayName = 'NhostListItemAvatar';
|
||||||
|
|
||||||
|
export default ListItemAvatar;
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import ListItemAvatar from './ListItemAvatar';
|
||||||
import ListItemButton from './ListItemButton';
|
import ListItemButton from './ListItemButton';
|
||||||
import ListItemIcon from './ListItemIcon';
|
import ListItemIcon from './ListItemIcon';
|
||||||
import ListItemRoot from './ListItemRoot';
|
import ListItemRoot from './ListItemRoot';
|
||||||
import ListItemText from './ListItemText';
|
import ListItemText from './ListItemText';
|
||||||
|
|
||||||
|
export * from './ListItemAvatar';
|
||||||
|
export { default as ListItemAvatar } from './ListItemAvatar';
|
||||||
export * from './ListItemButton';
|
export * from './ListItemButton';
|
||||||
export { default as ListItemButton } from './ListItemButton';
|
export { default as ListItemButton } from './ListItemButton';
|
||||||
export * from './ListItemIcon';
|
export * from './ListItemIcon';
|
||||||
@@ -13,6 +16,7 @@ export * from './ListItemText';
|
|||||||
export { default as ListItemText } from './ListItemText';
|
export { default as ListItemText } from './ListItemText';
|
||||||
|
|
||||||
export const ListItem = {
|
export const ListItem = {
|
||||||
|
Avatar: ListItemAvatar,
|
||||||
Root: ListItemRoot,
|
Root: ListItemRoot,
|
||||||
Button: ListItemButton,
|
Button: ListItemButton,
|
||||||
Icon: ListItemIcon,
|
Icon: ListItemIcon,
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { IconProps } from '@/ui/v2/icons';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
function ArrowCounterclockwiseIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
aria-label="A counterclockwise arrow"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.99 6.232h-3v-3"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4.11 11.89a5.5 5.5 0 1 0 0-7.78L1.99 6.233"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrowCounterclockwiseIcon.displayName = 'NhostArrowCounterclockwiseIcon';
|
||||||
|
|
||||||
|
export default ArrowCounterclockwiseIcon;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './ArrowCounterclockwiseIcon';
|
||||||
@@ -20,6 +20,23 @@ query getDeployments($id: uuid!, $limit: Int!, $offset: Int!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscription ScheduledOrPendingDeploymentsSub($appId: uuid!) {
|
||||||
|
deployments(
|
||||||
|
where: {
|
||||||
|
deploymentStatus: { _in: ["PENDING", "SCHEDULED"] }
|
||||||
|
appId: { _eq: $appId }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation InsertDeployment($object: deployments_insert_input!) {
|
||||||
|
insertDeployment(object: $object) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
||||||
deployments(
|
deployments(
|
||||||
where: { appId: { _eq: $id } }
|
where: { appId: { _eq: $id } }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AppDeploymentDuration } from '@/components/applications/AppDeployments';
|
import AppDeploymentDuration from '@/components/deployments/AppDeploymentDuration';
|
||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
import ProjectLayout from '@/components/layout/ProjectLayout';
|
import ProjectLayout from '@/components/layout/ProjectLayout';
|
||||||
import { useDeploymentSubSubscription } from '@/generated/graphql';
|
import { useDeploymentSubSubscription } from '@/generated/graphql';
|
||||||
@@ -8,6 +8,7 @@ import DelayedLoading from '@/ui/DelayedLoading';
|
|||||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
||||||
import { StatusCircle } from '@/ui/StatusCircle';
|
import { StatusCircle } from '@/ui/StatusCircle';
|
||||||
import { Text } from '@/ui/Text';
|
import { Text } from '@/ui/Text';
|
||||||
|
import Link from '@/ui/v2/Link';
|
||||||
import { format, formatDistanceToNowStrict, parseISO } from 'date-fns';
|
import { format, formatDistanceToNowStrict, parseISO } from 'date-fns';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
@@ -50,6 +51,16 @@ export default function DeploymentDetailsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showTime =
|
||||||
|
!['SCHEDULED', 'PENDING'].includes(deployment.deploymentStatus) &&
|
||||||
|
deployment.deploymentStartedAt;
|
||||||
|
|
||||||
|
const relativeDateOfDeployment = showTime
|
||||||
|
? formatDistanceToNowStrict(parseISO(deployment.deploymentStartedAt), {
|
||||||
|
addSuffix: true,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@@ -104,35 +115,37 @@ export default function DeploymentDetailsPage() {
|
|||||||
{deployment.commitMessage}
|
{deployment.commitMessage}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm+ text-greyscaleGrey">
|
<div className="text-sm+ text-greyscaleGrey">
|
||||||
{formatDistanceToNowStrict(
|
{relativeDateOfDeployment}
|
||||||
parseISO(deployment.deploymentStartedAt),
|
|
||||||
{
|
|
||||||
addSuffix: true,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className=" flex items-center">
|
<div className=" flex items-center">
|
||||||
<a
|
<Link
|
||||||
className="self-center font-mono text-sm- font-medium text-greyscaleDark"
|
className="self-center font-mono text-sm- font-medium"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
href={`https://github.com/${currentApplication.githubRepository?.fullName}/commit/${deployment.commitSHA}`}
|
href={`https://github.com/${currentApplication.githubRepository?.fullName}/commit/${deployment.commitSHA}`}
|
||||||
|
underline="hover"
|
||||||
>
|
>
|
||||||
{deployment.commitSHA.substring(0, 7)}
|
{deployment.commitSHA.substring(0, 7)}
|
||||||
</a>
|
</Link>
|
||||||
|
|
||||||
<div className="w-20 text-right">
|
{showTime && (
|
||||||
<AppDeploymentDuration
|
<div className="w-20 text-right">
|
||||||
startedAt={deployment.deploymentStartedAt}
|
<AppDeploymentDuration
|
||||||
endedAt={deployment.deploymentEndedAt}
|
startedAt={deployment.deploymentStartedAt}
|
||||||
/>
|
endedAt={deployment.deploymentEndedAt}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="rounded-lg bg-verydark p-4 text-sm- text-white">
|
<div className="rounded-lg bg-verydark p-4 text-sm- text-white">
|
||||||
|
{deployment.deploymentLogs.length === 0 && (
|
||||||
|
<span className="font-mono">No message.</span>
|
||||||
|
)}
|
||||||
|
|
||||||
{deployment.deploymentLogs.map((log) => (
|
{deployment.deploymentLogs.map((log) => (
|
||||||
<div key={log.id} className="flex font-mono">
|
<div key={log.id} className="flex font-mono">
|
||||||
<div className=" mr-2 flex-shrink-0">
|
<div className=" mr-2 flex-shrink-0">
|
||||||
|
|||||||
79
dashboard/src/utils/__generated__/graphql.ts
generated
79
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -17215,6 +17215,20 @@ export type GetDeploymentsQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetDeploymentsQuery = { __typename?: 'query_root', deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null }> };
|
export type GetDeploymentsQuery = { __typename?: 'query_root', deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null }> };
|
||||||
|
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscriptionVariables = Exact<{
|
||||||
|
appId: Scalars['uuid'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscription = { __typename?: 'subscription_root', deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null }> };
|
||||||
|
|
||||||
|
export type InsertDeploymentMutationVariables = Exact<{
|
||||||
|
object: Deployments_Insert_Input;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type InsertDeploymentMutation = { __typename?: 'mutation_root', insertDeployment?: { __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null } | null };
|
||||||
|
|
||||||
export type GetDeploymentsSubSubscriptionVariables = Exact<{
|
export type GetDeploymentsSubSubscriptionVariables = Exact<{
|
||||||
id: Scalars['uuid'];
|
id: Scalars['uuid'];
|
||||||
limit: Scalars['Int'];
|
limit: Scalars['Int'];
|
||||||
@@ -19147,6 +19161,71 @@ export type GetDeploymentsQueryResult = Apollo.QueryResult<GetDeploymentsQuery,
|
|||||||
export function refetchGetDeploymentsQuery(variables: GetDeploymentsQueryVariables) {
|
export function refetchGetDeploymentsQuery(variables: GetDeploymentsQueryVariables) {
|
||||||
return { query: GetDeploymentsDocument, variables: variables }
|
return { query: GetDeploymentsDocument, variables: variables }
|
||||||
}
|
}
|
||||||
|
export const ScheduledOrPendingDeploymentsSubDocument = gql`
|
||||||
|
subscription ScheduledOrPendingDeploymentsSub($appId: uuid!) {
|
||||||
|
deployments(
|
||||||
|
where: {deploymentStatus: {_in: ["PENDING", "SCHEDULED"]}, appId: {_eq: $appId}}
|
||||||
|
) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${DeploymentRowFragmentDoc}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useScheduledOrPendingDeploymentsSubSubscription__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useScheduledOrPendingDeploymentsSubSubscription` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useScheduledOrPendingDeploymentsSubSubscription` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useScheduledOrPendingDeploymentsSubSubscription({
|
||||||
|
* variables: {
|
||||||
|
* appId: // value for 'appId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useScheduledOrPendingDeploymentsSubSubscription(baseOptions: Apollo.SubscriptionHookOptions<ScheduledOrPendingDeploymentsSubSubscription, ScheduledOrPendingDeploymentsSubSubscriptionVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSubscription<ScheduledOrPendingDeploymentsSubSubscription, ScheduledOrPendingDeploymentsSubSubscriptionVariables>(ScheduledOrPendingDeploymentsSubDocument, options);
|
||||||
|
}
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscriptionHookResult = ReturnType<typeof useScheduledOrPendingDeploymentsSubSubscription>;
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscriptionResult = Apollo.SubscriptionResult<ScheduledOrPendingDeploymentsSubSubscription>;
|
||||||
|
export const InsertDeploymentDocument = gql`
|
||||||
|
mutation InsertDeployment($object: deployments_insert_input!) {
|
||||||
|
insertDeployment(object: $object) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${DeploymentRowFragmentDoc}`;
|
||||||
|
export type InsertDeploymentMutationFn = Apollo.MutationFunction<InsertDeploymentMutation, InsertDeploymentMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useInsertDeploymentMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useInsertDeploymentMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useInsertDeploymentMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [insertDeploymentMutation, { data, loading, error }] = useInsertDeploymentMutation({
|
||||||
|
* variables: {
|
||||||
|
* object: // value for 'object'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useInsertDeploymentMutation(baseOptions?: Apollo.MutationHookOptions<InsertDeploymentMutation, InsertDeploymentMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<InsertDeploymentMutation, InsertDeploymentMutationVariables>(InsertDeploymentDocument, options);
|
||||||
|
}
|
||||||
|
export type InsertDeploymentMutationHookResult = ReturnType<typeof useInsertDeploymentMutation>;
|
||||||
|
export type InsertDeploymentMutationResult = Apollo.MutationResult<InsertDeploymentMutation>;
|
||||||
|
export type InsertDeploymentMutationOptions = Apollo.BaseMutationOptions<InsertDeploymentMutation, InsertDeploymentMutationVariables>;
|
||||||
export const GetDeploymentsSubDocument = gql`
|
export const GetDeploymentsSubDocument = gql`
|
||||||
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
||||||
deployments(
|
deployments(
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ module.exports = {
|
|||||||
...defaultTheme.screens,
|
...defaultTheme.screens,
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
|
animation: {
|
||||||
|
'spin-reverse': 'spin 1.5s linear infinite reverse',
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
primary: '#0052cd',
|
primary: '#0052cd',
|
||||||
'primary-light': '#ebf3ff',
|
'primary-light': '#ebf3ff',
|
||||||
|
|||||||
Reference in New Issue
Block a user