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

feat: display node details in sidepanel #1072 #1151

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
59 changes: 56 additions & 3 deletions dashboard/components/explorer/DependencyGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import EmptyState from '@components/empty-state/EmptyState';

import Tooltip from '@components/tooltip/Tooltip';
import WarningIcon from '@components/icons/WarningIcon';
import useInventory from '@components/inventory/hooks/useInventory/useInventory';
import settingsService from '@services/settingsService';
import InventorySidePanel from '@components/inventory/components/InventorySidePanel';
import { ReactFlowData } from './hooks/useDependencyGraph';
import {
edgeAnimationConfig,
Expand All @@ -37,9 +40,38 @@ nodeHtmlLabel(Cytoscape.use(COSEBilkent));
Cytoscape.use(popper);
const DependencyGraph = ({ data }: DependencyGraphProps) => {
const [initDone, setInitDone] = useState(false);

const dataIsEmpty: boolean = data.nodes.length === 0;

const {
openModal,
isOpen,
closeModal,
data: inventoryItem,
page,
goTo,
tags,
handleChange,
addNewTag,
removeTag,
updateTags,
loading,
deleteLoading,
bulkItems,
updateBulkTags
} = useInventory();

// opens modal to display details of clicked node
const handleNodeClick = async (event: EventObject) => {
const nodeData = event.target.data();
settingsService.getResourceById(`?resourceId=${nodeData.id}`).then(res => {
if (res === Error) {
faisal7008 marked this conversation as resolved.
Show resolved Hide resolved
console.log('Error retrieving resource by id');
} else {
openModal(res);
}
});
};

// Type technically is Cytoscape.EdgeCollection but that throws an unexpected error
const loopAnimation = (eles: any) => {
const ani = eles.animation(edgeAnimationConfig[0], edgeAnimationConfig[1]);
Expand Down Expand Up @@ -81,6 +113,8 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => {
cy.nodes().roots().addClass('root');
// Animate edges
cy.edges().forEach(loopAnimation);
// Add a click event listener to the Cytoscape graph
cy.on('tap', 'node', handleNodeClick);
faisal7008 marked this conversation as resolved.
Show resolved Hide resolved

// Add hover tooltip on edges
cy.edges().bind('mouseover', event => {
Expand Down Expand Up @@ -141,12 +175,13 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => {

return (
<div className="relative h-full flex-1 bg-dependency-graph bg-[length:40px_40px]">
{/* <CytoscapeComponent
<CytoscapeComponent
className="h-full w-full"
elements={CytoscapeComponent.normalizeElements({
nodes: data.nodes,
edges: data.edges
})}
// forEach={(data.nodes, node => {console.log(node)})}
maxZoom={maxZoom}
minZoom={minZoom}
layout={graphLayoutConfig}
Expand All @@ -165,7 +200,7 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => {
}
]}
cy={(cy: Cytoscape.Core) => cyActionHandlers(cy)}
/> */}
/>
{dataIsEmpty ? (
<>
<div className="translate-y-[201px]">
Expand Down Expand Up @@ -216,6 +251,24 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => {
</div>
)}
</div>
{/* Modal */}
<InventorySidePanel
isOpen={isOpen}
closeModal={closeModal}
data={inventoryItem}
goTo={goTo}
page={page}
updateTags={updateTags}
tags={tags}
tabs={['resource details', 'tags']}
handleChange={handleChange}
removeTag={removeTag}
addNewTag={addNewTag}
loading={loading}
deleteLoading={deleteLoading}
bulkItems={bulkItems}
updateBulkTags={updateBulkTags}
/>
</div>
);
};
Expand Down
22 changes: 22 additions & 0 deletions dashboard/components/icons/HyperLinkIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { SVGProps } from 'react';

const HyperLinkIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M13 11l8.2-8.2M22 6.8V2h-4.8M11 2H9C4 2 2 4 2 9v6c0 5 2 7 7 7h6c5 0 7-2 7-7v-2"
></path>
</svg>
);

export default HyperLinkIcon;
178 changes: 114 additions & 64 deletions dashboard/components/inventory/components/InventorySidePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import SidepanelHeader from '@components/sidepanel/SidepanelHeader';
import SidepanelPage from '@components/sidepanel/SidepanelPage';
import Pill from '@components/pill/Pill';
import formatNumber from '../../../utils/formatNumber';
import providers from '../../../utils/providerHelper';
import Button from '../../button/Button';
Expand Down Expand Up @@ -27,6 +30,7 @@ type InventorySidePanelProps = {
isOpen: boolean;
bulkItems: [] | string[];
updateBulkTags: (action?: 'delete' | undefined) => void;
tabs: string[];
};

function InventorySidePanel({
Expand All @@ -43,80 +47,125 @@ function InventorySidePanel({
deleteLoading,
isOpen,
bulkItems,
updateBulkTags
updateBulkTags,
tabs
}: InventorySidePanelProps) {
const getLastFetched = (date: string) => {
const givenDate = new Date(date);
// Current date
faisal7008 marked this conversation as resolved.
Show resolved Hide resolved
const currentDate = new Date();

// Calculate "since last month" date
const lastMonthDate = new Date(currentDate);
faisal7008 marked this conversation as resolved.
Show resolved Hide resolved
lastMonthDate.setMonth(currentDate.getMonth() - 1);

// Calculate "since last week" date
const lastWeekDate = new Date(currentDate);
lastWeekDate.setDate(currentDate.getDate() - 7);
faisal7008 marked this conversation as resolved.
Show resolved Hide resolved

let message;
// Compare the given date to the calculated dates
if (givenDate > lastMonthDate) {
message = 'Since last month';
} else if (givenDate > lastWeekDate) {
message = 'Since last week';
} else {
message = 'More than a month ago';
}
return message;
};

return (
<>
<Sidepanel isOpen={isOpen} closeModal={closeModal}>
{/* Modal headers */}
<div className="flex flex-wrap-reverse items-center justify-between gap-6 sm:flex-nowrap">
{data && (
<div className="flex flex-wrap items-center gap-4 sm:flex-nowrap">
<picture className="flex-shrink-0">
<img
src={providers.providerImg(data.provider)}
className="h-8 w-8 rounded-full"
alt={data.provider}
/>
</picture>

{data && (
<SidepanelHeader
title={data.service}
subtitle={data.name}
closeModal={closeModal}
href={data.link}
imgSrc={providers.providerImg(data.provider)}
imgAlt={data.provider}
>
{!data && bulkItems && (
<div className="flex flex-col gap-1 py-1">
<p className="... w-48 truncate font-medium text-black-900">
{data.service}
<p className="font-medium text-black-900">
Managing tags for {formatNumber(bulkItems.length)}{' '}
{bulkItems.length > 1 ? 'resources' : 'resource'}
</p>
<p className="flex items-center gap-2 text-xs text-black-300">
{data.name}
<a
target="_blank"
href={data.link}
rel="noreferrer"
className="hover:text-primary"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M13 11l8.2-8.2M22 6.8V2h-4.8M11 2H9C4 2 2 4 2 9v6c0 5 2 7 7 7h6c5 0 7-2 7-7v-2"
></path>
</svg>
</a>
<p className="text-xs text-black-300">
All actions will overwrite previous tags for these resources
</p>
</div>
</div>
)}
{!data && bulkItems && (
<div className="flex flex-col gap-1 py-1">
<p className="font-medium text-black-900">
Managing tags for {formatNumber(bulkItems.length)}{' '}
{bulkItems.length > 1 ? 'resources' : 'resource'}
</p>
<p className="text-xs text-black-300">
All actions will overwrite previous tags for these resources
</p>
</div>
)}

<div className="flex flex-shrink-0 items-center gap-2">
<Button style="secondary" onClick={closeModal}>
Close
</Button>
</div>
</div>
)}
</SidepanelHeader>
)}

{/* Tabs */}
<SidepanelTabs goTo={goTo} page={page} tabs={['Tags']} />
<SidepanelTabs goTo={goTo} page={page} tabs={tabs} />

{/* Tab Content */}
{tabs.includes('resource details') && (
<SidepanelPage page={page} param={'resource details'}>
<div className="space-y-6 pt-1">
<div className="space-y-2">
<h2 className="font-['Noto Sans'] text-sm font-normal leading-tight text-neutral-500">
Cloud account
</h2>
<h2 className="font-['Noto Sans'] text-sm font-normal leading-tight text-neutral-900">
{!data && (
<p className="h-3 w-48 animate-pulse rounded-xl bg-komiser-200"></p>
)}
{data && <span>{data.account}</span>}
</h2>
</div>
<div className="space-y-2">
<h2 className="font-['Noto Sans'] text-sm font-normal leading-tight text-neutral-500">
Region
</h2>
<h2 className="font-['Noto Sans'] text-sm font-normal leading-tight text-neutral-900">
{!data && (
<p className="h-3 w-48 animate-pulse rounded-xl bg-komiser-200"></p>
)}
{data && <span>{data.region}</span>}
</h2>
</div>
<div className="space-y-2">
<h2 className="font-['Noto Sans'] text-sm font-normal leading-tight text-neutral-500">
Cost
</h2>
<h2 className=" flex items-center gap-2 text-sm">
{!data && (
<p className="h-3 w-48 animate-pulse rounded-xl bg-komiser-200"></p>
)}
{data && <span>{data?.cost.toFixed(2)}$</span>}
{data && (
<Pill status="removed">
{getLastFetched(data.fetchedAt)}
</Pill>
)}
</h2>
</div>
<div className="space-y-2">
<h2 className="font-['Noto Sans'] text-sm font-normal leading-tight text-neutral-500">
Relations
</h2>
<h2 className="font-['Noto Sans'] text-sm font-normal leading-tight text-neutral-900">
{!data && (
<p className="h-3 w-48 animate-pulse rounded-xl bg-komiser-200"></p>
)}
{data && (
<span>{data.relations.length} related resources</span>
)}
</h2>
</div>
</div>
</SidepanelPage>
)}
{/* Tags form */}
<div>
{page === 'tags' && (
{tabs.includes('tags') && (
<SidepanelPage page={page} param={'tags'}>
<form
onSubmit={e => {
e.preventDefault();
Expand All @@ -127,7 +176,7 @@ function InventorySidePanel({
updateTags();
}
}}
className="flex flex-col gap-6 pt-2"
className="flex flex-col gap-6 px-1 pt-2"
>
{tags &&
tags.map((tag, id) => (
Expand Down Expand Up @@ -185,8 +234,9 @@ function InventorySidePanel({
</Button>
</div>
</form>
)}

</SidepanelPage>
)}
<div>
{page === 'delete' && (
<>
<div className="flex flex-col gap-6 bg-black-100 p-6">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export type Tag = {
};

export type InventoryItem = {
relations: any[];
account: string;
accountId: string;
cost: number;
Expand All @@ -55,7 +56,7 @@ export type InventoryItem = {
service: string;
tags: Tag[] | [] | null;
};
export type Pages = 'tags' | 'delete';
export type Pages = 'resource details' | 'tags' | 'delete';

export type View = {
id: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function useInventory() {
const [shouldFetchMore, setShouldFetchMore] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [data, setData] = useState<InventoryItem>();
const [page, setPage] = useState<Pages>('tags');
const [page, setPage] = useState<Pages>('resource details');
const [tags, setTags] = useState<Tag[]>();
const [loading, setLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
Expand Down Expand Up @@ -461,7 +461,7 @@ function useInventory() {
*/
function cleanModal() {
setData(undefined);
setPage('tags');
setPage('resource details');
}

/** Opens the modal, as well as:
Expand Down
Loading