Skip to content

Commit 8e053fa

Browse files
authored
Merge pull request #509 from newrelic/update-workloads-status
fix: multiple workload issues in sidebar
2 parents a67cecc + 82273b9 commit 8e053fa

File tree

3 files changed

+194
-121
lines changed

3 files changed

+194
-121
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import React, { useEffect, useState } from 'react';
22
import PropTypes from 'prop-types';
33

4-
import { Button, Card, CardBody, HeadingText, Link, SectionMessage } from 'nr1';
4+
import {
5+
Button,
6+
Card,
7+
CardBody,
8+
HeadingText,
9+
Link,
10+
SectionMessage,
11+
Spinner,
12+
} from 'nr1';
513

614
import {
715
durationStringForViolation,
@@ -11,7 +19,7 @@ import {
1119
} from '../../utils';
1220
import { SIGNAL_TYPES, UI_CONTENT } from '../../constants';
1321

14-
const Incidents = ({ type, data, timeWindow }) => {
22+
const Incidents = ({ type, data, timeWindow, isLoading }) => {
1523
const [bannerMessage, setBannerMessage] = useState('');
1624
const [incidentsList, setIncidentsList] = useState([]);
1725
const [maxIncidentsShown, setMaxIncidentsShown] = useState(1);
@@ -44,83 +52,90 @@ const Incidents = ({ type, data, timeWindow }) => {
4452
>
4553
Open Incidents
4654
</HeadingText>
47-
<div className="alert-incidents">
48-
{bannerMessage && <SectionMessage description={bannerMessage} />}
49-
{data?.type === 'SERVICE_LEVEL' &&
50-
data?.alertSeverity !== 'NOT_ALERTING' &&
51-
!incidentsList?.length ? (
52-
<SectionMessage
53-
type={SectionMessage.TYPE.WARNING}
54-
description={UI_CONTENT.SIGNAL.DETAILS.ALERTING_SL_NO_INCIDENT}
55-
/>
56-
) : null}
57-
{incidentsList.reduce(
58-
(
59-
acc,
60-
{ id, name, curStatus, state, classname, opened, closed, link },
61-
i
62-
) =>
63-
i < maxIncidentsShown
64-
? [
65-
...acc,
66-
<div key={id} className="alert-incident">
67-
<Card>
68-
<CardBody className="incident-card-body">
69-
<div className="incident-header">
70-
<div className={`square ${classname}`}></div>
71-
<div className={`signal-status ${classname}`}>
72-
<span>
73-
<span className="priority">{curStatus}</span>
74-
{' Issue '}
75-
<span className="event">{state}</span>
76-
</span>
77-
</div>
78-
</div>
79-
<HeadingText type={HeadingText.TYPE.HEADING_5}>
80-
{name}
81-
</HeadingText>
82-
<div className="incident-links">
83-
<Link
84-
className="detail-link"
85-
to={link}
86-
onClick={(e) =>
87-
e.target.setAttribute('target', '_blank')
88-
}
89-
>
90-
View issue
91-
</Link>
92-
</div>
93-
<div>Started: {formatTimestamp(opened)}</div>
94-
<div>
95-
Duration: {durationStringForViolation(closed, opened)}
96-
</div>
97-
{closed ? (
98-
<div>Closed: {formatTimestamp(closed)}</div>
99-
) : null}
100-
</CardBody>
101-
</Card>
102-
</div>,
103-
]
104-
: acc,
105-
[]
106-
)}
107-
</div>
108-
{type === !shouldShowAllIncidents && incidentsList?.length > 1 ? (
109-
<div className="incidents-footer">
110-
<Button
111-
variant={Button.VARIANT.SECONDARY}
112-
onClick={() => {
113-
setMaxIncidentsShown((mis) =>
114-
mis === 1 ? incidentsList.length : 1
115-
);
116-
}}
117-
>
118-
{maxIncidentsShown === 1
119-
? `Show ${incidentsList.length - 1} more incidents`
120-
: 'Show less incidents'}
121-
</Button>
122-
</div>
123-
) : null}
55+
{isLoading ? (
56+
<Spinner />
57+
) : (
58+
<>
59+
<div className="alert-incidents">
60+
{bannerMessage && <SectionMessage description={bannerMessage} />}
61+
{data?.type === 'SERVICE_LEVEL' &&
62+
data?.alertSeverity !== 'NOT_ALERTING' &&
63+
!incidentsList?.length ? (
64+
<SectionMessage
65+
type={SectionMessage.TYPE.WARNING}
66+
description={UI_CONTENT.SIGNAL.DETAILS.ALERTING_SL_NO_INCIDENT}
67+
/>
68+
) : null}
69+
{incidentsList.reduce(
70+
(
71+
acc,
72+
{ id, name, curStatus, state, classname, opened, closed, link },
73+
i
74+
) =>
75+
i < maxIncidentsShown
76+
? [
77+
...acc,
78+
<div key={id} className="alert-incident">
79+
<Card>
80+
<CardBody className="incident-card-body">
81+
<div className="incident-header">
82+
<div className={`square ${classname}`}></div>
83+
<div className={`signal-status ${classname}`}>
84+
<span>
85+
<span className="priority">{curStatus}</span>
86+
{' Issue '}
87+
<span className="event">{state}</span>
88+
</span>
89+
</div>
90+
</div>
91+
<HeadingText type={HeadingText.TYPE.HEADING_5}>
92+
{name}
93+
</HeadingText>
94+
<div className="incident-links">
95+
<Link
96+
className="detail-link"
97+
to={link}
98+
onClick={(e) =>
99+
e.target.setAttribute('target', '_blank')
100+
}
101+
>
102+
View issue
103+
</Link>
104+
</div>
105+
<div>Started: {formatTimestamp(opened)}</div>
106+
<div>
107+
Duration:{' '}
108+
{durationStringForViolation(closed, opened)}
109+
</div>
110+
{closed ? (
111+
<div>Closed: {formatTimestamp(closed)}</div>
112+
) : null}
113+
</CardBody>
114+
</Card>
115+
</div>,
116+
]
117+
: acc,
118+
[]
119+
)}
120+
</div>
121+
{type === !shouldShowAllIncidents && incidentsList?.length > 1 ? (
122+
<div className="incidents-footer">
123+
<Button
124+
variant={Button.VARIANT.SECONDARY}
125+
onClick={() => {
126+
setMaxIncidentsShown((mis) =>
127+
mis === 1 ? incidentsList.length : 1
128+
);
129+
}}
130+
>
131+
{maxIncidentsShown === 1
132+
? `Show ${incidentsList.length - 1} more incidents`
133+
: 'Show less incidents'}
134+
</Button>
135+
</div>
136+
) : null}
137+
</>
138+
)}
124139
</div>
125140
);
126141
};
@@ -129,6 +144,7 @@ Incidents.propTypes = {
129144
type: PropTypes.oneOf(Object.values(SIGNAL_TYPES)),
130145
data: PropTypes.object,
131146
timeWindow: PropTypes.object,
147+
isLoading: PropTypes.bool,
132148
};
133149

134150
export default Incidents;

src/components/signal-detail-sidebar/index.js

+59-42
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect, useRef, useState } from 'react';
1+
import React, { useContext, useEffect, useState } from 'react';
22
import PropTypes from 'prop-types';
33

44
import {
@@ -12,15 +12,17 @@ import {
1212
navigation,
1313
} from 'nr1';
1414

15-
import { SIGNAL_TYPES, STATUSES } from '../../constants';
16-
1715
import Incidents from './incidents';
1816
import GoldenMetrics from './golden-metrics';
1917
import { AppContext } from '../../contexts';
18+
import {
19+
workloadEntitiesQuery,
20+
workloadEntitiesViolationsFromGuidsArray,
21+
} from '../../queries';
22+
import { formatTimestamp, isWorkload } from '../../utils';
23+
import { MAX_PARAMS_IN_QUERY, SIGNAL_TYPES, STATUSES } from '../../constants';
2024

2125
import typesList from '../../../nerdlets/signal-selection/types.json';
22-
import { formatTimestamp, isWorkload } from '../../utils';
23-
import { statusesFromGuidsArray, workloadEntitiesQuery } from '../../queries';
2426

2527
const NO_ENTITY_TYPE = '(unknown entity type)';
2628

@@ -33,13 +35,27 @@ const entityTypeFromData = (entityData) => {
3335
);
3436
};
3537

38+
const isWorkloadNotOK = ({ statusValueCode, alertSeverity } = {}) => {
39+
const notOKStatusValueCodes = [2, 3];
40+
const notOKAlertSeveritiesRE = new RegExp(
41+
`${STATUSES.CRITICAL}|${STATUSES.WARNING}`,
42+
'i'
43+
);
44+
45+
return (
46+
notOKStatusValueCodes.includes(statusValueCode) ||
47+
notOKAlertSeveritiesRE.test(alertSeverity)
48+
);
49+
};
50+
3651
const SignalDetailSidebar = ({ guid, name, type, data, timeWindow }) => {
3752
const { account = {}, accounts = [] } = useContext(AppContext);
3853
const [hasAccessToEntity, setHasAccessToEntity] = useState(false);
3954
const [signalAccount, setSignalAccount] = useState();
4055
const [detailLinkText, setDetailLinkText] = useState('View entity details');
4156
const [incidentsData, setIncidentsData] = useState(null);
42-
const prevTimeWindow = useRef({});
57+
const [isLoadingWorkloadViolations, setIsLoadingWorkloadViolations] =
58+
useState(false);
4359

4460
useEffect(() => {
4561
const [acctId, condId] = ((atob(guid) || '').split('|') || []).reduce(
@@ -64,27 +80,19 @@ const SignalDetailSidebar = ({ guid, name, type, data, timeWindow }) => {
6480
}, [guid, type, account, accounts]);
6581

6682
useEffect(() => {
83+
if (!guid || !type) return;
84+
let curData = { ...data };
6785
if (
86+
!data ||
6887
type !== SIGNAL_TYPES.ENTITY ||
6988
!isWorkload(data) ||
70-
!(
71-
data.statusValueCode === 3 ||
72-
data.statusValueCode === 2 ||
73-
data.alertSeverity === STATUSES.CRITICAL ||
74-
data.alertSeverity === STATUSES.WARNING
75-
)
89+
!isWorkloadNotOK(data)
7690
) {
7791
setIncidentsData(data);
7892
return;
7993
}
8094

81-
let shouldClearData = false;
82-
const { start: prevStart, end: prevEnd } = prevTimeWindow.current || {};
83-
if (timeWindow.start !== prevStart || timeWindow.end !== prevEnd) {
84-
shouldClearData = true;
85-
prevTimeWindow.current = timeWindow;
86-
}
87-
95+
setIsLoadingWorkloadViolations(true);
8896
const loadWorkloadIncidents = async (g) => {
8997
const {
9098
data: {
@@ -101,15 +109,27 @@ const SignalDetailSidebar = ({ guid, name, type, data, timeWindow }) => {
101109
console.error('Error listing workload entities', error);
102110
return;
103111
}
104-
const workloadEntitiesGuids = results?.reduce(
105-
(acc, { target: { guid: g } = {} }) => (g ? [...acc, g] : acc),
106-
[]
107-
);
112+
let workloadEntitiesGuids = [];
113+
for (let i = 0; i < results?.length; i += MAX_PARAMS_IN_QUERY) {
114+
const guidsArr = results
115+
.slice(i, i + MAX_PARAMS_IN_QUERY)
116+
.map(({ target: { guid: g } = {} }) => g)
117+
.filter((g) => !!g);
118+
workloadEntitiesGuids = [...workloadEntitiesGuids, guidsArr];
119+
}
120+
if (!workloadEntitiesGuids.length) {
121+
console.error('Unable to fetch workload entities');
122+
setIsLoadingWorkloadViolations(false);
123+
return;
124+
}
108125
const {
109126
data: { actor: { __typename, ...entitiesObj } = {} } = {}, // eslint-disable-line no-unused-vars
110127
error: error2,
111128
} = await NerdGraphQuery.query({
112-
query: statusesFromGuidsArray([workloadEntitiesGuids], timeWindow),
129+
query: workloadEntitiesViolationsFromGuidsArray(
130+
workloadEntitiesGuids,
131+
timeWindow
132+
),
113133
});
114134
if (error2) {
115135
console.error('Error loading workload entities', error2);
@@ -127,26 +147,18 @@ const SignalDetailSidebar = ({ guid, name, type, data, timeWindow }) => {
127147
}
128148
const violations = entity?.[violationsKey] || [];
129149
if (violations.length) {
130-
setIncidentsData((d) =>
131-
d && !shouldClearData
132-
? {
133-
...d,
134-
[violationsKey]: [
135-
...(d[violationsKey] || []),
136-
...violations,
137-
],
138-
}
139-
: {
140-
...data,
141-
[violationsKey]: [
142-
...(data[violationsKey] || []),
143-
...violations,
144-
],
145-
}
146-
);
150+
curData = {
151+
...curData,
152+
[violationsKey]: [
153+
...(curData[violationsKey] || []),
154+
...violations,
155+
],
156+
};
147157
}
148158
});
149159
});
160+
setIncidentsData(curData);
161+
setIsLoadingWorkloadViolations(false);
150162
};
151163
loadWorkloadIncidents(guid);
152164
}, [guid, type, data, timeWindow]);
@@ -190,7 +202,12 @@ const SignalDetailSidebar = ({ guid, name, type, data, timeWindow }) => {
190202
</div>
191203
{hasAccessToEntity ? (
192204
<>
193-
<Incidents type={type} data={incidentsData} timeWindow={timeWindow} />
205+
<Incidents
206+
type={type}
207+
data={incidentsData}
208+
timeWindow={timeWindow}
209+
isLoading={isLoadingWorkloadViolations}
210+
/>
194211
{type === SIGNAL_TYPES.ENTITY ? (
195212
<GoldenMetrics guid={guid} data={data} timeWindow={timeWindow} />
196213
) : null}

0 commit comments

Comments
 (0)