Skip to content

Commit c80a0b1

Browse files
authored
Merge pull request #20 from newrelic/drilldown_tag_filter
fix: global tags filter for alerts/entity coverage tabs
2 parents 6aa4e21 + 43d42a4 commit c80a0b1

File tree

10 files changed

+550
-625
lines changed

10 files changed

+550
-625
lines changed

nerdlets/drilldown/entities.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import React, { useEffect, useState } from 'react';
22
import PropTypes from 'prop-types';
3-
import { getCardColor, getEntities, getTooltip } from '../shared/utils';
3+
import {
4+
filtersArrayToNrql,
5+
getCardColor,
6+
getEntities,
7+
getTooltip,
8+
} from '../shared/utils';
49
import ExportButton from '../shared/export';
510
import { Progress } from 'semantic-ui-react';
611
import {
@@ -13,15 +18,18 @@ import {
1318
Spinner,
1419
} from 'nr1';
1520

16-
const Entities = ({ selectedAccount }) => {
21+
const Entities = ({ selectedAccount, filters }) => {
1722
const [loading, setLoading] = useState(true);
1823
const [entities, setEntities] = useState(null);
1924
const [searchText, setSearchText] = useState('');
2025

2126
useEffect(() => {
2227
const fetchAndSetEntities = async () => {
2328
setLoading(true);
24-
const data = await getEntities(selectedAccount, null);
29+
const filterClause =
30+
filters && filters !== '' ? filtersArrayToNrql(filters) : '';
31+
32+
const data = await getEntities(selectedAccount, filterClause, null);
2533

2634
if (data == null) {
2735
setLoading(false);
@@ -43,7 +51,7 @@ const Entities = ({ selectedAccount }) => {
4351
};
4452

4553
fetchAndSetEntities();
46-
}, [selectedAccount]);
54+
}, [selectedAccount, filters]);
4755

4856
if (loading) {
4957
return (
@@ -114,6 +122,7 @@ const Entities = ({ selectedAccount }) => {
114122

115123
Entities.propTypes = {
116124
selectedAccount: PropTypes.object,
125+
filters: PropTypes.array,
117126
};
118127

119128
export default Entities;

nerdlets/drilldown/incidents.js

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import React, { useContext, useEffect, useMemo, useState } from 'react';
22
import PropTypes from 'prop-types';
3-
import { getIncidents, getCardColor, getTooltip } from '../shared/utils';
3+
import {
4+
getIncidents,
5+
getCardColor,
6+
getTooltip,
7+
filtersArrayToNrql,
8+
} from '../shared/utils';
49
import ExportButton from '../shared/export';
510
import { Card, Icon, Statistic } from 'semantic-ui-react';
611
import {
@@ -16,14 +21,28 @@ import {
1621
Tooltip,
1722
} from 'nr1';
1823

19-
const openQWindow = (type, policy, condition, timeRange, selectedAccount) => {
24+
const openQWindow = (
25+
type,
26+
policy,
27+
condition,
28+
timeRange,
29+
filters,
30+
selectedAccount
31+
) => {
2032
const timeClause = `SINCE ${timeRange.duration / 60000} minutes ago`;
33+
const filterClause =
34+
filters && filters !== '' ? filtersArrayToNrql(filters) : '';
35+
2136
let q = ``;
2237

2338
if (type == 'short') {
24-
q = `FROM NrAiIncident SELECT count(*) where event = 'close' and durationSeconds <= 300 and policyName = '${policy}' and conditionName = '${condition}' ${timeClause} TIMESERIES MAX`;
39+
q = `FROM NrAiIncident SELECT count(*) where ${
40+
filterClause !== '' ? `${filterClause} and` : ''
41+
} event = 'close' and durationSeconds <= 300 and policyName = '${policy}' and conditionName = '${condition}' ${timeClause} TIMESERIES MAX`;
2542
} else {
26-
q = `FROM NrAiIncident SELECT count(*) where event = 'close' and durationSeconds >= 86400 and policyName = '${policy}' and conditionName = '${condition}' ${timeClause} TIMESERIES MAX`;
43+
q = `FROM NrAiIncident SELECT count(*) where ${
44+
filterClause !== '' ? `${filterClause} and` : ''
45+
} event = 'close' and durationSeconds >= 86400 and policyName = '${policy}' and conditionName = '${condition}' ${timeClause} TIMESERIES MAX`;
2746
}
2847

2948
const qBuilder = {
@@ -44,7 +63,7 @@ const openQWindow = (type, policy, condition, timeRange, selectedAccount) => {
4463
navigation.openStackedNerdlet(qBuilder);
4564
};
4665

47-
const Incidents = ({ selectedAccount }) => {
66+
const Incidents = ({ selectedAccount, filters }) => {
4867
const { timeRange } = useContext(PlatformStateContext);
4968
const [incidents, setIncidents] = useState(null);
5069
const [loading, setLoading] = useState(true);
@@ -54,15 +73,21 @@ const Incidents = ({ selectedAccount }) => {
5473
useEffect(() => {
5574
const fetchAndSetIncidents = async () => {
5675
setLoading(true);
76+
const filterClause =
77+
filters && filters !== '' ? filtersArrayToNrql(filters) : '';
5778
const timeClause = `SINCE ${timeRange.duration / 60000} minutes ago`;
58-
const data = await getIncidents(selectedAccount, timeClause);
79+
const data = await getIncidents(
80+
selectedAccount,
81+
filterClause,
82+
timeClause
83+
);
5984

6085
setIncidents(data);
6186
setLoading(false);
6287
};
6388

6489
fetchAndSetIncidents();
65-
}, [selectedAccount, timeRange]);
90+
}, [selectedAccount, timeRange, filters]);
6691

6792
const renderShortIncidents = useMemo(() => {
6893
if (
@@ -112,6 +137,7 @@ const Incidents = ({ selectedAccount }) => {
112137
item.facet[0],
113138
item.facet[1],
114139
timeRange,
140+
filters,
115141
selectedAccount
116142
)
117143
}
@@ -182,6 +208,7 @@ const Incidents = ({ selectedAccount }) => {
182208
item.facet[0],
183209
item.facet[1],
184210
timeRange,
211+
filters,
185212
selectedAccount
186213
)
187214
}
@@ -290,6 +317,7 @@ const Incidents = ({ selectedAccount }) => {
290317

291318
Incidents.propTypes = {
292319
selectedAccount: PropTypes.object,
320+
filters: PropTypes.array,
293321
};
294322

295323
export default Incidents;

nerdlets/home/drilldown.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import ConditionDrilldown from '../drilldown/condition-drilldown';
77
import Entities from '../drilldown/entities';
88
import CcuOptimization from '../drilldown/ccu';
99

10-
const Drilldown = ({ account }) => {
10+
const Drilldown = ({ account, filters, activeIndex, onTabChange }) => {
1111
const panes = [
1212
{
1313
menuItem: 'Alerts',
1414
render: () => (
1515
<Tab.Pane>
16-
<Incidents selectedAccount={account} />
16+
<Incidents selectedAccount={account} filters={filters} />
1717
</Tab.Pane>
1818
),
1919
},
@@ -29,7 +29,7 @@ const Drilldown = ({ account }) => {
2929
menuItem: 'Entity Coverage',
3030
render: () => (
3131
<Tab.Pane>
32-
<Entities selectedAccount={account} />
32+
<Entities selectedAccount={account} filters={filters} />
3333
</Tab.Pane>
3434
),
3535
},
@@ -51,11 +51,16 @@ const Drilldown = ({ account }) => {
5151
},
5252
];
5353

54-
return <Tab panes={panes} />;
54+
return (
55+
<Tab panes={panes} activeIndex={activeIndex} onTabChange={onTabChange} />
56+
);
5557
};
5658

5759
Drilldown.propTypes = {
5860
account: PropTypes.object,
61+
filters: PropTypes.array,
62+
activeIndex: PropTypes.number.isRequired,
63+
onTabChange: PropTypes.func.isRequired,
5964
};
6065

6166
export default Drilldown;

nerdlets/home/filter.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React, { useState, useEffect, useRef, useCallback } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { FilterBar } from '@newrelic/nr-labs-components';
4+
import { Spinner } from 'nr1';
5+
import { getFilterKeys, getFilterValues } from '../shared/utils';
6+
7+
// custom hook to dynamically fetch/cache values based on key selected
8+
const useFetchValues = (accountId) => {
9+
const [cache, setCache] = useState({});
10+
11+
const fetchValues = useCallback(
12+
async (key) => {
13+
if (cache[key]) return cache[key];
14+
15+
const data = await getFilterValues(key, accountId);
16+
setCache((prev) => ({ ...prev, [key]: data }));
17+
return data;
18+
},
19+
[cache]
20+
);
21+
22+
return fetchValues;
23+
};
24+
25+
const Filter = ({ account, selections, setSelections, isDisabled }) => {
26+
const [filterOptions, setFilterOptions] = useState();
27+
const [isLoadingKeys, setIsLoadingKeys] = useState(true);
28+
const valueFetcher = useFetchValues(account);
29+
const lastSelectionRef = useRef([]);
30+
31+
useEffect(() => {
32+
const fetchFilterKeys = async () => {
33+
setIsLoadingKeys(true);
34+
try {
35+
const optionsWithKeys = await getFilterKeys(account);
36+
setFilterOptions(optionsWithKeys);
37+
} catch (err) {
38+
console.error('Failed to fetch filter keys: ', err);
39+
} finally {
40+
setIsLoadingKeys(false);
41+
}
42+
};
43+
44+
fetchFilterKeys();
45+
}, [account]);
46+
47+
const triggerFetchValues = useCallback(
48+
async (key) => {
49+
const filterOption = filterOptions.find((opt) => opt.option === key);
50+
51+
if (
52+
!filterOption ||
53+
filterOption.values.length > 0 ||
54+
filterOption.isLoading
55+
) {
56+
return;
57+
}
58+
59+
setFilterOptions((currentOptions) =>
60+
currentOptions.map((opt) =>
61+
opt.option === key
62+
? {
63+
...opt,
64+
isLoading: true,
65+
values: [{ value: 'Loading...', id: 'loading' }],
66+
}
67+
: opt
68+
)
69+
);
70+
71+
const newValues = await valueFetcher(key);
72+
setFilterOptions((currentOptions) =>
73+
currentOptions.map((opt) =>
74+
opt.option === key
75+
? { ...opt, isLoading: false, values: newValues }
76+
: opt
77+
)
78+
);
79+
},
80+
[filterOptions, valueFetcher]
81+
);
82+
83+
const handleFilterChange = (newSelections) => {
84+
for (const newSelection of newSelections) {
85+
const oldSelection = lastSelectionRef.current.find(
86+
(c) => c.id === newSelection.id
87+
);
88+
if (newSelection.key && (!oldSelection || !oldSelection.key)) {
89+
triggerFetchValues(newSelection.key.value);
90+
}
91+
}
92+
93+
setSelections(newSelections);
94+
lastSelectionRef.current = newSelections;
95+
};
96+
97+
if (isLoadingKeys) return <Spinner />;
98+
99+
return (
100+
<div className={`filters ${isDisabled ? 'disabled' : ''}`}>
101+
<FilterBar
102+
options={filterOptions}
103+
onChange={handleFilterChange}
104+
defaultSelections={selections}
105+
isDisabled={isDisabled}
106+
/>
107+
</div>
108+
);
109+
};
110+
111+
Filter.propTypes = {
112+
account: PropTypes.number,
113+
selections: PropTypes.array,
114+
setSelections: PropTypes.func,
115+
isDisabled: PropTypes.string,
116+
};
117+
118+
export default Filter;

0 commit comments

Comments
 (0)