Skip to content

Commit

Permalink
feat(ai.notebooks): backup tabs (#14358)
Browse files Browse the repository at this point in the history
* feat(ai.notebooks): backup tabs
* feat(ai.notebooks): fix sonar issue
REF:DATATR-1640
* Feat/pci ai notebooks modal tracking (#14553)
* feat(ai.notebooks): modal and tracking
* feat(ai.notebooks): resolve conflit & fix sonar issue
* feat(ai.notebooks): fix pr comments
* feat(ai.notebooks): fix pr comments
REF:DATATR-1744
Signed-off-by: Arthur Bullet <[email protected]>
  • Loading branch information
abullet33 authored Dec 13, 2024
1 parent 2ac6a0f commit 2f29b73
Show file tree
Hide file tree
Showing 59 changed files with 1,079 additions and 552 deletions.
1 change: 0 additions & 1 deletion packages/manager/apps/pci-ai-notebooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.50.1",
"react-i18next": "^14.0.5",
"react-router": "^6.21.3",
"react-router-dom": "^6.3.0",
"sonner": "^1.4.0",
"tailwind-merge": "^2.2.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"createNewNotebook": "Créer un notebook",
"tableHeaderName": "Name",
"tableHeaderLocation": "Région",
"tableHeaderEnvironment": "Environnement",
"tableHeaderFramework": "Framework",
"tableHeaderEditor": "Editeur",
"tableHeaderResources": "Ressources",
"tableHeaderPrivacy": "Confidentialité ",
"networkSecureTitle": "Privé",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@
"errorGetCommandCli": "Une erreur est survenue lors de la génération de code équivalent pour la CLI",
"cliEquivalentModalTitle": "Création d’un notebook équivalent",
"cliEquivalentModalDescription": "Commande CLI",
"cliEquivalentModalToastMessage": "Le code a été copié"
"cliEquivalentModalToastMessage": "Le code a été copié",
"errorCreatingNotebook": "Erreur",
"successCreatingNotebookTitle": "Succès",
"successCreatingNotebookDescription": "Votre notebook a été créé avec succès"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dashboardTab": "Dashboard",
"dataTab": "Données attachées",
"backupTab": "Backup",
"backupTab": "Backups",
"logsTab": "Logs",
"publicAccessLabel": "Public",
"privateAccessLabel": "Privé",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"breadcrumb": "Backups",
"title": "Sauvegardes",
"description": "Vous trouverez ci-dessous la liste de vos sauvegardes. Vous pouvez créer un nouveau notebook à partir du chacune d'entre elles",
"tableHeaderId": "Id",
"tableHeaderCreationDate": "Date de création",
"tableHeaderUpdateDate": "Date de modification",
"backupDropdownMenuLabel": "Action",
"tableActionFork": "Fork",
"forkBackupTitle": "Commander un notebook depuis un backup",
"forkBackupDescription": "Utiliser le backup {{id}} créé le {{date}} pour commander un nouveau notebook?",
"forkButtonCancel": "Annuler",
"forkBackupButtonConfirm": "Commander",
"forkToastErrorTitle": "Une erreur est survenue",
"forkToastSuccessTitle": "Succés",
"forkToastSuccessDescription": "votre notebook a été créé avec succès"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"billingSupportTitle": "Support & Facturation",
"configurationTitle": "Configuration",
"cliTitle": "CLI",
"cliCodeTitle": "Vous pouvez créer le même notebook en utilisant ces lignes de commande dans votre ovhai CLI.",
"powerTitleSection": "Power",
"computeTitleSection": "Compute",
"storageTitleSection": "Stockage",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useNavigate } from 'react-router-dom';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
} from '../ui/dialog';
import { Skeleton } from '../ui/skeleton';

interface RouteModalProps {
backUrl?: string;
isLoading?: boolean;
children: React.ReactNode | React.ReactNode[];
onClose?: () => void;
}
const RouteModal = ({
backUrl,
isLoading = false,
children,
onClose,
}: RouteModalProps) => {
const navigate = useNavigate();
const onOpenChange = (open: boolean) => {
if (!open) {
if (onClose) {
onClose();
return;
}
if (backUrl) navigate(backUrl);
}
};

return (
<Dialog defaultOpen onOpenChange={onOpenChange}>
{isLoading ? (
<DialogContent>
<DialogHeader>
<Skeleton className="w-3/5 h-5" />
</DialogHeader>
<DialogDescription className="flex flex-col gap-2">
<Skeleton className="w-4/5 h-4" />
<Skeleton className="w-100 h-4" />
<Skeleton className="w-2/3 h-4" />
<Skeleton className="w-4/5 h-4" />
<Skeleton className="w-1/3 h-4" />
<Skeleton className="w-4/5 h-4" />
</DialogDescription>
<DialogFooter className="flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 flex justify-end mt-2">
<Skeleton className="w-20 h-10" />
<Skeleton className="w-20 h-10" />
</DialogFooter>
</DialogContent>
) : (
children
)}
</Dialog>
);
};

export default RouteModal;
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ DataTable.Skeleton = function DataTableSkeleton({
{Array.from({ length: columns }).map((col, iCol) => (
<TableCell key={`${col}${iCol}`}>
<Skeleton
style={{ width: `${width}px`, height: `${height}px` }}
className='block'
style={{ width: `${width}px`, height: `${height}px`}}
/>
</TableCell>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function Skeleton({
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
<span
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const POLLING = {
NOTEBOOKS: 30_000,
NOTEBOOK: 30_000,
LOGS: 30_000,
BACKUPS: 30_000,
};

export const USER_INACTIVITY_TIMEOUT = 5 * 60_000; // inactivity after 5 minutes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,2 @@
const APP_TRACKING_PREFIX = 'PublicCloud::databases_analytics::databases';
export const APP_TRACKING_PREFIX = 'PublicCloud::ai::notebooks';
export const PCI_LEVEL2 = '86';
export const TRACKING = {
onboarding: {
page: () => `${APP_TRACKING_PREFIX}::databases::onboarding`,
createDatabaseClick: () =>
`${APP_TRACKING_PREFIX}::page::button::create_databases`,
guideClick: (guideName: string) =>
`${APP_TRACKING_PREFIX}::page::tile-tutorial::go-to-${guideName}`,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { apiClient } from '@ovh-ux/manager-core-api';
import * as ai from '@/types/cloud/project/ai';
import { NotebookData } from '@/data/api';

export const getBackups = async ({ projectId, notebookId }: NotebookData) =>
apiClient.v6
.get(`/cloud/project/${projectId}/ai/notebook/${notebookId}/backup`)
.then((res) => res.data as ai.notebook.Backup[]);

export interface BackupData extends NotebookData {
backupId: string;
}

export const getBackup = async ({
projectId,
notebookId,
backupId,
}: BackupData) =>
apiClient.v6
.get(
`/cloud/project/${projectId}/ai/notebook/${notebookId}/backup/${backupId}`,
)
.then((res) => res.data as ai.notebook.Backup);

export const forkBackup = async ({
projectId,
notebookId,
backupId,
}: BackupData) =>
apiClient.v6
.post(
`/cloud/project/${projectId}/ai/notebook/${notebookId}/backup/${backupId}/fork`,
)
.then((res) => res.data as ai.notebook.Notebook);
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams } from 'react-router-dom';
import { AIError } from '@/data/api';
import * as ai from '@/types/cloud/project/ai';
import {
BackupData,
forkBackup,
} from '@/data/api/ai/notebook/backups/backups.api';

interface UseForkBackup {
onError: (cause: AIError) => void;
onSuccess: (notebook: ai.notebook.Notebook) => void;
}

export function useForkBackup({ onError, onSuccess }: UseForkBackup) {
const queryClient = useQueryClient();
const { projectId } = useParams();
const mutation = useMutation({
mutationFn: (forkInfo: BackupData) => {
return forkBackup(forkInfo);
},
onError,
onSuccess: (data) => {
// invalidate notebooks list to avoid displaying
// old list
queryClient.invalidateQueries({
queryKey: [projectId, 'ai/notebook'],
refetchType: 'none',
});
onSuccess(data);
},
});

return {
forkBackup: (forkInfo: BackupData) => {
return mutation.mutate(forkInfo);
},
...mutation,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { QueryObserverOptions, UseQueryResult } from '@tanstack/react-query';
import * as ai from '@/types/cloud/project/ai';
import { useQueryImmediateRefetch } from '@/hooks/api/useImmediateRefetch';
import { AIError } from '@/data/api';
import { getBackup } from '@/data/api/ai/notebook/backups/backups.api';

export function useGetBackup(
projectId: string,
notebookId: string,
backupId: string,
options: Omit<QueryObserverOptions, 'queryKey'> = {},
) {
const queryKey = [
projectId,
'ai',
'notebook',
notebookId,
'backup',
backupId,
];
return useQueryImmediateRefetch({
queryKey,
queryFn: () => getBackup({ projectId, notebookId, backupId }),
...options,
}) as UseQueryResult<ai.notebook.Backup, AIError>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { QueryObserverOptions, UseQueryResult } from '@tanstack/react-query';
import * as ai from '@/types/cloud/project/ai';
import { useQueryImmediateRefetch } from '@/hooks/api/useImmediateRefetch';
import { AIError } from '@/data/api';
import { getBackups } from '@/data/api/ai/notebook/backups/backups.api';

export function useGetBackups(
projectId: string,
notebookId: string,
options: Omit<QueryObserverOptions, 'queryKey'> = {},
) {
const queryKey = [projectId, 'ai', 'notebook', notebookId, 'backup'];
return useQueryImmediateRefetch({
queryKey,
queryFn: () => getBackups({ projectId, notebookId }),
...options,
}) as UseQueryResult<ai.notebook.Backup[], AIError>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function useAddNotebook({ onError, onSuccess }: AddNotebookProps) {
},
onError,
onSuccess: (data) => {
// invalidate services list to avoid displaying
// invalidate notebook list to avoid displaying
// old list
queryClient.invalidateQueries({
queryKey: [projectId, 'ai/notebook'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@ import { deleteNotebook } from '@/data/api/ai/notebook/notebook.api';

interface UseDeleteNotebook {
onError: (cause: AIError) => void;
onSuccess: () => void;
onDeleteSuccess: () => void;
}

export function useDeleteNotebook({
onError,
onSuccess,
onDeleteSuccess,
}: UseDeleteNotebook) {
const queryClient = useQueryClient();
const { projectId } = useParams();
const mutation = useMutation({
mutationFn: (notebookInfo: NotebookData) => {
return deleteNotebook(notebookInfo);
},
onError,
onSuccess,
onSuccess: () => {
// Invalidate notebooks list query to get the latest data
queryClient.invalidateQueries({
queryKey: [projectId, 'ai/notebook', { exact: true }],
});
onDeleteSuccess();
},
});

return {
Expand All @@ -26,4 +34,4 @@ export function useDeleteNotebook({
},
...mutation,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ describe('useDeleteNotebooks', () => {
it('should delete a Notebook', async () => {
const projectId = 'projectId';
const notebookId = 'notebookId';
const onSuccess = vi.fn();
const onDeleteSuccess = vi.fn();
const onError = vi.fn();

vi.mocked(notebookApi.deleteNotebook).mockResolvedValue(undefined);

const { result } = renderHook(
() => useDeleteNotebook({ onError, onSuccess }),
() => useDeleteNotebook({ onError, onDeleteSuccess }),
{
wrapper: QueryClientWrapper,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';

import { useParams } from 'react-router-dom';
import * as ai from '@/types/cloud/project/ai';
Expand All @@ -11,22 +11,13 @@ interface GetCommandProps {
}

export function useGetCommand({ onError, onSuccess }: GetCommandProps) {
const queryClient = useQueryClient();
const { projectId } = useParams();
const mutation = useMutation({
mutationFn: (notebookInfo: ai.notebook.NotebookSpecInput) => {
return getCommand({ projectId, notebookInfo });
},
onError,
onSuccess: (data) => {
// invalidate services list to avoid displaying
// old list
queryClient.invalidateQueries({
queryKey: [projectId, 'ai/notebook'],
refetchType: 'none',
});
onSuccess(data);
},
onSuccess,
});

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function useStartNotebook({
},
onError,
onSuccess: () => {
// Invalidate service list query to get the latest data
// Invalidate notebooks list query to get the latest data
queryClient.invalidateQueries({
queryKey: [projectId, 'ai/notebook'],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function useStopNotebook({ onError, onStopSuccess }: UseStopNotebook) {
},
onError,
onSuccess: () => {
// Invalidate service list query to get the latest data
// Invalidate notebooks list query to get the latest data
queryClient.invalidateQueries({
queryKey: [projectId, 'ai/notebook'],
});
Expand Down
Loading

0 comments on commit 2f29b73

Please sign in to comment.