Skip to content

Commit

Permalink
feat(ui): Helm/Container/Git Repository is shown in DAG (#735)
Browse files Browse the repository at this point in the history
Signed-off-by: Rafal Pelczar <rafal@akuity.io>
  • Loading branch information
rpelczar authored Sep 13, 2023
1 parent 9b17043 commit 1b75083
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.node {
background-color: #ddd;
border-radius: 10px;
display: flex;
flex-direction: column;
padding: 2px;

h3 {
padding: 0.4em 0.5em;
color: #333;
font-size: 15px;
font-weight: 600;
margin-left: 0.25em;
margin-bottom: 0;
}
}

.body {
background-color: white;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
padding: 8px;
font-size: 12px;
flex: 1;
}

.value {
margin-top: 3px;
color: #777;
font-size: 11px;
}
54 changes: 54 additions & 0 deletions ui/src/features/project/project-details/nodes/repo-node.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { faDocker, faGit } from '@fortawesome/free-brands-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from 'antd';

import { NodeType, NodesRepoType } from '../types';

import * as styles from './repo-node.module.less';

const MAX_CHARS = 19;

type Props = {
nodeData: NodesRepoType;
height: number;
};

const name = {
[NodeType.REPO_IMAGE]: 'Image',
[NodeType.REPO_GIT]: 'Git',
[NodeType.REPO_CHART]: 'Chart'
};

const ico = {
[NodeType.REPO_IMAGE]: faDocker,
[NodeType.REPO_GIT]: faGit
};

export const RepoNode = ({ nodeData, height }: Props) => (
<div style={{ height }} className={styles.node}>
<h3 className='flex justify-between'>
<span>{name[nodeData.type]}</span>
{nodeData.type !== NodeType.REPO_CHART && <FontAwesomeIcon icon={ico[nodeData.type]} />}
</h3>
<div className={styles.body}>
{(nodeData.type === NodeType.REPO_IMAGE || nodeData.type === NodeType.REPO_GIT) && (
<RepoNodeBody label='Repo URL' value={nodeData.data.repoUrl} />
)}
{nodeData.type === NodeType.REPO_CHART && (
<RepoNodeBody label='Registry URL' value={nodeData.data.registryUrl} />
)}
</div>
</div>
);

const RepoNodeBody = ({ label, value }: { label: string; value: string }) => (
<>
<div className={styles.label}>{label}</div>
<Tooltip title={value}>
<div className={styles.value}>
{value.length > MAX_CHARS && '...'}
{value.substring(value.length - MAX_CHARS)}
</div>
</Tooltip>
</>
);
85 changes: 85 additions & 0 deletions ui/src/features/project/project-details/nodes/stage-node.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { faGear } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Space, Tooltip } from 'antd';
import { generatePath, useNavigate } from 'react-router-dom';

import { paths } from '@ui/config/paths';
import { HealthStatusIcon } from '@ui/features/common/health-status/health-status-icon';
import { Stage } from '@ui/gen/v1alpha1/types_pb';

import * as styles from './stage-node.module.less';

export const StageNode = ({
stage,
color,
height,
projectName,
onPromoteSubscribersClick
}: {
stage: Stage;
color: string;
height: number;
projectName?: string;
onPromoteSubscribersClick: () => void;
}) => {
const navigate = useNavigate();
return (
<div
className={styles.node}
style={{ backgroundColor: color, position: 'relative', cursor: 'pointer' }}
onClick={() =>
navigate(generatePath(paths.stage, { name: projectName, stageName: stage.metadata?.name }))
}
>
<h3 className='flex items-center text-white'>
<div>{stage.metadata?.name}</div>
<Space className='absolute right-1'>
{stage.status?.currentPromotion && (
<Tooltip
title={`Freight ${stage.status?.currentPromotion.freight?.id} is being promoted`}
>
<FontAwesomeIcon icon={faGear} spin={true} />
</Tooltip>
)}
{stage.status?.health && (
<HealthStatusIcon
health={stage.status?.health}
style={{ marginLeft: 'auto', fontSize: '14px' }}
hideColor={true}
/>
)}
</Space>
</h3>
<div className={styles.body}>
<h3>Current Freight</h3>
<p className='font-mono text-sm font-semibold'>
{stage.status?.currentFreight?.id?.slice(0, 7) || 'N/A'}{' '}
</p>
</div>
<Nodule nodeHeight={height} onClick={onPromoteSubscribersClick} />
</div>
);
};

const Nodule = (props: { begin?: boolean; nodeHeight: number; onClick?: () => void }) => {
const noduleHeight = 16;
const top = props.nodeHeight / 2 - noduleHeight / 2;
return (
<div
onClick={(e) => {
e.stopPropagation();
if (props.onClick) {
props.onClick();
}
}}
style={{
top: top,
height: noduleHeight,
width: noduleHeight
}}
className={`z-10 bg-gray-400 hover:bg-blue-400 absolute ${
props.begin ? '-left-2' : '-right-2'
} rounded-md`}
/>
);
};
135 changes: 85 additions & 50 deletions ui/src/features/project/project-details/project-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Empty } from 'antd';
import { graphlib, layout } from 'dagre';
import React from 'react';
import { generatePath, useNavigate, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';

import { paths } from '@ui/config/paths';
import { transport } from '@ui/config/transport';
import { LoadingState } from '@ui/features/common';
import { useModal } from '@ui/features/common/modal/use-modal';
Expand All @@ -25,16 +24,17 @@ import { Stage } from '@ui/gen/v1alpha1/types_pb';
import { useDocumentEvent } from '@ui/utils/document';

import { Images } from './images';
import { RepoNode } from './nodes/repo-node';
import { StageNode } from './nodes/stage-node';
import { PromoteSubscribersModal } from './promote-subscribers-modal';
import { StageNode } from './stage-node';
import { NodeType, NodesItemType } from './types';

const lineThickness = 2;
const nodeWidth = 144;
const nodeHeight = 100;

export const ProjectDetails = () => {
const { name, stageName } = useParams();
const navigate = useNavigate();
const { data, isLoading } = useQuery(listStages.useQuery({ project: name }));
const { data: freightData, isLoading: isLoadingFreight } = useQuery(
queryFreight.useQuery({ project: name })
Expand Down Expand Up @@ -100,39 +100,71 @@ export const ProjectDetails = () => {
const g = new graphlib.Graph();
g.setGraph({ rankdir: 'LR' });
g.setDefaultEdgeLabel(() => ({}));
const stageByName = new Map<string, Stage>();
const colorByStage = new Map<string, string>();
const stages = data.stages

const colors = getStageColors(data.stages);

const myNodes = data.stages
.slice()
.sort((a, b) => a.metadata?.name?.localeCompare(b.metadata?.name || '') || 0);

const colors = getStageColors(stages);
stages?.forEach((stage) => {
const curColor = colors[stage?.metadata?.uid || ''];
colorByStage.set(stage.metadata?.name || '', curColor);
stageByName.set(stage.metadata?.name || '', stage);
g.setNode(stage.metadata?.name || '', {
label: stage.metadata?.name || '',
.sort((a, b) => a.metadata?.name?.localeCompare(b.metadata?.name || '') || 0)
.flatMap((stage) => {
return [
{
data: stage,
type: NodeType.STAGE,
color: colors[stage?.metadata?.uid || '']
},
...(stage.spec?.subscriptions?.repos?.images || []).map((image) => ({
data: image,
stageName: stage.metadata?.name,
type: NodeType.REPO_IMAGE
})),
...(stage.spec?.subscriptions?.repos?.git || []).map((git) => ({
data: git,
stageName: stage.metadata?.name,
type: NodeType.REPO_GIT
})),
...(stage.spec?.subscriptions?.repos?.charts || []).map((chart) => ({
data: chart,
stageName: stage.metadata?.name,
type: NodeType.REPO_CHART
}))
] as NodesItemType[];
});

myNodes.forEach((item, index) => {
g.setNode(String(index), {
width: nodeWidth,
height: nodeHeight
});

if (item.type === NodeType.STAGE) {
item.data?.spec?.subscriptions?.upstreamStages.forEach((upstramStage) => {
const subsIndex = myNodes.findIndex((node) => {
return node.type === NodeType.STAGE && node.data.metadata?.name === upstramStage.name;
});

g.setEdge(String(subsIndex), String(index));
});
} else {
const subsIndex = myNodes.findIndex((node) => {
return node.type === NodeType.STAGE && node.data.metadata?.name === item.stageName;
});

g.setEdge(String(index), String(subsIndex));
}
});
stages.forEach((stage) => {
stage?.spec?.subscriptions?.upstreamStages.forEach((item) => {
g.setEdge(item.name || '', stage.metadata?.name || '');
});
});
layout(g);

const nodes = g.nodes().map((name) => {
const node = g.node(name);
layout(g, { lablepos: 'c' });

const nodes = myNodes.map((node, index) => {
const gNode = g.node(String(index));

return {
left: node.x - node.width / 2,
top: node.y - node.height / 2,
width: node.width,
height: node.height,
stage: stageByName.get(name) as Stage,
color: colorByStage.get(name) as string
...node,
left: gNode.x - gNode.width / 2,
top: gNode.y - gNode.height / 2,
width: gNode.width,
height: gNode.height
};
});

Expand All @@ -157,6 +189,7 @@ export const ProjectDetails = () => {
}
return lines;
});

const box = nodes.reduce(
(acc, node) => ({
width: Math.max(acc.width, node.left + node.width),
Expand Down Expand Up @@ -216,34 +249,36 @@ export const ProjectDetails = () => {
className='relative'
style={{ width: box?.width, height: box?.height, margin: '0 auto' }}
>
{nodes?.map((node) => (
{nodes?.map((node, index) => (
<div
key={node.stage?.metadata?.name}
className='absolute cursor-pointer'
onClick={() =>
navigate(generatePath(paths.stage, { name, stageName: node.stage.metadata?.name }))
}
key={index}
className='absolute'
style={{
left: node.left,
top: node.top,
width: node.width,
height: node.height
}}
>
<StageNode
stage={node.stage}
color={node.color}
height={node.height}
onPromoteSubscribersClick={() =>
showPromoteSubscribersModal((p) => (
<PromoteSubscribersModal
{...p}
stages={data.stages}
selectedStage={node.stage}
/>
))
}
/>
{node.type === NodeType.STAGE ? (
<StageNode
stage={node.data}
color={node.color}
height={node.height}
projectName={name}
onPromoteSubscribersClick={() =>
showPromoteSubscribersModal((p) => (
<PromoteSubscribersModal
{...p}
stages={data.stages}
selectedStage={node.data}
/>
))
}
/>
) : (
<RepoNode nodeData={node} height={node.height} />
)}
</div>
))}
{connectors?.map((connector) =>
Expand Down

This file was deleted.

15 changes: 0 additions & 15 deletions ui/src/features/project/project-details/stage-item.tsx

This file was deleted.

Loading

0 comments on commit 1b75083

Please sign in to comment.