feat(releases): delete older than x (#924)

* feat: delete releases older than x

* check timestamp

* incomplete front end changes

commiting changes from codespace to not lose them

* change to dropdown with options

* using int comparisons to avoid nightmares

* Revert "using int comparisons to avoid nightmares"

This reverts commit dc55966a73e9f6ad79ed28c3a3e0dbe0e35448a6.

* suggestions by stacksmash76

come back to discord @stacksmash76

* Curves - a touch of warmth in our pixel realm

* replace inline css with tailwind

* remove unnecessary comment

* align label with dropdown
changed first paragraph to something more sensible

* change font weight for duration label

* padding changes

* nitpicky

* merged divs where possible

* small adjustments for light theme

* attempt to fix for postgres

* refactor: split into component and add confirmation modal

also restyle component

* fix: go fmt

---------

Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com>
This commit is contained in:
soup 2023-05-21 18:39:28 +02:00 committed by GitHub
parent 1f76aa38f4
commit f774831d76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 54 deletions

View file

@ -196,6 +196,7 @@ export const APIClient = {
indexerOptions: () => appClient.Get<string[]>("api/release/indexers"),
stats: () => appClient.Get<ReleaseStats>("api/release/stats"),
delete: () => appClient.Delete("api/release/all"),
deleteOlder: (duration: number) => appClient.Delete(`api/release/older-than/${duration}`),
replayAction: (releaseId: number, actionId: number) => appClient.Post(`api/release/${releaseId}/actions/${actionId}/retry`)
},
updates: {

View file

@ -3,84 +3,147 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { useRef } from "react";
import React, { useRef, useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "react-hot-toast";
import { APIClient } from "@api/APIClient";
import Toast from "@components/notifications/Toast";
import { releaseKeys } from "@screens/releases/ReleaseTable";
import { useToggle } from "@hooks/hooks";
import { DeleteModal } from "@components/modals";
import { releaseKeys } from "@screens/releases/ReleaseTable";
function ReleaseSettings() {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const queryClient = useQueryClient();
const deleteMutation = useMutation({
mutationFn: APIClient.release.delete,
onSuccess: () => {
toast.custom((t) => (
<Toast type="success" body={"All releases were deleted"} t={t}/>
));
// Invalidate filters just in case, most likely not necessary but can't hurt.
queryClient.invalidateQueries({ queryKey: releaseKeys.lists() });
}
});
const deleteAction = () => deleteMutation.mutate();
const cancelModalButtonRef = useRef(null);
return (
<form
className="lg:col-span-9"
action="#"
method="POST"
>
<DeleteModal
isOpen={deleteModalIsOpen}
toggle={toggleDeleteModal}
buttonRef={cancelModalButtonRef}
deleteAction={deleteAction}
title={"Delete all releases"}
text="Are you sure you want to delete all releases? This action cannot be undone."
/>
<div className="lg:col-span-9">
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<div>
<h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Releases
</h2>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Release settings. Reset state.
Manage release history.
</p>
</div>
</div>
<div className="pb-6 divide-y divide-gray-200 dark:divide-gray-700">
<div className="px-4 py-5 sm:p-0">
<div className="px-4 py-5 sm:p-6">
<div className="py-6 px-4">
<div className="border border-red-500 rounded">
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<div>
<h3 style={{ textAlign: "center" }} className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Danger Zone
</h3>
<p style={{ textAlign: "center" }} className="mt-1 text-sm text-gray-900 dark:text-white">This will clear all release history in your database.</p>
</div>
<div className="flex justify-between items-center p-2 mt-2 max-w-sm m-auto">
<button
type="button"
onClick={toggleDeleteModal}
className="w-full inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 hover:text-red-900 dark:text-white bg-red-100 dark:bg-red-800 hover:bg-red-200 dark:hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
>
Delete all releases
</button>
<h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Danger zone</h2>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
This will clear release history in your database
</p>
</div>
</div>
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<DeleteReleases />
</div>
</div>
</div>
</form>
</div>
);
}
const getDurationLabel = (durationValue: number): string => {
const durationOptions: Record<number, string> = {
0: "all time",
1: "1 hour",
12: "12 hours",
24: "1 day",
168: "1 week",
720: "1 month",
2160: "3 months",
4320: "6 months",
8760: "1 year"
};
return durationOptions[durationValue] || "Invalid duration";
};
function DeleteReleases() {
const queryClient = useQueryClient();
const [duration, setDuration] = useState<string>("");
const [parsedDuration, setParsedDuration] = useState<number>(0);
const cancelModalButtonRef = useRef<HTMLInputElement | null>(null);
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const deleteOlderMutation = useMutation({
mutationFn: (duration: number) => APIClient.release.deleteOlder(duration),
onSuccess: () => {
if (parsedDuration === 0) {
toast.custom((t) => (
<Toast type="success" body={"All releases were deleted."} t={t} />
));
} else {
toast.custom((t) => (
<Toast type="success" body={`Releases older than ${getDurationLabel(parsedDuration)} were deleted.`} t={t} />
));
}
// Invalidate filters just in case, most likely not necessary but can't hurt.
queryClient.invalidateQueries({ queryKey: releaseKeys.lists() });
}
});
const deleteOlderReleases = () => {
if (isNaN(parsedDuration) || parsedDuration < 0) {
toast.custom((t) => <Toast type="error" body={"Please select a valid duration."} t={t} />);
return;
}
deleteOlderMutation.mutate(parsedDuration);
};
return (
<div className="flex justify-between items-center rounded-md shadow-sm">
<DeleteModal
isOpen={deleteModalIsOpen}
toggle={toggleDeleteModal}
buttonRef={cancelModalButtonRef}
deleteAction={deleteOlderReleases}
title="Remove releases"
text={`Are you sure you want to remove releases older than ${getDurationLabel(parsedDuration)}? This action cannot be undone.`}
/>
<label htmlFor="duration" className="flex flex-col">
<p className="text-sm font-medium text-gray-900 dark:text-white">Delete</p>
<p className="text-sm text-gray-500 dark:text-gray-400">Delete releases older than select duration</p>
</label>
<div>
<select
name="duration"
id="duration"
className="focus:outline-none focus:ring-1 focus:ring-offset-0 focus:ring-blue-500 dark:focus:ring-blue-500 rounded-md sm:text-sm border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white"
value={duration}
onChange={(e) => {
const parsedDuration = parseInt(e.target.value, 10);
setParsedDuration(parsedDuration);
setDuration(e.target.value);
}}
>
<option value="">Select duration</option>
<option value="1">1 hour</option>
<option value="12">12 hours</option>
<option value="24">1 day</option>
<option value="168">1 week</option>
<option value="720">1 month</option>
<option value="2160">3 months</option>
<option value="4320">6 months</option>
<option value="8760">1 year</option>
<option value="0">Delete everything</option>
</select>
<button
type="button"
onClick={toggleDeleteModal}
className="ml-2 inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-red-700 hover:text-red-800 dark:text-white bg-red-200 dark:bg-red-700 hover:bg-red-300 dark:hover:bg-red-800 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-red-600"
>
Delete
</button>
</div>
</div>
);
}