Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update action buttons on details views #4362

Merged
merged 18 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/4362.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Update action buttons in details view and relatisonhips views
* in the details view, we can edit / delete the object and manage its groups
* in the relationships views, we can add new relationships (it replaces the "+" button at the bottom)
99 changes: 99 additions & 0 deletions frontend/app/src/components/modals/modal-delete-object.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { Fragment, useState } from "react";
import ModalDelete from "./modal-delete";
import { deleteObject } from "@/graphql/mutations/objects/deleteObject";
import { toast } from "react-toastify";
import { Alert, ALERT_TYPES } from "../ui/alert";
import graphqlClient from "@/graphql/graphqlClientApollo";
import { ACCOUNT_TOKEN_OBJECT } from "@/config/constants";
import { stringifyWithoutQuotes } from "@/utils/string";
import { gql } from "@apollo/client";
import { useAtomValue } from "jotai";
import { currentBranchAtom } from "@/state/atoms/branches.atom";
import { datetimeAtom } from "@/state/atoms/time.atom";
import { useParams } from "react-router-dom";

interface iProps {
label?: string | null;
rowToDelete: any;
isLoading?: boolean;
open: boolean;
close: () => void;
onDelete?: () => void;
}

export default function ModalDeleteObject({ label, rowToDelete, open, close, onDelete }: iProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const branch = useAtomValue(currentBranchAtom);
const date = useAtomValue(datetimeAtom);
const { objectKind } = useParams();

const objectDisplay = rowToDelete?.display_label || rowToDelete?.name?.value || rowToDelete?.name;

const handleDeleteObject = async () => {
if (!rowToDelete?.id) {
return;
}

setIsLoading(true);

try {
const mutationString = deleteObject({
kind:
rowToDelete.__typename === "AccountTokenNode"
? ACCOUNT_TOKEN_OBJECT
: rowToDelete.__typename,
data: stringifyWithoutQuotes({
id: rowToDelete?.id,
}),
});

const mutation = gql`
${mutationString}
`;

await graphqlClient.mutate({
mutation,
context: { branch: branch?.name, date },
});

if (objectKind) await graphqlClient.refetchQueries({ include: [objectKind!] });

if (onDelete) await onDelete();

close();

toast(<Alert type={ALERT_TYPES.SUCCESS} message={`Object ${objectDisplay} deleted`} />);
} catch (error) {
console.error("Error while deleting object: ", error);
}

setIsLoading(false);
};

return (
<ModalDelete
title="Delete"
description={
objectDisplay ? (
<>
Are you sure you want to remove the <i>{label}</i>
<b className="ml-2">
&quot;{objectDisplay}
&quot;
</b>
?
</>
) : (
<>
Are you sure you want to remove this <i>{label}</i>?
</>
)
}
onCancel={close}
onDelete={handleDeleteObject}
open={!!open}
setOpen={close}
isLoading={isLoading}
/>
);
}
4 changes: 3 additions & 1 deletion frontend/app/src/components/modals/modal-delete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface iProps {
onDelete: () => void;
onCancel: () => void;
children?: ReactNode;
confirmLabel?: string;
}

export default function ModalDelete({
Expand All @@ -23,6 +24,7 @@ export default function ModalDelete({
setOpen,
isLoading,
children,
confirmLabel,
}: iProps) {
const cancelButtonRef = useRef(null);

Expand Down Expand Up @@ -84,7 +86,7 @@ export default function ModalDelete({
isLoading={isLoading}
data-cy="modal-delete-confirm"
data-testid="modal-delete-confirm">
Delete
{confirmLabel ?? "Delete"}
</Button>
<Button onClick={onCancel} ref={cancelButtonRef}>
Cancel
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/src/components/search/search-anywhere.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type SearchModalProps = {
export function SearchAnywhere({ className = "" }: SearchModalProps) {
let [isOpen, setIsOpen] = useState(false);

function closeModal() {
function closeDrawer() {
setIsOpen(false);
}

Expand Down Expand Up @@ -85,7 +85,7 @@ export function SearchAnywhere({ className = "" }: SearchModalProps) {
</div>

<Transition appear show={isOpen} as={Fragment}>
<Dialog onClose={closeModal}>
<Dialog onClose={closeDrawer}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
Expand All @@ -107,7 +107,7 @@ export function SearchAnywhere({ className = "" }: SearchModalProps) {
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95">
<SearchAnywhereDialog onSelection={closeModal} />
<SearchAnywhereDialog onSelection={closeDrawer} />
</Transition.Child>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ButtonWithTooltip } from "@/components/buttons/button-primitive";
import SlideOver, { SlideOverTitle } from "@/components/display/slide-over";
import ModalDeleteObject from "@/components/modals/modal-delete-object";
import { ARTIFACT_DEFINITION_OBJECT, GENERIC_REPOSITORY_KIND } from "@/config/constants";
import graphqlClient from "@/graphql/graphqlClientApollo";
import { usePermission } from "@/hooks/usePermission";
import { Generate } from "@/screens/artifacts/generate";
import { GroupsManagerTriggerButton } from "@/screens/groups/groups-manager-trigger-button";
import ObjectItemEditComponent from "@/screens/object-item-edit/object-item-edit-paginated";
import RepositoryActionMenu from "@/screens/repository/repository-action-menu";
import { IModelSchema } from "@/state/atoms/schema.atom";
import { isGeneric } from "@/utils/common";
import { Icon } from "@iconify-icon/react";
import { useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";

type DetailsButtonsProps = {
schema: IModelSchema;
objectDetailsData: any;
};

export function DetailsButtons({ schema, objectDetailsData }: DetailsButtonsProps) {
const permission = usePermission();
const location = useLocation();
const { objectid } = useParams();
const navigate = useNavigate();

const redirect = location.pathname.replace(objectid ?? "", "");

const [showEditModal, setShowEditModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);

return (
<>
<div className="flex items-center gap-2">
{schema.kind === ARTIFACT_DEFINITION_OBJECT && <Generate />}

<ButtonWithTooltip
disabled={!permission.write.allow}
tooltipEnabled
tooltipContent={permission.write.message ?? "Edit object"}
onClick={() => setShowEditModal(true)}
data-testid="edit-button">
<Icon icon="mdi:pencil" className="mr-1.5" aria-hidden="true" /> Edit {schema.label}
</ButtonWithTooltip>

{!schema.kind?.match(/Core.*Group/g)?.length && ( // Hide group buttons on group list view
<GroupsManagerTriggerButton
schema={schema}
objectId={objectDetailsData.id}
className="text-custom-blue-600 p-4"
/>
)}

{!isGeneric(schema) && schema.inherit_from?.includes(GENERIC_REPOSITORY_KIND) && (
<RepositoryActionMenu repositoryId={objectDetailsData.id} />
)}

<ButtonWithTooltip
disabled={!permission.write.allow}
tooltipEnabled
tooltipContent={permission.write.message ?? "Delete object"}
data-testid="delete-button"
variant={"danger"}
size={"square"}
onClick={() => setShowDeleteModal(true)}>
<Icon icon="mdi:trash-can-outline" className="" aria-hidden="true" />
</ButtonWithTooltip>
</div>

<SlideOver
title={
<SlideOverTitle
schema={schema}
currentObjectLabel={objectDetailsData.display_label}
title={`Edit ${objectDetailsData.display_label}`}
subtitle={schema.description}
/>
}
open={showEditModal}
setOpen={setShowEditModal}>
<ObjectItemEditComponent
closeDrawer={() => setShowEditModal(false)}
onUpdateComplete={() => graphqlClient.refetchQueries({ include: [schema.kind!] })}
objectid={objectDetailsData.id!}
objectname={schema.kind!}
/>
</SlideOver>

<ModalDeleteObject
label={schema.label ?? schema.kind}
rowToDelete={objectDetailsData}
open={!!showDeleteModal}
close={() => setShowDeleteModal(false)}
onDelete={() => navigate(redirect)}
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { StringParam, useQueryParam } from "use-query-params";
import { DetailsButtons } from "./details-buttons";
import { QSP } from "@/config/qsp";
import { TASK_TAB } from "@/config/constants";
import { RelationshipsButtons } from "./relationships-buttons";

export function ActionButtons(props: any) {
const [qspTab] = useQueryParam(QSP.TAB, StringParam);

if (!qspTab) {
return <DetailsButtons {...props} />;
}

if (qspTab && qspTab !== TASK_TAB) {
return <RelationshipsButtons {...props} />;
}

return null;
}
Loading
Loading