Skip to content

Commit

Permalink
feat: tanstack query for client cache and state management (#30)
Browse files Browse the repository at this point in the history
Co-authored-by: Oleksandr Shevtsov <oshevtsov@bellum.ai>
  • Loading branch information
Shelex and Oleksandr Shevtsov authored Oct 22, 2024
1 parent 6eca06f commit 7d06216
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 155 deletions.
18 changes: 14 additions & 4 deletions app/components/delete-report-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,28 @@ import {
Button,
} from '@nextui-org/react';
import { useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';

import useMutation from '@/app/hooks/useMutation';
import ErrorMessage from '@/app/components/error-message';
import { DeleteIcon } from '@/app/components/icons';
import { invalidateCache } from '@/app/lib/query-cache';

interface DeleteProjectButtonProps {
reportId: string;
onDeleted: () => void;
}

export default function DeleteReportButton({ reportId, onDeleted }: DeleteProjectButtonProps) {
const { mutate: deleteReport, isLoading, error } = useMutation('/api/report/delete', { method: 'DELETE' });
const queryClient = useQueryClient();
const {
mutate: deleteReport,
isPending,
error,
} = useMutation('/api/report/delete', {
method: 'DELETE',
onSuccess: () => invalidateCache(queryClient, { queryKeys: ['/api/info'], predicate: '/api/report' }),
});
const [confirm, setConfirm] = useState('');

const { isOpen, onOpen, onOpenChange } = useDisclosure();
Expand All @@ -33,7 +43,7 @@ export default function DeleteReportButton({ reportId, onDeleted }: DeleteProjec
return;
}

await deleteReport({ reportsIds: [reportId] });
deleteReport({ body: { reportsIds: [reportId] } });

onDeleted?.();
};
Expand All @@ -42,7 +52,7 @@ export default function DeleteReportButton({ reportId, onDeleted }: DeleteProjec
!!reportId && (
<>
<Tooltip color="danger" content="Delete Report" placement="top">
<Button color="danger" isLoading={isLoading} size="md" onPress={onOpen}>
<Button color="danger" isLoading={isPending} size="md" onPress={onOpen}>
<DeleteIcon />
</Button>
</Tooltip>
Expand All @@ -68,7 +78,7 @@ export default function DeleteReportButton({ reportId, onDeleted }: DeleteProjec
<Button
color="danger"
isDisabled={confirm !== reportId}
isLoading={isLoading}
isLoading={isPending}
onClick={() => {
DeleteReport();
onClose();
Expand Down
20 changes: 15 additions & 5 deletions app/components/delete-results-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,28 @@ import {
Button,
} from '@nextui-org/react';
import { useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';

import useMutation from '@/app/hooks/useMutation';
import ErrorMessage from '@/app/components/error-message';
import { DeleteIcon } from '@/app/components/icons';
import { invalidateCache } from '@/app/lib/query-cache';

interface DeleteProjectButtonProps {
resultIds: string[];
onDeletedResult?: () => void;
}

export default function DeleteResultsButton({ resultIds, onDeletedResult }: DeleteProjectButtonProps) {
const { mutate: deleteResult, isLoading, error } = useMutation('/api/result/delete', { method: 'DELETE' });
export default function DeleteResultsButton({ resultIds, onDeletedResult }: Readonly<DeleteProjectButtonProps>) {
const queryClient = useQueryClient();
const {
mutate: deleteResult,
isPending,
error,
} = useMutation('/api/result/delete', {
method: 'DELETE',
onSuccess: () => invalidateCache(queryClient, { queryKeys: ['/api/info'], predicate: '/api/result' }),
});
const [confirm, setConfirm] = useState('');

const { isOpen, onOpen, onOpenChange } = useDisclosure();
Expand All @@ -33,15 +43,15 @@ export default function DeleteResultsButton({ resultIds, onDeletedResult }: Dele
return;
}

await deleteResult({ resultsIds: resultIds });
deleteResult({ body: { resultsIds: resultIds } });

onDeletedResult?.();
};

return (
<>
<Tooltip color="danger" content="Delete Result" placement="top">
<Button color="danger" isDisabled={!resultIds?.length} isLoading={isLoading} size="md" onPress={onOpen}>
<Button color="danger" isDisabled={!resultIds?.length} isLoading={isPending} size="md" onPress={onOpen}>
<DeleteIcon />
</Button>
</Tooltip>
Expand Down Expand Up @@ -74,7 +84,7 @@ export default function DeleteResultsButton({ resultIds, onDeletedResult }: Dele
<Button
color="danger"
isDisabled={confirm !== resultIds?.at(0)}
isLoading={isLoading}
isLoading={isPending}
onClick={() => {
DeleteResult();
setConfirm('');
Expand Down
2 changes: 1 addition & 1 deletion app/components/fs-stat-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function FilesystemStatTabs({ info, selected, onSelect, onUpdate
key="trends"
isDisabled={!info?.numOfReports || info?.numOfReports <= 1}
title={
<div className="flex flex-col p-5">
<div className="flex flex-col w-20 items-center">
<TrendIcon />
<p>Trends</p>
</div>
Expand Down
22 changes: 16 additions & 6 deletions app/components/generate-report-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import {
AutocompleteItem,
} from '@nextui-org/react';
import { useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';

import { type Result } from '../lib/storage';

import useMutation from '@/app/hooks/useMutation';
import ErrorMessage from '@/app/components/error-message';
import { invalidateCache } from '@/app/lib/query-cache';

interface DeleteProjectButtonProps {
results: Result[];
Expand All @@ -30,7 +32,15 @@ export default function GenerateReportButton({
projects,
onGeneratedReport,
}: Readonly<DeleteProjectButtonProps>) {
const { mutate: generateReport, isLoading, error } = useMutation('/api/report/generate', { method: 'POST' });
const queryClient = useQueryClient();
const {
mutate: generateReport,
isPending,
error,
} = useMutation('/api/report/generate', {
method: 'POST',
onSuccess: () => invalidateCache(queryClient, { queryKeys: ['/api/info'], predicate: '/api/report' }),
});

const [projectName, setProjectName] = useState('');

Expand All @@ -41,7 +51,7 @@ export default function GenerateReportButton({
return;
}

await generateReport({ resultsIds: results.map((r) => r.resultID), project: projectName });
generateReport({ body: { resultsIds: results.map((r) => r.resultID), project: projectName } });

setProjectName('');
onClose();
Expand All @@ -52,7 +62,7 @@ export default function GenerateReportButton({
<>
{error && <ErrorMessage message={error.message} />}
<Tooltip color="secondary" content="Generate Report" placement="top">
<Button color="secondary" isDisabled={!results?.length} isLoading={isLoading} size="md" onClick={onOpen}>
<Button color="secondary" isDisabled={!results?.length} isLoading={isPending} size="md" onClick={onOpen}>
Generate Report
</Button>
</Tooltip>
Expand All @@ -65,7 +75,7 @@ export default function GenerateReportButton({
<Autocomplete
allowsCustomValue
defaultInputValue={projects.at(0) ?? ''}
isDisabled={isLoading}
isDisabled={isPending}
items={projects.map((project) => ({ label: project, value: project }))}
label="Project name"
placeholder="leave empty if not required"
Expand All @@ -76,10 +86,10 @@ export default function GenerateReportButton({
</Autocomplete>
</ModalBody>
<ModalFooter>
<Button color="danger" isDisabled={isLoading} onClick={onClose}>
<Button color="danger" isDisabled={isPending} onClick={onClose}>
Close
</Button>
<Button color="success" isLoading={isLoading} type="submit" onClick={GenerateReport}>
<Button color="success" isLoading={isPending} type="submit" onClick={GenerateReport}>
Generate
</Button>
</ModalFooter>
Expand Down
26 changes: 17 additions & 9 deletions app/components/report-trends.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { Spinner } from '@nextui-org/react';
import { useCallback } from 'react';
import { useCallback, useState } from 'react';

import { defaultProjectName } from '../lib/constants';

Expand All @@ -12,24 +12,32 @@ import { title } from '@/app/components/primitives';
import useQuery from '@/app/hooks/useQuery';
import ErrorMessage from '@/app/components/error-message';
import { type ReportHistory } from '@/app/lib/storage';
import { withQueryParams } from '@/app/lib/network';

export default function ReportTrends() {
const getProjectQueryParam = (project: string) =>
project === defaultProjectName ? '' : `?project=${encodeURIComponent(project)}`;

const getUrl = (project: string) => `/api/report/trend${getProjectQueryParam(project)}`;

const { data: reports, error, isLoading, refetch } = useQuery<ReportHistory[]>(getUrl(defaultProjectName));
const [project, setProject] = useState(defaultProjectName);

const {
data: reports,
error,
isFetching,
isPending,
} = useQuery<ReportHistory[]>(
withQueryParams('/api/report/trend', {
project,
}),
{ dependencies: [project] },
);

const onProjectChange = useCallback((project: string) => {
refetch({ path: getUrl(project) });
setProject(project);
}, []);

return (
<>
<div className="flex flex-row justify-between">
<h1 className={title()}>Trends</h1>
{isLoading && <Spinner />}
{(isFetching || isPending) && <Spinner />}
<div className="min-w-[30%]">
<ProjectSelect entity="report" onSelect={onProjectChange} />
</div>
Expand Down
40 changes: 19 additions & 21 deletions app/components/reports-table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useState } from 'react';
import {
Table,
TableHeader,
Expand All @@ -15,12 +15,13 @@ import {
LinkIcon,
} from '@nextui-org/react';
import Link from 'next/link';
import { keepPreviousData } from '@tanstack/react-query';

import TablePaginationOptions from './table-pagination-options';

import { withQueryParams } from '@/app/lib/network';
import { defaultProjectName } from '@/app/lib/constants';
import useMutation from '@/app/hooks/useMutation';
import useQuery from '@/app/hooks/useQuery';
import ErrorMessage from '@/app/components/error-message';
import DeleteReportButton from '@/app/components/delete-report-button';
import FormattedDate from '@/app/components/date-format';
Expand Down Expand Up @@ -50,30 +51,22 @@ export default function ReportsTable({ onChange }: ReportsTableProps) {
project,
});

const { isLoading, error, mutate } = useMutation(withQueryParams(reportListEndpoint, getQueryParams()), {
method: 'GET',
const {
data: reportResponse,
isFetching,
isPending,
error,
refetch,
} = useQuery<ReadReportsOutput>(withQueryParams(reportListEndpoint, getQueryParams()), {
dependencies: [project, rowsPerPage, page],
placeholderData: keepPreviousData,
});

const fetchReports = async () => {
mutate(null, {
path: withQueryParams(reportListEndpoint, getQueryParams()),
}).then((res) => setReportResponse(res));
};

const [reportResponse, setReportResponse] = useState<ReadReportsOutput>({ reports: [], total: 0 });

useEffect(() => {
if (isLoading) {
return;
}
fetchReports();
}, [rowsPerPage, project, page]);

const { reports, total } = reportResponse ?? {};

const onDeleted = () => {
onChange?.();
fetchReports();
refetch();
};

const onPageChange = useCallback(
Expand Down Expand Up @@ -132,7 +125,12 @@ export default function ReportsTable({ onChange }: ReportsTableProps) {
</TableColumn>
)}
</TableHeader>
<TableBody emptyContent="No reports." isLoading={isLoading} items={reports ?? []} loadingContent={<Spinner />}>
<TableBody
emptyContent="No reports."
isLoading={isFetching || isPending}
items={reports ?? []}
loadingContent={<Spinner />}
>
{(item) => (
<TableRow key={item.reportID}>
<TableCell className="w-1/2">
Expand Down
Loading

0 comments on commit 7d06216

Please sign in to comment.