feat(releases): replay actions (#932)

* feat(releases): replay actions

* feat(releases): replay actions component

* fix: update filter actions

* fix: select filter_id from ras
This commit is contained in:
ze0s 2023-05-15 21:30:04 +02:00 committed by GitHub
parent 97333d334f
commit 6898ad8315
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 752 additions and 189 deletions

View file

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

View file

@ -5,11 +5,17 @@
import * as React from "react";
import { formatDistanceToNowStrict } from "date-fns";
import { CheckIcon } from "@heroicons/react/24/solid";
import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid";
import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
import { classNames, simplifyDate } from "@utils";
import { Tooltip } from "../tooltips/Tooltip";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { APIClient } from "@api/APIClient";
import { filterKeys } from "@screens/filters/list";
import { toast } from "react-hot-toast";
import Toast from "@components/notifications/Toast";
import { RingResizeSpinner } from "@components/Icons";
interface CellProps {
value: string;
@ -57,6 +63,46 @@ export const TitleCell = ({ value }: CellProps) => (
</div>
);
interface RetryActionButtonProps {
status: ReleaseActionStatus;
}
interface RetryAction {
releaseId: number;
actionId: number;
}
const RetryActionButton = ({ status }: RetryActionButtonProps) => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (vars: RetryAction) => APIClient.release.replayAction(vars.releaseId, vars.actionId),
onSuccess: () => {
// Invalidate filters just in case, most likely not necessary but can't hurt.
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
toast.custom((t) => (
<Toast type="success" body={`${status?.action} replayed`} t={t} />
));
}
});
const replayAction = () => {
console.log("replay action");
mutation.mutate({ releaseId: status.release_id,actionId: status.id });
};
return (
<button className="flex items-center px-1.5 py-1 ml-2 border-gray-500 bg-gray-700 rounded hover:bg-gray-600" onClick={replayAction}>
<span className="mr-1.5">Retry</span>
{mutation.isLoading
? <RingResizeSpinner className="text-blue-500 w-4 h-4 iconHeight" aria-hidden="true" />
: <ArrowPathIcon className="h-4 w-4" />
}
</button>
);
};
interface ReleaseStatusCellProps {
value: ReleaseActionStatus[];
}
@ -64,69 +110,89 @@ interface ReleaseStatusCellProps {
interface StatusCellMapEntry {
colors: string;
icon: React.ReactElement;
textFormatter: (text: string) => React.ReactElement;
textFormatter: (status: ReleaseActionStatus) => React.ReactElement;
}
const StatusCellMap: Record<string, StatusCellMapEntry> = {
"PUSH_ERROR": {
colors: "bg-pink-100 text-pink-800 hover:bg-pink-300",
icon: <ExclamationCircleIcon className="h-5 w-5" aria-hidden="true" />,
textFormatter: (text: string) => (
textFormatter: (status: ReleaseActionStatus) => (
<>
<span>
Action
{" "}
<span className="font-bold underline underline-offset-2 decoration-2 decoration-red-500">
{" "}
<span className="font-bold underline underline-offset-2 decoration-2 decoration-red-500">
error
</span>
{": "}
{status.action}
</span>
{": "}
{text}
<div>
{status.action_id > 0 && <RetryActionButton status={status} />}
</div>
</>
)
},
"PUSH_REJECTED": {
colors: "bg-blue-100 dark:bg-blue-100 text-blue-400 dark:text-blue-800 hover:bg-blue-300 dark:hover:bg-blue-400",
icon: <NoSymbolIcon className="h-5 w-5" aria-hidden="true" />,
textFormatter: (text: string) => (
textFormatter: (status: ReleaseActionStatus) => (
<>
<span>
Action
{" "}
<span
className="font-bold underline underline-offset-2 decoration-2 decoration-sky-500"
>
{" "}
<span
className="font-bold underline underline-offset-2 decoration-2 decoration-sky-500"
>
rejected
</span>
{": "}
{status.action}
</span>
{": "}
{text}
<div>
{status.action_id > 0 && <RetryActionButton status={status} />}
</div>
</>
)
},
"PUSH_APPROVED": {
colors: "bg-green-100 text-green-800 hover:bg-green-300",
icon: <CheckIcon className="h-5 w-5" aria-hidden="true" />,
textFormatter: (text: string) => (
textFormatter: (status: ReleaseActionStatus) => (
<>
Action
{" "}
<span className="font-bold underline underline-offset-2 decoration-2 decoration-green-500">
<span>
Action
{" "}
<span className="font-bold underline underline-offset-2 decoration-2 decoration-green-500">
approved
</span>
{": "}
{status.action}
</span>
{": "}
{text}
{/*<div>*/}
{/* {status.action_id > 0 && <RetryActionButton status={status} />}*/}
{/*</div>*/}
</>
)
},
"PENDING": {
colors: "bg-yellow-100 text-yellow-800 hover:bg-yellow-200",
icon: <ClockIcon className="h-5 w-5" aria-hidden="true" />,
textFormatter: (text: string) => (
textFormatter: (status: ReleaseActionStatus) => (
<>
Action
{" "}
<span className="font-bold underline underline-offset-2 decoration-2 decoration-yellow-500">
<span>
Action
{" "}
<span className="font-bold underline underline-offset-2 decoration-2 decoration-yellow-500">
pending
</span>
{": "}
{status.action}
</span>
{": "}
{text}
<div>
{status.action_id > 0 && <RetryActionButton status={status} />}
</div>
</>
)
}
@ -156,7 +222,7 @@ export const ReleaseStatusCell = ({ value }: ReleaseStatusCellProps) => (
>
<Tooltip
label={StatusCellMap[v.status].icon}
title={StatusCellMap[v.status].textFormatter(v.action)}
title={StatusCellMap[v.status].textFormatter(v)}
>
<div className="mb-1">
<CellLine title="Type">{v.type}</CellLine>

View file

@ -48,7 +48,7 @@ export const Tooltip = ({
})}
>
{title ? (
<div className="p-2 border-b border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-800 rounded-t-md">
<div className="flex justify-between items-center p-2 border-b border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-800 rounded-t-md">
{title}
</div>
) : null}

View file

@ -4,11 +4,12 @@
*/
import React, { Fragment, useEffect, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Field, FieldArray, FieldProps, FormikValues, useFormikContext } from "formik";
import { Dialog, Switch as SwitchBasic, Transition } from "@headlessui/react";
import { ChevronRightIcon } from "@heroicons/react/24/solid";
import { Link } from "react-router-dom";
import { toast } from "react-hot-toast";
import {
ActionContentLayoutOptions,
@ -25,6 +26,7 @@ import { classNames } from "@utils";
import { DeleteModal } from "@components/modals";
import { CollapsableSection } from "./details";
import { TextArea } from "@components/inputs/input";
import Toast from "@components/notifications/Toast";
interface FilterActionsProps {
filter: Filter;
@ -543,6 +545,23 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const [edit, toggleEdit] = useToggle(initialEdit);
const removeMutation = useMutation({
mutationFn: (id: number) => APIClient.actions.delete(id),
onSuccess: () => {
remove(idx);
// Invalidate filters just in case, most likely not necessary but can't hurt.
// queryClient.invalidateQueries({ queryKey: filterKeys.detail(id) });
toast.custom((t) => (
<Toast type="success" body={`Action ${action?.name} was deleted`} t={t} />
));
}
});
const removeAction = (id: number) => {
removeMutation.mutate(id);
};
return (
<li>
<div
@ -622,7 +641,7 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
isOpen={deleteModalIsOpen}
buttonRef={cancelButtonRef}
toggle={toggleDeleteModal}
deleteAction={() => remove(idx)}
deleteAction={() => removeAction(action.id)}
title="Remove filter action"
text="Are you sure you want to remove this action? This action cannot be undone."
/>

View file

@ -23,9 +23,12 @@ interface ReleaseActionStatus {
id: number;
status: string;
action: string;
action_id: number;
type: string;
client: string;
filter: string;
filter_id: number;
release_id: number;
rejections: string[];
timestamp: string
}