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:
parent
43b82de5ce
commit
8c3bd228a5
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}),
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export const WORKFLOW_DETAILS = gql`
|
|||
cluster_name
|
||||
last_updated
|
||||
cluster_type
|
||||
cluster_id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
>;
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
) : (
|
||||
<></>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue