(litmus-portal): Graph pop out mode, state-less prometheus queries for chaos events, mongoDB resync updates removed, optimization of analytics code for monitoring, reducing heap-size usage, time range / refresh rate selection and chaos metrics on graphs with selection module. (#2593)
* Added clean up feature for disabled workflow events. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * temp-no-push Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * optimizations and state-less chaos events Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * Updating prometheus query sync logic Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * expand button working Signed-off-by: Ritik Srivastava <ritik.srivastava@mayadata.io> * styling for grids Signed-off-by: Ritik Srivastava <ritik.srivastava@mayadata.io> * translations added for tooltip Signed-off-by: Ritik Srivastava <ritik.srivastava@mayadata.io> * config reset Signed-off-by: Ritik Srivastava <ritik.srivastava@mayadata.io> * Adding refresh rate selector and basic time range selector with some state management fixes. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * styling fixes for card background in dashboard graphs Signed-off-by: Ritik Srivastava <ritik.srivastava@mayadata.io> * reset config Signed-off-by: Ritik Srivastava <ritik.srivastava@mayadata.io> * Adding initial event mapping with refresh rate and time range selection fixes. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * added filtering for chaos metrics. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fixes Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * enabled real time updates. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * fixes Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * Fixes for events. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * linting fixes. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * fixes Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor fix Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * color fixes. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * reverting to old package lock file. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * fixes after review. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * adding constants also removed uneccesary style classes. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * divided index into modules for the page. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * fixes after review. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * moving component overrides to tsx files from styles. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * minor NaN% fix. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * removed an unused variable. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> * fix after review. Signed-off-by: ishangupta-ds <ishan@chaosnative.com> Co-authored-by: Ritik Srivastava <ritik.srivastava@mayadata.io>
This commit is contained in:
parent
b5fc3f0e6a
commit
d5173bf1c1
|
@ -0,0 +1,3 @@
|
|||
<svg width="74" height="74" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.0508 47.9562C62.4191 47.9562 59.3509 50.4076 58.4045 53.7437H50.7305V45.2715H58.5659C67.0764 45.2715 74 38.3404 74 29.8207C74 22.9899 69.4744 16.9891 63.0497 15.0347C61.1077 7.24987 54.0881 1.66651 45.9583 1.66651C42.8068 1.66651 39.7879 2.48009 37.1236 4.03447C34.3019 1.45122 30.602 0 26.7146 0C18.3761 0 11.5609 6.65434 11.2889 14.9366C4.68946 16.7832 0 22.8322 0 29.8207C0 38.3404 6.92363 45.2715 15.4341 45.2715H23.2695V53.7437H15.5955C14.6493 50.4076 11.5809 47.9562 7.94922 47.9562C3.56602 47.9562 0 51.5261 0 55.914C0 60.302 3.56602 63.8718 7.94922 63.8718C11.5809 63.8718 14.6491 61.4204 15.5955 58.0843H25.4375C26.6348 58.0843 27.6055 57.1126 27.6055 55.914V45.2715H34.832V58.3876C31.4996 59.3349 29.0508 62.4066 29.0508 66.0422C29.0508 70.4301 32.6168 74 37 74C41.3832 74 44.9492 70.4301 44.9492 66.0422C44.9492 62.4066 42.5004 59.335 39.168 58.3876V45.2715H46.3945V55.914C46.3945 57.1126 47.3652 58.0843 48.5625 58.0843H58.4045C59.3507 61.4204 62.4191 63.8718 66.0508 63.8718C70.434 63.8718 74 60.302 74 55.914C74 51.5261 70.434 47.9562 66.0508 47.9562ZM7.94922 59.5312C5.95686 59.5312 4.33594 57.9085 4.33594 55.914C4.33594 53.9195 5.95686 52.2968 7.94922 52.2968C9.94158 52.2968 11.5625 53.9195 11.5625 55.914C11.5625 57.9085 9.94158 59.5312 7.94922 59.5312ZM40.6133 66.0422C40.6133 68.0367 38.9924 69.6594 37 69.6594C35.0076 69.6594 33.3867 68.0367 33.3867 66.0422C33.3867 64.0476 35.0076 62.425 37 62.425C38.9924 62.425 40.6133 64.0476 40.6133 66.0422ZM15.4341 40.9309C9.31461 40.9309 4.33594 35.947 4.33594 29.8207C4.33594 24.3453 8.41273 19.6194 13.8186 18.8279C14.9594 18.661 15.7699 17.6293 15.6636 16.4802C15.6322 16.1412 15.6163 15.7948 15.6163 15.4506C15.6163 9.32469 20.595 4.34078 26.7144 4.34078C29.9794 4.34078 33.0583 5.76161 35.1616 8.23896C35.9 9.10868 37.185 9.25713 38.1021 8.57883C40.3758 6.8964 43.0924 6.00715 45.9582 6.00715C52.5529 6.00715 58.0371 10.8512 59.0623 17.1524C59.2072 18.0434 59.8881 18.7515 60.7719 18.9307C65.9243 19.9749 69.6639 24.5547 69.6639 29.8207C69.6639 35.947 64.6852 40.9309 58.5658 40.9309H15.4341ZM66.0508 59.5312C64.0584 59.5312 62.4375 57.9085 62.4375 55.914C62.4375 53.9195 64.0584 52.2968 66.0508 52.2968C68.0431 52.2968 69.6641 53.9195 69.6641 55.914C69.6641 57.9085 68.0431 59.5312 66.0508 59.5312Z" fill="#F6B92B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,12 @@
|
|||
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M1.3 8.96875L0 12.9988L4.03 11.6988L1.3 8.96875Z" fill="#5B44BA"/>
|
||||
<path d="M8.55602 1.69643L2.21338 8.03906L4.97105 10.7967L11.3137 4.4541L8.55602 1.69643Z" fill="#5B44BA"/>
|
||||
<path d="M12.8052 2.015L10.9852 0.195C10.7252 -0.065 10.3352 -0.065 10.0752 0.195L9.49023 0.78L12.2202 3.51L12.8052 2.925C13.0652 2.665 13.0652 2.275 12.8052 2.015Z" fill="#5B44BA"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="13" height="13" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 596 B |
|
@ -191,8 +191,35 @@ analyticsDashboard:
|
|||
overview: Overview
|
||||
monitoringDashboardPage:
|
||||
dataSourceError: Reconfigure this dashboard with a different data source or update data source information.
|
||||
dataSourceIs: Data source is
|
||||
reConfigureDashboard: Re-configure dashboard
|
||||
updateDataSource: Update data source
|
||||
headerInfoText: "View the chaos events and metrics in a given \n time interval by selecting a time interval."
|
||||
rangeSelector:
|
||||
to: to
|
||||
selectPeriod: Select Period
|
||||
refresh:
|
||||
heading: Refresh
|
||||
off: Off
|
||||
every5Seconds: Every 5 seconds
|
||||
every10Seconds: Every 10 seconds
|
||||
every30Seconds: Every 30 seconds
|
||||
every1Minute: Every 1 minute
|
||||
every5Minutes: Every 5 minutes
|
||||
every15Minutes: Every 15 minutes
|
||||
every30Minutes: Every 30 minutes
|
||||
every1Hour: Every 1 hour
|
||||
every2Hours: Every 2 hours
|
||||
every1Day: Every 1 day
|
||||
chaosTable:
|
||||
tableHead1: Legend
|
||||
tableHead2: Workflow
|
||||
tableHead3: Experiment
|
||||
tableHead4: "Target \n ( namespace / pod )"
|
||||
tableHead5: Result
|
||||
noRecords: No chaos happened during the interval
|
||||
showTable: Show Chaos during this interval
|
||||
hideTable: Hide Chaos during this interval
|
||||
workflowClusterDashboard:
|
||||
title: Dashboard
|
||||
tab1: Hourly
|
||||
|
@ -273,6 +300,11 @@ analyticsDashboard:
|
|||
day: day
|
||||
days: days
|
||||
few: few
|
||||
toolTip:
|
||||
viewChaosMetric: View Chaos Metric Info
|
||||
hideChaosMetric: Hide Chaos Metric Info
|
||||
editPanel: Edit
|
||||
popout: Popout
|
||||
|
||||
error:
|
||||
whoops: Whoops!
|
||||
|
@ -973,4 +1005,4 @@ customWorkflow:
|
|||
addEnv: Add Key
|
||||
addExp: Add experiment
|
||||
viewYAML:
|
||||
view: View the YAML here
|
||||
view: View the YAML here
|
|
@ -17,6 +17,7 @@ interface DateRangeSelectorProps {
|
|||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
callbackToSetRange: RangeCallBackType;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const DateRangeSelector: React.FC<DateRangeSelectorProps> = ({
|
||||
|
@ -24,6 +25,7 @@ const DateRangeSelector: React.FC<DateRangeSelectorProps> = ({
|
|||
isOpen,
|
||||
onClose,
|
||||
callbackToSetRange,
|
||||
className,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { palette } = useTheme();
|
||||
|
@ -54,6 +56,7 @@ const DateRangeSelector: React.FC<DateRangeSelectorProps> = ({
|
|||
classes={{
|
||||
paper: classes.popoverDateRangeSelector,
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
<div className={classes.dateRangeSelectorContainer}>
|
||||
<DateRangePicker
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { createStyles, TableCell, withStyles } from '@material-ui/core';
|
||||
|
||||
export const StyledTableCell = withStyles((theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
borderBottom: `1px solid ${theme.palette.border.main}`,
|
||||
},
|
||||
})
|
||||
)(TableCell);
|
|
@ -14,13 +14,13 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
textSection: {
|
||||
height: 'fit-content',
|
||||
width: '60%',
|
||||
margin: '0 3rem',
|
||||
margin: theme.spacing(0, 6),
|
||||
alignSelf: 'center',
|
||||
minWidth: '20rem',
|
||||
},
|
||||
|
||||
mainHeading: {
|
||||
fontSize: '1.4rem',
|
||||
lineHeight: '1rem',
|
||||
width: '95%',
|
||||
display: 'inline-block',
|
||||
marginBottom: theme.spacing(2),
|
||||
|
|
|
@ -44,6 +44,7 @@ export const WORKFLOW_LIST_DETAILS = gql`
|
|||
query workflowListDetails($projectID: String!, $workflowIDs: [ID]) {
|
||||
ListWorkflow(project_id: $projectID, workflow_ids: $workflowIDs) {
|
||||
workflow_id
|
||||
workflow_manifest
|
||||
cronSyntax
|
||||
cluster_name
|
||||
workflow_name
|
||||
|
@ -58,6 +59,7 @@ export const WORKFLOW_LIST_DETAILS = gql`
|
|||
project_id
|
||||
cluster_id
|
||||
cluster_type
|
||||
isRemoved
|
||||
workflow_runs {
|
||||
execution_data
|
||||
workflow_run_id
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { PanelGroupResponse } from './graphql/dashboardsDetails';
|
||||
import { GraphMetric } from 'litmus-ui';
|
||||
import { PanelGroupResponse, PanelResponse } from './graphql/dashboardsDetails';
|
||||
import { promQueryInput } from './graphql/prometheus';
|
||||
import { ChaosData } from './graphql/workflowListData';
|
||||
|
||||
export interface PanelGroupMap {
|
||||
groupName: string;
|
||||
|
@ -34,4 +37,78 @@ export interface ChaosResultNamesAndNamespacesMap {
|
|||
resultNamespace: string;
|
||||
workflowName: string;
|
||||
experimentName: string;
|
||||
selectedWorkflowIds: string[];
|
||||
}
|
||||
|
||||
export interface RunWiseChaosMetrics {
|
||||
runIndex: number;
|
||||
runID: string;
|
||||
lastUpdatedTimeStamp: number;
|
||||
probeSuccessPercentage: string;
|
||||
experimentStatus: string;
|
||||
experimentVerdict: string;
|
||||
resilienceScore: string;
|
||||
workflowStatus: string;
|
||||
}
|
||||
|
||||
export interface WorkflowAndExperimentMetaDataMap {
|
||||
workflowID: string;
|
||||
workflowName: string;
|
||||
experimentName: string;
|
||||
targetApp: string;
|
||||
targetNamespace: string;
|
||||
runWiseChaosMetrics: RunWiseChaosMetrics[];
|
||||
}
|
||||
|
||||
export interface ExperimentNameAndChaosDataMap {
|
||||
experimentName: string;
|
||||
chaosData: ChaosData;
|
||||
}
|
||||
|
||||
export interface WorkflowRunWiseDetails {
|
||||
idsOfWorkflowRuns: string[];
|
||||
resilienceScoreForWorkflowRuns: number[];
|
||||
statusOfWorkflowRuns: string[];
|
||||
experimentNameWiseChaosDataOfWorkflowRuns: ExperimentNameAndChaosDataMap[][];
|
||||
}
|
||||
|
||||
export interface ChaosEventDetails {
|
||||
id: string;
|
||||
legend: string;
|
||||
workflow: string;
|
||||
experiment: string;
|
||||
target: string;
|
||||
result: string;
|
||||
chaosMetrics: WorkflowAndExperimentMetaDataMap;
|
||||
showOnTable: Boolean;
|
||||
}
|
||||
|
||||
export interface ChaosInformation {
|
||||
promQueries: promQueryInput[];
|
||||
chaosQueryIDs: string[];
|
||||
chaosEventList: ChaosEventDetails[];
|
||||
numberOfWorkflowsUnderConsideration: number;
|
||||
}
|
||||
|
||||
export interface ChaosDataUpdates {
|
||||
queryIDs: string[];
|
||||
chaosData: Array<EventMetric>;
|
||||
reGenerate: Boolean;
|
||||
latestEventResult: string[];
|
||||
}
|
||||
|
||||
export interface EventMetric extends GraphMetric {
|
||||
subData?: Array<{
|
||||
subDataName: string;
|
||||
value: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface GraphPanelProps extends PanelResponse {
|
||||
className?: string;
|
||||
chaos_data?: Array<EventMetric>;
|
||||
}
|
||||
|
||||
export interface GraphPanelGroupProps extends PanelGroupResponse {
|
||||
chaos_data?: Array<EventMetric>;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export interface PrometheusQueryInput {
|
|||
url: string;
|
||||
start: string;
|
||||
end: string;
|
||||
queries?: promQueryInput[];
|
||||
queries: promQueryInput[];
|
||||
}
|
||||
|
||||
export interface timeStampValue {
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface Nodes {
|
|||
}
|
||||
|
||||
export interface ExecutionData {
|
||||
resiliency_score?: number;
|
||||
event_type: string;
|
||||
uid: string;
|
||||
namespace: string;
|
||||
|
@ -52,6 +53,7 @@ export interface WorkflowRun {
|
|||
|
||||
export interface Workflow {
|
||||
workflow_id: string;
|
||||
workflow_manifest: string;
|
||||
cronSyntax: string;
|
||||
cluster_name: string;
|
||||
workflow_name: string;
|
||||
|
@ -63,6 +65,7 @@ export interface Workflow {
|
|||
project_id: string;
|
||||
cluster_id: string;
|
||||
cluster_type: string;
|
||||
isRemoved: Boolean;
|
||||
workflow_runs: WorkflowRun[];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,11 @@ export interface PanelGroupMap {
|
|||
panels: string[];
|
||||
}
|
||||
|
||||
export interface RangeType {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
selectedDashboardID: string;
|
||||
selectedDashboardName?: string;
|
||||
|
@ -13,6 +18,8 @@ export interface DashboardData {
|
|||
selectedAgentID?: string;
|
||||
selectedAgentName?: string;
|
||||
refreshRate?: number;
|
||||
range: RangeType;
|
||||
forceUpdate: Boolean;
|
||||
}
|
||||
|
||||
export enum DashboardSelectionActions {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
export const PROMETHEUS_ERROR_QUERY_RESOLUTION_LIMIT_REACHED: string = `bad_data: exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)`;
|
||||
export const DEFAULT_CHAOS_EVENT_PROMETHEUS_QUERY_RESOLUTION: string = '1/2';
|
||||
export const CHAOS_EXPERIMENT_VERDICT_PASS: string = 'Pass';
|
||||
export const CHAOS_EXPERIMENT_VERDICT_FAIL: string = 'Fail';
|
||||
export const STATUS_RUNNING: string = 'Running';
|
||||
export const PROMETHEUS_QUERY_RESOLUTION_LIMIT: number = 11000;
|
||||
export const MAX_REFRESH_RATE: number = 2147483647;
|
||||
export const DEFAULT_REFRESH_RATE: number = 10000;
|
||||
export const ACTIVE: string = 'Active';
|
||||
export const MINIMUM_TOLERANCE_LIMIT: number = 4;
|
||||
export const DEFAULT_TOLERANCE_LIMIT: number = 14;
|
||||
export const INVALID_RESILIENCE_SCORE_STRING: string = 'NaN';
|
||||
export const DEFAULT_METRIC_SERIES_NAME: string = 'metric';
|
||||
export const DEFAULT_CHAOS_EVENT_NAME: string = 'chaos';
|
||||
export const DEFAULT_RELATIVE_TIME_RANGE: number = 1800;
|
||||
export const DASHBOARD_TYPE_1: string = 'Kubernetes Platform';
|
||||
export const DASHBOARD_TYPE_2: string = 'Sock Shop';
|
|
@ -1,14 +1,39 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core';
|
||||
import { ApolloError, useQuery } from '@apollo/client';
|
||||
import {
|
||||
FormControl,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
Menu,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import AutorenewOutlinedIcon from '@material-ui/icons/AutorenewOutlined';
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
import { ButtonFilled, Modal } from 'litmus-ui';
|
||||
import WatchLaterRoundedIcon from '@material-ui/icons/WatchLaterRounded';
|
||||
import { ButtonOutlined, GraphMetric } from 'litmus-ui';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import BackButton from '../../components/Button/BackButton';
|
||||
import DateRangeSelector from '../../components/DateRangeSelector';
|
||||
import Scaffold from '../../containers/layouts/Scaffold';
|
||||
import { LIST_DASHBOARD, LIST_DATASOURCE } from '../../graphql';
|
||||
import {
|
||||
LIST_DASHBOARD,
|
||||
LIST_DATASOURCE,
|
||||
PROM_QUERY,
|
||||
WORKFLOW_LIST_DETAILS,
|
||||
} from '../../graphql';
|
||||
import {
|
||||
ChaosDataUpdates,
|
||||
ChaosEventDetails,
|
||||
ChaosInformation,
|
||||
EventMetric,
|
||||
} from '../../models/dashboardsData';
|
||||
import {
|
||||
DashboardList,
|
||||
ListDashboardResponse,
|
||||
|
@ -20,15 +45,38 @@ import {
|
|||
ListDataSourceResponse,
|
||||
ListDataSourceVars,
|
||||
} from '../../models/graphql/dataSourceDetails';
|
||||
import {
|
||||
PrometheusQueryInput,
|
||||
PrometheusQueryVars,
|
||||
PrometheusResponse,
|
||||
} from '../../models/graphql/prometheus';
|
||||
import {
|
||||
WorkflowList,
|
||||
WorkflowListDataVars,
|
||||
} from '../../models/graphql/workflowListData';
|
||||
import useActions from '../../redux/actions';
|
||||
import * as DashboardActions from '../../redux/actions/dashboards';
|
||||
import * as DataSourceActions from '../../redux/actions/dataSource';
|
||||
import { history } from '../../redux/configureStore';
|
||||
import { RootState } from '../../redux/reducers';
|
||||
import { ReactComponent as CrossMarkIcon } from '../../svg/crossmark.svg';
|
||||
import { getProjectID, getProjectRole } from '../../utils/getSearchParams';
|
||||
import DashboardPanelGroup from '../../views/AnalyticsDashboard/MonitoringDashboardPage/DashboardPanelGroup';
|
||||
import useStyles from './styles';
|
||||
import { getProjectID } from '../../utils/getSearchParams';
|
||||
import {
|
||||
chaosEventDataParserForPrometheus,
|
||||
getChaosQueryPromInputAndID,
|
||||
} from '../../utils/promUtils';
|
||||
import ChaosAccordion from '../../views/AnalyticsDashboard/MonitoringDashboardPage/ChaosAccordion';
|
||||
import DataSourceInactiveModal from '../../views/AnalyticsDashboard/MonitoringDashboardPage/DataSourceInactiveModal';
|
||||
import DashboardPanelGroup from '../../views/AnalyticsDashboard/MonitoringDashboardPage/Panel/DashboardPanelGroup';
|
||||
import {
|
||||
ACTIVE,
|
||||
DEFAULT_REFRESH_RATE,
|
||||
DEFAULT_RELATIVE_TIME_RANGE,
|
||||
DEFAULT_TOLERANCE_LIMIT,
|
||||
MAX_REFRESH_RATE,
|
||||
MINIMUM_TOLERANCE_LIMIT,
|
||||
PROMETHEUS_ERROR_QUERY_RESOLUTION_LIMIT_REACHED,
|
||||
} from './constants';
|
||||
import refreshData from './refreshData';
|
||||
import useStyles, { useOutlinedInputStyles } from './styles';
|
||||
|
||||
interface SelectedDashboardInformation {
|
||||
id: string;
|
||||
|
@ -39,25 +87,51 @@ interface SelectedDashboardInformation {
|
|||
dashboardListForAgent: ListDashboardResponse[];
|
||||
metaData: ListDashboardResponse[];
|
||||
dashboardKey: string;
|
||||
selectionOverride: Boolean;
|
||||
}
|
||||
|
||||
interface PrometheusQueryDataInterface {
|
||||
promInput: PrometheusQueryInput;
|
||||
chaosInput: string[];
|
||||
numOfWorkflows: number;
|
||||
firstLoad: Boolean;
|
||||
chaosEvents: ChaosEventDetails[];
|
||||
chaosEventsToBeShown: ChaosEventDetails[];
|
||||
}
|
||||
|
||||
interface EventsToShowInterface {
|
||||
eventsToShow: string[];
|
||||
selectEvents: Boolean;
|
||||
}
|
||||
|
||||
interface RefreshObjectType {
|
||||
label: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface ChaosDataSet {
|
||||
queryIDs: string[];
|
||||
chaosData: Array<EventMetric>;
|
||||
visibleChaos: Array<EventMetric>;
|
||||
latestEventResult: string[];
|
||||
}
|
||||
|
||||
const DashboardPage: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const outlinedInputClasses = useOutlinedInputStyles();
|
||||
const { t } = useTranslation();
|
||||
const ACTIVE: string = 'Active';
|
||||
const { palette } = useTheme();
|
||||
const areaGraph: string[] = palette.graph.area;
|
||||
const dataSource = useActions(DataSourceActions);
|
||||
const dashboard = useActions(DashboardActions);
|
||||
// get ProjectID
|
||||
const projectID = getProjectID();
|
||||
const projectRole = getProjectRole();
|
||||
const selectedDashboard = useSelector(
|
||||
(state: RootState) => state.selectDashboard
|
||||
);
|
||||
|
||||
const selectedDataSource = useSelector(
|
||||
(state: RootState) => state.selectDataSource
|
||||
);
|
||||
|
||||
const [
|
||||
selectedDashboardInformation,
|
||||
setSelectedDashboardInformation,
|
||||
|
@ -70,19 +144,129 @@ const DashboardPage: React.FC = () => {
|
|||
dashboardListForAgent: [],
|
||||
metaData: [],
|
||||
dashboardKey: 'Default',
|
||||
selectionOverride: false,
|
||||
});
|
||||
const [
|
||||
prometheusQueryData,
|
||||
setPrometheusQueryData,
|
||||
] = React.useState<PrometheusQueryDataInterface>({
|
||||
promInput: {
|
||||
url: '',
|
||||
start: '',
|
||||
end: '',
|
||||
queries: [],
|
||||
},
|
||||
chaosInput: [],
|
||||
chaosEvents: [],
|
||||
chaosEventsToBeShown: [],
|
||||
numOfWorkflows: 0,
|
||||
firstLoad: true,
|
||||
});
|
||||
const [eventsToShow, setEventsToShow] = React.useState<EventsToShowInterface>(
|
||||
{
|
||||
eventsToShow: [],
|
||||
selectEvents: false,
|
||||
}
|
||||
);
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const [refreshRate, setRefreshRate] = React.useState<number>(10000);
|
||||
const [refreshRate, setRefreshRate] = React.useState<number>(
|
||||
selectedDashboard.refreshRate && !prometheusQueryData.firstLoad
|
||||
? selectedDashboard.refreshRate
|
||||
: 0
|
||||
);
|
||||
const [dataSourceStatus, setDataSourceStatus] = React.useState<string>(
|
||||
'ACTIVE'
|
||||
);
|
||||
const open = Boolean(anchorEl);
|
||||
const [chaosDataSet, setChaosDataSet] = React.useState<ChaosDataSet>({
|
||||
queryIDs: [],
|
||||
chaosData: [],
|
||||
visibleChaos: [],
|
||||
latestEventResult: [],
|
||||
});
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const dateRangeSelectorRef = React.useRef<HTMLButtonElement>(null);
|
||||
const [
|
||||
isDateRangeSelectorPopoverOpen,
|
||||
setDateRangeSelectorPopoverOpen,
|
||||
] = React.useState(false);
|
||||
|
||||
const clearTimeOuts = async () => {
|
||||
let id = window.setTimeout(() => {}, 0);
|
||||
while (id--) {
|
||||
window.clearTimeout(id);
|
||||
}
|
||||
|
||||
return Promise.resolve(id === 0);
|
||||
};
|
||||
|
||||
const CallbackFromRangeSelector = (
|
||||
selectedStartDate: string,
|
||||
selectedEndDate: string
|
||||
) => {
|
||||
const startDateFormatted: string = moment(selectedStartDate).format();
|
||||
const endDateFormatted: string = moment(selectedEndDate)
|
||||
.add(23, 'hours')
|
||||
.add(59, 'minutes')
|
||||
.add(59, 'seconds')
|
||||
.format();
|
||||
dashboard.selectDashboard({
|
||||
range: { startDate: startDateFormatted, endDate: endDateFormatted },
|
||||
});
|
||||
const endDate: number =
|
||||
new Date(moment(endDateFormatted).format()).getTime() / 1000;
|
||||
const now: number = Math.round(new Date().getTime() / 1000);
|
||||
const diff: number = Math.abs(now - endDate);
|
||||
const maxLim: number =
|
||||
(selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 !== 0
|
||||
? (selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 +
|
||||
MINIMUM_TOLERANCE_LIMIT
|
||||
: DEFAULT_TOLERANCE_LIMIT;
|
||||
if (
|
||||
!(diff >= 0 && diff <= maxLim) &&
|
||||
selectedDashboard.refreshRate !== MAX_REFRESH_RATE
|
||||
) {
|
||||
clearTimeOuts().then(() => {
|
||||
setPrometheusQueryData({
|
||||
...prometheusQueryData,
|
||||
firstLoad: true,
|
||||
});
|
||||
setRefreshRate(MAX_REFRESH_RATE);
|
||||
});
|
||||
} else if (!(diff >= 0 && diff <= maxLim)) {
|
||||
clearTimeOuts().then(() => {
|
||||
setPrometheusQueryData({
|
||||
...prometheusQueryData,
|
||||
firstLoad: true,
|
||||
});
|
||||
});
|
||||
} else if (
|
||||
diff >= 0 &&
|
||||
diff <= maxLim &&
|
||||
selectedDashboard.refreshRate === MAX_REFRESH_RATE
|
||||
) {
|
||||
clearTimeOuts().then(() => {
|
||||
setPrometheusQueryData({
|
||||
...prometheusQueryData,
|
||||
firstLoad: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
// If none of the above conditions match, then user has selected a relative time range.
|
||||
};
|
||||
const [openRefresh, setOpenRefresh] = React.useState(false);
|
||||
const handleCloseRefresh = () => {
|
||||
setOpenRefresh(false);
|
||||
};
|
||||
|
||||
const handleOpenRefresh = () => {
|
||||
setOpenRefresh(true);
|
||||
};
|
||||
|
||||
// Apollo query to get the dashboards data
|
||||
const { data: dashboards } = useQuery<DashboardList, ListDashboardVars>(
|
||||
|
@ -102,6 +286,105 @@ const DashboardPage: React.FC = () => {
|
|||
}
|
||||
);
|
||||
|
||||
// Apollo query to get the scheduled workflow data
|
||||
const { data: analyticsData, refetch } = useQuery<
|
||||
WorkflowList,
|
||||
WorkflowListDataVars
|
||||
>(WORKFLOW_LIST_DETAILS, {
|
||||
variables: { projectID, workflowIDs: [] },
|
||||
notifyOnNetworkStatusChange: true,
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
// Apollo query to get the prometheus data
|
||||
useQuery<PrometheusResponse, PrometheusQueryVars>(PROM_QUERY, {
|
||||
variables: {
|
||||
prometheusInput: prometheusQueryData?.promInput ?? {
|
||||
url: '',
|
||||
start: '',
|
||||
end: '',
|
||||
queries: [],
|
||||
},
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
skip: prometheusQueryData?.promInput.url === '',
|
||||
onCompleted: (eventData) => {
|
||||
let chaosDataUpdates: ChaosDataUpdates = {
|
||||
queryIDs: [],
|
||||
chaosData: [],
|
||||
reGenerate: false,
|
||||
latestEventResult: [],
|
||||
};
|
||||
if (eventData && analyticsData) {
|
||||
const selectedEndTime: number =
|
||||
new Date(moment(selectedDashboard.range.endDate).format()).getTime() /
|
||||
1000;
|
||||
const selectedStartTime: number =
|
||||
new Date(
|
||||
moment(selectedDashboard.range.startDate).format()
|
||||
).getTime() / 1000;
|
||||
chaosDataUpdates = chaosEventDataParserForPrometheus(
|
||||
prometheusQueryData?.numOfWorkflows,
|
||||
analyticsData,
|
||||
eventData,
|
||||
prometheusQueryData?.chaosEvents,
|
||||
selectedStartTime,
|
||||
selectedEndTime
|
||||
);
|
||||
if (
|
||||
chaosDataUpdates.reGenerate &&
|
||||
!prometheusQueryData.firstLoad &&
|
||||
!selectedDashboard.forceUpdate
|
||||
) {
|
||||
clearTimeOuts().then(() => {
|
||||
dashboard.selectDashboard({
|
||||
forceUpdate: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!chaosDataUpdates.reGenerate) {
|
||||
if (selectedDashboard.forceUpdate) {
|
||||
dashboard.selectDashboard({
|
||||
forceUpdate: false,
|
||||
});
|
||||
}
|
||||
if (!selectedDashboardInformation.selectionOverride) {
|
||||
setChaosDataSet({
|
||||
...chaosDataSet,
|
||||
queryIDs: chaosDataUpdates.queryIDs,
|
||||
chaosData: chaosDataUpdates.chaosData,
|
||||
visibleChaos: chaosDataUpdates.chaosData,
|
||||
latestEventResult: chaosDataUpdates.latestEventResult,
|
||||
});
|
||||
} else {
|
||||
setChaosDataSet({
|
||||
...chaosDataSet,
|
||||
queryIDs: chaosDataUpdates.queryIDs,
|
||||
chaosData: chaosDataUpdates.chaosData,
|
||||
latestEventResult: chaosDataUpdates.latestEventResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
chaosDataUpdates = {
|
||||
queryIDs: [],
|
||||
chaosData: [],
|
||||
reGenerate: false,
|
||||
latestEventResult: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
onError: (error: ApolloError) => {
|
||||
if (error.message === PROMETHEUS_ERROR_QUERY_RESOLUTION_LIMIT_REACHED) {
|
||||
if (selectedDashboard.refreshRate !== MAX_REFRESH_RATE) {
|
||||
dashboard.selectDashboard({
|
||||
refreshRate: MAX_REFRESH_RATE,
|
||||
});
|
||||
}
|
||||
setPrometheusQueryData({ ...prometheusQueryData, firstLoad: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dashboards && dashboards.ListDashboard.length) {
|
||||
if (
|
||||
|
@ -118,6 +401,12 @@ const DashboardPage: React.FC = () => {
|
|||
return data.db_id === selectedDashboardInformation.id;
|
||||
}
|
||||
)[0];
|
||||
dashboard.selectDashboard({
|
||||
selectedDashboardID: selectedDashboardInformation.id,
|
||||
selectedDashboardName: selectedDashboard.db_name,
|
||||
selectedDashboardTemplateName: selectedDashboard.db_type,
|
||||
refreshRate: 0,
|
||||
});
|
||||
setSelectedDashboardInformation({
|
||||
...selectedDashboardInformation,
|
||||
dashboardListForAgent: availableDashboards,
|
||||
|
@ -130,10 +419,10 @@ const DashboardPage: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (dataSources && dataSources.ListDataSource.length) {
|
||||
dashboard.selectDashboard({
|
||||
refreshRate: 0,
|
||||
});
|
||||
if (selectedDataSource.selectedDataSourceID === '') {
|
||||
dashboard.selectDashboard({
|
||||
refreshRate,
|
||||
});
|
||||
if (
|
||||
selectedDashboardInformation.metaData &&
|
||||
selectedDashboardInformation.metaData[0] &&
|
||||
|
@ -152,6 +441,10 @@ const DashboardPage: React.FC = () => {
|
|||
selectedDataSourceID: selectedDataSource.ds_id,
|
||||
selectedDataSourceName: selectedDataSource.ds_name,
|
||||
});
|
||||
setSelectedDashboardInformation({
|
||||
...selectedDashboardInformation,
|
||||
selectionOverride: false,
|
||||
});
|
||||
}
|
||||
if (
|
||||
selectedDataSource &&
|
||||
|
@ -164,6 +457,239 @@ const DashboardPage: React.FC = () => {
|
|||
}
|
||||
}, [selectedDashboardInformation.dashboardKey, dataSources]);
|
||||
|
||||
const generateChaosQueries = () => {
|
||||
let chaosInformation: ChaosInformation = {
|
||||
promQueries: prometheusQueryData.promInput.queries,
|
||||
chaosQueryIDs: prometheusQueryData.chaosInput,
|
||||
chaosEventList: prometheusQueryData.chaosEvents,
|
||||
numberOfWorkflowsUnderConsideration: prometheusQueryData.numOfWorkflows,
|
||||
};
|
||||
if (prometheusQueryData.firstLoad && analyticsData?.ListWorkflow) {
|
||||
const selectedEndTime: number =
|
||||
new Date(moment(selectedDashboard.range.endDate).format()).getTime() /
|
||||
1000;
|
||||
const selectedStartTime: number =
|
||||
new Date(moment(selectedDashboard.range.startDate).format()).getTime() /
|
||||
1000;
|
||||
const timeRangeDiff: number = selectedEndTime - selectedStartTime;
|
||||
chaosInformation = getChaosQueryPromInputAndID(
|
||||
analyticsData,
|
||||
selectedDashboardInformation.agentID,
|
||||
areaGraph,
|
||||
timeRangeDiff,
|
||||
selectedStartTime,
|
||||
selectedEndTime,
|
||||
prometheusQueryData.chaosEvents
|
||||
);
|
||||
}
|
||||
setPrometheusQueryData({
|
||||
...prometheusQueryData,
|
||||
promInput: {
|
||||
url: selectedDataSource.selectedDataSourceURL,
|
||||
start: `${
|
||||
selectedDashboard.range
|
||||
? new Date(
|
||||
moment(selectedDashboard.range.startDate).format()
|
||||
).getTime() / 1000
|
||||
: Math.round(new Date().getTime() / 1000) -
|
||||
DEFAULT_RELATIVE_TIME_RANGE
|
||||
}`,
|
||||
end: `${
|
||||
selectedDashboard.range
|
||||
? new Date(
|
||||
moment(selectedDashboard.range.endDate).format()
|
||||
).getTime() / 1000
|
||||
: Math.round(new Date().getTime() / 1000)
|
||||
}`,
|
||||
queries: chaosInformation.promQueries,
|
||||
},
|
||||
chaosInput: chaosInformation.chaosQueryIDs,
|
||||
chaosEvents: chaosInformation.chaosEventList,
|
||||
chaosEventsToBeShown: chaosInformation.chaosEventList.filter(
|
||||
(event) => event.showOnTable || chaosDataSet.queryIDs.includes(event.id)
|
||||
),
|
||||
numOfWorkflows: chaosInformation.numberOfWorkflowsUnderConsideration,
|
||||
firstLoad: !analyticsData?.ListWorkflow,
|
||||
});
|
||||
const existingEventIDs: string[] = prometheusQueryData.chaosEvents.map(
|
||||
({ id }) => id
|
||||
);
|
||||
const newEventIDs: string[] = chaosInformation.chaosEventList
|
||||
.map(({ id }) => id)
|
||||
.filter((id: string) => !existingEventIDs.includes(id));
|
||||
if (newEventIDs.length) {
|
||||
setEventsToShow({
|
||||
eventsToShow: selectedDashboardInformation.selectionOverride
|
||||
? eventsToShow.eventsToShow
|
||||
: eventsToShow.eventsToShow.concat(newEventIDs),
|
||||
selectEvents: true,
|
||||
});
|
||||
}
|
||||
chaosInformation = {
|
||||
promQueries: [],
|
||||
chaosQueryIDs: [],
|
||||
chaosEventList: [],
|
||||
numberOfWorkflowsUnderConsideration: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const postEventSelectionRoutine = (selectedEvents: string[]) => {
|
||||
setEventsToShow({
|
||||
selectEvents: true,
|
||||
eventsToShow: selectedEvents,
|
||||
});
|
||||
if (!selectedDashboardInformation.selectionOverride) {
|
||||
setSelectedDashboardInformation({
|
||||
...selectedDashboardInformation,
|
||||
selectionOverride: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (prometheusQueryData.firstLoad) {
|
||||
refetch();
|
||||
generateChaosQueries();
|
||||
if (selectedDashboard.refreshRate !== MAX_REFRESH_RATE) {
|
||||
dashboard.selectDashboard({
|
||||
range: {
|
||||
startDate: moment
|
||||
.unix(
|
||||
Math.round(new Date().getTime() / 1000) -
|
||||
DEFAULT_RELATIVE_TIME_RANGE
|
||||
)
|
||||
.format(),
|
||||
endDate: moment
|
||||
.unix(Math.round(new Date().getTime() / 1000))
|
||||
.format(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!prometheusQueryData.firstLoad) {
|
||||
// check and update range for the default relative time selection
|
||||
// for user selections / check with entire range of relative time differences
|
||||
// for absolute time no updates to the range therefore no data refresh / feature disabled
|
||||
const endDate: number =
|
||||
new Date(moment(selectedDashboard.range.endDate).format()).getTime() /
|
||||
1000;
|
||||
const now: number = Math.round(new Date().getTime() / 1000);
|
||||
const diff: number = Math.abs(now - endDate);
|
||||
const maxLim: number =
|
||||
(selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 !== 0
|
||||
? (selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 +
|
||||
MINIMUM_TOLERANCE_LIMIT
|
||||
: DEFAULT_TOLERANCE_LIMIT;
|
||||
if (
|
||||
diff >= 0 &&
|
||||
diff <= maxLim &&
|
||||
selectedDashboard.refreshRate !== MAX_REFRESH_RATE
|
||||
) {
|
||||
const startDate: number =
|
||||
new Date(
|
||||
moment(selectedDashboard.range.startDate).format()
|
||||
).getTime() / 1000;
|
||||
const interval: number = endDate - startDate;
|
||||
dashboard.selectDashboard({
|
||||
range: {
|
||||
startDate: moment
|
||||
.unix(Math.round(new Date().getTime() / 1000) - interval)
|
||||
.format(),
|
||||
endDate: moment
|
||||
.unix(Math.round(new Date().getTime() / 1000))
|
||||
.format(),
|
||||
},
|
||||
});
|
||||
}
|
||||
setTimeout(
|
||||
() => {
|
||||
refetch();
|
||||
generateChaosQueries();
|
||||
},
|
||||
selectedDashboard.refreshRate !== 0
|
||||
? selectedDashboard.refreshRate
|
||||
: DEFAULT_REFRESH_RATE
|
||||
);
|
||||
}
|
||||
}, [prometheusQueryData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
chaosDataSet.chaosData.length <
|
||||
prometheusQueryData?.chaosEventsToBeShown.length
|
||||
) {
|
||||
clearTimeOuts().then(() => {
|
||||
dashboard.selectDashboard({
|
||||
forceUpdate: true,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
let matchingEventsFound: ChaosEventDetails[] = [];
|
||||
chaosDataSet.queryIDs.forEach((chaosQueryID: string, index: number) => {
|
||||
matchingEventsFound = prometheusQueryData?.chaosEventsToBeShown.filter(
|
||||
(event: ChaosEventDetails) => event.id === chaosQueryID
|
||||
);
|
||||
if (matchingEventsFound?.length === 0) {
|
||||
clearTimeOuts().then(() => {
|
||||
dashboard.selectDashboard({
|
||||
forceUpdate: true,
|
||||
});
|
||||
});
|
||||
} else if (
|
||||
matchingEventsFound[0].result !==
|
||||
chaosDataSet.latestEventResult[index]
|
||||
) {
|
||||
clearTimeOuts().then(() => {
|
||||
dashboard.selectDashboard({
|
||||
forceUpdate: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [chaosDataSet]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDashboard.forceUpdate) {
|
||||
refetch();
|
||||
setPrometheusQueryData({ ...prometheusQueryData, firstLoad: true });
|
||||
}
|
||||
}, [selectedDashboard.forceUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (eventsToShow.selectEvents) {
|
||||
const filteredChaosData: Array<GraphMetric> = chaosDataSet.chaosData.filter(
|
||||
(data, index) =>
|
||||
eventsToShow.eventsToShow.includes(chaosDataSet.queryIDs[index])
|
||||
);
|
||||
setEventsToShow({ ...eventsToShow, selectEvents: false });
|
||||
setChaosDataSet({ ...chaosDataSet, visibleChaos: filteredChaosData });
|
||||
}
|
||||
}, [eventsToShow.selectEvents]);
|
||||
|
||||
const getRefreshRateStatus = () => {
|
||||
if (selectedDashboard.range) {
|
||||
const endDate: number =
|
||||
new Date(moment(selectedDashboard.range.endDate).format()).getTime() /
|
||||
1000;
|
||||
const now: number = Math.round(new Date().getTime() / 1000);
|
||||
const diff: number = Math.abs(now - endDate);
|
||||
const maxLim: number =
|
||||
(selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 !== 0
|
||||
? (selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 +
|
||||
MINIMUM_TOLERANCE_LIMIT
|
||||
: DEFAULT_TOLERANCE_LIMIT;
|
||||
if (!(diff >= 0 && diff <= maxLim)) {
|
||||
// A non relative time range has been selected.
|
||||
// Refresh rate switch is not acknowledged and it's state is locked (Off).
|
||||
// Select a relative time range or select a different refresh rate to unlock again.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// For relative time ranges.
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<Scaffold>
|
||||
<div className={classes.rootContainer}>
|
||||
|
@ -171,10 +697,10 @@ const DashboardPage: React.FC = () => {
|
|||
<div className={classes.button}>
|
||||
<BackButton />
|
||||
</div>
|
||||
<Typography variant="h3" className={classes.weightedFont}>
|
||||
<Typography variant="h4" className={classes.weightedFont}>
|
||||
{selectedDashboardInformation.agentName} /{' '}
|
||||
<Typography
|
||||
variant="h3"
|
||||
variant="h4"
|
||||
display="inline"
|
||||
className={classes.italic}
|
||||
>
|
||||
|
@ -215,15 +741,20 @@ const DashboardPage: React.FC = () => {
|
|||
selectedDataSourceID: '',
|
||||
selectedDataSourceName: '',
|
||||
});
|
||||
setRefreshRate(0);
|
||||
setAnchorEl(null);
|
||||
}}
|
||||
className={classes.menuItem}
|
||||
className={
|
||||
data.db_id === selectedDashboardInformation.id
|
||||
? classes.menuItemSelected
|
||||
: classes.menuItem
|
||||
}
|
||||
>
|
||||
<div className={classes.expDiv}>
|
||||
<Typography
|
||||
data-cy="switchDashboard"
|
||||
className={classes.btnText}
|
||||
variant="h6"
|
||||
className={`${classes.btnText} ${classes.italic}`}
|
||||
variant="h5"
|
||||
>
|
||||
{data.db_name}
|
||||
</Typography>
|
||||
|
@ -235,17 +766,164 @@ const DashboardPage: React.FC = () => {
|
|||
</Menu>
|
||||
</Typography>
|
||||
<div className={classes.headerDiv}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
className={`${classes.weightedFont} ${classes.dashboardType}`}
|
||||
>
|
||||
{selectedDashboardInformation.type}
|
||||
<Typography className={classes.headerInfoText}>
|
||||
{t('analyticsDashboard.monitoringDashboardPage.headerInfoText')}
|
||||
</Typography>
|
||||
<div className={classes.controls}>
|
||||
<ButtonOutlined
|
||||
className={classes.selectDate}
|
||||
onClick={() => setDateRangeSelectorPopoverOpen(true)}
|
||||
ref={dateRangeSelectorRef}
|
||||
aria-label="time range"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<Typography className={classes.displayDate}>
|
||||
<IconButton className={classes.rangeSelectorClockIcon}>
|
||||
<WatchLaterRoundedIcon />
|
||||
</IconButton>
|
||||
{!selectedDashboard.range ||
|
||||
selectedDashboard.range.startDate === ' '
|
||||
? `${t(
|
||||
'analyticsDashboard.monitoringDashboardPage.rangeSelector.selectPeriod'
|
||||
)}`
|
||||
: `${selectedDashboard.range.startDate.split('-')[0]}-${
|
||||
selectedDashboard.range.startDate.split('-')[1]
|
||||
}-${selectedDashboard.range.startDate.substring(
|
||||
selectedDashboard.range.startDate.lastIndexOf('-') + 1,
|
||||
selectedDashboard.range.startDate.lastIndexOf('T')
|
||||
)}
|
||||
|
||||
${selectedDashboard.range.startDate.substring(
|
||||
selectedDashboard.range.startDate.lastIndexOf('T') + 1,
|
||||
selectedDashboard.range.startDate.lastIndexOf('+')
|
||||
)}
|
||||
${t(
|
||||
'analyticsDashboard.monitoringDashboardPage.rangeSelector.to'
|
||||
)}
|
||||
${selectedDashboard.range.endDate.split('-')[0]}-${
|
||||
selectedDashboard.range.endDate.split('-')[1]
|
||||
}-${selectedDashboard.range.endDate.substring(
|
||||
selectedDashboard.range.endDate.lastIndexOf('-') + 1,
|
||||
selectedDashboard.range.endDate.lastIndexOf('T')
|
||||
)}
|
||||
|
||||
${selectedDashboard.range.endDate.substring(
|
||||
selectedDashboard.range.endDate.lastIndexOf('T') + 1,
|
||||
selectedDashboard.range.endDate.lastIndexOf('+')
|
||||
)}`}
|
||||
|
||||
<IconButton className={classes.rangeSelectorIcon}>
|
||||
<KeyboardArrowDownIcon />
|
||||
</IconButton>
|
||||
</Typography>
|
||||
</ButtonOutlined>
|
||||
<DateRangeSelector
|
||||
anchorEl={dateRangeSelectorRef.current as HTMLElement}
|
||||
isOpen={isDateRangeSelectorPopoverOpen}
|
||||
onClose={() => {
|
||||
setDateRangeSelectorPopoverOpen(false);
|
||||
}}
|
||||
callbackToSetRange={CallbackFromRangeSelector}
|
||||
className={classes.rangeSelectorPopover}
|
||||
/>
|
||||
<FormControl className={classes.formControl} variant="outlined">
|
||||
<InputLabel
|
||||
id="refresh-controlled-open-select-label"
|
||||
className={classes.inputLabel}
|
||||
>
|
||||
<AutorenewOutlinedIcon className={classes.refreshIcon} />
|
||||
<Typography className={classes.refreshText}>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.refresh.heading'
|
||||
)}
|
||||
</Typography>
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="refresh-controlled-open-select-label"
|
||||
id="refresh-controlled-open-select"
|
||||
open={openRefresh}
|
||||
disabled={getRefreshRateStatus()}
|
||||
onClose={handleCloseRefresh}
|
||||
onOpen={handleOpenRefresh}
|
||||
value={refreshRate !== 0 ? refreshRate : null}
|
||||
onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
|
||||
// When viewing data for non-relative time range, refresh should be Off ideally.
|
||||
// UI can auto detect if it is not Off and switches it to Off.
|
||||
// Now the user can try to view the non-relative time range data again.
|
||||
if (selectedDashboard.refreshRate !== MAX_REFRESH_RATE) {
|
||||
dashboard.selectDashboard({
|
||||
refreshRate: event.target.value as number,
|
||||
});
|
||||
setRefreshRate(event.target.value as number);
|
||||
} else {
|
||||
dashboard.selectDashboard({
|
||||
refreshRate: event.target.value as number,
|
||||
});
|
||||
setRefreshRate(event.target.value as number);
|
||||
dashboard.selectDashboard({
|
||||
forceUpdate: true,
|
||||
});
|
||||
setPrometheusQueryData({
|
||||
...prometheusQueryData,
|
||||
firstLoad: true,
|
||||
});
|
||||
}
|
||||
}}
|
||||
input={<OutlinedInput classes={outlinedInputClasses} />}
|
||||
IconComponent={KeyboardArrowDownIcon}
|
||||
MenuProps={{
|
||||
anchorOrigin: {
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
},
|
||||
transformOrigin: {
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
},
|
||||
getContentAnchorEl: null,
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
key="Off-refresh-option"
|
||||
value={MAX_REFRESH_RATE}
|
||||
className={
|
||||
refreshRate === MAX_REFRESH_RATE
|
||||
? classes.menuListItemSelected
|
||||
: classes.menuListItem
|
||||
}
|
||||
>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.refresh.off'
|
||||
)}
|
||||
</MenuItem>
|
||||
{refreshData.map((data: RefreshObjectType) => (
|
||||
<MenuItem
|
||||
key={`${data.label}-refresh-option`}
|
||||
value={data.value}
|
||||
className={
|
||||
refreshRate === data.value
|
||||
? classes.menuListItemSelected
|
||||
: classes.menuListItem
|
||||
}
|
||||
>
|
||||
{t(data.label)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classes.analyticsDiv}
|
||||
key={selectedDashboardInformation.dashboardKey}
|
||||
>
|
||||
<div className={classes.chaosTableSection}>
|
||||
<ChaosAccordion
|
||||
dashboardKey={selectedDashboardInformation.dashboardKey}
|
||||
chaosEventsToBeShown={prometheusQueryData?.chaosEventsToBeShown}
|
||||
postEventSelectionRoutine={postEventSelectionRoutine}
|
||||
/>
|
||||
</div>
|
||||
{selectedDashboardInformation.metaData[0] &&
|
||||
selectedDashboardInformation.metaData[0].panel_groups.map(
|
||||
(panelGroup: PanelGroupResponse) => (
|
||||
|
@ -258,6 +936,7 @@ const DashboardPage: React.FC = () => {
|
|||
panel_group_id={panelGroup.panel_group_id}
|
||||
panel_group_name={panelGroup.panel_group_name}
|
||||
panels={panelGroup.panels}
|
||||
chaos_data={chaosDataSet.visibleChaos}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -266,70 +945,12 @@ const DashboardPage: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
{dataSourceStatus !== 'ACTIVE' ? (
|
||||
<Modal open onClose={() => {}} width="60%">
|
||||
<div className={classes.modal}>
|
||||
<Typography align="center">
|
||||
<CrossMarkIcon className={classes.icon} />
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.modalHeading}
|
||||
align="center"
|
||||
variant="h3"
|
||||
>
|
||||
Data source is {dataSourceStatus}
|
||||
</Typography>
|
||||
<Typography
|
||||
align="center"
|
||||
variant="body1"
|
||||
className={classes.modalBody}
|
||||
>
|
||||
{t('analyticsDashboard.monitoringDashboardPage.dataSourceError')}
|
||||
</Typography>
|
||||
<div className={classes.flexButtons}>
|
||||
<ButtonFilled
|
||||
variant="success"
|
||||
onClick={() => {
|
||||
let dashboardTemplateID: number = -1;
|
||||
if (
|
||||
selectedDashboardInformation.type === 'Kubernetes Platform'
|
||||
) {
|
||||
dashboardTemplateID = 0;
|
||||
} else if (
|
||||
selectedDashboardInformation.type === 'Sock Shop'
|
||||
) {
|
||||
dashboardTemplateID = 1;
|
||||
}
|
||||
dashboard.selectDashboard({
|
||||
selectedDashboardID: selectedDashboardInformation.id,
|
||||
selectedDashboardName: selectedDashboardInformation.name,
|
||||
selectedDashboardTemplateID: dashboardTemplateID,
|
||||
});
|
||||
history.push({
|
||||
pathname: '/analytics/dashboard/configure',
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.reConfigureDashboard'
|
||||
)}
|
||||
</ButtonFilled>
|
||||
<ButtonFilled
|
||||
variant="success"
|
||||
onClick={() => {
|
||||
history.push({
|
||||
pathname: '/analytics/datasource/configure',
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.updateDataSource'
|
||||
)}
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<DataSourceInactiveModal
|
||||
dataSourceStatus={dataSourceStatus}
|
||||
dashboardType={selectedDashboardInformation.type}
|
||||
dashboardID={selectedDashboardInformation.id}
|
||||
dashboardName={selectedDashboardInformation.name}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
export default [
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every5Seconds',
|
||||
value: 5000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every10Seconds',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every30Seconds',
|
||||
value: 30000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every1Minute',
|
||||
value: 60000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every5Minutes',
|
||||
value: 300000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every15Minutes',
|
||||
value: 900000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every30Minutes',
|
||||
value: 1800000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every1Hour',
|
||||
value: 3600000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every2Hours',
|
||||
value: 7200000,
|
||||
},
|
||||
{
|
||||
label: 'analyticsDashboard.monitoringDashboardPage.refresh.every1Day',
|
||||
value: 86400000,
|
||||
},
|
||||
];
|
|
@ -1,4 +1,4 @@
|
|||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { fade, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
|
||||
// Component styles
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
@ -10,15 +10,15 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
|
||||
root: {
|
||||
marginTop: theme.spacing(5),
|
||||
marginLeft: theme.spacing(6),
|
||||
marginBottom: theme.spacing(8),
|
||||
marginLeft: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2.5),
|
||||
},
|
||||
|
||||
headerDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: theme.spacing(5),
|
||||
paddingTop: theme.spacing(2.15),
|
||||
backgroundColor: theme.palette.disabledBackground,
|
||||
minHeight: '5rem',
|
||||
},
|
||||
|
@ -27,19 +27,8 @@ const useStyles = makeStyles((theme) => ({
|
|||
fontStyle: 'italic',
|
||||
},
|
||||
|
||||
dashboardType: {
|
||||
marginLeft: theme.spacing(5),
|
||||
marginTop: theme.spacing(3),
|
||||
fontSize: '1.5rem',
|
||||
},
|
||||
|
||||
loaderText: {
|
||||
textAlign: 'center',
|
||||
marginBottom: '3%',
|
||||
},
|
||||
|
||||
analyticsDiv: {
|
||||
paddingTop: theme.spacing(2),
|
||||
padding: theme.spacing(1, 0, 2),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
minHeight: '26rem',
|
||||
},
|
||||
|
@ -58,6 +47,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
dashboardSwitchIcon: {
|
||||
height: '2rem',
|
||||
width: '2rem',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
|
||||
weightedFont: {
|
||||
|
@ -66,9 +56,15 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
// Menu option
|
||||
menuItem: {
|
||||
minWidth: '10rem',
|
||||
height: '2.5rem',
|
||||
},
|
||||
|
||||
menuItemSelected: {
|
||||
background: theme.palette.primary.light,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
'&:hover': {
|
||||
background: theme.palette.primary.light,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
},
|
||||
minWidth: '10rem',
|
||||
height: '2.5rem',
|
||||
|
@ -76,40 +72,142 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
expDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
|
||||
btnText: {
|
||||
fontWeight: 500,
|
||||
},
|
||||
|
||||
icon: {
|
||||
width: '6rem',
|
||||
height: '6rem',
|
||||
},
|
||||
|
||||
modalHeading: {
|
||||
marginTop: theme.spacing(3.5),
|
||||
fontSize: '2.25rem',
|
||||
marginBottom: theme.spacing(4.5),
|
||||
},
|
||||
|
||||
modalBody: {
|
||||
marginBottom: theme.spacing(4.5),
|
||||
},
|
||||
|
||||
flexButtons: {
|
||||
selectDate: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
height: '2.9rem',
|
||||
minWidth: '9rem',
|
||||
marginLeft: theme.spacing(3.75),
|
||||
border: `0.1px solid ${theme.palette.border.main}`,
|
||||
background: theme.palette.background.paper,
|
||||
borderRadius: 4,
|
||||
textTransform: 'none',
|
||||
},
|
||||
|
||||
loader: {
|
||||
padding: '22%',
|
||||
displayDate: {
|
||||
width: '100%',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
|
||||
modal: {
|
||||
padding: theme.spacing(15, 0),
|
||||
rangeSelectorIcon: {
|
||||
width: '0.625rem',
|
||||
height: '0.625rem',
|
||||
marginLeft: theme.spacing(1),
|
||||
marginRight: theme.spacing(-1),
|
||||
},
|
||||
|
||||
rangeSelectorClockIcon: {
|
||||
width: '0.825rem',
|
||||
height: '0.825rem',
|
||||
marginLeft: theme.spacing(-1),
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
|
||||
rangeSelectorPopover: {
|
||||
marginTop: theme.spacing(37.5),
|
||||
},
|
||||
|
||||
formControl: {
|
||||
width: '9rem',
|
||||
marginLeft: theme.spacing(1.5),
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
marginLeft: theme.spacing(3.75),
|
||||
},
|
||||
'& .MuiSelect-outlined': {
|
||||
padding: '0.925rem',
|
||||
'&:focus': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
'& .MuiInputLabel-root': {
|
||||
color: `${theme.palette.text.hint} !important`,
|
||||
marginTop: `${theme.spacing(2)} !important`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputLabel: {
|
||||
color: theme.palette.text.hint,
|
||||
marginTop: theme.spacing(-1),
|
||||
'&.MuiInputLabel-shrink': {
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
|
||||
menuListItem: {
|
||||
color: theme.palette.text.hint,
|
||||
height: '2.4rem',
|
||||
},
|
||||
|
||||
menuListItemSelected: {
|
||||
height: '2.4rem',
|
||||
background: `${theme.palette.primary.light} !important`,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
'&:hover': {
|
||||
background: `${theme.palette.primary.light} !important`,
|
||||
},
|
||||
},
|
||||
|
||||
refreshIcon: {
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
|
||||
refreshText: {
|
||||
marginTop: theme.spacing(-2.85),
|
||||
marginLeft: theme.spacing(3.5),
|
||||
},
|
||||
|
||||
headerInfoText: {
|
||||
fontWeight: 500,
|
||||
fontSize: '1rem',
|
||||
lineHeight: '150%',
|
||||
letterSpacing: '0.02em',
|
||||
width: '35%',
|
||||
marginLeft: theme.spacing(2.5),
|
||||
},
|
||||
|
||||
controls: {
|
||||
marginRight: theme.spacing(2.25),
|
||||
display: 'flex',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
flexDirection: 'column',
|
||||
marginRight: 0,
|
||||
},
|
||||
},
|
||||
|
||||
chaosTableSection: {
|
||||
padding: theme.spacing(2.5, 2, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
export const useOutlinedInputStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
'& $notchedOutline': {
|
||||
borderColor: theme.palette.border.main,
|
||||
},
|
||||
'&:hover $notchedOutline': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: `0px 4px 5px -2px ${fade(
|
||||
theme.palette.highlight,
|
||||
0.2
|
||||
)},0px 7px 10px 1px ${fade(
|
||||
theme.palette.highlight,
|
||||
0.14
|
||||
)},0px 2px 16px 1px ${fade(theme.palette.highlight, 0.12)}`,
|
||||
},
|
||||
'&$focused $notchedOutline': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
'& .MuiInputLabel-root': {
|
||||
color: `${theme.palette.text.hint} !important`,
|
||||
marginTop: `${theme.spacing(2)} !important`,
|
||||
},
|
||||
color: theme.palette.text.hint,
|
||||
background: theme.palette.background.paper,
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { ButtonFilled, ButtonOutlined, Modal } from 'litmus-ui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import YAML from 'yaml';
|
||||
import DashboardList from '../../components/PreconfiguredDashboards/data';
|
||||
import Scaffold from '../../containers/layouts/Scaffold';
|
||||
import { SCHEDULE_DETAILS } from '../../graphql';
|
||||
import { CREATE_DASHBOARD, UPDATE_DASHBOARD } from '../../graphql/mutations';
|
||||
import {
|
||||
Artifact,
|
||||
CronWorkflowYaml,
|
||||
Parameter,
|
||||
Template,
|
||||
WorkflowYaml,
|
||||
} from '../../models/chaosWorkflowYaml';
|
||||
import {
|
||||
ChaosResultNamesAndNamespacesMap,
|
||||
DashboardDetails,
|
||||
} from '../../models/dashboardsData';
|
||||
import { DashboardDetails } from '../../models/dashboardsData';
|
||||
import {
|
||||
CreateDashboardInput,
|
||||
Panel,
|
||||
|
@ -30,20 +15,10 @@ import {
|
|||
UpdateDashboardInput,
|
||||
updatePanelGroupInput,
|
||||
} from '../../models/graphql/dashboardsDetails';
|
||||
import {
|
||||
ScheduleDataVars,
|
||||
Schedules,
|
||||
ScheduleWorkflow,
|
||||
} from '../../models/graphql/scheduleData';
|
||||
import { history } from '../../redux/configureStore';
|
||||
import { RootState } from '../../redux/reducers';
|
||||
import { ReactComponent as CrossMarkIcon } from '../../svg/crossmark.svg';
|
||||
import { getProjectID, getProjectRole } from '../../utils/getSearchParams';
|
||||
import { validateWorkflowParameter } from '../../utils/validate';
|
||||
import {
|
||||
generateChaosQuery,
|
||||
getWorkflowParameter,
|
||||
} from '../../utils/yamlUtils';
|
||||
import ConfigureDashboard from '../../views/AnalyticsDashboard/KubernetesDashboards/Form';
|
||||
import useStyles from './styles';
|
||||
|
||||
|
@ -84,15 +59,6 @@ const DashboardConfigurePage: React.FC<DashboardConfigurePageProps> = ({
|
|||
const [mutate, setMutate] = React.useState(false);
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
|
||||
// Apollo query to get the scheduled data
|
||||
const { data: workflowSchedules } = useQuery<Schedules, ScheduleDataVars>(
|
||||
SCHEDULE_DETAILS,
|
||||
{
|
||||
variables: { projectID },
|
||||
fetchPolicy: 'cache-and-network',
|
||||
}
|
||||
);
|
||||
|
||||
const [createDashboard] = useMutation<CreateDashboardInput>(
|
||||
CREATE_DASHBOARD,
|
||||
{
|
||||
|
@ -124,118 +90,12 @@ const DashboardConfigurePage: React.FC<DashboardConfigurePageProps> = ({
|
|||
|
||||
const getPanelGroups = () => {
|
||||
if (configure === false) {
|
||||
const chaosResultNamesAndNamespacesMap: ChaosResultNamesAndNamespacesMap[] = [];
|
||||
workflowSchedules?.getScheduledWorkflows.forEach(
|
||||
(schedule: ScheduleWorkflow) => {
|
||||
if (
|
||||
schedule.cluster_id === dashboardVars.agentID &&
|
||||
!schedule.isRemoved
|
||||
) {
|
||||
let workflowYaml: WorkflowYaml | CronWorkflowYaml;
|
||||
let parametersMap: Parameter[];
|
||||
let workflowYamlCheck: boolean = true;
|
||||
try {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as WorkflowYaml).spec.arguments
|
||||
.parameters;
|
||||
} catch (err) {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as CronWorkflowYaml).spec
|
||||
.workflowSpec.arguments.parameters;
|
||||
workflowYamlCheck = false;
|
||||
}
|
||||
(workflowYamlCheck
|
||||
? (workflowYaml as WorkflowYaml).spec.templates
|
||||
: (workflowYaml as CronWorkflowYaml).spec.workflowSpec.templates
|
||||
).forEach((template: Template) => {
|
||||
if (template.inputs && template.inputs.artifacts) {
|
||||
template.inputs.artifacts.forEach((artifact: Artifact) => {
|
||||
const parsedEmbeddedYaml = YAML.parse(artifact.raw.data);
|
||||
if (parsedEmbeddedYaml.kind === 'ChaosEngine') {
|
||||
let engineNamespace: string = '';
|
||||
if (
|
||||
typeof parsedEmbeddedYaml.metadata.namespace === 'string'
|
||||
) {
|
||||
engineNamespace = (parsedEmbeddedYaml.metadata
|
||||
.namespace as string).substring(
|
||||
1,
|
||||
(parsedEmbeddedYaml.metadata.namespace as string)
|
||||
.length - 1
|
||||
);
|
||||
} else {
|
||||
engineNamespace = Object.keys(
|
||||
parsedEmbeddedYaml.metadata.namespace
|
||||
)[0];
|
||||
}
|
||||
if (validateWorkflowParameter(engineNamespace)) {
|
||||
engineNamespace = getWorkflowParameter(engineNamespace);
|
||||
parametersMap.forEach((parameterKeyValue: Parameter) => {
|
||||
if (parameterKeyValue.name === engineNamespace) {
|
||||
engineNamespace = parameterKeyValue.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
engineNamespace = parsedEmbeddedYaml.metadata.namespace;
|
||||
}
|
||||
let matchIndex: number = -1;
|
||||
const check: number = chaosResultNamesAndNamespacesMap.filter(
|
||||
(data, index) => {
|
||||
if (
|
||||
data.resultName.includes(
|
||||
parsedEmbeddedYaml.metadata.name
|
||||
) &&
|
||||
data.resultNamespace === engineNamespace
|
||||
) {
|
||||
matchIndex = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
).length;
|
||||
if (check === 0) {
|
||||
chaosResultNamesAndNamespacesMap.push({
|
||||
resultName: `${parsedEmbeddedYaml.metadata.name}-${parsedEmbeddedYaml.spec.experiments[0].name}`,
|
||||
resultNamespace: engineNamespace,
|
||||
workflowName: workflowYaml.metadata.name,
|
||||
experimentName:
|
||||
parsedEmbeddedYaml.spec.experiments[0].name,
|
||||
});
|
||||
} else {
|
||||
chaosResultNamesAndNamespacesMap[
|
||||
matchIndex
|
||||
].workflowName = `${chaosResultNamesAndNamespacesMap[matchIndex].workflowName}, \n${workflowYaml.metadata.name}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const panelGroups: PanelGroup[] = [];
|
||||
DashboardList[DashboardTemplateID ?? 0].panelGroups.forEach(
|
||||
(panelGroup) => {
|
||||
const selectedPanels: Panel[] = [];
|
||||
panelGroup.panels.forEach((panel) => {
|
||||
const selectedPanel: Panel = panel;
|
||||
chaosResultNamesAndNamespacesMap.forEach((keyValue) => {
|
||||
selectedPanel.prom_queries.push({
|
||||
queryid: uuidv4(),
|
||||
prom_query_name: generateChaosQuery(
|
||||
DashboardList[DashboardTemplateID ?? 0]
|
||||
.chaosEventQueryTemplate,
|
||||
keyValue.resultName,
|
||||
keyValue.resultNamespace
|
||||
),
|
||||
legend: `${keyValue.workflowName} / \n${keyValue.experimentName}`,
|
||||
resolution: '1/1',
|
||||
minstep: '1',
|
||||
line: false,
|
||||
close_area: true,
|
||||
});
|
||||
});
|
||||
selectedPanels.push(selectedPanel);
|
||||
selectedPanels.push(panel);
|
||||
});
|
||||
panelGroups.push({
|
||||
panel_group_name: panelGroup.panel_group_name,
|
||||
|
|
|
@ -9,6 +9,11 @@ import createReducer from './createReducer';
|
|||
const initialState: DashboardData = {
|
||||
selectedDashboardID: '',
|
||||
refreshRate: 0,
|
||||
range: {
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
},
|
||||
forceUpdate: false,
|
||||
};
|
||||
|
||||
export const selectDashboard = createReducer<DashboardData>(initialState, {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M0.360848 1.13672H11.6401C11.8383 1.13672 12.0005 1.29888 12.0005 1.49708C12.0005 1.69528 11.8383 1.85744 11.6401 1.85744H0.360848C0.16265 1.85744 0.000488281 1.69528 0.000488281 1.49708C0.000488281 1.29888 0.16265 1.13672 0.360848 1.13672Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M0.361031 4.125H8.25292C8.45112 4.125 8.61328 4.28716 8.61328 4.48536C8.61328 4.68356 8.45112 4.84572 8.25292 4.84572H0.361031C0.162832 4.84572 0.000670433 4.68356 0.000670433 4.48536C0.000670433 4.28716 0.162832 4.125 0.361031 4.125Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M0.360848 7.15234H11.6401C11.8383 7.15234 12.0005 7.3145 12.0005 7.5127C12.0005 7.71089 11.8383 7.87305 11.6401 7.87305H0.360848C0.16265 7.87305 0.000488281 7.71089 0.000488281 7.5127C0.000488281 7.3145 0.16265 7.15234 0.360848 7.15234Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M0.360804 10.1445H5.44189C5.64008 10.1445 5.80225 10.3067 5.80225 10.5049C5.80225 10.7031 5.64008 10.8653 5.44189 10.8653H0.360804C0.162605 10.8653 0.000442982 10.7031 0.000442982 10.5049C0.000442982 10.3067 0.162605 10.1445 0.360804 10.1445Z" fill="black" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="12" height="12" fill="white" transform="matrix(-1 0 0 1 12.0005 0)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,7 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.36036 1.13672H11.6396C11.8378 1.13672 12 1.29888 12 1.49708C12 1.69528 11.8378 1.85744 11.6396 1.85744H0.36036C0.162162 1.85744 0 1.69528 0 1.49708C0 1.29888 0.162162 1.13672 0.36036 1.13672Z" fill="#858CDD"/>
|
||||
<path d="M0.360542 4.125H8.25243C8.45063 4.125 8.61279 4.28716 8.61279 4.48536C8.61279 4.68356 8.45063 4.84572 8.25243 4.84572H0.360542C0.162344 4.84572 0.000182152 4.68356 0.000182152 4.48536C0.000182152 4.28716 0.162344 4.125 0.360542 4.125Z" fill="#858CDD"/>
|
||||
<path d="M0.36036 7.15234H11.6396C11.8378 7.15234 12 7.3145 12 7.5127C12 7.71089 11.8378 7.87305 11.6396 7.87305H0.36036C0.162162 7.87305 0 7.71089 0 7.5127C0 7.3145 0.162162 7.15234 0.36036 7.15234Z" fill="#858CDD"/>
|
||||
<path d="M0.360315 10.1445H5.4414C5.6396 10.1445 5.80176 10.3067 5.80176 10.5049C5.80176 10.7031 5.6396 10.8653 5.4414 10.8653H0.360315C0.162117 10.8653 -4.52995e-05 10.7031 -4.52995e-05 10.5049C-4.52995e-05 10.3067 0.162117 10.1445 0.360315 10.1445Z" fill="#858CDD"/>
|
||||
<rect x="-0.141421" width="15.2184" height="0.890909" rx="0.445455" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 11.4324 10.8023)" fill="#858CDD" stroke="white" stroke-width="0.2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,10 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.5 3H13V5.5" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 6.5L13 3" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.5 13H3V10.5" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.5 9.5L3 13" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 10.5V13H10.5" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 9.5L13 13" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 5.5V3H5.5" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.5 6.5L3 3" stroke="black" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 979 B |
|
@ -0,0 +1,12 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M1.2 8.28125L0 12.0013L3.72 10.8013L1.2 8.28125Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M7.89771 1.56323L2.04297 7.41797L4.58851 9.96351L10.4433 4.10877L7.89771 1.56323Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M11.8203 1.86L10.1403 0.18C9.90025 -0.06 9.54025 -0.06 9.30025 0.18L8.76025 0.72L11.2803 3.24L11.8203 2.7C12.0603 2.46 12.0603 2.1 11.8203 1.86Z" fill="black" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 637 B |
|
@ -1,6 +1,55 @@
|
|||
import { ChaosResultNamesAndNamespacesMap } from '../models/dashboardsData';
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable prefer-destructuring */
|
||||
/* eslint-disable no-loop-func */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable max-len */
|
||||
import { GraphMetric } from 'litmus-ui';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import YAML from 'yaml';
|
||||
import DashboardListData from '../components/PreconfiguredDashboards/data';
|
||||
import {
|
||||
Artifact,
|
||||
CronWorkflowYaml,
|
||||
Parameter,
|
||||
Template,
|
||||
WorkflowYaml,
|
||||
} from '../models/chaosWorkflowYaml';
|
||||
import {
|
||||
ChaosDataUpdates,
|
||||
ChaosEventDetails,
|
||||
ChaosInformation,
|
||||
ChaosResultNamesAndNamespacesMap,
|
||||
ExperimentNameAndChaosDataMap,
|
||||
RunWiseChaosMetrics,
|
||||
WorkflowAndExperimentMetaDataMap,
|
||||
WorkflowRunWiseDetails,
|
||||
} from '../models/dashboardsData';
|
||||
import { PromQuery } from '../models/graphql/dashboardsDetails';
|
||||
import {
|
||||
PrometheusResponse,
|
||||
promQueryInput,
|
||||
} from '../models/graphql/prometheus';
|
||||
import {
|
||||
ChaosData,
|
||||
ExecutionData,
|
||||
Workflow,
|
||||
WorkflowList,
|
||||
WorkflowRun,
|
||||
} from '../models/graphql/workflowListData';
|
||||
import {
|
||||
CHAOS_EXPERIMENT_VERDICT_FAIL,
|
||||
CHAOS_EXPERIMENT_VERDICT_PASS,
|
||||
DEFAULT_CHAOS_EVENT_NAME,
|
||||
DEFAULT_CHAOS_EVENT_PROMETHEUS_QUERY_RESOLUTION,
|
||||
DEFAULT_METRIC_SERIES_NAME,
|
||||
INVALID_RESILIENCE_SCORE_STRING,
|
||||
PROMETHEUS_QUERY_RESOLUTION_LIMIT,
|
||||
STATUS_RUNNING,
|
||||
} from '../pages/MonitoringDashboardPage/constants';
|
||||
import { validateWorkflowParameter } from './validate';
|
||||
import { generateChaosQuery, getWorkflowParameter } from './yamlUtils';
|
||||
|
||||
const getResultNameAndNamespace = (chaosQueryString: string) => {
|
||||
export const getResultNameAndNamespace = (chaosQueryString: string) => {
|
||||
const parsedChaosInfoMap: ChaosResultNamesAndNamespacesMap = {
|
||||
resultName: chaosQueryString
|
||||
.split(',')[0]
|
||||
|
@ -14,8 +63,597 @@ const getResultNameAndNamespace = (chaosQueryString: string) => {
|
|||
.slice(1, -1),
|
||||
workflowName: '',
|
||||
experimentName: '',
|
||||
selectedWorkflowIds: [],
|
||||
};
|
||||
return parsedChaosInfoMap;
|
||||
};
|
||||
|
||||
export default getResultNameAndNamespace;
|
||||
export const getWorkflowRunWiseDetails = (schedule: Workflow) => {
|
||||
const workflowRunWiseDetailsForSchedule: WorkflowRunWiseDetails = {
|
||||
idsOfWorkflowRuns: [],
|
||||
resilienceScoreForWorkflowRuns: [],
|
||||
statusOfWorkflowRuns: [],
|
||||
experimentNameWiseChaosDataOfWorkflowRuns: [],
|
||||
};
|
||||
if (schedule.workflow_runs) {
|
||||
schedule.workflow_runs.forEach((data: WorkflowRun, runIndex) => {
|
||||
try {
|
||||
const executionData: ExecutionData = JSON.parse(data.execution_data);
|
||||
workflowRunWiseDetailsForSchedule.idsOfWorkflowRuns[runIndex] =
|
||||
data.workflow_run_id;
|
||||
workflowRunWiseDetailsForSchedule.statusOfWorkflowRuns[runIndex] =
|
||||
executionData.finishedAt.length === 0
|
||||
? STATUS_RUNNING
|
||||
: executionData.phase;
|
||||
const { nodes } = executionData;
|
||||
for (const key of Object.keys(nodes)) {
|
||||
const node = nodes[key];
|
||||
if (node.chaosData) {
|
||||
const { chaosData } = node;
|
||||
if (
|
||||
!workflowRunWiseDetailsForSchedule
|
||||
.experimentNameWiseChaosDataOfWorkflowRuns[runIndex]
|
||||
) {
|
||||
workflowRunWiseDetailsForSchedule.experimentNameWiseChaosDataOfWorkflowRuns[
|
||||
runIndex
|
||||
] = [];
|
||||
}
|
||||
workflowRunWiseDetailsForSchedule.experimentNameWiseChaosDataOfWorkflowRuns[
|
||||
runIndex
|
||||
].push({
|
||||
experimentName: chaosData.experimentName,
|
||||
chaosData,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (executionData.event_type === 'UPDATE') {
|
||||
workflowRunWiseDetailsForSchedule.resilienceScoreForWorkflowRuns[
|
||||
runIndex
|
||||
] = executionData.resiliency_score ?? NaN;
|
||||
} else if (
|
||||
executionData.finishedAt.length === 0 ||
|
||||
executionData.phase === STATUS_RUNNING
|
||||
) {
|
||||
workflowRunWiseDetailsForSchedule.resilienceScoreForWorkflowRuns[
|
||||
runIndex
|
||||
] = NaN;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
return workflowRunWiseDetailsForSchedule;
|
||||
};
|
||||
|
||||
const getRunWiseChaosMetrics = (
|
||||
idsOfWorkflowRuns: string[],
|
||||
experimentNameWiseChaosDataOfWorkflowRuns: ExperimentNameAndChaosDataMap[][],
|
||||
experimentNameFromYaml: string,
|
||||
resilienceScoreForWorkflowRuns: number[],
|
||||
statusOfWorkflowRuns: string[]
|
||||
) => {
|
||||
const workflowRunMetricsPerExperiment: RunWiseChaosMetrics[] = [];
|
||||
experimentNameWiseChaosDataOfWorkflowRuns.forEach(
|
||||
(dataArray: ExperimentNameAndChaosDataMap[], runIndex) => {
|
||||
try {
|
||||
let selectedChaosData: ChaosData = {
|
||||
engineName: '',
|
||||
engineUID: '',
|
||||
experimentName: '',
|
||||
experimentPod: '',
|
||||
experimentStatus: '',
|
||||
experimentVerdict: '',
|
||||
failStep: '',
|
||||
lastUpdatedAt: '',
|
||||
namespace: '',
|
||||
probeSuccessPercentage: '',
|
||||
runnerPod: '',
|
||||
};
|
||||
dataArray.forEach(
|
||||
(experimentNameAndChaosData: ExperimentNameAndChaosDataMap) => {
|
||||
if (
|
||||
experimentNameAndChaosData.experimentName ===
|
||||
experimentNameFromYaml
|
||||
) {
|
||||
selectedChaosData = experimentNameAndChaosData.chaosData;
|
||||
}
|
||||
}
|
||||
);
|
||||
workflowRunMetricsPerExperiment.push({
|
||||
runIndex,
|
||||
runID: idsOfWorkflowRuns[runIndex],
|
||||
lastUpdatedTimeStamp: parseInt(selectedChaosData.lastUpdatedAt, 10),
|
||||
probeSuccessPercentage: `${selectedChaosData.probeSuccessPercentage}%`,
|
||||
experimentStatus: selectedChaosData.experimentStatus,
|
||||
experimentVerdict: selectedChaosData.experimentVerdict,
|
||||
resilienceScore: `${
|
||||
resilienceScoreForWorkflowRuns[runIndex] +
|
||||
(Number.isNaN(resilienceScoreForWorkflowRuns[runIndex]) ? '' : '%')
|
||||
}`,
|
||||
workflowStatus: statusOfWorkflowRuns[runIndex],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
return workflowRunMetricsPerExperiment;
|
||||
};
|
||||
|
||||
export const getChaosQueryPromInputAndID = (
|
||||
analyticsData: WorkflowList,
|
||||
agentID: string,
|
||||
areaGraph: string[],
|
||||
timeRangeDiff: number,
|
||||
selectedStartTime: number,
|
||||
selectedEndTime: number,
|
||||
existingEvents: ChaosEventDetails[]
|
||||
) => {
|
||||
const chaosInformation: ChaosInformation = {
|
||||
promQueries: [],
|
||||
chaosQueryIDs: [],
|
||||
chaosEventList: [],
|
||||
numberOfWorkflowsUnderConsideration: 0,
|
||||
};
|
||||
const chaosResultNamesAndNamespacesMap: ChaosResultNamesAndNamespacesMap[] = [];
|
||||
const workflowAndExperimentMetaDataMap: WorkflowAndExperimentMetaDataMap[] = [];
|
||||
analyticsData?.ListWorkflow.forEach((schedule: Workflow) => {
|
||||
if (schedule.cluster_id === agentID && !schedule.isRemoved) {
|
||||
const workflowRunWiseDetails: WorkflowRunWiseDetails = getWorkflowRunWiseDetails(
|
||||
schedule
|
||||
);
|
||||
let workflowYaml: WorkflowYaml | CronWorkflowYaml;
|
||||
let parametersMap: Parameter[];
|
||||
let workflowYamlCheck: boolean = true;
|
||||
try {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as WorkflowYaml).spec.arguments
|
||||
.parameters;
|
||||
} catch (err) {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as CronWorkflowYaml).spec.workflowSpec
|
||||
.arguments.parameters;
|
||||
workflowYamlCheck = false;
|
||||
}
|
||||
(workflowYamlCheck
|
||||
? (workflowYaml as WorkflowYaml).spec.templates
|
||||
: (workflowYaml as CronWorkflowYaml).spec.workflowSpec.templates
|
||||
).forEach((template: Template) => {
|
||||
if (template.inputs && template.inputs.artifacts) {
|
||||
template.inputs.artifacts.forEach((artifact: Artifact) => {
|
||||
const parsedEmbeddedYaml = YAML.parse(artifact.raw.data);
|
||||
if (parsedEmbeddedYaml.kind === 'ChaosEngine') {
|
||||
let engineNamespace: string = '';
|
||||
if (typeof parsedEmbeddedYaml.metadata.namespace === 'string') {
|
||||
engineNamespace = (parsedEmbeddedYaml.metadata
|
||||
.namespace as string).substring(
|
||||
1,
|
||||
(parsedEmbeddedYaml.metadata.namespace as string).length - 1
|
||||
);
|
||||
} else {
|
||||
engineNamespace = Object.keys(
|
||||
parsedEmbeddedYaml.metadata.namespace
|
||||
)[0];
|
||||
}
|
||||
if (validateWorkflowParameter(engineNamespace)) {
|
||||
engineNamespace = getWorkflowParameter(engineNamespace);
|
||||
parametersMap.forEach((parameterKeyValue: Parameter) => {
|
||||
if (parameterKeyValue.name === engineNamespace) {
|
||||
engineNamespace = parameterKeyValue.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
engineNamespace = parsedEmbeddedYaml.metadata.namespace;
|
||||
}
|
||||
let matchIndex: number = -1;
|
||||
const check: number = chaosResultNamesAndNamespacesMap.filter(
|
||||
(data, index) => {
|
||||
if (
|
||||
data.resultName.includes(
|
||||
parsedEmbeddedYaml.metadata.name
|
||||
) &&
|
||||
data.resultNamespace === engineNamespace
|
||||
) {
|
||||
matchIndex = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
).length;
|
||||
if (check === 0) {
|
||||
chaosResultNamesAndNamespacesMap.push({
|
||||
resultName: `${parsedEmbeddedYaml.metadata.name}-${parsedEmbeddedYaml.spec.experiments[0].name}`,
|
||||
resultNamespace: engineNamespace,
|
||||
workflowName: workflowYaml.metadata.name,
|
||||
experimentName: parsedEmbeddedYaml.spec.experiments[0].name,
|
||||
selectedWorkflowIds: [schedule.workflow_id],
|
||||
});
|
||||
workflowAndExperimentMetaDataMap.push({
|
||||
workflowID: schedule.workflow_id,
|
||||
workflowName: workflowYaml.metadata.name,
|
||||
experimentName: parsedEmbeddedYaml.spec.experiments[0].name,
|
||||
targetApp: parsedEmbeddedYaml.spec.appinfo.applabel.split(
|
||||
'='
|
||||
)[1],
|
||||
targetNamespace: parsedEmbeddedYaml.spec.appinfo.appns,
|
||||
runWiseChaosMetrics: getRunWiseChaosMetrics(
|
||||
workflowRunWiseDetails.idsOfWorkflowRuns,
|
||||
workflowRunWiseDetails.experimentNameWiseChaosDataOfWorkflowRuns,
|
||||
parsedEmbeddedYaml.spec.experiments[0].name,
|
||||
workflowRunWiseDetails.resilienceScoreForWorkflowRuns,
|
||||
workflowRunWiseDetails.statusOfWorkflowRuns
|
||||
),
|
||||
});
|
||||
} else {
|
||||
chaosResultNamesAndNamespacesMap[
|
||||
matchIndex
|
||||
].workflowName = `${chaosResultNamesAndNamespacesMap[matchIndex].workflowName}, \n${workflowYaml.metadata.name}`;
|
||||
chaosResultNamesAndNamespacesMap[
|
||||
matchIndex
|
||||
].selectedWorkflowIds.push(schedule.workflow_id);
|
||||
workflowAndExperimentMetaDataMap.push({
|
||||
workflowID: schedule.workflow_id,
|
||||
workflowName: workflowYaml.metadata.name,
|
||||
experimentName:
|
||||
chaosResultNamesAndNamespacesMap[matchIndex].experimentName,
|
||||
targetApp: parsedEmbeddedYaml.spec.appinfo.applabel.split(
|
||||
'='
|
||||
)[1],
|
||||
targetNamespace: parsedEmbeddedYaml.spec.appinfo.appns,
|
||||
runWiseChaosMetrics: getRunWiseChaosMetrics(
|
||||
workflowRunWiseDetails.idsOfWorkflowRuns,
|
||||
workflowRunWiseDetails.experimentNameWiseChaosDataOfWorkflowRuns,
|
||||
chaosResultNamesAndNamespacesMap[matchIndex].experimentName,
|
||||
workflowRunWiseDetails.resilienceScoreForWorkflowRuns,
|
||||
workflowRunWiseDetails.statusOfWorkflowRuns
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
chaosResultNamesAndNamespacesMap.forEach((keyValue, index) => {
|
||||
let queryID: string = uuidv4();
|
||||
|
||||
const matchingEvent: ChaosEventDetails[] = existingEvents.filter(
|
||||
(event: ChaosEventDetails) =>
|
||||
event.workflow === keyValue.workflowName &&
|
||||
event.experiment === keyValue.experimentName
|
||||
);
|
||||
|
||||
if (matchingEvent.length) {
|
||||
queryID = matchingEvent[0].id;
|
||||
}
|
||||
|
||||
const chaosEventMetrics: WorkflowAndExperimentMetaDataMap = workflowAndExperimentMetaDataMap.filter(
|
||||
(data) =>
|
||||
keyValue.selectedWorkflowIds.includes(data.workflowID) &&
|
||||
data.experimentName === keyValue.experimentName
|
||||
)[0];
|
||||
|
||||
const availableRunMetrics: RunWiseChaosMetrics[] = chaosEventMetrics.runWiseChaosMetrics.filter(
|
||||
(eventMetric) =>
|
||||
eventMetric.lastUpdatedTimeStamp >= selectedStartTime &&
|
||||
eventMetric.lastUpdatedTimeStamp <= selectedEndTime
|
||||
);
|
||||
|
||||
const latestResult: string = availableRunMetrics.length
|
||||
? availableRunMetrics[availableRunMetrics.length - 1].experimentVerdict
|
||||
: '--';
|
||||
|
||||
chaosInformation.promQueries.push({
|
||||
queryid: queryID,
|
||||
query: generateChaosQuery(
|
||||
DashboardListData[0].chaosEventQueryTemplate,
|
||||
keyValue.resultName,
|
||||
keyValue.resultNamespace
|
||||
),
|
||||
legend: `${keyValue.workflowName} / \n${keyValue.experimentName}`,
|
||||
resolution: DEFAULT_CHAOS_EVENT_PROMETHEUS_QUERY_RESOLUTION,
|
||||
minstep:
|
||||
timeRangeDiff * chaosResultNamesAndNamespacesMap.length <
|
||||
PROMETHEUS_QUERY_RESOLUTION_LIMIT - 1
|
||||
? 1
|
||||
: Math.floor(
|
||||
(timeRangeDiff * chaosResultNamesAndNamespacesMap.length) /
|
||||
(PROMETHEUS_QUERY_RESOLUTION_LIMIT + 1)
|
||||
),
|
||||
});
|
||||
chaosInformation.chaosQueryIDs.push(queryID);
|
||||
chaosInformation.chaosEventList.push({
|
||||
id: queryID,
|
||||
legend: areaGraph[index % areaGraph.length],
|
||||
workflow: keyValue.workflowName,
|
||||
experiment: keyValue.experimentName,
|
||||
target: `${chaosEventMetrics.targetNamespace} / ${chaosEventMetrics.targetApp}`,
|
||||
result: latestResult,
|
||||
chaosMetrics: chaosEventMetrics,
|
||||
showOnTable: availableRunMetrics.length > 0,
|
||||
});
|
||||
});
|
||||
|
||||
chaosInformation.numberOfWorkflowsUnderConsideration =
|
||||
analyticsData?.ListWorkflow.length;
|
||||
|
||||
return chaosInformation;
|
||||
};
|
||||
|
||||
export const chaosEventDataParserForPrometheus = (
|
||||
numOfWorkflows: number,
|
||||
workflowAnalyticsData: WorkflowList,
|
||||
eventData: PrometheusResponse,
|
||||
chaosEventList: ChaosEventDetails[],
|
||||
selectedStartTime: number,
|
||||
selectedEndTime: number
|
||||
) => {
|
||||
const chaosDataUpdates: ChaosDataUpdates = {
|
||||
queryIDs: [],
|
||||
chaosData: [],
|
||||
reGenerate: false,
|
||||
latestEventResult: [],
|
||||
};
|
||||
|
||||
if (workflowAnalyticsData.ListWorkflow) {
|
||||
if (numOfWorkflows !== workflowAnalyticsData.ListWorkflow.length) {
|
||||
chaosDataUpdates.reGenerate = true;
|
||||
}
|
||||
}
|
||||
|
||||
const workflowCheckList: string[] = [];
|
||||
eventData?.GetPromQuery.forEach((queryResponse) => {
|
||||
if (
|
||||
queryResponse.legends &&
|
||||
queryResponse.legends[0] &&
|
||||
parseInt(queryResponse.tsvs[0][0].timestamp ?? '0', 10) >=
|
||||
selectedStartTime &&
|
||||
parseInt(queryResponse.tsvs[0][0].timestamp ?? '0', 10) <= selectedEndTime
|
||||
) {
|
||||
const chaosEventDetails: ChaosEventDetails = chaosEventList.filter(
|
||||
(e) => queryResponse.queryid === e.id
|
||||
)[0];
|
||||
let latestRunMetric: RunWiseChaosMetrics | undefined;
|
||||
if (chaosEventDetails && workflowAnalyticsData.ListWorkflow) {
|
||||
const workflowAndExperiments: WorkflowAndExperimentMetaDataMap =
|
||||
chaosEventDetails.chaosMetrics;
|
||||
|
||||
const updatedWorkflowDetails: Workflow = workflowAnalyticsData.ListWorkflow.filter(
|
||||
(workflow: Workflow) =>
|
||||
workflow.workflow_id === workflowAndExperiments.workflowID
|
||||
)[0];
|
||||
|
||||
const updatedWorkflowRunWiseDetailsFromAnalytics: WorkflowRunWiseDetails = getWorkflowRunWiseDetails(
|
||||
updatedWorkflowDetails
|
||||
);
|
||||
|
||||
if (!workflowCheckList.includes(workflowAndExperiments.workflowID)) {
|
||||
workflowCheckList.push(workflowAndExperiments.workflowID);
|
||||
updatedWorkflowRunWiseDetailsFromAnalytics.experimentNameWiseChaosDataOfWorkflowRuns.forEach(
|
||||
(mapList: ExperimentNameAndChaosDataMap[], index) => {
|
||||
const workflowRunID: string =
|
||||
updatedWorkflowRunWiseDetailsFromAnalytics.idsOfWorkflowRuns[
|
||||
index
|
||||
];
|
||||
|
||||
const filteredChaosEventDetails: ChaosEventDetails[] = chaosEventList.filter(
|
||||
(e) =>
|
||||
workflowAndExperiments.workflowID ===
|
||||
e.chaosMetrics.workflowID
|
||||
);
|
||||
|
||||
filteredChaosEventDetails.forEach((event: ChaosEventDetails) => {
|
||||
const experimentInWorkflowRunEvent: RunWiseChaosMetrics[] = event.chaosMetrics.runWiseChaosMetrics.filter(
|
||||
(runWiseMetric) => runWiseMetric.runID === workflowRunID
|
||||
);
|
||||
|
||||
if (
|
||||
experimentInWorkflowRunEvent.length === 0 &&
|
||||
!event.showOnTable
|
||||
) {
|
||||
chaosDataUpdates.reGenerate = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const updatedRunWiseMetricsPerExperiment: RunWiseChaosMetrics[] = getRunWiseChaosMetrics(
|
||||
updatedWorkflowRunWiseDetailsFromAnalytics.idsOfWorkflowRuns,
|
||||
updatedWorkflowRunWiseDetailsFromAnalytics.experimentNameWiseChaosDataOfWorkflowRuns,
|
||||
workflowAndExperiments.experimentName,
|
||||
updatedWorkflowRunWiseDetailsFromAnalytics.resilienceScoreForWorkflowRuns,
|
||||
updatedWorkflowRunWiseDetailsFromAnalytics.statusOfWorkflowRuns
|
||||
);
|
||||
|
||||
if (
|
||||
updatedWorkflowRunWiseDetailsFromAnalytics.idsOfWorkflowRuns
|
||||
.length !== workflowAndExperiments.runWiseChaosMetrics.length &&
|
||||
updatedRunWiseMetricsPerExperiment.length !== 0
|
||||
) {
|
||||
chaosDataUpdates.reGenerate = true;
|
||||
}
|
||||
|
||||
const updatedMetrics: RunWiseChaosMetrics[] = [];
|
||||
|
||||
workflowAndExperiments.runWiseChaosMetrics.forEach(
|
||||
(metric: RunWiseChaosMetrics) => {
|
||||
const changeIndex: number = updatedWorkflowRunWiseDetailsFromAnalytics.idsOfWorkflowRuns.indexOf(
|
||||
metric.runID
|
||||
);
|
||||
|
||||
updatedMetrics.push({
|
||||
runIndex: metric.runIndex,
|
||||
runID: metric.runID,
|
||||
lastUpdatedTimeStamp:
|
||||
updatedRunWiseMetricsPerExperiment[changeIndex]
|
||||
.lastUpdatedTimeStamp,
|
||||
probeSuccessPercentage:
|
||||
updatedRunWiseMetricsPerExperiment[changeIndex]
|
||||
.probeSuccessPercentage,
|
||||
experimentStatus:
|
||||
updatedRunWiseMetricsPerExperiment[changeIndex]
|
||||
.experimentStatus,
|
||||
experimentVerdict:
|
||||
updatedRunWiseMetricsPerExperiment[changeIndex]
|
||||
.experimentVerdict,
|
||||
resilienceScore:
|
||||
updatedRunWiseMetricsPerExperiment[changeIndex].resilienceScore,
|
||||
workflowStatus:
|
||||
updatedRunWiseMetricsPerExperiment[changeIndex].workflowStatus,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const availableRunMetrics: RunWiseChaosMetrics[] = updatedMetrics.filter(
|
||||
(eventMetric) =>
|
||||
eventMetric.lastUpdatedTimeStamp >= selectedStartTime &&
|
||||
eventMetric.lastUpdatedTimeStamp <= selectedEndTime
|
||||
);
|
||||
latestRunMetric = availableRunMetrics[availableRunMetrics.length - 1];
|
||||
}
|
||||
chaosDataUpdates.queryIDs.push(queryResponse.queryid);
|
||||
chaosDataUpdates.chaosData.push(
|
||||
...queryResponse.legends.map((elem, index) => ({
|
||||
metricName: elem[0] ?? DEFAULT_CHAOS_EVENT_NAME,
|
||||
data: queryResponse.tsvs[index].map((dataPoint) => ({
|
||||
date: parseInt(dataPoint.timestamp ?? '0', 10) * 1000,
|
||||
value: parseInt(dataPoint.value ?? '0', 10),
|
||||
})),
|
||||
baseColor: chaosEventDetails ? chaosEventDetails.legend : '',
|
||||
subData: [
|
||||
{
|
||||
subDataName: 'Workflow Status',
|
||||
value: latestRunMetric
|
||||
? latestRunMetric.workflowStatus
|
||||
: STATUS_RUNNING,
|
||||
},
|
||||
{
|
||||
subDataName: 'Experiment Status',
|
||||
value: latestRunMetric
|
||||
? latestRunMetric.experimentStatus
|
||||
: STATUS_RUNNING,
|
||||
},
|
||||
{
|
||||
subDataName: 'Resilience Score',
|
||||
value:
|
||||
latestRunMetric &&
|
||||
latestRunMetric.workflowStatus !== STATUS_RUNNING &&
|
||||
latestRunMetric.resilienceScore !==
|
||||
INVALID_RESILIENCE_SCORE_STRING
|
||||
? latestRunMetric.resilienceScore
|
||||
: '--',
|
||||
},
|
||||
{
|
||||
subDataName: 'Probe Success Percentage',
|
||||
value: latestRunMetric
|
||||
? latestRunMetric.probeSuccessPercentage
|
||||
: '--',
|
||||
},
|
||||
{
|
||||
subDataName: 'Experiment Verdict',
|
||||
value: latestRunMetric
|
||||
? latestRunMetric.experimentVerdict +
|
||||
(latestRunMetric.experimentVerdict ===
|
||||
CHAOS_EXPERIMENT_VERDICT_PASS ||
|
||||
latestRunMetric.experimentVerdict ===
|
||||
CHAOS_EXPERIMENT_VERDICT_FAIL
|
||||
? 'ed'
|
||||
: '')
|
||||
: '--',
|
||||
},
|
||||
],
|
||||
// Filter subData within the start and end time of interleaving on experiment's lastUpdatedTimeStamp.
|
||||
// Add one extra subData field - lastUpdatedTimeStamp to filter subData in graph.
|
||||
// This method sends the latest run details within selected dashboard time range as the subData.
|
||||
// Needs to be updated to send every run detail with lastUpdatedTimeStamp in the ${NEW_SUBDATA_FIELD}.
|
||||
/*
|
||||
Schema:
|
||||
(to be updated as)
|
||||
- subData: [
|
||||
{ subDataName: "subData-1-1", value: "1-1", lastUpdatedTimeStamp: 1616832979 },
|
||||
{ subDataName: "subData-1-2", value: "1-2", lastUpdatedTimeStamp: 1616833006 },
|
||||
],
|
||||
subData:
|
||||
(to be filtered by lastUpdatedTimeStamp in litmus-ui graph on hovering over chaos events)
|
||||
- Workflow Status
|
||||
- Experiment Status
|
||||
- Resilience Score
|
||||
- Probe Success Percentage
|
||||
- Experiment Verdict
|
||||
*/
|
||||
}))
|
||||
);
|
||||
chaosDataUpdates.latestEventResult.push(
|
||||
latestRunMetric ? latestRunMetric.experimentVerdict : '--'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (workflowAnalyticsData.ListWorkflow) {
|
||||
if (eventData?.GetPromQuery.length < chaosDataUpdates.chaosData.length) {
|
||||
chaosDataUpdates.reGenerate = true;
|
||||
}
|
||||
if (
|
||||
numOfWorkflows !== workflowAnalyticsData.ListWorkflow.length &&
|
||||
workflowAnalyticsData.ListWorkflow.length !== 0
|
||||
) {
|
||||
chaosDataUpdates.reGenerate = true;
|
||||
}
|
||||
}
|
||||
|
||||
return chaosDataUpdates;
|
||||
};
|
||||
|
||||
export const getPromQueryInput = (
|
||||
prom_queries: PromQuery[],
|
||||
timeRangeDiff: number
|
||||
) => {
|
||||
const promQueries: promQueryInput[] = [];
|
||||
prom_queries.forEach((query: PromQuery) => {
|
||||
promQueries.push({
|
||||
queryid: query.queryid,
|
||||
query: query.prom_query_name,
|
||||
legend: query.legend,
|
||||
resolution: query.resolution,
|
||||
minstep:
|
||||
Math.floor(timeRangeDiff / parseInt(query.minstep, 10)) *
|
||||
prom_queries.length <
|
||||
PROMETHEUS_QUERY_RESOLUTION_LIMIT - 1
|
||||
? parseInt(query.minstep, 10)
|
||||
: Math.floor(
|
||||
(timeRangeDiff * prom_queries.length) /
|
||||
(PROMETHEUS_QUERY_RESOLUTION_LIMIT + 1)
|
||||
),
|
||||
});
|
||||
});
|
||||
return promQueries;
|
||||
};
|
||||
|
||||
export const seriesDataParserForPrometheus = (
|
||||
prometheusData: PrometheusResponse,
|
||||
lineGraph: string[]
|
||||
) => {
|
||||
const seriesData: Array<GraphMetric> = [];
|
||||
prometheusData.GetPromQuery.forEach((queryResponse, mainIndex) => {
|
||||
if (queryResponse.legends && queryResponse.legends[0]) {
|
||||
seriesData.push(
|
||||
...queryResponse.legends.map((elem, index) => ({
|
||||
metricName: elem[0] ?? DEFAULT_METRIC_SERIES_NAME,
|
||||
data: queryResponse.tsvs[index].map((dataPoint) => ({
|
||||
date: parseInt(dataPoint.timestamp ?? '0', 10) * 1000,
|
||||
value: parseFloat(dataPoint.value ?? '0.0'),
|
||||
})),
|
||||
baseColor:
|
||||
lineGraph[
|
||||
(mainIndex + (index % lineGraph.length)) % lineGraph.length
|
||||
],
|
||||
}))
|
||||
);
|
||||
}
|
||||
});
|
||||
return seriesData;
|
||||
};
|
||||
|
|
|
@ -1,42 +1,15 @@
|
|||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable prefer-destructuring */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import { ButtonFilled, ButtonOutlined, Modal } from 'litmus-ui';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import YAML from 'yaml';
|
||||
import DashboardList from '../../../../components/PreconfiguredDashboards/data';
|
||||
import { SCHEDULE_DETAILS } from '../../../../graphql';
|
||||
import { DELETE_DASHBOARD, UPDATE_PANEL } from '../../../../graphql/mutations';
|
||||
import {
|
||||
Artifact,
|
||||
CronWorkflowYaml,
|
||||
Parameter,
|
||||
Template,
|
||||
WorkflowYaml,
|
||||
} from '../../../../models/chaosWorkflowYaml';
|
||||
import { ChaosResultNamesAndNamespacesMap } from '../../../../models/dashboardsData';
|
||||
import { DELETE_DASHBOARD } from '../../../../graphql/mutations';
|
||||
import {
|
||||
DeleteDashboardInput,
|
||||
ListDashboardResponse,
|
||||
Panel,
|
||||
PanelGroup,
|
||||
PanelGroupResponse,
|
||||
PanelOption,
|
||||
PanelResponse,
|
||||
PromQuery,
|
||||
UpdatePanelInput,
|
||||
} from '../../../../models/graphql/dashboardsDetails';
|
||||
import {
|
||||
ScheduleDataVars,
|
||||
Schedules,
|
||||
ScheduleWorkflow,
|
||||
} from '../../../../models/graphql/scheduleData';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as DashboardActions from '../../../../redux/actions/dashboards';
|
||||
import * as DataSourceActions from '../../../../redux/actions/dataSource';
|
||||
|
@ -47,12 +20,6 @@ import {
|
|||
getProjectID,
|
||||
getProjectRole,
|
||||
} from '../../../../utils/getSearchParams';
|
||||
import getEngineNameAndNamespace from '../../../../utils/promUtils';
|
||||
import { validateWorkflowParameter } from '../../../../utils/validate';
|
||||
import {
|
||||
generateChaosQuery,
|
||||
getWorkflowParameter,
|
||||
} from '../../../../utils/yamlUtils';
|
||||
import useStyles, { StyledTableCell } from './styles';
|
||||
|
||||
interface TableDataProps {
|
||||
|
@ -77,20 +44,6 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
] = React.useState<DeleteDashboardInput>({
|
||||
dbID: '',
|
||||
});
|
||||
// Apollo query to get the scheduled data
|
||||
const { data: workflowSchedules } = useQuery<Schedules, ScheduleDataVars>(
|
||||
SCHEDULE_DETAILS,
|
||||
{
|
||||
variables: { projectID },
|
||||
fetchPolicy: 'cache-and-network',
|
||||
}
|
||||
);
|
||||
|
||||
const [updatePanel] = useMutation<UpdatePanelInput>(UPDATE_PANEL, {
|
||||
onError: () => {
|
||||
console.error('error updating dashboard details');
|
||||
},
|
||||
});
|
||||
|
||||
const [deleteDashboard] = useMutation<boolean, DeleteDashboardInput>(
|
||||
DELETE_DASHBOARD,
|
||||
|
@ -126,239 +79,6 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const reSyncChaosQueries = () => {
|
||||
const chaosResultNamesAndNamespacesMap: ChaosResultNamesAndNamespacesMap[] = [];
|
||||
workflowSchedules?.getScheduledWorkflows.forEach(
|
||||
(schedule: ScheduleWorkflow) => {
|
||||
if (schedule.cluster_id === data.cluster_id && !schedule.isRemoved) {
|
||||
let workflowYaml: WorkflowYaml | CronWorkflowYaml;
|
||||
let parametersMap: Parameter[];
|
||||
let workflowYamlCheck: boolean = true;
|
||||
try {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as WorkflowYaml).spec.arguments
|
||||
.parameters;
|
||||
} catch (err) {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as CronWorkflowYaml).spec.workflowSpec
|
||||
.arguments.parameters;
|
||||
workflowYamlCheck = false;
|
||||
}
|
||||
(workflowYamlCheck
|
||||
? (workflowYaml as WorkflowYaml).spec.templates
|
||||
: (workflowYaml as CronWorkflowYaml).spec.workflowSpec.templates
|
||||
).forEach((template: Template) => {
|
||||
if (template.inputs && template.inputs.artifacts) {
|
||||
template.inputs.artifacts.forEach((artifact: Artifact) => {
|
||||
const parsedEmbeddedYaml = YAML.parse(artifact.raw.data);
|
||||
if (parsedEmbeddedYaml.kind === 'ChaosEngine') {
|
||||
let engineNamespace: string = '';
|
||||
if (
|
||||
typeof parsedEmbeddedYaml.metadata.namespace === 'string'
|
||||
) {
|
||||
engineNamespace = (parsedEmbeddedYaml.metadata
|
||||
.namespace as string).substring(
|
||||
1,
|
||||
(parsedEmbeddedYaml.metadata.namespace as string).length -
|
||||
1
|
||||
);
|
||||
} else {
|
||||
engineNamespace = Object.keys(
|
||||
parsedEmbeddedYaml.metadata.namespace
|
||||
)[0];
|
||||
}
|
||||
if (validateWorkflowParameter(engineNamespace)) {
|
||||
engineNamespace = getWorkflowParameter(engineNamespace);
|
||||
parametersMap.forEach((parameterKeyValue: Parameter) => {
|
||||
if (parameterKeyValue.name === engineNamespace) {
|
||||
engineNamespace = parameterKeyValue.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
engineNamespace = parsedEmbeddedYaml.metadata.namespace;
|
||||
}
|
||||
let matchIndex: number = -1;
|
||||
const check: number = chaosResultNamesAndNamespacesMap.filter(
|
||||
(data, index) => {
|
||||
if (
|
||||
data.resultName.includes(
|
||||
parsedEmbeddedYaml.metadata.name
|
||||
) &&
|
||||
data.resultNamespace === engineNamespace
|
||||
) {
|
||||
matchIndex = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
).length;
|
||||
if (check === 0) {
|
||||
chaosResultNamesAndNamespacesMap.push({
|
||||
resultName: `${parsedEmbeddedYaml.metadata.name}-${parsedEmbeddedYaml.spec.experiments[0].name}`,
|
||||
resultNamespace: engineNamespace,
|
||||
workflowName: workflowYaml.metadata.name,
|
||||
experimentName:
|
||||
parsedEmbeddedYaml.spec.experiments[0].name,
|
||||
});
|
||||
} else {
|
||||
chaosResultNamesAndNamespacesMap[
|
||||
matchIndex
|
||||
].workflowName = `${chaosResultNamesAndNamespacesMap[matchIndex].workflowName}, \n${workflowYaml.metadata.name}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const isChaosQueryPresent: number[] = Array(
|
||||
chaosResultNamesAndNamespacesMap.length
|
||||
).fill(0);
|
||||
|
||||
data.panel_groups[0].panels[0].prom_queries.forEach(
|
||||
(existingPromQuery: PromQuery) => {
|
||||
if (
|
||||
existingPromQuery.prom_query_name.startsWith(
|
||||
'litmuschaos_awaited_experiments'
|
||||
)
|
||||
) {
|
||||
const chaosDetails: ChaosResultNamesAndNamespacesMap = getEngineNameAndNamespace(
|
||||
existingPromQuery.prom_query_name
|
||||
);
|
||||
chaosResultNamesAndNamespacesMap.forEach(
|
||||
(
|
||||
chaosDetailsFomSchedule: ChaosResultNamesAndNamespacesMap,
|
||||
index: number
|
||||
) => {
|
||||
if (
|
||||
chaosDetailsFomSchedule.resultName.includes(
|
||||
chaosDetails.resultName
|
||||
) &&
|
||||
chaosDetailsFomSchedule.resultNamespace ===
|
||||
chaosDetails.resultNamespace
|
||||
) {
|
||||
isChaosQueryPresent[index] = 1;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const dashboardTemplateID: number =
|
||||
data.db_type === 'Kubernetes Platform' ? 0 : 1;
|
||||
|
||||
const updatedPanelGroups: PanelGroup[] = [];
|
||||
|
||||
data.panel_groups.forEach((panelGroup: PanelGroupResponse) => {
|
||||
const updatedPanels: Panel[] = [];
|
||||
panelGroup.panels.forEach((panel: PanelResponse) => {
|
||||
const updatedQueries: PromQuery[] = [];
|
||||
panel.prom_queries.forEach((query: PromQuery) => {
|
||||
let updatedLegend: string = query.legend;
|
||||
if (
|
||||
query.prom_query_name.startsWith('litmuschaos_awaited_experiments')
|
||||
) {
|
||||
const chaosDetails: ChaosResultNamesAndNamespacesMap = getEngineNameAndNamespace(
|
||||
query.prom_query_name
|
||||
);
|
||||
chaosResultNamesAndNamespacesMap.forEach(
|
||||
(chaosDetailsFomSchedule: ChaosResultNamesAndNamespacesMap) => {
|
||||
if (
|
||||
chaosDetailsFomSchedule.resultName.includes(
|
||||
chaosDetails.resultName
|
||||
) &&
|
||||
chaosDetailsFomSchedule.resultNamespace ===
|
||||
chaosDetails.resultNamespace &&
|
||||
!query.legend.includes(chaosDetailsFomSchedule.workflowName)
|
||||
) {
|
||||
updatedLegend = `${chaosDetailsFomSchedule.workflowName}, \n${query.legend}`;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
const updatedQuery: PromQuery = {
|
||||
queryid: query.queryid,
|
||||
prom_query_name: query.prom_query_name,
|
||||
resolution: query.resolution,
|
||||
minstep: query.minstep,
|
||||
line: query.line,
|
||||
close_area: query.close_area,
|
||||
legend: updatedLegend,
|
||||
};
|
||||
updatedQueries.push(updatedQuery);
|
||||
});
|
||||
chaosResultNamesAndNamespacesMap.forEach(
|
||||
(keyValue: ChaosResultNamesAndNamespacesMap, index: number) => {
|
||||
if (isChaosQueryPresent[index] === 0) {
|
||||
updatedQueries.push({
|
||||
queryid: uuidv4(),
|
||||
prom_query_name: generateChaosQuery(
|
||||
DashboardList[dashboardTemplateID].chaosEventQueryTemplate,
|
||||
keyValue.resultName,
|
||||
keyValue.resultNamespace
|
||||
),
|
||||
legend: `${keyValue.workflowName} / \n${keyValue.experimentName}`,
|
||||
resolution: '1/1',
|
||||
minstep: '1',
|
||||
line: false,
|
||||
close_area: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
const existingPanelOptions: PanelOption = {
|
||||
points: panel.panel_options.points,
|
||||
grids: panel.panel_options.grids,
|
||||
left_axis: panel.panel_options.left_axis,
|
||||
};
|
||||
const updatedPanel: Panel = {
|
||||
panel_id: panel.panel_id,
|
||||
panel_name: panel.panel_name,
|
||||
panel_options: existingPanelOptions,
|
||||
prom_queries: updatedQueries,
|
||||
y_axis_left: panel.y_axis_left,
|
||||
y_axis_right: panel.y_axis_right,
|
||||
x_axis_down: panel.x_axis_down,
|
||||
unit: panel.unit,
|
||||
};
|
||||
updatedPanels.push(updatedPanel);
|
||||
});
|
||||
updatedPanelGroups.push({
|
||||
panel_group_id: panelGroup.panel_group_id,
|
||||
panel_group_name: panelGroup.panel_group_name,
|
||||
panels: updatedPanels,
|
||||
});
|
||||
});
|
||||
|
||||
const panelInputData: Panel[] = [];
|
||||
|
||||
updatedPanelGroups.forEach((panelGroup: PanelGroup) => {
|
||||
panelGroup.panels.forEach((panel: Panel) => {
|
||||
panelInputData.push({
|
||||
panel_id: panel.panel_id,
|
||||
db_id: data.db_id,
|
||||
panel_group_id: panelGroup.panel_group_id,
|
||||
prom_queries: panel.prom_queries,
|
||||
panel_options: panel.panel_options,
|
||||
panel_name: panel.panel_name,
|
||||
y_axis_left: panel.y_axis_left,
|
||||
y_axis_right: panel.y_axis_right,
|
||||
x_axis_down: panel.x_axis_down,
|
||||
unit: panel.unit,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
updatePanel({
|
||||
variables: { panelInput: panelInputData },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const onDashboardLoadRoutine = async () => {
|
||||
dashboard.selectDashboard({
|
||||
selectedDashboardID: data.db_id,
|
||||
|
@ -366,8 +86,14 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
selectedDashboardTemplateName: data.db_type,
|
||||
selectedAgentID: data.cluster_id,
|
||||
selectedAgentName: data.cluster_name,
|
||||
refreshRate: 0,
|
||||
});
|
||||
return Promise.resolve(reSyncChaosQueries());
|
||||
dataSource.selectDataSource({
|
||||
selectedDataSourceURL: '',
|
||||
selectedDataSourceID: '',
|
||||
selectedDataSourceName: '',
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -439,11 +165,6 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
value="Analysis"
|
||||
onClick={() => {
|
||||
onDashboardLoadRoutine().then(() => {
|
||||
dataSource.selectDataSource({
|
||||
selectedDataSourceURL: '',
|
||||
selectedDataSourceID: '',
|
||||
selectedDataSourceName: '',
|
||||
});
|
||||
history.push({
|
||||
pathname: '/analytics/dashboard',
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
import { IconButton, Typography, withStyles } from '@material-ui/core';
|
||||
import MuiAccordion from '@material-ui/core/Accordion';
|
||||
import AccordionDetails from '@material-ui/core/AccordionDetails';
|
||||
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
|
||||
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
|
||||
import ArrowDropUpIcon from '@material-ui/icons/ArrowDropUp';
|
||||
import { ButtonFilled } from 'litmus-ui';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChaosEventDetails } from '../../../../models/dashboardsData';
|
||||
import ChaosTable from '../ChaosTable';
|
||||
import useStyles from './styles';
|
||||
|
||||
const Accordion = withStyles((theme) => ({
|
||||
root: {
|
||||
border: 0,
|
||||
boxShadow: 'none',
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0,
|
||||
},
|
||||
'&:before': {
|
||||
display: 'none',
|
||||
},
|
||||
'&$expanded': {
|
||||
margin: 'auto',
|
||||
},
|
||||
'& .MuiAccordionSummary-root.Mui-expanded': {
|
||||
cursor: 'default',
|
||||
minHeight: '1rem !important',
|
||||
height: '2.75rem',
|
||||
paddingTop: theme.spacing(0.5),
|
||||
},
|
||||
'& .MuiAccordionSummary-root': {
|
||||
cursor: 'default',
|
||||
minHeight: '1rem !important',
|
||||
height: '2.75rem',
|
||||
paddingTop: theme.spacing(0.5),
|
||||
},
|
||||
'& .MuiButtonBase-root:hover': {
|
||||
cursor: 'default',
|
||||
},
|
||||
},
|
||||
}))(MuiAccordion);
|
||||
|
||||
const AccordionSummary = withStyles({
|
||||
content: {
|
||||
flexGrow: 0,
|
||||
},
|
||||
})(MuiAccordionSummary);
|
||||
|
||||
const StyledAccordionDetails = withStyles((theme) => ({
|
||||
root: {
|
||||
padding: theme.spacing(0, 0, 1),
|
||||
},
|
||||
}))(AccordionDetails);
|
||||
|
||||
interface ChaosAccordionProps {
|
||||
dashboardKey: string;
|
||||
chaosEventsToBeShown: ChaosEventDetails[];
|
||||
postEventSelectionRoutine: (selectedEvents: string[]) => void;
|
||||
}
|
||||
|
||||
const ChaosAccordion: React.FC<ChaosAccordionProps> = ({
|
||||
dashboardKey,
|
||||
chaosEventsToBeShown,
|
||||
postEventSelectionRoutine,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const [chaosTableOpen, setChaosTableOpen] = React.useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Accordion expanded={chaosTableOpen}>
|
||||
<AccordionSummary
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
className={classes.accordionSummary}
|
||||
key={`chaos-table-${dashboardKey}`}
|
||||
>
|
||||
<ButtonFilled
|
||||
className={classes.button}
|
||||
onClick={() => {
|
||||
setChaosTableOpen(!chaosTableOpen);
|
||||
}}
|
||||
startIcon={
|
||||
!chaosTableOpen ? (
|
||||
<ArrowDropDownIcon className={classes.tableDropIcon} />
|
||||
) : (
|
||||
<ArrowDropUpIcon className={classes.tableDropIcon} />
|
||||
)
|
||||
}
|
||||
>
|
||||
<Typography className={classes.chaosHelperText}>
|
||||
{!chaosTableOpen
|
||||
? `${t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.showTable'
|
||||
)}`
|
||||
: `${t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.hideTable'
|
||||
)}`}
|
||||
</Typography>
|
||||
</ButtonFilled>
|
||||
<IconButton
|
||||
aria-label="edit chaos query"
|
||||
aria-haspopup="true"
|
||||
disabled
|
||||
data-cy="editChaosQueryButton"
|
||||
className={classes.editIconButton}
|
||||
>
|
||||
<img src="/icons/editIcon.svg" alt="Edit" />
|
||||
</IconButton>
|
||||
</AccordionSummary>
|
||||
<StyledAccordionDetails className={classes.accordionDetails}>
|
||||
<ChaosTable
|
||||
chaosList={chaosEventsToBeShown}
|
||||
selectEvents={(selectedEvents: string[]) => {
|
||||
postEventSelectionRoutine(selectedEvents);
|
||||
}}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChaosAccordion;
|
|
@ -0,0 +1,42 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
accordionSummary: {
|
||||
display: 'flex',
|
||||
justifyItems: 'center',
|
||||
background: theme.palette.disabledBackground,
|
||||
},
|
||||
|
||||
accordionDetails: {
|
||||
width: '100%',
|
||||
},
|
||||
|
||||
button: {
|
||||
background: 'none',
|
||||
boxShadow: 'none',
|
||||
padding: 0,
|
||||
'&:hover': {
|
||||
background: 'none',
|
||||
boxShadow: 'none',
|
||||
cursor: 'pointer !important',
|
||||
},
|
||||
},
|
||||
|
||||
chaosHelperText: {
|
||||
fontWeight: 500,
|
||||
fontSize: '1rem',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
|
||||
tableDropIcon: {
|
||||
width: '1.75rem',
|
||||
height: '1.75rem',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
|
||||
editIconButton: {
|
||||
marginTop: theme.spacing(-0.75),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
|
@ -0,0 +1,70 @@
|
|||
import { Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import CheckBox from '../../../../components/CheckBox';
|
||||
import { StyledTableCell } from '../../../../components/StyledTableCell';
|
||||
import { ChaosEventDetails } from '../../../../models/dashboardsData';
|
||||
import {
|
||||
CHAOS_EXPERIMENT_VERDICT_FAIL,
|
||||
CHAOS_EXPERIMENT_VERDICT_PASS,
|
||||
} from '../../../../pages/MonitoringDashboardPage/constants';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface TableDataProps {
|
||||
data: ChaosEventDetails;
|
||||
itemSelectionStatus: boolean;
|
||||
labelIdentifier: string;
|
||||
}
|
||||
|
||||
const TableData: React.FC<TableDataProps> = ({
|
||||
data,
|
||||
itemSelectionStatus,
|
||||
labelIdentifier,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledTableCell padding="checkbox" className={classes.checkbox}>
|
||||
<CheckBox
|
||||
checked={itemSelectionStatus}
|
||||
inputProps={{ 'aria-labelledby': labelIdentifier }}
|
||||
/>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<div
|
||||
className={classes.colorBar}
|
||||
style={{
|
||||
background: data.legend,
|
||||
}}
|
||||
/>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Typography className={classes.tableObjects}>
|
||||
{data.workflow}
|
||||
</Typography>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Typography className={classes.tableObjects}>
|
||||
{data.experiment}
|
||||
</Typography>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Typography className={classes.tableObjects}>{data.target}</Typography>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Typography
|
||||
className={`${classes.tableObjects} ${
|
||||
data.result === CHAOS_EXPERIMENT_VERDICT_PASS
|
||||
? classes.passColor
|
||||
: data.result === CHAOS_EXPERIMENT_VERDICT_FAIL
|
||||
? classes.failColor
|
||||
: classes.awaitedColor
|
||||
}`}
|
||||
>
|
||||
{data.result}
|
||||
</Typography>
|
||||
</StyledTableCell>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default TableData;
|
|
@ -0,0 +1,83 @@
|
|||
import { TableHead, TableRow } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CheckBox from '../../../../components/CheckBox';
|
||||
import { StyledTableCell } from '../../../../components/StyledTableCell';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface TableHeaderProps {
|
||||
onSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
numSelected: number;
|
||||
rowCount: number;
|
||||
}
|
||||
|
||||
const TableHeader: React.FC<TableHeaderProps> = ({
|
||||
onSelectAllClick,
|
||||
numSelected,
|
||||
rowCount,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow className={classes.tableHead}>
|
||||
<StyledTableCell padding="checkbox" className={classes.checkbox}>
|
||||
<CheckBox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={onSelectAllClick}
|
||||
inputProps={{ 'aria-label': 'select all desserts' }}
|
||||
/>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell className={classes.headSpacing}>
|
||||
<div className={classes.nameContent}>
|
||||
<div className={classes.nameHead}>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead1'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell className={classes.headSpacing}>
|
||||
<div className={classes.nameContent}>
|
||||
<div className={classes.nameHead}>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead2'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell className={classes.headSpacing}>
|
||||
<div className={classes.nameContent}>
|
||||
<div className={classes.nameHead}>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead3'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell className={classes.headSpacing}>
|
||||
<div className={classes.nameContent}>
|
||||
<div className={classes.nameHead}>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead4'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell className={classes.headSpacing}>
|
||||
<div className={classes.nameContent}>
|
||||
<div className={classes.nameHead}>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead5'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableHeader;
|
|
@ -0,0 +1,165 @@
|
|||
import {
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TablePagination,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChaosEventDetails } from '../../../../models/dashboardsData';
|
||||
import useStyles from './styles';
|
||||
import TableData from './TableData';
|
||||
import TableHeader from './TableHeader';
|
||||
|
||||
interface ChaosTableProps {
|
||||
chaosList: ChaosEventDetails[];
|
||||
selectEvents: (selectedEvents: string[]) => void;
|
||||
}
|
||||
|
||||
const ChaosTable: React.FC<ChaosTableProps> = ({ chaosList, selectEvents }) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const [page, setPage] = React.useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState(5);
|
||||
const [selected, setSelected] = React.useState<string[]>([]);
|
||||
const isSelected = (name: string) => selected.indexOf(name) !== -1;
|
||||
const emptyRows =
|
||||
rowsPerPage - Math.min(rowsPerPage, chaosList.length - page * rowsPerPage);
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
const newSelecteds = chaosList.map((n: ChaosEventDetails) => n.id);
|
||||
setSelected(newSelecteds);
|
||||
selectEvents(newSelecteds);
|
||||
return;
|
||||
}
|
||||
setSelected([]);
|
||||
selectEvents([]);
|
||||
};
|
||||
|
||||
const handleClick = (name: string) => {
|
||||
const selectedIndex = selected.indexOf(name);
|
||||
let newSelected: string[] = [];
|
||||
|
||||
if (selectedIndex === -1) {
|
||||
newSelected = newSelected.concat(selected, name);
|
||||
} else if (selectedIndex === 0) {
|
||||
newSelected = newSelected.concat(selected.slice(1));
|
||||
} else if (selectedIndex === selected.length - 1) {
|
||||
newSelected = newSelected.concat(selected.slice(0, -1));
|
||||
} else if (selectedIndex > 0) {
|
||||
newSelected = newSelected.concat(
|
||||
selected.slice(0, selectedIndex),
|
||||
selected.slice(selectedIndex + 1)
|
||||
);
|
||||
}
|
||||
setSelected(newSelected);
|
||||
selectEvents(newSelected);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root} id="chaos">
|
||||
<div>
|
||||
<section className="table section">
|
||||
<Paper className={classes.tableBody}>
|
||||
<TableContainer className={classes.tableMain}>
|
||||
<Table aria-label="simple table">
|
||||
<TableHeader
|
||||
onSelectAllClick={handleSelectAllClick}
|
||||
numSelected={selected.length}
|
||||
rowCount={chaosList.length}
|
||||
/>
|
||||
<TableBody>
|
||||
{chaosList.length ? (
|
||||
chaosList
|
||||
.slice(0)
|
||||
.slice(
|
||||
page * rowsPerPage,
|
||||
page * rowsPerPage + rowsPerPage
|
||||
)
|
||||
.map((data: ChaosEventDetails, index: number) => {
|
||||
const isItemSelected = isSelected(data.id);
|
||||
const labelId = `enhanced-table-checkbox-${index}`;
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
onClick={() => {
|
||||
handleClick(data.id);
|
||||
}}
|
||||
role="checkbox"
|
||||
aria-checked={isItemSelected}
|
||||
tabIndex={-1}
|
||||
key={data.id}
|
||||
selected={isItemSelected}
|
||||
>
|
||||
<TableData
|
||||
data={data}
|
||||
itemSelectionStatus={isItemSelected}
|
||||
labelIdentifier={labelId}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>
|
||||
<div className={classes.noRecords}>
|
||||
<img
|
||||
src="/icons/cloudIcon.svg"
|
||||
className={classes.cloudIcon}
|
||||
alt="Chaos cloud"
|
||||
/>
|
||||
<Typography
|
||||
align="center"
|
||||
className={classes.noRecordsText}
|
||||
>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.chaosTable.noRecords'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{chaosList.length > 0 && emptyRows > 0 && (
|
||||
<TableRow style={{ height: 75 * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{chaosList.length > 0 && (
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25, 50]}
|
||||
component="div"
|
||||
count={chaosList.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChaosTable;
|
|
@ -0,0 +1,109 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
||||
tableMain: {
|
||||
background: theme.palette.cards.header,
|
||||
maxHeight: '30rem',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '0.2em',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
webkitBoxShadow: `inset 0 0 6px ${theme.palette.common.black}`,
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0,
|
||||
},
|
||||
'& td': {
|
||||
borderBottom: `1px solid ${theme.palette.border.main}`,
|
||||
},
|
||||
},
|
||||
|
||||
tableBody: {
|
||||
background: theme.palette.cards.header,
|
||||
},
|
||||
|
||||
tableHead: {
|
||||
background: theme.palette.cards.header,
|
||||
},
|
||||
|
||||
nameHead: {
|
||||
color: theme.palette.text.hint,
|
||||
margin: theme.spacing(2, 0, 1.5),
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 500,
|
||||
letterSpacing: '0.02em',
|
||||
},
|
||||
|
||||
tableObjects: {
|
||||
paddingLeft: theme.spacing(1),
|
||||
color: theme.palette.text.primary,
|
||||
height: '1.75rem',
|
||||
marginTop: theme.spacing(2),
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '130%',
|
||||
},
|
||||
|
||||
headSpacing: {
|
||||
paddingLeft: theme.spacing(2.5),
|
||||
},
|
||||
|
||||
nameContent: {
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
fontSize: '0.8rem',
|
||||
},
|
||||
|
||||
checkbox: {
|
||||
padding: theme.spacing(0.5, 0, 0, 2.5),
|
||||
},
|
||||
|
||||
noRecords: {
|
||||
height: '10rem',
|
||||
display: 'flex',
|
||||
padding: theme.spacing(5, 3),
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
cloudIcon: {
|
||||
height: '5rem',
|
||||
width: '5rem',
|
||||
},
|
||||
|
||||
noRecordsText: {
|
||||
color: theme.palette.text.hint,
|
||||
padding: theme.spacing(2),
|
||||
fontSize: '1.5rem',
|
||||
},
|
||||
|
||||
passColor: {
|
||||
color: theme.palette.success.main,
|
||||
},
|
||||
|
||||
failColor: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
|
||||
awaitedColor: {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
|
||||
colorBar: {
|
||||
height: '0.45rem',
|
||||
width: '2.75rem',
|
||||
margin: theme.spacing(0.75, 0, 0, 0.75),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { PanelGroupResponse } from '../../../models/graphql/dashboardsDetails';
|
||||
import PanelGroupContainer from './PanelGroupContainer';
|
||||
import PanelGroupContent from './PanelGroupContent';
|
||||
|
||||
const DashboardPanelGroup: React.FC<PanelGroupResponse> = ({ ...props }) => {
|
||||
return (
|
||||
<PanelGroupContainer>
|
||||
<PanelGroupContent {...props} />
|
||||
</PanelGroupContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardPanelGroup;
|
|
@ -0,0 +1,103 @@
|
|||
import { Typography } from '@material-ui/core';
|
||||
import { ButtonFilled, Modal } from 'litmus-ui';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
DASHBOARD_TYPE_1,
|
||||
DASHBOARD_TYPE_2,
|
||||
} from '../../../../pages/MonitoringDashboardPage/constants';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as DashboardActions from '../../../../redux/actions/dashboards';
|
||||
import { history } from '../../../../redux/configureStore';
|
||||
import { ReactComponent as CrossMarkIcon } from '../../../../svg/crossmark.svg';
|
||||
import {
|
||||
getProjectID,
|
||||
getProjectRole,
|
||||
} from '../../../../utils/getSearchParams';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface DataSourceInactiveModalProps {
|
||||
dataSourceStatus: string;
|
||||
dashboardType: string;
|
||||
dashboardID: string;
|
||||
dashboardName: string;
|
||||
}
|
||||
|
||||
const DataSourceInactiveModal: React.FC<DataSourceInactiveModalProps> = ({
|
||||
dataSourceStatus,
|
||||
dashboardType,
|
||||
dashboardID,
|
||||
dashboardName,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
// get ProjectID
|
||||
const projectID = getProjectID();
|
||||
const projectRole = getProjectRole();
|
||||
const dashboard = useActions(DashboardActions);
|
||||
|
||||
return (
|
||||
<Modal open onClose={() => {}} width="60%">
|
||||
<div className={classes.modal}>
|
||||
<Typography align="center">
|
||||
<CrossMarkIcon className={classes.icon} />
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.modalHeading}
|
||||
align="center"
|
||||
variant="h3"
|
||||
>
|
||||
{`${t(
|
||||
'analyticsDashboard.monitoringDashboardPage.dataSourceIs'
|
||||
)} ${dataSourceStatus}`}
|
||||
</Typography>
|
||||
<Typography
|
||||
align="center"
|
||||
variant="body1"
|
||||
className={classes.modalBody}
|
||||
>
|
||||
{t('analyticsDashboard.monitoringDashboardPage.dataSourceError')}
|
||||
</Typography>
|
||||
<div className={classes.flexButtons}>
|
||||
<ButtonFilled
|
||||
variant="success"
|
||||
onClick={() => {
|
||||
let dashboardTemplateID: number = -1;
|
||||
if (dashboardType === DASHBOARD_TYPE_1) {
|
||||
dashboardTemplateID = 0;
|
||||
} else if (dashboardType === DASHBOARD_TYPE_2) {
|
||||
dashboardTemplateID = 1;
|
||||
}
|
||||
dashboard.selectDashboard({
|
||||
selectedDashboardID: dashboardID,
|
||||
selectedDashboardName: dashboardName,
|
||||
selectedDashboardTemplateID: dashboardTemplateID,
|
||||
});
|
||||
history.push({
|
||||
pathname: '/analytics/dashboard/configure',
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
'analyticsDashboard.monitoringDashboardPage.reConfigureDashboard'
|
||||
)}
|
||||
</ButtonFilled>
|
||||
<ButtonFilled
|
||||
variant="success"
|
||||
onClick={() => {
|
||||
history.push({
|
||||
pathname: '/analytics/datasource/configure',
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('analyticsDashboard.monitoringDashboardPage.updateDataSource')}
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataSourceInactiveModal;
|
|
@ -0,0 +1,28 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
icon: {
|
||||
width: '6rem',
|
||||
height: '6rem',
|
||||
},
|
||||
|
||||
modalHeading: {
|
||||
margin: theme.spacing(3.5, 0, 4.5),
|
||||
fontSize: '2.25rem',
|
||||
},
|
||||
|
||||
modalBody: {
|
||||
marginBottom: theme.spacing(4.5),
|
||||
},
|
||||
|
||||
flexButtons: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-evenly',
|
||||
},
|
||||
|
||||
modal: {
|
||||
padding: theme.spacing(15, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { PanelResponse } from '../../../models/graphql/dashboardsDetails';
|
||||
import PanelContainer from './PanelContainer';
|
||||
import PanelContent from './PanelContent';
|
||||
|
||||
const GraphPanel: React.FC<PanelResponse> = ({ ...props }) => {
|
||||
return (
|
||||
<PanelContainer>
|
||||
<PanelContent {...props} />
|
||||
</PanelContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default GraphPanel;
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import { GraphPanelGroupProps } from '../../../../models/dashboardsData';
|
||||
import PanelGroupContent from './PanelGroupContent';
|
||||
|
||||
const DashboardPanelGroup: React.FC<GraphPanelGroupProps> = ({ ...props }) => {
|
||||
return <PanelGroupContent {...props} />;
|
||||
};
|
||||
|
||||
export default DashboardPanelGroup;
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import { GraphPanelProps } from '../../../../models/dashboardsData';
|
||||
import PanelContent from './PanelContent';
|
||||
|
||||
const GraphPanel: React.FC<GraphPanelProps> = ({ ...props }) => {
|
||||
return <PanelContent {...props} />;
|
||||
};
|
||||
|
||||
export default GraphPanel;
|
|
@ -0,0 +1,304 @@
|
|||
import { ApolloError, useQuery } from '@apollo/client';
|
||||
import { IconButton, Tooltip, Typography } from '@material-ui/core';
|
||||
import useTheme from '@material-ui/core/styles/useTheme';
|
||||
import { ButtonOutlined, GraphMetric, LineAreaGraph, Modal } from 'litmus-ui';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { PROM_QUERY } from '../../../../graphql';
|
||||
import { GraphPanelProps } from '../../../../models/dashboardsData';
|
||||
import {
|
||||
PrometheusQueryInput,
|
||||
PrometheusQueryVars,
|
||||
PrometheusResponse,
|
||||
promQueryInput,
|
||||
} from '../../../../models/graphql/prometheus';
|
||||
import {
|
||||
DEFAULT_REFRESH_RATE,
|
||||
DEFAULT_TOLERANCE_LIMIT,
|
||||
MAX_REFRESH_RATE,
|
||||
MINIMUM_TOLERANCE_LIMIT,
|
||||
PROMETHEUS_ERROR_QUERY_RESOLUTION_LIMIT_REACHED,
|
||||
} from '../../../../pages/MonitoringDashboardPage/constants';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as DashboardActions from '../../../../redux/actions/dashboards';
|
||||
import { RootState } from '../../../../redux/reducers';
|
||||
import { ReactComponent as ViewChaosMetric } from '../../../../svg/aligment.svg';
|
||||
import { ReactComponent as DisableViewChaosMetric } from '../../../../svg/alignmentStriked.svg';
|
||||
import { ReactComponent as Expand } from '../../../../svg/arrowsOut.svg';
|
||||
import { ReactComponent as Edit } from '../../../../svg/edit.svg';
|
||||
import {
|
||||
getPromQueryInput,
|
||||
seriesDataParserForPrometheus,
|
||||
} from '../../../../utils/promUtils';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface PrometheusQueryDataInterface {
|
||||
promInput: PrometheusQueryInput;
|
||||
firstLoad: Boolean;
|
||||
}
|
||||
|
||||
const PanelContent: React.FC<GraphPanelProps> = ({
|
||||
panel_name,
|
||||
prom_queries,
|
||||
y_axis_left,
|
||||
unit,
|
||||
chaos_data,
|
||||
className,
|
||||
}) => {
|
||||
const { palette } = useTheme();
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const dashboard = useActions(DashboardActions);
|
||||
const lineGraph: string[] = palette.graph.line;
|
||||
const [popout, setPopout] = useState(false);
|
||||
const [viewEventMetric, setViewEventMetric] = useState(false);
|
||||
const [
|
||||
prometheusQueryData,
|
||||
setPrometheusQueryData,
|
||||
] = React.useState<PrometheusQueryDataInterface>({
|
||||
promInput: {
|
||||
url: '',
|
||||
start: '',
|
||||
end: '',
|
||||
queries: [],
|
||||
},
|
||||
firstLoad: true,
|
||||
});
|
||||
|
||||
const [graphData, setGraphData] = React.useState<Array<GraphMetric>>([]);
|
||||
|
||||
const selectedDashboard = useSelector(
|
||||
(state: RootState) => state.selectDashboard
|
||||
);
|
||||
|
||||
const selectedDataSource = useSelector(
|
||||
(state: RootState) => state.selectDataSource
|
||||
);
|
||||
|
||||
// Apollo query to get the prometheus data
|
||||
useQuery<PrometheusResponse, PrometheusQueryVars>(PROM_QUERY, {
|
||||
variables: {
|
||||
prometheusInput: prometheusQueryData?.promInput ?? {
|
||||
url: '',
|
||||
start: '',
|
||||
end: '',
|
||||
queries: [],
|
||||
},
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
skip:
|
||||
prometheusQueryData?.promInput.queries?.length === 0 ||
|
||||
prometheusQueryData?.promInput.url === '',
|
||||
onCompleted: (prometheusData) => {
|
||||
let seriesData: Array<GraphMetric> = [];
|
||||
if (prometheusData) {
|
||||
seriesData = seriesDataParserForPrometheus(prometheusData, lineGraph);
|
||||
setGraphData(seriesData);
|
||||
seriesData = [];
|
||||
}
|
||||
},
|
||||
onError: (error: ApolloError) => {
|
||||
if (error.message === PROMETHEUS_ERROR_QUERY_RESOLUTION_LIMIT_REACHED) {
|
||||
if (selectedDashboard.refreshRate !== MAX_REFRESH_RATE) {
|
||||
dashboard.selectDashboard({
|
||||
refreshRate: MAX_REFRESH_RATE,
|
||||
});
|
||||
}
|
||||
setPrometheusQueryData({ ...prometheusQueryData, firstLoad: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const generatePromQueries = () => {
|
||||
let promQueries: promQueryInput[] = prometheusQueryData.promInput.queries;
|
||||
if (prometheusQueryData.firstLoad) {
|
||||
const timeRangeDiff: number =
|
||||
new Date(moment(selectedDashboard.range.endDate).format()).getTime() /
|
||||
1000 -
|
||||
new Date(moment(selectedDashboard.range.startDate).format()).getTime() /
|
||||
1000;
|
||||
promQueries = getPromQueryInput(prom_queries, timeRangeDiff);
|
||||
}
|
||||
setPrometheusQueryData({
|
||||
promInput: {
|
||||
url: selectedDataSource.selectedDataSourceURL,
|
||||
start: `${
|
||||
new Date(
|
||||
moment(selectedDashboard.range.startDate).format()
|
||||
).getTime() / 1000
|
||||
}`,
|
||||
end: `${
|
||||
new Date(moment(selectedDashboard.range.endDate).format()).getTime() /
|
||||
1000
|
||||
}`,
|
||||
queries: promQueries,
|
||||
},
|
||||
firstLoad: false,
|
||||
});
|
||||
promQueries = [];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (prometheusQueryData.firstLoad) {
|
||||
generatePromQueries();
|
||||
}
|
||||
if (!prometheusQueryData.firstLoad) {
|
||||
setTimeout(
|
||||
() => {
|
||||
generatePromQueries();
|
||||
},
|
||||
selectedDashboard.refreshRate !== 0
|
||||
? selectedDashboard.refreshRate
|
||||
: DEFAULT_REFRESH_RATE
|
||||
);
|
||||
}
|
||||
}, [prometheusQueryData]);
|
||||
|
||||
useEffect(() => {
|
||||
const endDate: number =
|
||||
new Date(moment(selectedDashboard.range.endDate).format()).getTime() /
|
||||
1000;
|
||||
const now: number = Math.round(new Date().getTime() / 1000);
|
||||
const diff: number = Math.abs(now - endDate);
|
||||
const maxLim: number =
|
||||
(selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 !== 0
|
||||
? (selectedDashboard.refreshRate ?? DEFAULT_REFRESH_RATE) / 1000 +
|
||||
MINIMUM_TOLERANCE_LIMIT
|
||||
: DEFAULT_TOLERANCE_LIMIT;
|
||||
if (
|
||||
!(diff >= 0 && diff <= maxLim) &&
|
||||
selectedDashboard.refreshRate !== MAX_REFRESH_RATE
|
||||
) {
|
||||
setPrometheusQueryData({ ...prometheusQueryData, firstLoad: true });
|
||||
dashboard.selectDashboard({
|
||||
refreshRate: MAX_REFRESH_RATE,
|
||||
});
|
||||
}
|
||||
if (
|
||||
diff >= 0 &&
|
||||
diff <= maxLim &&
|
||||
selectedDashboard.refreshRate === MAX_REFRESH_RATE
|
||||
) {
|
||||
setPrometheusQueryData({ ...prometheusQueryData, firstLoad: true });
|
||||
}
|
||||
}, [selectedDashboard.range]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDashboard.forceUpdate) {
|
||||
setPrometheusQueryData({ ...prometheusQueryData, firstLoad: true });
|
||||
}
|
||||
}, [selectedDashboard.forceUpdate]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={` ${classes.rootPanel} ${className} ${
|
||||
viewEventMetric ? classes.expand : ''
|
||||
}`}
|
||||
>
|
||||
<div className={classes.wrapperParentIconsTitle}>
|
||||
<Typography className={classes.title}>{panel_name}</Typography>
|
||||
<div className={classes.wrapperIcons}>
|
||||
{viewEventMetric ? (
|
||||
<Tooltip
|
||||
title={`${t('analyticsDashboard.toolTip.hideChaosMetric')}`}
|
||||
>
|
||||
<IconButton
|
||||
className={classes.panelIconButton}
|
||||
onClick={() => {
|
||||
setViewEventMetric(false);
|
||||
}}
|
||||
>
|
||||
<DisableViewChaosMetric className={classes.panelIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
title={`${t('analyticsDashboard.toolTip.viewChaosMetric')}`}
|
||||
>
|
||||
<IconButton
|
||||
className={classes.panelIconButton}
|
||||
onClick={() => {
|
||||
setViewEventMetric(true);
|
||||
}}
|
||||
>
|
||||
<ViewChaosMetric className={classes.panelIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={`${t('analyticsDashboard.toolTip.editPanel')}`}>
|
||||
<IconButton
|
||||
disabled
|
||||
className={classes.panelIconButton}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<Edit className={classes.panelIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={`${t('analyticsDashboard.toolTip.popout')}`}>
|
||||
<IconButton
|
||||
className={classes.panelIconButton}
|
||||
onClick={() => {
|
||||
setPopout(true);
|
||||
}}
|
||||
>
|
||||
<Expand className={classes.panelIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Modal
|
||||
open={popout}
|
||||
onClose={() => setPopout(false)}
|
||||
disableBackdropClick
|
||||
disableEscapeKeyDown
|
||||
modalActions={
|
||||
<ButtonOutlined onClick={() => setPopout(false)}>
|
||||
✕
|
||||
</ButtonOutlined>
|
||||
}
|
||||
height="95% !important"
|
||||
width="95%"
|
||||
>
|
||||
<div className={classes.popOutModal}>
|
||||
<Typography className={classes.title}>{panel_name}</Typography>
|
||||
<LineAreaGraph
|
||||
legendTableHeight={120}
|
||||
openSeries={graphData}
|
||||
eventSeries={chaos_data}
|
||||
showPoints={false}
|
||||
showLegendTable
|
||||
showEventTable
|
||||
showTips
|
||||
showEventMarkers
|
||||
marginLeftEventTable={10}
|
||||
unit={unit}
|
||||
yLabel={y_axis_left}
|
||||
yLabelOffset={55}
|
||||
margin={{ left: 75, right: 20, top: 20, bottom: 10 }}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
<div className={classes.singleGraph}>
|
||||
<LineAreaGraph
|
||||
legendTableHeight={120}
|
||||
openSeries={graphData}
|
||||
eventSeries={chaos_data}
|
||||
showPoints={false}
|
||||
showEventTable={viewEventMetric}
|
||||
showLegendTable
|
||||
showTips
|
||||
showEventMarkers
|
||||
unit={unit}
|
||||
yLabel={y_axis_left}
|
||||
yLabelOffset={55}
|
||||
margin={{ left: 75, right: 20, top: 20, bottom: 10 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default PanelContent;
|
|
@ -0,0 +1,92 @@
|
|||
import { Typography, withStyles } from '@material-ui/core';
|
||||
import MuiAccordion from '@material-ui/core/Accordion';
|
||||
import AccordionDetails from '@material-ui/core/AccordionDetails';
|
||||
import AccordionSummary from '@material-ui/core/AccordionSummary';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import React from 'react';
|
||||
import { EventMetric } from '../../../../models/dashboardsData';
|
||||
import { PanelResponse } from '../../../../models/graphql/dashboardsDetails';
|
||||
import GraphPanel from './GraphPanel';
|
||||
import useStyles from './styles';
|
||||
|
||||
const Accordion = withStyles({
|
||||
root: {
|
||||
border: 0,
|
||||
boxShadow: 'none',
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0,
|
||||
},
|
||||
'&:before': {
|
||||
display: 'none',
|
||||
},
|
||||
'&$expanded': {
|
||||
margin: 'auto',
|
||||
},
|
||||
'& .MuiAccordionSummary-root.Mui-expanded': {
|
||||
minHeight: '1rem !important',
|
||||
height: '2.5rem',
|
||||
},
|
||||
'& .MuiAccordionSummary-root': {
|
||||
minHeight: '1rem !important',
|
||||
height: '2.5rem',
|
||||
},
|
||||
},
|
||||
})(MuiAccordion);
|
||||
|
||||
interface DashboardPanelGroupContentProps {
|
||||
panels: PanelResponse[];
|
||||
panel_group_name: string;
|
||||
panel_group_id: string;
|
||||
chaos_data?: Array<EventMetric>;
|
||||
}
|
||||
|
||||
const DashboardPanelGroupContent: React.FC<DashboardPanelGroupContentProps> = ({
|
||||
panels,
|
||||
panel_group_id,
|
||||
panel_group_name,
|
||||
chaos_data,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
|
||||
return (
|
||||
<div className={classes.rootPanelGroup}>
|
||||
<Accordion expanded={open}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
className={classes.panelGroup}
|
||||
key={`${panel_group_id}`}
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
<Typography className={classes.panelGroupTitle}>
|
||||
{panel_group_name}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails className={classes.panelGroupContainer}>
|
||||
{panels &&
|
||||
panels.map((panel: PanelResponse) => (
|
||||
<GraphPanel
|
||||
key={panel.panel_id}
|
||||
data-cy="dashboardPanel"
|
||||
panel_id={panel.panel_id}
|
||||
panel_name={panel.panel_name}
|
||||
panel_options={panel.panel_options}
|
||||
prom_queries={panel.prom_queries}
|
||||
y_axis_left={panel.y_axis_left}
|
||||
y_axis_right={panel.y_axis_right}
|
||||
x_axis_down={panel.x_axis_down}
|
||||
unit={panel.unit}
|
||||
chaos_data={chaos_data}
|
||||
/>
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardPanelGroupContent;
|
|
@ -0,0 +1,88 @@
|
|||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
rootPanel: {
|
||||
display: 'inline-block',
|
||||
background: theme.palette.background.paper,
|
||||
padding: theme.spacing(2),
|
||||
margin: theme.spacing(1.5, 0, 1),
|
||||
},
|
||||
|
||||
rootPanelGroup: {
|
||||
width: '100%',
|
||||
display: 'inline-block',
|
||||
background: theme.palette.background.paper,
|
||||
padding: theme.spacing(2),
|
||||
paddingBottom: 0,
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
|
||||
panelGroup: {
|
||||
display: 'flex',
|
||||
alignContent: 'left',
|
||||
background: theme.palette.disabledBackground,
|
||||
},
|
||||
|
||||
panelGroupContainer: {
|
||||
width: '100%',
|
||||
background: theme.palette.cards.header,
|
||||
display: 'inline-grid',
|
||||
gridTemplateColumns: '49% 49%',
|
||||
gridGap: theme.spacing(1.75),
|
||||
padding: theme.spacing(1, 1, 1, 1.75),
|
||||
},
|
||||
|
||||
expand: {
|
||||
gridColumnStart: 1,
|
||||
gridColumnEnd: 3,
|
||||
},
|
||||
|
||||
panelGroupTitle: {
|
||||
fontWeight: 700,
|
||||
fontSize: '1rem',
|
||||
},
|
||||
|
||||
title: {
|
||||
fontWeight: 700,
|
||||
fontSize: '0.9rem',
|
||||
color: theme.palette.text.primary,
|
||||
textAlign: 'center',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
paddingTop: theme.spacing(2),
|
||||
},
|
||||
|
||||
singleGraph: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
position: 'relative',
|
||||
height: '27.5rem',
|
||||
},
|
||||
|
||||
popOutModal: {
|
||||
width: '85%',
|
||||
height: '95%',
|
||||
padding: `${theme.spacing(4)} ${theme.spacing(4)} ${theme.spacing(4)} 10%`,
|
||||
},
|
||||
|
||||
wrapperParentIconsTitle: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
|
||||
wrapperIcons: {
|
||||
display: 'flex',
|
||||
},
|
||||
|
||||
panelIcon: {
|
||||
width: '0.9rem',
|
||||
height: '0.9rem',
|
||||
},
|
||||
|
||||
panelIconButton: {
|
||||
backgroundColor: 'transparent !important',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
padding: theme.spacing(0.5, 0.5, 0, 0.5),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
|
@ -1,6 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const PanelContainer: React.FC = ({ children }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
export default PanelContainer;
|
|
@ -1,258 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import useTheme from '@material-ui/core/styles/useTheme';
|
||||
import { GraphMetric, LineAreaGraph } from 'litmus-ui';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { PROM_QUERY } from '../../../graphql';
|
||||
import {
|
||||
PanelResponse,
|
||||
PromQuery,
|
||||
} from '../../../models/graphql/dashboardsDetails';
|
||||
import {
|
||||
PrometheusQueryInput,
|
||||
PrometheusQueryVars,
|
||||
PrometheusResponse,
|
||||
promQueryInput,
|
||||
} from '../../../models/graphql/prometheus';
|
||||
import { RootState } from '../../../redux/reducers';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface PrometheusQueryDataInterface {
|
||||
promInput: PrometheusQueryInput;
|
||||
chaosInput: string[];
|
||||
}
|
||||
|
||||
interface GraphDataInterface {
|
||||
seriesData: Array<GraphMetric>;
|
||||
eventData: Array<GraphMetric>;
|
||||
}
|
||||
|
||||
interface SynchronizerInterface {
|
||||
updateQueries: Boolean;
|
||||
firstLoad: Boolean;
|
||||
fetch: Boolean;
|
||||
}
|
||||
|
||||
const PanelContent: React.FC<PanelResponse> = ({
|
||||
panel_id,
|
||||
panel_name,
|
||||
panel_options,
|
||||
prom_queries,
|
||||
y_axis_left,
|
||||
y_axis_right,
|
||||
x_axis_down,
|
||||
unit,
|
||||
}) => {
|
||||
const { palette } = useTheme();
|
||||
const classes = useStyles();
|
||||
const lineGraph: string[] = palette.graph.line;
|
||||
const areaGraph: string[] = palette.graph.area;
|
||||
|
||||
const [
|
||||
prometheusQueryData,
|
||||
setPrometheusQueryData,
|
||||
] = React.useState<PrometheusQueryDataInterface>({
|
||||
promInput: {
|
||||
url: '',
|
||||
start: '',
|
||||
end: '',
|
||||
queries: [],
|
||||
},
|
||||
chaosInput: [],
|
||||
});
|
||||
|
||||
const [graphData, setGraphData] = React.useState<GraphDataInterface>({
|
||||
seriesData: [],
|
||||
eventData: [],
|
||||
});
|
||||
|
||||
const [synchronizer, setSynchronizer] = React.useState<SynchronizerInterface>(
|
||||
{
|
||||
updateQueries: false,
|
||||
firstLoad: true,
|
||||
fetch: false,
|
||||
}
|
||||
);
|
||||
|
||||
const selectedDashboard = useSelector(
|
||||
(state: RootState) => state.selectDashboard
|
||||
);
|
||||
|
||||
const selectedDataSource = useSelector(
|
||||
(state: RootState) => state.selectDataSource
|
||||
);
|
||||
|
||||
// Apollo query to get the prometheus data
|
||||
const { data: prometheusData } = useQuery<
|
||||
PrometheusResponse,
|
||||
PrometheusQueryVars
|
||||
>(PROM_QUERY, {
|
||||
variables: { prometheusInput: prometheusQueryData.promInput },
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
const generatePrometheusQueryData = () => {
|
||||
let promQueries: promQueryInput[] = [];
|
||||
let chaosQueries: string[] = [];
|
||||
prom_queries.forEach((query: PromQuery) => {
|
||||
if (query.prom_query_name.startsWith('litmuschaos_awaited_experiments')) {
|
||||
chaosQueries.push(query.queryid);
|
||||
}
|
||||
promQueries.push({
|
||||
queryid: query.queryid,
|
||||
query: query.prom_query_name,
|
||||
legend: query.legend,
|
||||
resolution: query.resolution,
|
||||
minstep: parseInt(query.minstep, 10),
|
||||
});
|
||||
});
|
||||
const prometheusQueryInput: PrometheusQueryInput = {
|
||||
url: selectedDataSource.selectedDataSourceURL,
|
||||
start: `${Math.round(new Date().getTime() / 1000) - 1800}`,
|
||||
end: `${Math.round(new Date().getTime() / 1000)}`,
|
||||
queries: promQueries,
|
||||
};
|
||||
setPrometheusQueryData({
|
||||
promInput: prometheusQueryInput,
|
||||
chaosInput: chaosQueries,
|
||||
});
|
||||
promQueries = [];
|
||||
chaosQueries = [];
|
||||
};
|
||||
|
||||
const updateGraphData = () => {
|
||||
let seriesData: Array<GraphMetric> = [];
|
||||
let eventData: Array<GraphMetric> = [];
|
||||
if (prometheusData) {
|
||||
prometheusData.GetPromQuery.forEach((queryResponse) => {
|
||||
if (prometheusQueryData.chaosInput.includes(queryResponse.queryid)) {
|
||||
if (queryResponse.legends && queryResponse.legends[0]) {
|
||||
eventData.push(
|
||||
...queryResponse.legends.map((elem, index) => ({
|
||||
metricName: elem[0] ?? 'chaos',
|
||||
data: queryResponse.tsvs[index].map((dataPoint) => ({
|
||||
date: parseInt(dataPoint.timestamp ?? '0', 10) * 1000,
|
||||
value: parseInt(dataPoint.value ?? '0', 10),
|
||||
})),
|
||||
baseColor: palette.error.main,
|
||||
}))
|
||||
);
|
||||
}
|
||||
} else if (queryResponse.legends && queryResponse.legends[0]) {
|
||||
seriesData.push(
|
||||
...queryResponse.legends.map((elem, index) => ({
|
||||
metricName: elem[0] ?? 'metric',
|
||||
data: queryResponse.tsvs[index].map((dataPoint) => ({
|
||||
date: parseInt(dataPoint.timestamp ?? '0', 10) * 1000,
|
||||
value: parseFloat(dataPoint.value ?? '0.0'),
|
||||
})),
|
||||
baseColor: lineGraph[index % lineGraph.length],
|
||||
}))
|
||||
);
|
||||
}
|
||||
});
|
||||
setGraphData({
|
||||
seriesData,
|
||||
eventData,
|
||||
});
|
||||
seriesData = [];
|
||||
eventData = [];
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
synchronizer.firstLoad === true &&
|
||||
synchronizer.updateQueries === false
|
||||
) {
|
||||
if (prom_queries.length) {
|
||||
generatePrometheusQueryData();
|
||||
setSynchronizer({
|
||||
updateQueries: true,
|
||||
firstLoad: false,
|
||||
fetch: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (
|
||||
synchronizer.updateQueries === true &&
|
||||
synchronizer.firstLoad === false
|
||||
) {
|
||||
setTimeout(() => {
|
||||
if (prom_queries.length) {
|
||||
generatePrometheusQueryData();
|
||||
setSynchronizer({
|
||||
...synchronizer,
|
||||
fetch: true,
|
||||
});
|
||||
}
|
||||
}, selectedDashboard.refreshRate);
|
||||
}
|
||||
}, [prometheusQueryData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
prometheusData &&
|
||||
prometheusData.GetPromQuery.length &&
|
||||
prometheusData.GetPromQuery[0].legends?.length &&
|
||||
prometheusData.GetPromQuery[0].legends !== null &&
|
||||
prometheusData.GetPromQuery[0].legends[0] !== null
|
||||
) {
|
||||
if (synchronizer.fetch === true) {
|
||||
updateGraphData();
|
||||
setSynchronizer({
|
||||
...synchronizer,
|
||||
fetch: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [prometheusData]);
|
||||
|
||||
return (
|
||||
<div className={classes.rootPanel}>
|
||||
<div>
|
||||
{/* <Typography>panel_id: {panel_id}</Typography>
|
||||
<Typography>panel_name: {panel_name}</Typography>
|
||||
<Typography>
|
||||
panel_options: {JSON.stringify(panel_options)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
panel_axes {y_axis_left}, {y_axis_right}, {x_axis_down}
|
||||
</Typography>
|
||||
<Typography>
|
||||
panel_unit: {unit}
|
||||
</Typography>
|
||||
<Typography>prom_queries: {JSON.stringify(prom_queries)}</Typography>
|
||||
<Typography>
|
||||
data_for_graph: {JSON.stringify(prometheusData)}
|
||||
</Typography> */}
|
||||
<Typography className={classes.title}>{panel_name}</Typography>
|
||||
{/* <Typography
|
||||
style={{ display: 'none' }}
|
||||
>{`${panel_id}-${panel_options.grids}`}</Typography> */}
|
||||
{/* panel_id and panel_options has been used temporaryly will be removed later */}
|
||||
<div className={classes.singleGraph}>
|
||||
<LineAreaGraph
|
||||
legendTableHeight={120}
|
||||
openSeries={graphData.seriesData}
|
||||
eventSeries={graphData.eventData}
|
||||
showPoints={false}
|
||||
showLegendTable
|
||||
showTips
|
||||
showEventMarkers
|
||||
unit={unit}
|
||||
yLabel={y_axis_left}
|
||||
yLabelOffset={55}
|
||||
margin={{ left: 75, right: 20, top: 20, bottom: 10 }}
|
||||
/>
|
||||
</div>
|
||||
{/* <Typography>
|
||||
data_for_graph: {JSON.stringify(prometheusData)}
|
||||
</Typography> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default PanelContent;
|
|
@ -1,6 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const PanelGroupContainer: React.FC = ({ children }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
export default PanelGroupContainer;
|
|
@ -1,68 +0,0 @@
|
|||
import { Typography } from '@material-ui/core';
|
||||
import AccordionDetails from '@material-ui/core/AccordionDetails';
|
||||
import AccordionSummary from '@material-ui/core/AccordionSummary';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import React from 'react';
|
||||
import { PanelResponse } from '../../../models/graphql/dashboardsDetails';
|
||||
import GraphPanel from './GraphPanel';
|
||||
import useStyles, { Accordion } from './styles';
|
||||
|
||||
interface DashboardPanelGroupContentProps {
|
||||
panels: PanelResponse[];
|
||||
panel_group_name: string;
|
||||
panel_group_id: string;
|
||||
}
|
||||
|
||||
const DashboardPanelGroupContent: React.FC<DashboardPanelGroupContentProps> = ({
|
||||
panels,
|
||||
panel_group_id,
|
||||
panel_group_name,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
|
||||
return (
|
||||
<div className={classes.rootPanelGroup}>
|
||||
{/* <Typography>panel_group_id: {panel_group_id}</Typography> */}
|
||||
{/* <Typography>panel_group_name: {panel_group_name}</Typography> */}
|
||||
|
||||
<div>
|
||||
<Accordion expanded={open}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
className={classes.panelGroup}
|
||||
key={`${panel_group_id}`}
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
<Typography className={classes.panelGroupTitle}>
|
||||
{panel_group_name}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails className={classes.panelGroupContainer}>
|
||||
{panels &&
|
||||
panels.map((panel: PanelResponse) => (
|
||||
<div key={panel.panel_id} data-cy="dashboardPanel">
|
||||
<GraphPanel
|
||||
panel_id={panel.panel_id}
|
||||
panel_name={panel.panel_name}
|
||||
panel_options={panel.panel_options}
|
||||
prom_queries={panel.prom_queries}
|
||||
y_axis_left={panel.y_axis_left}
|
||||
y_axis_right={panel.y_axis_right}
|
||||
x_axis_down={panel.x_axis_down}
|
||||
unit={panel.unit}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardPanelGroupContent;
|
|
@ -1,81 +0,0 @@
|
|||
import MuiAccordion from '@material-ui/core/Accordion';
|
||||
import { makeStyles, withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
rootPanel: {
|
||||
display: 'inline-block',
|
||||
background: theme.palette.background.paper,
|
||||
padding: theme.spacing(2),
|
||||
margin: theme.spacing(3, 1),
|
||||
},
|
||||
|
||||
rootPanelGroup: {
|
||||
width: '100%',
|
||||
display: 'inline-block',
|
||||
background: theme.palette.background.paper,
|
||||
padding: theme.spacing(2),
|
||||
paddingBottom: 0,
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
panelGroup: {
|
||||
display: 'flex',
|
||||
alignItems: 'left',
|
||||
background: theme.palette.disabledBackground,
|
||||
},
|
||||
panelGroupContainer: {
|
||||
background: theme.palette.cards.header,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
panelGroupTitle: {
|
||||
fontWeight: 700,
|
||||
fontSize: '1rem',
|
||||
},
|
||||
|
||||
title: {
|
||||
fontWeight: 700,
|
||||
fontSize: '0.9rem',
|
||||
color: theme.palette.text.primary,
|
||||
textAlign: 'center',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
paddingTop: theme.spacing(2.5),
|
||||
},
|
||||
|
||||
singleGraph: {
|
||||
// TODO remove this later
|
||||
'& hr': {
|
||||
position: 'relative !important',
|
||||
},
|
||||
width: '32.5vw',
|
||||
height: '27.5rem',
|
||||
[theme.breakpoints.down(1200)]: {
|
||||
width: '30vw',
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width: '42.5vw',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export const Accordion = withStyles({
|
||||
root: {
|
||||
border: 0,
|
||||
boxShadow: 'none',
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0,
|
||||
},
|
||||
'&:before': {
|
||||
display: 'none',
|
||||
},
|
||||
'&$expanded': {
|
||||
margin: 'auto',
|
||||
},
|
||||
},
|
||||
expanded: {},
|
||||
})(MuiAccordion);
|
||||
|
||||
export default useStyles;
|
|
@ -1,8 +1,3 @@
|
|||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable prefer-destructuring */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import {
|
||||
IconButton,
|
||||
Paper,
|
||||
|
@ -13,33 +8,7 @@ import {
|
|||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import YAML from 'yaml';
|
||||
import DashboardTemplatesList from '../../../../components/PreconfiguredDashboards/data';
|
||||
import { SCHEDULE_DETAILS, UPDATE_PANEL } from '../../../../graphql';
|
||||
import {
|
||||
Artifact,
|
||||
CronWorkflowYaml,
|
||||
Parameter,
|
||||
Template,
|
||||
WorkflowYaml,
|
||||
} from '../../../../models/chaosWorkflowYaml';
|
||||
import { ChaosResultNamesAndNamespacesMap } from '../../../../models/dashboardsData';
|
||||
import {
|
||||
ListDashboardResponse,
|
||||
Panel,
|
||||
PanelGroup,
|
||||
PanelGroupResponse,
|
||||
PanelOption,
|
||||
PanelResponse,
|
||||
PromQuery,
|
||||
UpdatePanelInput,
|
||||
} from '../../../../models/graphql/dashboardsDetails';
|
||||
import {
|
||||
ScheduleDataVars,
|
||||
Schedules,
|
||||
ScheduleWorkflow,
|
||||
} from '../../../../models/graphql/scheduleData';
|
||||
import { ListDashboardResponse } from '../../../../models/graphql/dashboardsDetails';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as DashboardActions from '../../../../redux/actions/dashboards';
|
||||
import * as DataSourceActions from '../../../../redux/actions/dataSource';
|
||||
|
@ -50,13 +19,7 @@ import {
|
|||
getProjectID,
|
||||
getProjectRole,
|
||||
} from '../../../../utils/getSearchParams';
|
||||
import getEngineNameAndNamespace from '../../../../utils/promUtils';
|
||||
import { GetTimeDiff } from '../../../../utils/timeDifferenceString';
|
||||
import { validateWorkflowParameter } from '../../../../utils/validate';
|
||||
import {
|
||||
generateChaosQuery,
|
||||
getWorkflowParameter,
|
||||
} from '../../../../utils/yamlUtils';
|
||||
import useStyles from '../styles';
|
||||
|
||||
interface TableDashboardData {
|
||||
|
@ -71,262 +34,9 @@ const TableDashboardData: React.FC<TableDashboardData> = ({
|
|||
const currentTime = new Date().valueOf();
|
||||
const dataSource = useActions(DataSourceActions);
|
||||
const dashboard = useActions(DashboardActions);
|
||||
// selecedProjectID
|
||||
const projectID = getProjectID();
|
||||
const projectRole = getProjectRole();
|
||||
|
||||
// schedule data
|
||||
const { data: schedulesData } = useQuery<Schedules, ScheduleDataVars>(
|
||||
SCHEDULE_DETAILS,
|
||||
{
|
||||
variables: {
|
||||
projectID,
|
||||
},
|
||||
}
|
||||
);
|
||||
// schedule data end
|
||||
// update pannel
|
||||
const [updatePanel] = useMutation<UpdatePanelInput>(UPDATE_PANEL, {
|
||||
onError: () => {
|
||||
console.error('error updating dashboard details');
|
||||
},
|
||||
});
|
||||
// update pannel end
|
||||
// reSyncChaos
|
||||
|
||||
const reSyncChaosQueries = (data: ListDashboardResponse) => {
|
||||
const chaosResultNamesAndNamespacesMap: ChaosResultNamesAndNamespacesMap[] = [];
|
||||
schedulesData?.getScheduledWorkflows.forEach(
|
||||
(schedule: ScheduleWorkflow) => {
|
||||
if (schedule.cluster_id === data.cluster_id && !schedule.isRemoved) {
|
||||
let workflowYaml: WorkflowYaml | CronWorkflowYaml;
|
||||
let parametersMap: Parameter[];
|
||||
let workflowYamlCheck: boolean = true;
|
||||
try {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as WorkflowYaml).spec.arguments
|
||||
.parameters;
|
||||
} catch (err) {
|
||||
workflowYaml = JSON.parse(schedule.workflow_manifest);
|
||||
parametersMap = (workflowYaml as CronWorkflowYaml).spec.workflowSpec
|
||||
.arguments.parameters;
|
||||
workflowYamlCheck = false;
|
||||
}
|
||||
(workflowYamlCheck
|
||||
? (workflowYaml as WorkflowYaml).spec.templates
|
||||
: (workflowYaml as CronWorkflowYaml).spec.workflowSpec.templates
|
||||
).forEach((template: Template) => {
|
||||
if (template.inputs && template.inputs.artifacts) {
|
||||
template.inputs.artifacts.forEach((artifact: Artifact) => {
|
||||
const parsedEmbeddedYaml = YAML.parse(artifact.raw.data);
|
||||
if (parsedEmbeddedYaml.kind === 'ChaosEngine') {
|
||||
let engineNamespace: string = '';
|
||||
if (
|
||||
typeof parsedEmbeddedYaml.metadata.namespace === 'string'
|
||||
) {
|
||||
engineNamespace = (parsedEmbeddedYaml.metadata
|
||||
.namespace as string).substring(
|
||||
1,
|
||||
(parsedEmbeddedYaml.metadata.namespace as string).length -
|
||||
1
|
||||
);
|
||||
} else {
|
||||
engineNamespace = Object.keys(
|
||||
parsedEmbeddedYaml.metadata.namespace
|
||||
)[0];
|
||||
}
|
||||
if (validateWorkflowParameter(engineNamespace)) {
|
||||
engineNamespace = getWorkflowParameter(engineNamespace);
|
||||
parametersMap.forEach((parameterKeyValue: Parameter) => {
|
||||
if (parameterKeyValue.name === engineNamespace) {
|
||||
engineNamespace = parameterKeyValue.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
engineNamespace = parsedEmbeddedYaml.metadata.namespace;
|
||||
}
|
||||
let matchIndex: number = -1;
|
||||
const check: number = chaosResultNamesAndNamespacesMap.filter(
|
||||
(data, index) => {
|
||||
if (
|
||||
data.resultName.includes(
|
||||
parsedEmbeddedYaml.metadata.name
|
||||
) &&
|
||||
data.resultNamespace === engineNamespace
|
||||
) {
|
||||
matchIndex = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
).length;
|
||||
if (check === 0) {
|
||||
chaosResultNamesAndNamespacesMap.push({
|
||||
resultName: `${parsedEmbeddedYaml.metadata.name}-${parsedEmbeddedYaml.spec.experiments[0].name}`,
|
||||
resultNamespace: engineNamespace,
|
||||
workflowName: workflowYaml.metadata.name,
|
||||
experimentName:
|
||||
parsedEmbeddedYaml.spec.experiments[0].name,
|
||||
});
|
||||
} else {
|
||||
chaosResultNamesAndNamespacesMap[
|
||||
matchIndex
|
||||
].workflowName = `${chaosResultNamesAndNamespacesMap[matchIndex].workflowName}, \n${workflowYaml.metadata.name}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const isChaosQueryPresent: number[] = Array(
|
||||
chaosResultNamesAndNamespacesMap.length
|
||||
).fill(0);
|
||||
|
||||
data.panel_groups[0].panels[0].prom_queries.forEach(
|
||||
(existingPromQuery: PromQuery) => {
|
||||
if (
|
||||
existingPromQuery.prom_query_name.startsWith(
|
||||
'litmuschaos_awaited_experiments'
|
||||
)
|
||||
) {
|
||||
const chaosDetails: ChaosResultNamesAndNamespacesMap = getEngineNameAndNamespace(
|
||||
existingPromQuery.prom_query_name
|
||||
);
|
||||
chaosResultNamesAndNamespacesMap.forEach(
|
||||
(
|
||||
chaosDetailsFomSchedule: ChaosResultNamesAndNamespacesMap,
|
||||
index: number
|
||||
) => {
|
||||
if (
|
||||
chaosDetailsFomSchedule.resultName.includes(
|
||||
chaosDetails.resultName
|
||||
) &&
|
||||
chaosDetailsFomSchedule.resultNamespace ===
|
||||
chaosDetails.resultNamespace
|
||||
) {
|
||||
isChaosQueryPresent[index] = 1;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const dashboardTemplateID: number =
|
||||
data.db_type === 'Kubernetes Platform' ? 0 : 1;
|
||||
|
||||
const updatedPanelGroups: PanelGroup[] = [];
|
||||
|
||||
data.panel_groups.forEach((panelGroup: PanelGroupResponse) => {
|
||||
const updatedPanels: Panel[] = [];
|
||||
panelGroup.panels.forEach((panel: PanelResponse) => {
|
||||
const updatedQueries: PromQuery[] = [];
|
||||
panel.prom_queries.forEach((query: PromQuery) => {
|
||||
let updatedLegend: string = query.legend;
|
||||
if (
|
||||
query.prom_query_name.startsWith('litmuschaos_awaited_experiments')
|
||||
) {
|
||||
const chaosDetails: ChaosResultNamesAndNamespacesMap = getEngineNameAndNamespace(
|
||||
query.prom_query_name
|
||||
);
|
||||
chaosResultNamesAndNamespacesMap.forEach(
|
||||
(chaosDetailsFomSchedule: ChaosResultNamesAndNamespacesMap) => {
|
||||
if (
|
||||
chaosDetailsFomSchedule.resultName.includes(
|
||||
chaosDetails.resultName
|
||||
) &&
|
||||
chaosDetailsFomSchedule.resultNamespace ===
|
||||
chaosDetails.resultNamespace &&
|
||||
!query.legend.includes(chaosDetailsFomSchedule.workflowName)
|
||||
) {
|
||||
updatedLegend = `${chaosDetailsFomSchedule.workflowName}, \n${query.legend}`;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
const updatedQuery: PromQuery = {
|
||||
queryid: query.queryid,
|
||||
prom_query_name: query.prom_query_name,
|
||||
resolution: query.resolution,
|
||||
minstep: query.minstep,
|
||||
line: query.line,
|
||||
close_area: query.close_area,
|
||||
legend: updatedLegend,
|
||||
};
|
||||
updatedQueries.push(updatedQuery);
|
||||
});
|
||||
chaosResultNamesAndNamespacesMap.forEach(
|
||||
(keyValue: ChaosResultNamesAndNamespacesMap, index: number) => {
|
||||
if (isChaosQueryPresent[index] === 0) {
|
||||
updatedQueries.push({
|
||||
queryid: uuidv4(),
|
||||
prom_query_name: generateChaosQuery(
|
||||
DashboardTemplatesList[dashboardTemplateID]
|
||||
.chaosEventQueryTemplate,
|
||||
keyValue.resultName,
|
||||
keyValue.resultNamespace
|
||||
),
|
||||
legend: `${keyValue.workflowName} / \n${keyValue.experimentName}`,
|
||||
resolution: '1/1',
|
||||
minstep: '1',
|
||||
line: false,
|
||||
close_area: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
const existingPanelOptions: PanelOption = {
|
||||
points: panel.panel_options.points,
|
||||
grids: panel.panel_options.grids,
|
||||
left_axis: panel.panel_options.left_axis,
|
||||
};
|
||||
const updatedPanel: Panel = {
|
||||
panel_id: panel.panel_id,
|
||||
panel_name: panel.panel_name,
|
||||
panel_options: existingPanelOptions,
|
||||
prom_queries: updatedQueries,
|
||||
y_axis_left: panel.y_axis_left,
|
||||
y_axis_right: panel.y_axis_right,
|
||||
x_axis_down: panel.x_axis_down,
|
||||
unit: panel.unit,
|
||||
};
|
||||
updatedPanels.push(updatedPanel);
|
||||
});
|
||||
updatedPanelGroups.push({
|
||||
panel_group_id: panelGroup.panel_group_id,
|
||||
panel_group_name: panelGroup.panel_group_name,
|
||||
panels: updatedPanels,
|
||||
});
|
||||
});
|
||||
|
||||
const panelInputData: Panel[] = [];
|
||||
|
||||
updatedPanelGroups.forEach((panelGroup: PanelGroup) => {
|
||||
panelGroup.panels.forEach((panel: Panel) => {
|
||||
panelInputData.push({
|
||||
panel_id: panel.panel_id,
|
||||
db_id: data.db_id,
|
||||
panel_group_id: panelGroup.panel_group_id,
|
||||
prom_queries: panel.prom_queries,
|
||||
panel_options: panel.panel_options,
|
||||
panel_name: panel.panel_name,
|
||||
y_axis_left: panel.y_axis_left,
|
||||
y_axis_right: panel.y_axis_right,
|
||||
x_axis_down: panel.x_axis_down,
|
||||
unit: panel.unit,
|
||||
});
|
||||
});
|
||||
});
|
||||
updatePanel({
|
||||
variables: { panelInput: panelInputData },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
//
|
||||
const onDashboardLoadRoutine = async (data: ListDashboardResponse) => {
|
||||
dashboard.selectDashboard({
|
||||
selectedDashboardID: data.db_id,
|
||||
|
@ -334,9 +44,16 @@ const TableDashboardData: React.FC<TableDashboardData> = ({
|
|||
selectedDashboardTemplateName: data.db_type,
|
||||
selectedAgentID: data.cluster_id,
|
||||
selectedAgentName: data.cluster_name,
|
||||
refreshRate: 0,
|
||||
});
|
||||
return Promise.resolve(reSyncChaosQueries(data));
|
||||
dataSource.selectDataSource({
|
||||
selectedDataSourceURL: '',
|
||||
selectedDataSourceID: '',
|
||||
selectedDataSourceName: '',
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{dashboardDataList && dashboardDataList.length > 0 ? (
|
||||
|
@ -391,11 +108,6 @@ const TableDashboardData: React.FC<TableDashboardData> = ({
|
|||
disableFocusRipple
|
||||
onClick={() => {
|
||||
onDashboardLoadRoutine(dashboard).then(() => {
|
||||
dataSource.selectDataSource({
|
||||
selectedDataSourceURL: '',
|
||||
selectedDataSourceID: '',
|
||||
selectedDataSourceName: '',
|
||||
});
|
||||
history.push({
|
||||
pathname: '/analytics/dashboard',
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScheduleWorkflow } from '../../../../models/graphql/scheduleData';
|
||||
import { Workflow } from '../../../../models/graphql/workflowListData';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as TabActions from '../../../../redux/actions/tabs';
|
||||
import { history } from '../../../../redux/configureStore';
|
||||
|
@ -21,7 +21,7 @@ import { GetTimeDiff } from '../../../../utils/timeDifferenceString';
|
|||
import useStyles from '../styles';
|
||||
|
||||
interface TableScheduleWorkflow {
|
||||
scheduleWorkflowList: ScheduleWorkflow[] | undefined;
|
||||
scheduleWorkflowList: Workflow[] | undefined;
|
||||
}
|
||||
|
||||
const TableScheduleWorkflow: React.FC<TableScheduleWorkflow> = ({
|
||||
|
|
|
@ -10,7 +10,7 @@ import { LocalQuickActionCard } from '../../../components/LocalQuickActionCard';
|
|||
import {
|
||||
LIST_DASHBOARD,
|
||||
LIST_DATASOURCE,
|
||||
SCHEDULE_DETAILS,
|
||||
WORKFLOW_LIST_DETAILS,
|
||||
} from '../../../graphql/queries';
|
||||
import {
|
||||
DashboardList,
|
||||
|
@ -23,10 +23,10 @@ import {
|
|||
ListDataSourceVars,
|
||||
} from '../../../models/graphql/dataSourceDetails';
|
||||
import {
|
||||
ScheduleDataVars,
|
||||
Schedules,
|
||||
ScheduleWorkflow,
|
||||
} from '../../../models/graphql/scheduleData';
|
||||
Workflow,
|
||||
WorkflowList,
|
||||
WorkflowListDataVars,
|
||||
} from '../../../models/graphql/workflowListData';
|
||||
import { getProjectID } from '../../../utils/getSearchParams';
|
||||
import { sortNumAsc } from '../../../utils/sort';
|
||||
import { OverviewConfigureBanner } from './OverviewConfigureBanner';
|
||||
|
@ -41,25 +41,26 @@ const Overview: React.FC = () => {
|
|||
const classes = useStyles();
|
||||
const projectID = getProjectID();
|
||||
|
||||
// Apollo query to get the scheduled data
|
||||
const { data: schedulesData } = useQuery<Schedules, ScheduleDataVars>(
|
||||
SCHEDULE_DETAILS,
|
||||
// Apollo query to get the scheduled workflow data
|
||||
const { data: schedulesData } = useQuery<WorkflowList, WorkflowListDataVars>(
|
||||
WORKFLOW_LIST_DETAILS,
|
||||
{
|
||||
variables: {
|
||||
projectID,
|
||||
workflowIDs: [],
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
pollInterval: 10000,
|
||||
}
|
||||
);
|
||||
|
||||
const filteredScheduleData = schedulesData?.getScheduledWorkflows
|
||||
.slice()
|
||||
.sort((a: ScheduleWorkflow, b: ScheduleWorkflow) => {
|
||||
const filteredScheduleData = schedulesData?.ListWorkflow.slice().sort(
|
||||
(a: Workflow, b: Workflow) => {
|
||||
const x = parseInt(a.updated_at, 10);
|
||||
const y = parseInt(b.updated_at, 10);
|
||||
return sortNumAsc(y, x);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Apollo query to get the dashboard data
|
||||
const { data: dashboardsList } = useQuery<DashboardList, ListDashboardVars>(
|
||||
|
|
|
@ -56,7 +56,8 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
background: 'transparent',
|
||||
},
|
||||
banner: {
|
||||
height: '18rem',
|
||||
height: 'fit-content',
|
||||
minHeight: '18rem',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue