mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2025-12-21 00:30:12 -06:00
Feat v1 UI tweaks (#1344)
* fix: drop uncached loader * feat: upgrade modal * add beta * hacky feature flag * fix: build * refetch interval * 5s * stop flashing on load * lint * fix: map * fix: last redir * nil check * small styling and wording things, change default canUpgrade -> true * switch link to github discussion --------- Co-authored-by: Alexander Belanger <alexander@hatchet.run>
This commit is contained in:
@@ -21,6 +21,22 @@ func (t *TenantService) TenantUpdate(ctx echo.Context, request gen.TenantUpdateR
|
|||||||
return gen.TenantUpdate400JSONResponse(*apiErrors), nil
|
return gen.TenantUpdate400JSONResponse(*apiErrors), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if the tenant version is being changed
|
||||||
|
if t.config.Runtime.PreventTenantVersionUpgrade &&
|
||||||
|
request.Body.Version != nil &&
|
||||||
|
*request.Body.Version == "V1" &&
|
||||||
|
!tenant.CanUpgradeV1 {
|
||||||
|
|
||||||
|
code := uint64(403)
|
||||||
|
message := "Tenant version upgrade is not enabled for this tenant"
|
||||||
|
|
||||||
|
return gen.TenantUpdate403JSONResponse(
|
||||||
|
gen.APIError{
|
||||||
|
Code: &code,
|
||||||
|
Description: message,
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
// construct the database query
|
// construct the database query
|
||||||
updateOpts := &repository.UpdateTenantOpts{}
|
updateOpts := &repository.UpdateTenantOpts{}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Tenant" ADD COLUMN IF NOT EXISTS "canUpgradeV1" BOOLEAN NOT NULL DEFAULT true;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Tenant" DROP COLUMN IF EXISTS "canUpgradeV1";
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -140,9 +140,8 @@ export function useTenant(): TenantContext {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLastRedirected(tenant?.slug);
|
|
||||||
|
|
||||||
if (tenant?.version == TenantVersion.V0 && pathname.startsWith('/v1')) {
|
if (tenant?.version == TenantVersion.V0 && pathname.startsWith('/v1')) {
|
||||||
|
setLastRedirected(tenant?.slug);
|
||||||
return navigate({
|
return navigate({
|
||||||
pathname: pathname.replace('/v1', ''),
|
pathname: pathname.replace('/v1', ''),
|
||||||
search: params.toString(),
|
search: params.toString(),
|
||||||
@@ -150,6 +149,7 @@ export function useTenant(): TenantContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tenant?.version == TenantVersion.V1 && !pathname.startsWith('/v1')) {
|
if (tenant?.version == TenantVersion.V1 && !pathname.startsWith('/v1')) {
|
||||||
|
setLastRedirected(tenant?.slug);
|
||||||
return navigate({
|
return navigate({
|
||||||
pathname: '/v1' + pathname,
|
pathname: '/v1' + pathname,
|
||||||
search: params.toString(),
|
search: params.toString(),
|
||||||
|
|||||||
@@ -1,121 +1,99 @@
|
|||||||
import MainNav from '@/components/molecules/nav-bar/nav-bar';
|
import MainNav from '@/components/molecules/nav-bar/nav-bar';
|
||||||
import {
|
import { Outlet } from 'react-router-dom';
|
||||||
LoaderFunctionArgs,
|
|
||||||
Outlet,
|
|
||||||
redirect,
|
|
||||||
useLoaderData,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import api, { queries } from '@/lib/api';
|
import api, { queries } from '@/lib/api';
|
||||||
import queryClient from '@/query-client';
|
|
||||||
import { useContextFromParent } from '@/lib/outlet';
|
|
||||||
import { Loading } from '@/components/ui/loading.tsx';
|
import { Loading } from '@/components/ui/loading.tsx';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import SupportChat from '@/components/molecules/support-chat';
|
import SupportChat from '@/components/molecules/support-chat';
|
||||||
import AnalyticsProvider from '@/components/molecules/analytics-provider';
|
import AnalyticsProvider from '@/components/molecules/analytics-provider';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useContextFromParent } from '@/lib/outlet';
|
||||||
const authMiddleware = async (currentUrl: string) => {
|
|
||||||
try {
|
|
||||||
const user = await queryClient.fetchQuery({
|
|
||||||
queryKey: ['user:get:current'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await api.userGetCurrent();
|
|
||||||
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
!user.emailVerified &&
|
|
||||||
!currentUrl.includes('/onboarding/verify-email')
|
|
||||||
) {
|
|
||||||
throw redirect('/onboarding/verify-email');
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Response) {
|
|
||||||
throw error;
|
|
||||||
} else if (
|
|
||||||
!currentUrl.includes('/auth/login') &&
|
|
||||||
!currentUrl.includes('/auth/register')
|
|
||||||
) {
|
|
||||||
throw redirect('/auth/login');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const invitesRedirector = async (currentUrl: string) => {
|
|
||||||
try {
|
|
||||||
const res = await api.userListTenantInvites();
|
|
||||||
|
|
||||||
const invites = res.data.rows || [];
|
|
||||||
|
|
||||||
if (invites.length > 0 && !currentUrl.includes('/onboarding/invites')) {
|
|
||||||
throw redirect('/onboarding/invites');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Response) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const membershipsPopulator = async (currentUrl: string) => {
|
|
||||||
try {
|
|
||||||
const res = await api.tenantMembershipsList();
|
|
||||||
|
|
||||||
const memberships = res.data;
|
|
||||||
|
|
||||||
if (memberships.rows?.length === 0 && !currentUrl.includes('/onboarding')) {
|
|
||||||
throw redirect('/onboarding/create-tenant');
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.data.rows;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Response) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function loader({ request }: LoaderFunctionArgs) {
|
|
||||||
const user = await authMiddleware(request.url);
|
|
||||||
await invitesRedirector(request.url);
|
|
||||||
const memberships = await membershipsPopulator(request.url);
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
memberships,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Authenticated() {
|
export default function Authenticated() {
|
||||||
|
const [hasHasBanner, setHasBanner] = useState(false);
|
||||||
|
|
||||||
|
const userQuery = useQuery({
|
||||||
|
queryKey: ['user:get:current'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await api.userGetCurrent();
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const invitesQuery = useQuery({
|
||||||
|
queryKey: ['user:list-tenant-invites'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await api.userListTenantInvites();
|
||||||
|
return res.data.rows || [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const listMembershipsQuery = useQuery({
|
const listMembershipsQuery = useQuery({
|
||||||
...queries.user.listTenantMemberships,
|
...queries.user.listTenantMemberships,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { user, memberships } = useLoaderData() as Awaited<
|
|
||||||
ReturnType<typeof loader>
|
|
||||||
>;
|
|
||||||
|
|
||||||
const ctx = useContextFromParent({
|
const ctx = useContextFromParent({
|
||||||
user,
|
user: userQuery.data,
|
||||||
memberships: listMembershipsQuery.data?.rows || memberships,
|
memberships: listMembershipsQuery.data?.rows,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [hasHasBanner, setHasBanner] = useState(false);
|
useEffect(() => {
|
||||||
|
const currentUrl = window.location.pathname;
|
||||||
|
|
||||||
if (!user || !memberships) {
|
if (
|
||||||
|
userQuery.data &&
|
||||||
|
!userQuery.data.emailVerified &&
|
||||||
|
!currentUrl.includes('/onboarding/verify-email')
|
||||||
|
) {
|
||||||
|
window.location.href = '/onboarding/verify-email';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
invitesQuery.data?.length &&
|
||||||
|
invitesQuery.data.length > 0 &&
|
||||||
|
!currentUrl.includes('/onboarding/invites')
|
||||||
|
) {
|
||||||
|
window.location.href = '/onboarding/invites';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
listMembershipsQuery.data?.rows?.length === 0 &&
|
||||||
|
!currentUrl.includes('/onboarding')
|
||||||
|
) {
|
||||||
|
window.location.href = '/onboarding/create-tenant';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [userQuery.data, invitesQuery.data, listMembershipsQuery.data]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
userQuery.isLoading ||
|
||||||
|
invitesQuery.isLoading ||
|
||||||
|
listMembershipsQuery.isLoading
|
||||||
|
) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userQuery.error) {
|
||||||
|
const currentUrl = window.location.pathname;
|
||||||
|
if (
|
||||||
|
!currentUrl.includes('/auth/login') &&
|
||||||
|
!currentUrl.includes('/auth/register')
|
||||||
|
) {
|
||||||
|
window.location.href = '/auth/login';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userQuery.data || !listMembershipsQuery.data?.rows) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnalyticsProvider user={user}>
|
<AnalyticsProvider user={userQuery.data}>
|
||||||
<SupportChat user={user}>
|
<SupportChat user={userQuery.data}>
|
||||||
<div className="flex flex-row flex-1 w-full h-full">
|
<div className="flex flex-row flex-1 w-full h-full">
|
||||||
<MainNav user={user} setHasBanner={setHasBanner} />
|
<MainNav user={userQuery.data} setHasBanner={setHasBanner} />
|
||||||
<div
|
<div
|
||||||
className={`${hasHasBanner ? 'pt-28' : 'pt-16'} flex-grow overflow-y-auto overflow-x-hidden`}
|
className={`${hasHasBanner ? 'pt-28' : 'pt-16'} flex-grow overflow-y-auto overflow-x-hidden`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { TenantContextType } from '@/lib/outlet';
|
import { TenantContextType } from '@/lib/outlet';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useLocation, useNavigate, useOutletContext } from 'react-router-dom';
|
import { useOutletContext } from 'react-router-dom';
|
||||||
import { useApiError } from '@/lib/hooks';
|
import { useApiError } from '@/lib/hooks';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import api, {
|
import api, {
|
||||||
@@ -16,8 +16,15 @@ import { Label } from '@radix-ui/react-label';
|
|||||||
import { Spinner } from '@/components/ui/loading';
|
import { Spinner } from '@/components/ui/loading';
|
||||||
import { capitalize } from '@/lib/utils';
|
import { capitalize } from '@/lib/utils';
|
||||||
import { UpdateTenantForm } from './components/update-tenant-form';
|
import { UpdateTenantForm } from './components/update-tenant-form';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
export default function TenantSettings() {
|
export default function TenantSettings() {
|
||||||
const { tenant } = useOutletContext<TenantContextType>();
|
const { tenant } = useOutletContext<TenantContextType>();
|
||||||
|
|
||||||
@@ -40,57 +47,128 @@ export default function TenantSettings() {
|
|||||||
|
|
||||||
const TenantVersionSwitcher = () => {
|
const TenantVersionSwitcher = () => {
|
||||||
const { tenant } = useOutletContext<TenantContextType>();
|
const { tenant } = useOutletContext<TenantContextType>();
|
||||||
const selectedVersion = tenant.version;
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
||||||
const { pathname } = useLocation();
|
const [upgradeRestrictedError, setUpgradeRestrictedError] =
|
||||||
|
useState<boolean>(false);
|
||||||
const { handleApiError } = useApiError({});
|
const { handleApiError } = useApiError({});
|
||||||
|
|
||||||
const { mutate: updateTenant, isPending } = useMutation({
|
const { mutate: updateTenant, isPending } = useMutation({
|
||||||
mutationKey: ['tenant:update'],
|
mutationKey: ['tenant:update'],
|
||||||
mutationFn: async (data: UpdateTenantRequest) => {
|
mutationFn: async (data: UpdateTenantRequest) => {
|
||||||
|
setUpgradeRestrictedError(false);
|
||||||
await api.tenantUpdate(tenant.metadata.id, data);
|
await api.tenantUpdate(tenant.metadata.id, data);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queries.user.listTenantMemberships.queryKey,
|
queryKey: queries.user.listTenantMemberships.queryKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
onError: (error: AxiosError) => {
|
||||||
|
if (error.response?.status === 403) {
|
||||||
|
setUpgradeRestrictedError(true);
|
||||||
|
} else {
|
||||||
|
setShowUpgradeModal(false);
|
||||||
|
handleApiError(error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: handleApiError,
|
|
||||||
});
|
});
|
||||||
const tenantVersions = Object.keys(TenantVersion) as Array<
|
|
||||||
keyof typeof TenantVersion
|
// Only show for V0 tenants
|
||||||
>;
|
if (tenant.version === TenantVersion.V1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-2">
|
<>
|
||||||
<h2 className="text-xl font-semibold leading-tight text-foreground">
|
<div className="flex flex-col gap-y-4">
|
||||||
Tenant Version
|
<h2 className="text-xl font-semibold leading-tight text-foreground">
|
||||||
</h2>
|
Tenant Version
|
||||||
<RadioGroup
|
</h2>
|
||||||
disabled={isPending}
|
<p className="text-sm text-muted-foreground">
|
||||||
value={selectedVersion}
|
Upgrade your tenant to v1 to access new features and improvements. v1
|
||||||
onValueChange={(value) => {
|
is currently in beta.
|
||||||
updateTenant({
|
</p>
|
||||||
version: value as TenantVersion,
|
<Button
|
||||||
});
|
onClick={() => setShowUpgradeModal(true)}
|
||||||
|
disabled={isPending}
|
||||||
|
className="w-fit"
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner /> : null}
|
||||||
|
Upgrade to v1 (beta)
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
if (value === 'V1' && !pathname.includes('v1')) {
|
<Dialog open={showUpgradeModal} onOpenChange={setShowUpgradeModal}>
|
||||||
navigate('/v1' + pathname);
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
} else if (value === 'V0' && pathname.includes('v1')) {
|
<DialogHeader>
|
||||||
navigate(pathname.replace('/v1', ''));
|
<DialogTitle>Upgrade to v1 (beta)</DialogTitle>
|
||||||
}
|
</DialogHeader>
|
||||||
}}
|
{!upgradeRestrictedError && (
|
||||||
>
|
<div className="space-y-4 py-4">
|
||||||
{tenantVersions.map((version) => (
|
<p className="text-sm">Upgrading your tenant to v1 will:</p>
|
||||||
<div key={version} className="flex items-center space-x-2">
|
<ul className="list-disc list-inside text-sm space-y-2">
|
||||||
<RadioGroupItem value={version} id={version.toLowerCase()} />
|
<li>Enable new v1 features and improvements</li>
|
||||||
<Label htmlFor={version.toLowerCase()}>{version}</Label>
|
<li>Redirect you to the v1 interface</li>
|
||||||
</div>
|
</ul>
|
||||||
))}
|
<Alert variant="warn">
|
||||||
</RadioGroup>
|
<AlertTitle>Warning</AlertTitle>
|
||||||
</div>
|
<AlertDescription>
|
||||||
|
This upgrade will not automatically migrate your existing
|
||||||
|
workflows or in-progress runs. To ensure zero downtime during
|
||||||
|
the upgrade, please follow our migration guide which includes
|
||||||
|
steps for parallel operation of v0 and v1 environments.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<p className="text-sm">
|
||||||
|
Please read our{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/hatchet-dev/hatchet/discussions/1348"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-indigo-400 hover:underline"
|
||||||
|
>
|
||||||
|
v1 preview announcement
|
||||||
|
</a>{' '}
|
||||||
|
before proceeding.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgradeRestrictedError && (
|
||||||
|
<Alert variant="warn">
|
||||||
|
<AlertDescription>
|
||||||
|
Tenant version upgrade has been restricted for this tenant.
|
||||||
|
Please contact us to request upgrade referencing tenant id:{' '}
|
||||||
|
{tenant.metadata.id}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowUpgradeModal(false)}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
updateTenant({
|
||||||
|
version: TenantVersion.V1,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner /> : null}
|
||||||
|
Confirm Upgrade
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Button } from '@/components/v1/ui/button';
|
|||||||
import { Separator } from '@/components/v1/ui/separator';
|
import { Separator } from '@/components/v1/ui/separator';
|
||||||
import { TenantContextType } from '@/lib/outlet';
|
import { TenantContextType } from '@/lib/outlet';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useLocation, useNavigate, useOutletContext } from 'react-router-dom';
|
import { useOutletContext } from 'react-router-dom';
|
||||||
import { useApiError } from '@/lib/hooks';
|
import { useApiError } from '@/lib/hooks';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import api, {
|
import api, {
|
||||||
@@ -16,7 +16,14 @@ import { Label } from '@radix-ui/react-label';
|
|||||||
import { Spinner } from '@/components/v1/ui/loading';
|
import { Spinner } from '@/components/v1/ui/loading';
|
||||||
import { capitalize } from '@/lib/utils';
|
import { capitalize } from '@/lib/utils';
|
||||||
import { UpdateTenantForm } from './components/update-tenant-form';
|
import { UpdateTenantForm } from './components/update-tenant-form';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/v1/ui/dialog';
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '@/components/v1/ui/alert';
|
||||||
|
|
||||||
export default function TenantSettings() {
|
export default function TenantSettings() {
|
||||||
const { tenant } = useOutletContext<TenantContextType>();
|
const { tenant } = useOutletContext<TenantContextType>();
|
||||||
@@ -40,10 +47,8 @@ export default function TenantSettings() {
|
|||||||
|
|
||||||
const TenantVersionSwitcher = () => {
|
const TenantVersionSwitcher = () => {
|
||||||
const { tenant } = useOutletContext<TenantContextType>();
|
const { tenant } = useOutletContext<TenantContextType>();
|
||||||
const selectedVersion = tenant.version;
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const [showDowngradeModal, setShowDowngradeModal] = useState(false);
|
||||||
const { pathname } = useLocation();
|
|
||||||
|
|
||||||
const { handleApiError } = useApiError({});
|
const { handleApiError } = useApiError({});
|
||||||
|
|
||||||
@@ -56,41 +61,85 @@ const TenantVersionSwitcher = () => {
|
|||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queries.user.listTenantMemberships.queryKey,
|
queryKey: queries.user.listTenantMemberships.queryKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
},
|
},
|
||||||
onError: handleApiError,
|
onError: handleApiError,
|
||||||
});
|
});
|
||||||
const tenantVersions = Object.keys(TenantVersion) as Array<
|
|
||||||
keyof typeof TenantVersion
|
// Only show for V1 tenants
|
||||||
>;
|
if (tenant.version === TenantVersion.V0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-2">
|
<>
|
||||||
<h2 className="text-xl font-semibold leading-tight text-foreground">
|
<div className="flex flex-col gap-y-2">
|
||||||
Tenant Version
|
<h2 className="text-xl font-semibold leading-tight text-foreground">
|
||||||
</h2>
|
Tenant Version
|
||||||
<RadioGroup
|
</h2>
|
||||||
disabled={isPending}
|
<p className="text-sm text-muted-foreground">
|
||||||
value={selectedVersion}
|
You can downgrade your tenant to v0 if needed. Please help us improve
|
||||||
onValueChange={(value) => {
|
v1 by reporting any bugs in our{' '}
|
||||||
updateTenant({
|
<a
|
||||||
version: value as TenantVersion,
|
href="https://github.com/hatchet-dev/hatchet/issues"
|
||||||
});
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-indigo-400 hover:underline"
|
||||||
|
>
|
||||||
|
Github issues.
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowDowngradeModal(true)}
|
||||||
|
disabled={isPending}
|
||||||
|
variant="destructive"
|
||||||
|
className="w-fit"
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner /> : null}
|
||||||
|
Downgrade to v0
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
if (value === 'V1' && !pathname.includes('v1')) {
|
<Dialog open={showDowngradeModal} onOpenChange={setShowDowngradeModal}>
|
||||||
navigate('/v1' + pathname);
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
} else if (value === 'V0' && pathname.includes('v1')) {
|
<DialogHeader>
|
||||||
navigate(pathname.replace('/v1', ''));
|
<DialogTitle>Downgrade to v0</DialogTitle>
|
||||||
}
|
</DialogHeader>
|
||||||
}}
|
<div className="space-y-4 py-4">
|
||||||
>
|
<Alert variant="warn">
|
||||||
{tenantVersions.map((version) => (
|
<AlertTitle>Warning</AlertTitle>
|
||||||
<div key={version} className="flex items-center space-x-2">
|
<AlertDescription>
|
||||||
<RadioGroupItem value={version} id={version.toLowerCase()} />
|
Downgrading to v0 will remove access to v1 features and may
|
||||||
<Label htmlFor={version.toLowerCase()}>{version}</Label>
|
affect your existing workflows. This action should only be taken
|
||||||
|
if absolutely necessary.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
))}
|
<DialogFooter>
|
||||||
</RadioGroup>
|
<Button
|
||||||
</div>
|
variant="outline"
|
||||||
|
onClick={() => setShowDowngradeModal(false)}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => {
|
||||||
|
updateTenant({
|
||||||
|
version: TenantVersion.V0,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner /> : null}
|
||||||
|
Confirm Downgrade
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DataTable } from '@/components/v1/molecules/data-table/data-table.tsx';
|
import { DataTable } from '@/components/v1/molecules/data-table/data-table.tsx';
|
||||||
import { columns } from './v1/task-runs-columns';
|
import { columns } from './v1/task-runs-columns';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { RowSelectionState, VisibilityState } from '@tanstack/react-table';
|
import { RowSelectionState, VisibilityState } from '@tanstack/react-table';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import invariant from 'tiny-invariant';
|
import invariant from 'tiny-invariant';
|
||||||
@@ -69,7 +69,7 @@ export function TaskRunsTable({
|
|||||||
createdAfter: createdAfterProp,
|
createdAfter: createdAfterProp,
|
||||||
initColumnVisibility = {},
|
initColumnVisibility = {},
|
||||||
filterVisibility = {},
|
filterVisibility = {},
|
||||||
refetchInterval = 100000,
|
refetchInterval = 5000,
|
||||||
showMetrics = false,
|
showMetrics = false,
|
||||||
showCounts = true,
|
showCounts = true,
|
||||||
disableTaskRunPagination = false,
|
disableTaskRunPagination = false,
|
||||||
@@ -106,6 +106,7 @@ export function TaskRunsTable({
|
|||||||
selectedRuns,
|
selectedRuns,
|
||||||
numPages,
|
numPages,
|
||||||
isLoading: isTaskRunsLoading,
|
isLoading: isTaskRunsLoading,
|
||||||
|
isFetching: isTaskRunsFetching,
|
||||||
refetch: refetchTaskRuns,
|
refetch: refetchTaskRuns,
|
||||||
getRowId,
|
getRowId,
|
||||||
} = useTaskRuns({
|
} = useTaskRuns({
|
||||||
@@ -120,6 +121,7 @@ export function TaskRunsTable({
|
|||||||
metrics,
|
metrics,
|
||||||
tenantMetrics,
|
tenantMetrics,
|
||||||
isLoading: isMetricsLoading,
|
isLoading: isMetricsLoading,
|
||||||
|
isFetching: isMetricsFetching,
|
||||||
refetch: refetchMetrics,
|
refetch: refetchMetrics,
|
||||||
} = useMetrics({
|
} = useMetrics({
|
||||||
workflow,
|
workflow,
|
||||||
@@ -153,7 +155,12 @@ export function TaskRunsTable({
|
|||||||
const hasTaskFiltersSelected = Object.values(v1TaskFilters).some(
|
const hasTaskFiltersSelected = Object.values(v1TaskFilters).some(
|
||||||
(filter) => !!filter,
|
(filter) => !!filter,
|
||||||
);
|
);
|
||||||
const isLoading = isTaskRunsLoading || isMetricsLoading;
|
|
||||||
|
const hasLoaded = useMemo(() => {
|
||||||
|
return !isTaskRunsLoading && !isMetricsLoading;
|
||||||
|
}, [isTaskRunsLoading, isMetricsLoading]);
|
||||||
|
|
||||||
|
const isFetching = !hasLoaded && (isTaskRunsFetching || isMetricsFetching);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -197,7 +204,7 @@ export function TaskRunsTable({
|
|||||||
code={JSON.stringify(tenantMetrics || '{}', null, 2)}
|
code={JSON.stringify(tenantMetrics || '{}', null, 2)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isMetricsLoading && <Skeleton className="w-full h-36" />}
|
{isMetricsLoading && 'Loading...'}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
@@ -319,7 +326,7 @@ export function TaskRunsTable({
|
|||||||
)}
|
)}
|
||||||
<DataTable
|
<DataTable
|
||||||
emptyState={<>No workflow runs found with the given filters.</>}
|
emptyState={<>No workflow runs found with the given filters.</>}
|
||||||
isLoading={isLoading}
|
isLoading={isFetching}
|
||||||
columns={columns(cf.setAdditionalMetadata, onTaskRunIdClick)}
|
columns={columns(cf.setAdditionalMetadata, onTaskRunIdClick)}
|
||||||
columnVisibility={columnVisibility}
|
columnVisibility={columnVisibility}
|
||||||
setColumnVisibility={setColumnVisibility}
|
setColumnVisibility={setColumnVisibility}
|
||||||
|
|||||||
@@ -38,11 +38,8 @@ export const useMetrics = ({
|
|||||||
const tenantMetrics = tenantMetricsQuery.data?.queues || {};
|
const tenantMetrics = tenantMetricsQuery.data?.queues || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading:
|
isLoading: metricsQuery.isLoading || tenantMetricsQuery.isLoading,
|
||||||
metricsQuery.isLoading ||
|
isFetching: metricsQuery.isFetching || tenantMetricsQuery.isFetching,
|
||||||
metricsQuery.isFetching ||
|
|
||||||
tenantMetricsQuery.isLoading ||
|
|
||||||
tenantMetricsQuery.isFetching,
|
|
||||||
tenantMetrics,
|
tenantMetrics,
|
||||||
metrics,
|
metrics,
|
||||||
refetch: () => {
|
refetch: () => {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export const usePagination = () => {
|
|||||||
|
|
||||||
const pagination = useMemo(
|
const pagination = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
pageIndex: Number(searchParams.get(pageSizeParamName)) || 0,
|
pageIndex: Number(searchParams.get(pageIndexParamName)) || 0,
|
||||||
pageSize: Number(searchParams.get(pageIndexParamName)) || 50,
|
pageSize: Number(searchParams.get(pageSizeParamName)) || 50,
|
||||||
}),
|
}),
|
||||||
[searchParams],
|
[searchParams],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export const useTaskRuns = ({
|
|||||||
refetch: listTasksQuery.refetch,
|
refetch: listTasksQuery.refetch,
|
||||||
isLoading: listTasksQuery.isLoading,
|
isLoading: listTasksQuery.isLoading,
|
||||||
isError: listTasksQuery.isError,
|
isError: listTasksQuery.isError,
|
||||||
|
isFetching: listTasksQuery.isFetching,
|
||||||
getRowId,
|
getRowId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export const routes: RouteObject[] = [
|
|||||||
lazy: async () =>
|
lazy: async () =>
|
||||||
import('./pages/onboarding/verify-email').then((res) => {
|
import('./pages/onboarding/verify-email').then((res) => {
|
||||||
return {
|
return {
|
||||||
loader: res.loader,
|
|
||||||
Component: res.default,
|
Component: res.default,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -62,7 +61,6 @@ export const routes: RouteObject[] = [
|
|||||||
lazy: async () =>
|
lazy: async () =>
|
||||||
import('./pages/authenticated').then((res) => {
|
import('./pages/authenticated').then((res) => {
|
||||||
return {
|
return {
|
||||||
loader: res.loader,
|
|
||||||
Component: res.default,
|
Component: res.default,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -100,7 +98,6 @@ export const routes: RouteObject[] = [
|
|||||||
lazy: async () =>
|
lazy: async () =>
|
||||||
import('./pages/onboarding/invites').then((res) => {
|
import('./pages/onboarding/invites').then((res) => {
|
||||||
return {
|
return {
|
||||||
loader: res.loader,
|
|
||||||
Component: res.default,
|
Component: res.default,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -343,7 +340,6 @@ export const routes: RouteObject[] = [
|
|||||||
lazy: async () =>
|
lazy: async () =>
|
||||||
import('./pages/authenticated').then((res) => {
|
import('./pages/authenticated').then((res) => {
|
||||||
return {
|
return {
|
||||||
loader: res.loader,
|
|
||||||
Component: res.default,
|
Component: res.default,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -181,6 +181,9 @@ type ConfigFileRuntime struct {
|
|||||||
QueueStepRunBuffer buffer.ConfigFileBuffer `mapstructure:"queueStepRunBuffer" json:"queueStepRunBuffer,omitempty"`
|
QueueStepRunBuffer buffer.ConfigFileBuffer `mapstructure:"queueStepRunBuffer" json:"queueStepRunBuffer,omitempty"`
|
||||||
|
|
||||||
Monitoring ConfigFileMonitoring `mapstructure:"monitoring" json:"monitoring,omitempty"`
|
Monitoring ConfigFileMonitoring `mapstructure:"monitoring" json:"monitoring,omitempty"`
|
||||||
|
|
||||||
|
// PreventTenantVersionUpgrade controls whether the server prevents tenant version upgrades
|
||||||
|
PreventTenantVersionUpgrade bool `mapstructure:"preventTenantVersionUpgrade" json:"preventTenantVersionUpgrade,omitempty" default:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InternalClientTLSConfigFile struct {
|
type InternalClientTLSConfigFile struct {
|
||||||
@@ -526,6 +529,7 @@ func BindAllEnv(v *viper.Viper) {
|
|||||||
_ = v.BindEnv("runtime.bufferCreateWorkflowRuns", "SERVER_BUFFER_CREATE_WORKFLOW_RUNS")
|
_ = v.BindEnv("runtime.bufferCreateWorkflowRuns", "SERVER_BUFFER_CREATE_WORKFLOW_RUNS")
|
||||||
_ = v.BindEnv("runtime.disableTenantPubs", "SERVER_DISABLE_TENANT_PUBS")
|
_ = v.BindEnv("runtime.disableTenantPubs", "SERVER_DISABLE_TENANT_PUBS")
|
||||||
_ = v.BindEnv("runtime.maxInternalRetryCount", "SERVER_MAX_INTERNAL_RETRY_COUNT")
|
_ = v.BindEnv("runtime.maxInternalRetryCount", "SERVER_MAX_INTERNAL_RETRY_COUNT")
|
||||||
|
_ = v.BindEnv("runtime.preventTenantVersionUpgrade", "SERVER_PREVENT_TENANT_VERSION_UPGRADE")
|
||||||
|
|
||||||
// security check options
|
// security check options
|
||||||
_ = v.BindEnv("securityCheck.enabled", "SERVER_SECURITY_CHECK_ENABLED")
|
_ = v.BindEnv("securityCheck.enabled", "SERVER_SECURITY_CHECK_ENABLED")
|
||||||
|
|||||||
@@ -1619,6 +1619,7 @@ type Tenant struct {
|
|||||||
WorkerPartitionId pgtype.Text `json:"workerPartitionId"`
|
WorkerPartitionId pgtype.Text `json:"workerPartitionId"`
|
||||||
DataRetentionPeriod string `json:"dataRetentionPeriod"`
|
DataRetentionPeriod string `json:"dataRetentionPeriod"`
|
||||||
SchedulerPartitionId pgtype.Text `json:"schedulerPartitionId"`
|
SchedulerPartitionId pgtype.Text `json:"schedulerPartitionId"`
|
||||||
|
CanUpgradeV1 bool `json:"canUpgradeV1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TenantAlertEmailGroup struct {
|
type TenantAlertEmailGroup struct {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ VALUES (
|
|||||||
),
|
),
|
||||||
COALESCE($4::text, '720h')
|
COALESCE($4::text, '720h')
|
||||||
)
|
)
|
||||||
RETURNING id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
RETURNING id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateTenantParams struct {
|
type CreateTenantParams struct {
|
||||||
@@ -131,6 +131,7 @@ func (q *Queries) CreateTenant(ctx context.Context, db DBTX, arg CreateTenantPar
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
)
|
)
|
||||||
return &i, err
|
return &i, err
|
||||||
}
|
}
|
||||||
@@ -370,7 +371,7 @@ func (q *Queries) GetEmailGroups(ctx context.Context, db DBTX, tenantid pgtype.U
|
|||||||
|
|
||||||
const getInternalTenantForController = `-- name: GetInternalTenantForController :one
|
const getInternalTenantForController = `-- name: GetInternalTenantForController :one
|
||||||
SELECT
|
SELECT
|
||||||
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
FROM
|
FROM
|
||||||
"Tenant" as tenants
|
"Tenant" as tenants
|
||||||
WHERE
|
WHERE
|
||||||
@@ -395,6 +396,7 @@ func (q *Queries) GetInternalTenantForController(ctx context.Context, db DBTX, c
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
)
|
)
|
||||||
return &i, err
|
return &i, err
|
||||||
}
|
}
|
||||||
@@ -520,7 +522,7 @@ func (q *Queries) GetTenantAlertingSettings(ctx context.Context, db DBTX, tenant
|
|||||||
|
|
||||||
const getTenantByID = `-- name: GetTenantByID :one
|
const getTenantByID = `-- name: GetTenantByID :one
|
||||||
SELECT
|
SELECT
|
||||||
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
FROM
|
FROM
|
||||||
"Tenant" as tenants
|
"Tenant" as tenants
|
||||||
WHERE
|
WHERE
|
||||||
@@ -544,13 +546,14 @@ func (q *Queries) GetTenantByID(ctx context.Context, db DBTX, id pgtype.UUID) (*
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
)
|
)
|
||||||
return &i, err
|
return &i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTenantBySlug = `-- name: GetTenantBySlug :one
|
const getTenantBySlug = `-- name: GetTenantBySlug :one
|
||||||
SELECT
|
SELECT
|
||||||
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
FROM
|
FROM
|
||||||
"Tenant" as tenants
|
"Tenant" as tenants
|
||||||
WHERE
|
WHERE
|
||||||
@@ -574,6 +577,7 @@ func (q *Queries) GetTenantBySlug(ctx context.Context, db DBTX, slug string) (*T
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
)
|
)
|
||||||
return &i, err
|
return &i, err
|
||||||
}
|
}
|
||||||
@@ -871,7 +875,7 @@ func (q *Queries) ListTenantMembers(ctx context.Context, db DBTX, tenantid pgtyp
|
|||||||
|
|
||||||
const listTenants = `-- name: ListTenants :many
|
const listTenants = `-- name: ListTenants :many
|
||||||
SELECT
|
SELECT
|
||||||
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
FROM
|
FROM
|
||||||
"Tenant" as tenants
|
"Tenant" as tenants
|
||||||
`
|
`
|
||||||
@@ -899,6 +903,7 @@ func (q *Queries) ListTenants(ctx context.Context, db DBTX) ([]*Tenant, error) {
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -912,7 +917,7 @@ func (q *Queries) ListTenants(ctx context.Context, db DBTX) ([]*Tenant, error) {
|
|||||||
|
|
||||||
const listTenantsByControllerPartitionId = `-- name: ListTenantsByControllerPartitionId :many
|
const listTenantsByControllerPartitionId = `-- name: ListTenantsByControllerPartitionId :many
|
||||||
SELECT
|
SELECT
|
||||||
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
FROM
|
FROM
|
||||||
"Tenant" as tenants
|
"Tenant" as tenants
|
||||||
WHERE
|
WHERE
|
||||||
@@ -948,6 +953,7 @@ func (q *Queries) ListTenantsByControllerPartitionId(ctx context.Context, db DBT
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -961,7 +967,7 @@ func (q *Queries) ListTenantsByControllerPartitionId(ctx context.Context, db DBT
|
|||||||
|
|
||||||
const listTenantsBySchedulerPartitionId = `-- name: ListTenantsBySchedulerPartitionId :many
|
const listTenantsBySchedulerPartitionId = `-- name: ListTenantsBySchedulerPartitionId :many
|
||||||
SELECT
|
SELECT
|
||||||
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
FROM
|
FROM
|
||||||
"Tenant" as tenants
|
"Tenant" as tenants
|
||||||
WHERE
|
WHERE
|
||||||
@@ -997,6 +1003,7 @@ func (q *Queries) ListTenantsBySchedulerPartitionId(ctx context.Context, db DBTX
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1010,7 +1017,7 @@ func (q *Queries) ListTenantsBySchedulerPartitionId(ctx context.Context, db DBTX
|
|||||||
|
|
||||||
const listTenantsByTenantWorkerPartitionId = `-- name: ListTenantsByTenantWorkerPartitionId :many
|
const listTenantsByTenantWorkerPartitionId = `-- name: ListTenantsByTenantWorkerPartitionId :many
|
||||||
SELECT
|
SELECT
|
||||||
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
FROM
|
FROM
|
||||||
"Tenant" as tenants
|
"Tenant" as tenants
|
||||||
WHERE
|
WHERE
|
||||||
@@ -1046,6 +1053,7 @@ func (q *Queries) ListTenantsByTenantWorkerPartitionId(ctx context.Context, db D
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1414,7 +1422,7 @@ SET
|
|||||||
"version" = COALESCE($4::"TenantMajorEngineVersion", "version")
|
"version" = COALESCE($4::"TenantMajorEngineVersion", "version")
|
||||||
WHERE
|
WHERE
|
||||||
"id" = $5::uuid
|
"id" = $5::uuid
|
||||||
RETURNING id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId"
|
RETURNING id, "createdAt", "updatedAt", "deletedAt", version, name, slug, "analyticsOptOut", "alertMemberEmails", "controllerPartitionId", "workerPartitionId", "dataRetentionPeriod", "schedulerPartitionId", "canUpgradeV1"
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTenantParams struct {
|
type UpdateTenantParams struct {
|
||||||
@@ -1448,6 +1456,7 @@ func (q *Queries) UpdateTenant(ctx context.Context, db DBTX, arg UpdateTenantPar
|
|||||||
&i.WorkerPartitionId,
|
&i.WorkerPartitionId,
|
||||||
&i.DataRetentionPeriod,
|
&i.DataRetentionPeriod,
|
||||||
&i.SchedulerPartitionId,
|
&i.SchedulerPartitionId,
|
||||||
|
&i.CanUpgradeV1,
|
||||||
)
|
)
|
||||||
return &i, err
|
return &i, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2199,6 +2199,7 @@ type Tenant struct {
|
|||||||
WorkerPartitionId pgtype.Text `json:"workerPartitionId"`
|
WorkerPartitionId pgtype.Text `json:"workerPartitionId"`
|
||||||
DataRetentionPeriod string `json:"dataRetentionPeriod"`
|
DataRetentionPeriod string `json:"dataRetentionPeriod"`
|
||||||
SchedulerPartitionId pgtype.Text `json:"schedulerPartitionId"`
|
SchedulerPartitionId pgtype.Text `json:"schedulerPartitionId"`
|
||||||
|
CanUpgradeV1 bool `json:"canUpgradeV1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TenantAlertEmailGroup struct {
|
type TenantAlertEmailGroup struct {
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ CREATE TABLE "Tenant" (
|
|||||||
"workerPartitionId" TEXT,
|
"workerPartitionId" TEXT,
|
||||||
"dataRetentionPeriod" TEXT NOT NULL DEFAULT '720h',
|
"dataRetentionPeriod" TEXT NOT NULL DEFAULT '720h',
|
||||||
"schedulerPartitionId" TEXT,
|
"schedulerPartitionId" TEXT,
|
||||||
|
"canUpgradeV1" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
CONSTRAINT "Tenant_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Tenant_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user