chore: (litmus-portal) Logs for workflows (#2131)

* fixed redux to fetch the information required for logs and removed redux logger
* completed subcription and display of logs
* fixed workflowDetails page styling
* Fixed Tab Switching

Signed-off-by: arkajyotiMukherjee <arkajyoti.mukherjee@mayadata.io>
Co-authored-by: Sayan Mondal <sayan.mondal@mayadata.io>
This commit is contained in:
Arkajyoti Mukherjee 2020-09-30 18:28:13 +05:30 committed by GitHub
parent 43b82de5ce
commit 8c3bd228a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 374 additions and 211 deletions

View File

@ -7682,11 +7682,6 @@
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
"dev": true
},
"deep-diff": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
"integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ="
},
"deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
@ -18613,14 +18608,6 @@
"resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz",
"integrity": "sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg=="
},
"redux-logger": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
"integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=",
"requires": {
"deep-diff": "^0.3.5"
}
},
"redux-persist": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",

View File

@ -47,7 +47,6 @@
"react-simple-maps": "^2.1.2",
"redux": "^4.0.1",
"redux-devtools-extension": "^2.13.8",
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"subscriptions-transport-ws": "^0.9.17",
@ -89,7 +88,6 @@
"@types/react-redux": "^7.1.7",
"@types/react-router-dom": "^5.1.3",
"@types/react-simple-maps": "^1.0.3",
"@types/redux-logger": "^3.0.7",
"@typescript-eslint/eslint-plugin": "^3.5.0",
"@typescript-eslint/parser": "^3.5.0",
"cross-env": "^5.2.0",

View File

@ -1,10 +1,10 @@
import React from 'react';
import Typography from '@material-ui/core/Typography';
import { useSelector } from 'react-redux';
import { useTheme } from '@material-ui/core/styles';
import useStyles from './styles';
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { useSelector } from 'react-redux';
import { RootState } from '../../redux/reducers';
import formatCount from '../../utils/formatCount';
import useStyles from './styles';
interface CardValueData {
color: string;
@ -51,6 +51,7 @@ const InfoFilledWrap: React.FC = () => {
const cardArray = cardData.map((individualCard) => {
return (
<div
key={individualCard.value}
style={{ backgroundColor: `${individualCard.color}` }}
className={classes.infoFilledDiv}
>

View File

@ -1,11 +1,10 @@
import React from 'react';
import Modal from '@material-ui/core/Modal';
import Button from '@material-ui/core/Button';
import Modal from '@material-ui/core/Modal';
import React from 'react';
import useStyles from './styles';
/* DelUser, NewUserModal, ResetModal need to be shifted */
interface UnimodalProps {
children?: React.ReactNode;
isOpen: boolean;
handleClose: () => void;
hasCloseBtn: boolean;
@ -21,7 +20,8 @@ const Unimodal: React.FC<UnimodalProps> = ({
isDark,
textAlign,
}) => {
const styleProps = { textAlign, isDark };
const isDarkBg = isDark ?? false;
const styleProps = { textAlign, isDarkBg };
const classes = useStyles(styleProps);
return (

View File

@ -6,7 +6,7 @@ const useStyles = makeStyles((theme: Theme) => ({
width: '70%',
padding: '1rem',
margin: '2rem auto',
background: props.isDark
background: props.isDarkBg
? theme.palette.editorBackground
: theme.palette.common.white,
borderRadius: 3,
@ -30,14 +30,14 @@ const useStyles = makeStyles((theme: Theme) => ({
minHeight: 0,
minWidth: 0,
borderRadius: 3,
color: props.isDark
color: props.isDarkBg
? theme.palette.secondary.contrastText
: theme.palette.customColors.black(0.4),
border: '1px solid',
borderColor: props.isDark
borderColor: props.isDarkBg
? theme.palette.customColors.white(0.2)
: theme.palette.customColors.black(0.4),
marginLeft: props.isDark ? '82.5%' : '60%',
marginLeft: props.isDarkBg ? '82.5%' : '60%',
marginTop: theme.spacing(5),
}),

View File

@ -11,6 +11,7 @@ export const WORKFLOW_DETAILS = gql`
cluster_name
last_updated
cluster_type
cluster_id
}
}
`;

View File

@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { gql } from '@apollo/client';
export const WORKFLOW_EVENTS = gql`
@ -11,6 +10,15 @@ export const WORKFLOW_EVENTS = gql`
project_id
cluster_name
last_updated
cluster_id
}
}
`;
export const WORKFLOW_LOGS = gql`
subscription podLog($podDetails: PodLogRequest!) {
getPodLog(podDetails: $podDetails) {
log
}
}
`;

View File

@ -0,0 +1,25 @@
export interface PodLogRequest {
cluster_id: string;
workflow_run_id: string;
pod_name: string;
pod_namespace: string;
pod_type: string;
exp_pod?: string;
runner_pod?: string;
chaos_namespace?: string;
}
export interface PodLogResponse {
workflow_run_id: string;
pod_name: string;
pod_type: string;
log: string;
}
export interface PodLogVars {
podDetails: PodLogRequest;
}
export interface PodLog {
getPodLog: PodLogResponse;
}

View File

@ -15,6 +15,7 @@ export interface ChaosData {
export interface Node {
children: string[] | null;
finishedAt: string;
message: string;
name: string;
phase: string;
startedAt: string;
@ -47,6 +48,7 @@ export interface WorkflowRun {
workflow_name: string;
workflow_run_id: string;
cluster_type: string;
cluster_id: string;
}
export interface Workflow {

View File

@ -1,5 +1,9 @@
import { Node } from '../graphql/workflowData';
export interface SelectedNode extends Node {
pod_name: string;
}
export enum NodeSelectionActions {
SELECT_NODE = 'SELECT_NODE',
}
@ -11,5 +15,5 @@ interface NodeSelectionActionType<T, P> {
export type NodeSelectionAction = NodeSelectionActionType<
typeof NodeSelectionActions.SELECT_NODE,
Node
SelectedNode
>;

View File

@ -1,11 +1,13 @@
export interface TabState {
workflows: number;
settings: number;
node: number;
}
export enum TabActions {
CHANGE_WORKFLOWS_TAB = 'CHANGE_WORKFLOWS_TAB',
CHANGE_SETTINGS_TAB = 'CHANGE_SETTINGS_TAB',
CHANGE_WORKFLOW_DETAILS_TAB = 'CHANGE_WORKFLOW_DETAILS_TAB',
}
interface TabActionType<T, P> {
@ -15,4 +17,5 @@ interface TabActionType<T, P> {
export type TabAction =
| TabActionType<typeof TabActions.CHANGE_WORKFLOWS_TAB, number>
| TabActionType<typeof TabActions.CHANGE_SETTINGS_TAB, number>;
| TabActionType<typeof TabActions.CHANGE_SETTINGS_TAB, number>
| TabActionType<typeof TabActions.CHANGE_WORKFLOW_DETAILS_TAB, number>;

View File

@ -91,10 +91,10 @@ const TopNavButtons: React.FC<Props> = ({ isToggled, setIsToggled }) => {
return (
<div className={classes.button}>
<div className={classes.buttonLeft}>
<div>
<BackButton isDisabled={false} />
</div>
<div className={classes.buttonRight}>
<div>
{AnalyticsButton()}
{ExportButton()}
{InfoButton()}

View File

@ -11,19 +11,18 @@ import Scaffold from '../../containers/layouts/Scaffold';
import { WORKFLOW_DETAILS, WORKFLOW_EVENTS } from '../../graphql';
import {
ExecutionData,
Node,
Workflow,
WorkflowDataVars,
WorkflowSubscription,
} from '../../models/graphql/workflowData';
import useActions from '../../redux/actions';
import * as TabActions from '../../redux/actions/tabs';
import { RootState } from '../../redux/reducers';
import ArgoWorkflow from '../../views/WorkflowDetails/ArgoWorkflow';
import WorkflowInfo from '../../views/WorkflowDetails/WorkflowInfo';
import WorkflowNodeInfo from '../../views/WorkflowDetails/WorkflowNodeInfo';
import useStyles from './styles';
import TopNavButtons from './TopNavButtons';
import WorkflowNodeInfo from '../../views/WorkflowDetails/NodeInfo';
import ButtonFilled from '../../components/Button/ButtonFilled';
import ButtonOutline from '../../components/Button/ButtonOutline';
interface TopNavButtonsProps {
isAnalyticsToggled: boolean;
@ -40,6 +39,7 @@ const WorkflowDetails: React.FC = () => {
isInfoToggled: false,
});
const tabs = useActions(TabActions);
const { pathname } = useLocation();
// Getting the workflow nome from the pathname
const workflowRunId = pathname.split('/')[3];
@ -49,8 +49,9 @@ const WorkflowDetails: React.FC = () => {
const selectedProjectID = useSelector(
(state: RootState) => state.userData.selectedProjectID
);
// get Selected Node
const selectedNode = useSelector((state: RootState) => state.selectedNode);
const workflowDetailsTabValue = useSelector(
(state: RootState) => state.tabNumber.node
);
// Query to get workflows
const { subscribeToMore, data, error } = useQuery<Workflow, WorkflowDataVars>(
@ -97,12 +98,17 @@ const WorkflowDetails: React.FC = () => {
}
}, [data]);
const [value, setValue] = React.useState(0);
const theme = useTheme();
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
tabs.changeWorkflowDetailsTabs(newValue);
};
// On fresh screen refresh 'Workflow' Tab would be selected
useEffect(() => {
tabs.changeWorkflowDetailsTabs(0);
}, []);
return (
<Scaffold>
<TopNavButtons isToggled={isToggled} setIsToggled={setIsToggled} />
@ -116,75 +122,60 @@ const WorkflowDetails: React.FC = () => {
<Typography>{t('workflowDetails.detailedLog')}</Typography>
{/* Argo Workflow DAG Graph */}
{isToggled.isInfoToggled ? (
<div className={classes.w100}>
<ArgoWorkflow
nodes={
(JSON.parse(workflow.execution_data) as ExecutionData).nodes
}
/>
</div>
) : (
<div className={classes.w140}>
<ArgoWorkflow
nodes={
(JSON.parse(workflow.execution_data) as ExecutionData).nodes
}
/>
</div>
)}
<ArgoWorkflow
nodes={
(JSON.parse(workflow.execution_data) as ExecutionData).nodes
}
/>
</div>
{isToggled.isInfoToggled ? (
<div>
<>
<AppBar
position="static"
color="default"
className={classes.appBar}
<div className={classes.workflowSideBar}>
<AppBar
position="static"
color="default"
className={classes.appBar}
>
<Tabs
value={workflowDetailsTabValue || 0}
onChange={handleChange}
TabIndicatorProps={{
style: {
backgroundColor: theme.palette.secondary.dark,
},
}}
variant="fullWidth"
>
<Tabs
value={value}
onChange={handleChange}
TabIndicatorProps={{
style: {
backgroundColor: theme.palette.secondary.dark,
},
}}
variant="fullWidth"
>
<StyledTab label="Workflow" />
<StyledTab label="Nodes" />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<div data-cy="browseWorkflow">
<WorkflowInfo
workflow_name={workflow.workflow_name}
execution_data={
JSON.parse(workflow?.execution_data) as ExecutionData
}
cluster_name={workflow.cluster_name}
/>
</div>
</TabPanel>
<TabPanel data-cy="scheduleWorkflow" value={value} index={1}>
<div data-cy="browseWorkflow">
<WorkflowNodeInfo nodeDetails={selectedNode as Node} />
</div>
</TabPanel>
</>
<div className={classes.footerButton}>
<ButtonFilled
isPrimary
isDisabled={false}
handleClick={() => {}}
>
Events
</ButtonFilled>
<ButtonOutline isDisabled={false} handleClick={() => {}}>
Logs
</ButtonOutline>
</div>
<StyledTab label="Workflow" />
<StyledTab label="Nodes" />
</Tabs>
</AppBar>
<TabPanel value={workflowDetailsTabValue} index={0}>
<div data-cy="browseWorkflow">
<WorkflowInfo
workflow_name={workflow.workflow_name}
execution_data={
JSON.parse(workflow?.execution_data) as ExecutionData
}
cluster_name={workflow.cluster_name}
/>
</div>
</TabPanel>
<TabPanel
data-cy="scheduleWorkflow"
value={workflowDetailsTabValue}
index={1}
>
<div data-cy="browseWorkflow">
<WorkflowNodeInfo
cluster_id={workflow.cluster_id}
workflow_run_id={workflow.workflow_run_id}
pod_namespace={
(JSON.parse(workflow.execution_data) as ExecutionData)
.namespace
}
/>
</div>
</TabPanel>
</div>
) : (
<></>

View File

@ -3,6 +3,7 @@ import { makeStyles, Theme } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) => ({
root: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: theme.spacing(3),
height: '75vh',
},
@ -10,25 +11,11 @@ const useStyles = makeStyles((theme: Theme) => ({
margin: theme.spacing(-0.4, 1),
width: '1rem',
},
w100: {
width: '100%',
height: '100%',
},
w140: {
width: '141%',
height: '100%',
},
button: {
position: 'relative',
display: 'flex',
margin: theme.spacing(4, 4, 0, 4),
},
buttonLeft: {
float: 'left',
},
buttonRight: {
position: 'absolute',
right: '4%',
justifyContent: 'space-between',
margin: theme.spacing(4, 0, 0, 4),
},
heading: {
fontSize: '2rem',
@ -36,7 +23,10 @@ const useStyles = makeStyles((theme: Theme) => ({
},
workflowGraph: {
padding: '0 3rem',
width: '70%',
width: '100%',
},
workflowSideBar: {
width: '20rem',
},
loaderDiv: {
height: '100%',
@ -48,12 +38,6 @@ const useStyles = makeStyles((theme: Theme) => ({
display: 'flex',
flexDirection: 'column',
},
footerButton: {
marginLeft: 'auto',
display: 'flex',
flexDirection: 'row',
padding: theme.spacing(3, 4, 4, 0),
},
}));
export default useStyles;

View File

@ -1,11 +1,11 @@
/* eslint-disable import/prefer-default-export */
import { Node } from '../../models/graphql/workflowData';
import {
NodeSelectionAction,
NodeSelectionActions,
SelectedNode,
} from '../../models/redux/nodeSelection';
export function selectNode(node: Node): NodeSelectionAction {
export function selectNode(node: SelectedNode): NodeSelectionAction {
return {
type: NodeSelectionActions.SELECT_NODE,
payload: node,

View File

@ -13,3 +13,10 @@ export function changeSettingsTabs(tabNumber: number): TabAction {
payload: tabNumber,
};
}
export function changeWorkflowDetailsTabs(tabNumber: number): TabAction {
return {
type: TabActions.CHANGE_WORKFLOW_DETAILS_TAB,
payload: tabNumber,
};
}

View File

@ -2,7 +2,6 @@ import { createBrowserHistory } from 'history'; // eslint-disable-line import/no
import * as localforage from 'localforage';
import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { createLogger } from 'redux-logger';
import { PersistConfig, persistReducer, persistStore } from 'redux-persist';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
@ -14,12 +13,11 @@ const persistConfig: PersistConfig<any> = {
blacklist: [],
};
const logger = (createLogger as any)();
const history = createBrowserHistory();
const dev = process.env.NODE_ENV === 'development';
let middleware = dev ? applyMiddleware(thunk, logger) : applyMiddleware(thunk);
let middleware = applyMiddleware(thunk);
if (dev) {
middleware = composeWithDevTools(middleware);

View File

@ -1,6 +1,6 @@
import { combineReducers } from 'redux';
import { Node } from '../../models/graphql/workflowData';
import { CommunityData } from '../../models/redux/analytics';
import { SelectedNode } from '../../models/redux/nodeSelection';
import { TabState } from '../../models/redux/tabs';
import { TemplateData } from '../../models/redux/template';
import { UserData } from '../../models/redux/user';
@ -16,7 +16,7 @@ export interface RootState {
communityData: CommunityData;
userData: UserData;
workflowData: WorkflowData;
selectedNode: Node;
selectedNode: SelectedNode;
tabNumber: TabState;
selectTemplate: TemplateData;
}

View File

@ -1,21 +1,26 @@
import { Node } from '../../models/graphql/workflowData';
import {
NodeSelectionAction,
NodeSelectionActions,
SelectedNode,
} from '../../models/redux/nodeSelection';
import createReducer from './createReducer';
const initialState: Node = {
const initialState: SelectedNode = {
children: null,
finishedAt: '',
message: '',
name: '',
pod_name: '',
phase: '',
startedAt: '',
type: '',
};
export const selectedNode = createReducer<Node>(initialState, {
[NodeSelectionActions.SELECT_NODE](state: Node, action: NodeSelectionAction) {
export const selectedNode = createReducer<SelectedNode>(initialState, {
[NodeSelectionActions.SELECT_NODE](
state: SelectedNode,
action: NodeSelectionAction
) {
return {
...state,
...action.payload,

View File

@ -5,6 +5,7 @@ import createReducer from './createReducer';
const initialState: TabState = {
workflows: 0,
settings: 0,
node: 0,
};
export const tabNumber = createReducer<TabState>(initialState, {
@ -20,6 +21,12 @@ export const tabNumber = createReducer<TabState>(initialState, {
settings: action.payload,
};
},
[TabActions.CHANGE_WORKFLOW_DETAILS_TAB](state: TabState, action: TabAction) {
return {
...state,
node: action.payload,
};
},
});
export default tabNumber;

View File

@ -7,13 +7,13 @@ import {
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import React from 'react';
import LinearProgressBar from '../../../components/ProgressBar/LinearProgressBar';
import {
ExecutionData,
WorkflowRun,
} from '../../../models/graphql/workflowData';
import { history } from '../../../redux/configureStore';
import timeDifferenceForDate from '../../../utils/datesModifier';
import LinearProgressBar from '../../../components/ProgressBar/LinearProgressBar';
import CustomStatus from '../CustomStatus/Status';
import useStyles from './styles';
@ -109,9 +109,9 @@ const TableData: React.FC<TableDataProps> = ({ data, exeData }) => {
>
<MenuItem
value="Workflow"
onClick={() =>
history.push(`/workflows/details/${data.workflow_run_id}`)
}
onClick={() => {
history.push(`/workflows/details/${data.workflow_run_id}`);
}}
>
<div className={classes.expDiv} data-cy="workflowDetails">
<img

View File

@ -3,6 +3,7 @@ import DagreGraph, { d3Link, d3Node } from '../../../components/DagreGraph';
import { Nodes } from '../../../models/graphql/workflowData';
import useActions from '../../../redux/actions';
import * as NodeSelectionActions from '../../../redux/actions/nodeSelection';
import * as TabActions from '../../../redux/actions/tabs';
import useStyles from './styles';
interface GraphData {
@ -17,12 +18,18 @@ const ArgoWorkflow: React.FC<ArgoWorkflowProps> = ({ nodes }) => {
const classes = useStyles();
// Redux action call for updating selected node
const nodeSelection = useActions(NodeSelectionActions);
const tabs = useActions(TabActions);
const [graphData, setGraphData] = useState<GraphData>({
nodes: [],
links: [],
});
// Get the selected Node
const [selectedNodeID, setSelectedNodeID] = useState<string>(
Object.keys(nodes)[0]
);
useEffect(() => {
const data: GraphData = {
nodes: [],
@ -58,6 +65,13 @@ const ArgoWorkflow: React.FC<ArgoWorkflowProps> = ({ nodes }) => {
});
}, [nodes]);
useEffect(() => {
nodeSelection.selectNode({
...nodes[selectedNodeID],
pod_name: selectedNodeID,
});
}, [selectedNodeID]);
return graphData.nodes.length ? (
<DagreGraph
className={classes.dagreGraph}
@ -74,7 +88,8 @@ const ArgoWorkflow: React.FC<ArgoWorkflowProps> = ({ nodes }) => {
const nodeID = Object.keys(nodes).filter(
(key) => key === original?.id
)[0];
nodeSelection.selectNode(nodes[nodeID]);
setSelectedNodeID(nodeID);
tabs.changeWorkflowDetailsTabs(1);
}}
/>
) : (

View File

@ -1,61 +0,0 @@
import { Typography } from '@material-ui/core';
import React from 'react';
import { Node } from '../../../models/graphql/workflowData';
import timeDifference from '../../../utils/datesModifier';
import useStyles from './styles';
interface NodeInfoProps {
nodeDetails: Node;
}
const WorkflowNodeInfo: React.FC<NodeInfoProps> = ({ nodeDetails }) => {
const classes = useStyles();
return (
<div className={classes.root}>
{/* Node Name */}
<div className={classes.heightMaintainer}>
<Typography className={classes.nodeSpacing}>
<span className={classes.bold}>Node name:</span>
<br />
{nodeDetails.name}
</Typography>
</div>
<hr />
{/* Node Phase */}
<div className={classes.nodeSpacing}>
<div className={classes.heightMaintainer}>
<Typography>
<span className={classes.bold}>Phase:</span> {nodeDetails.phase}
</Typography>
</div>
</div>
<hr />
{/* Node Durations */}
<div className={classes.nodeSpacing}>
<div className={classes.heightMaintainer}>
<Typography>
<span className={classes.bold}>Start time:</span>{' '}
{timeDifference(nodeDetails.startedAt)}
</Typography>
<Typography>
<span className={classes.bold}>End time:</span>{' '}
{timeDifference(nodeDetails.finishedAt)}
</Typography>
<Typography>
<span className={classes.bold}>Duration: </span>{' '}
{(
(parseInt(nodeDetails.finishedAt, 10) -
parseInt(nodeDetails.startedAt, 10)) /
60
).toFixed(1)}{' '}
minutes
</Typography>
</div>
</div>
</div>
);
};
export default WorkflowNodeInfo;

View File

@ -0,0 +1,59 @@
import { useSubscription } from '@apollo/client';
import { Typography } from '@material-ui/core';
import React from 'react';
import Unimodal from '../../../containers/layouts/Unimodal';
import { WORKFLOW_LOGS } from '../../../graphql';
import {
PodLog,
PodLogRequest,
PodLogVars,
} from '../../../models/graphql/podLog';
import useStyles from './styles';
interface NodeLogsProps extends PodLogRequest {
logsOpen: boolean;
handleClose: () => void;
}
const NodeLogs: React.FC<NodeLogsProps> = ({
logsOpen,
handleClose,
cluster_id,
workflow_run_id,
pod_namespace,
pod_name,
pod_type,
}) => {
const classes = useStyles();
const { data } = useSubscription<PodLog, PodLogVars>(WORKFLOW_LOGS, {
variables: {
podDetails: {
cluster_id,
workflow_run_id,
pod_name,
pod_namespace,
pod_type,
},
},
});
return (
<Unimodal
isOpen={logsOpen}
handleClose={handleClose}
hasCloseBtn
textAlign="left"
>
<div className={classes.root}>
{data !== undefined ? (
<Typography variant="h5">{data.getPodLog.log}</Typography>
) : (
<Typography variant="h5">Fetching Logs...</Typography>
)}
</div>
</Unimodal>
);
};
export default NodeLogs;

View File

@ -0,0 +1,12 @@
import { makeStyles, Theme } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) => ({
root: {
width: '100%',
height: '90%',
background: theme.palette.common.black,
color: theme.palette.common.white,
},
}));
export default useStyles;

View File

@ -1,10 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// TODO: remove this after creating UI for node details sidebar
import { Typography } from '@material-ui/core';
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { ExecutionData } from '../../../models/graphql/workflowData';
import { RootState } from '../../../redux/reducers';
import timeDifference from '../../../utils/datesModifier';
import useStyles from './styles';
@ -26,7 +22,6 @@ const WorkflowInfo: React.FC<WorkflowInfoProps> = ({
}) => {
const classes = useStyles();
// Get selected node data from redux
const selectedNode = useSelector((state: RootState) => state.selectedNode);
const [duration, setDuration] = useState<number>(0);
const [data, setData] = useState<SidebarState>({

View File

@ -0,0 +1,116 @@
/* eslint-disable */
import { Typography } from '@material-ui/core';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import ButtonOutline from '../../../components/Button/ButtonOutline';
import { RootState } from '../../../redux/reducers';
import timeDifference from '../../../utils/datesModifier';
import NodeLogs from '../NodeLogs';
import useStyles from './styles';
interface WorkflowNodeInfoProps {
cluster_id: string;
workflow_run_id: string;
pod_namespace: string;
}
const WorkflowNodeInfo: React.FC<WorkflowNodeInfoProps> = ({
cluster_id,
workflow_run_id,
pod_namespace,
}) => {
const classes = useStyles();
const [logsOpen, setLogsOpen] = useState<boolean>(false);
// Get the nelected node from redux
const { name, phase, pod_name, type, startedAt, finishedAt } = useSelector(
(state: RootState) => state.selectedNode
);
const handleClose = () => {
setLogsOpen(false);
};
return (
<div className={classes.root}>
{/* Logs Modal */}
{logsOpen ? (
<NodeLogs
logsOpen={logsOpen}
handleClose={handleClose}
cluster_id={cluster_id}
workflow_run_id={workflow_run_id}
pod_namespace={pod_namespace}
pod_name={pod_name}
pod_type={type}
/>
) : (
<></>
)}
{/* Node Name */}
<div className={classes.heightMaintainer}>
<Typography className={classes.nodeSpacing}>
<span className={classes.bold}>Name:</span>
<br />
{pod_name}
</Typography>
</div>
{/* Node Type */}
<div className={classes.heightMaintainer}>
<Typography className={classes.nodeSpacing}>
<span className={classes.bold}>Type:</span> {type}
</Typography>
</div>
<hr />
{/* Node Phase */}
<div className={classes.nodeSpacing}>
<div className={classes.heightMaintainer}>
<Typography>
<span className={classes.bold}>Phase:</span> {phase}
</Typography>
</div>
</div>
<hr />
{/* Node Durations */}
<div className={classes.nodeSpacing}>
<div className={classes.heightMaintainer}>
<Typography>
<span className={classes.bold}>Start time:</span>{' '}
{timeDifference(startedAt)}
</Typography>
<Typography>
<span className={classes.bold}>End time:</span>{' '}
{timeDifference(finishedAt)}
</Typography>
<Typography>
<span className={classes.bold}>Duration: </span>{' '}
{(
(parseInt(finishedAt, 10) - parseInt(startedAt, 10)) /
60
).toFixed(1)}{' '}
minutes
</Typography>
</div>
</div>
<hr />
{/* Node Name */}
<div className={classes.nodeSpacing}>
<div className={classes.heightMaintainer}>
<Typography>
<span className={classes.bold}>Node Name:</span> {name}
</Typography>
</div>
</div>
<div className={classes.footerButton}>
<ButtonOutline isDisabled={false} handleClick={() => setLogsOpen(true)}>
Logs
</ButtonOutline>
</div>
</div>
);
};
export default WorkflowNodeInfo;

View File

@ -17,6 +17,12 @@ const useStyles = makeStyles((theme: Theme) => ({
heightMaintainer: {
lineHeight: '2rem',
},
footerButton: {
marginLeft: 'auto',
display: 'flex',
flexDirection: 'row',
padding: theme.spacing(3, 4, 4, 0),
},
}));
export default useStyles;