@backstage-community/plugin-splunk-on-call 0.4.24 → 0.4.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/api/client.esm.js +114 -0
  3. package/dist/api/client.esm.js.map +1 -0
  4. package/dist/components/EntitySplunkOnCallCard.esm.js +189 -0
  5. package/dist/components/EntitySplunkOnCallCard.esm.js.map +1 -0
  6. package/dist/components/Errors/MissingApiKeyOrApiIdError.esm.js +24 -0
  7. package/dist/components/Errors/MissingApiKeyOrApiIdError.esm.js.map +1 -0
  8. package/dist/components/Escalation/EscalationPolicy.esm.js +65 -0
  9. package/dist/components/Escalation/EscalationPolicy.esm.js.map +1 -0
  10. package/dist/components/Escalation/EscalationUser.esm.js +30 -0
  11. package/dist/components/Escalation/EscalationUser.esm.js.map +1 -0
  12. package/dist/components/Escalation/EscalationUsersEmptyState.esm.js +23 -0
  13. package/dist/components/Escalation/EscalationUsersEmptyState.esm.js.map +1 -0
  14. package/dist/components/Incident/IncidentEmptyState.esm.js +28 -0
  15. package/dist/components/Incident/IncidentEmptyState.esm.js.map +1 -0
  16. package/dist/components/Incident/IncidentListItem.esm.js +189 -0
  17. package/dist/components/Incident/IncidentListItem.esm.js.map +1 -0
  18. package/dist/components/Incident/Incidents.esm.js +72 -0
  19. package/dist/components/Incident/Incidents.esm.js.map +1 -0
  20. package/dist/components/SplunkOnCallPage.esm.js +24 -0
  21. package/dist/components/SplunkOnCallPage.esm.js.map +1 -0
  22. package/dist/components/TriggerDialog/TriggerDialog.esm.js +205 -0
  23. package/dist/components/TriggerDialog/TriggerDialog.esm.js.map +1 -0
  24. package/dist/index.esm.js +3 -43
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/plugin.esm.js +37 -0
  27. package/dist/plugin.esm.js.map +1 -0
  28. package/package.json +14 -10
  29. package/dist/esm/SplunkOnCallPage-D9lfarwB.esm.js +0 -62
  30. package/dist/esm/SplunkOnCallPage-D9lfarwB.esm.js.map +0 -1
  31. package/dist/esm/index-D8KSFXg5.esm.js +0 -912
  32. package/dist/esm/index-D8KSFXg5.esm.js.map +0 -1
@@ -1,912 +0,0 @@
1
- import { createApiRef, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, configApiRef, createRoutableExtension, createComponentExtension, useApi, alertApiRef } from '@backstage/core-plugin-api';
2
- import React, { useEffect, useState, useCallback } from 'react';
3
- import useAsync from 'react-use/esm/useAsync';
4
- import { MissingAnnotationEmptyState, useEntity } from '@backstage/plugin-catalog-react';
5
- import Card from '@material-ui/core/Card';
6
- import CardContent from '@material-ui/core/CardContent';
7
- import CardHeader from '@material-ui/core/CardHeader';
8
- import Divider from '@material-ui/core/Divider';
9
- import Typography from '@material-ui/core/Typography';
10
- import { makeStyles, createStyles } from '@material-ui/core/styles';
11
- import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
12
- import WebIcon from '@material-ui/icons/Web';
13
- import Alert from '@material-ui/lab/Alert';
14
- import Button from '@material-ui/core/Button';
15
- import { EmptyState, StatusWarning, Progress, StatusOK, StatusError, HeaderIconLinkRow } from '@backstage/core-components';
16
- import List from '@material-ui/core/List';
17
- import ListSubheader from '@material-ui/core/ListSubheader';
18
- import ListItem from '@material-ui/core/ListItem';
19
- import ListItemIcon from '@material-ui/core/ListItemIcon';
20
- import ListItemText from '@material-ui/core/ListItemText';
21
- import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
22
- import Tooltip from '@material-ui/core/Tooltip';
23
- import IconButton from '@material-ui/core/IconButton';
24
- import Avatar from '@material-ui/core/Avatar';
25
- import EmailIcon from '@material-ui/icons/Email';
26
- import DoneIcon from '@material-ui/icons/Done';
27
- import DoneAllIcon from '@material-ui/icons/DoneAll';
28
- import { DateTime, Duration } from 'luxon';
29
- import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
30
- import useAsyncFn from 'react-use/esm/useAsyncFn';
31
- import Grid from '@material-ui/core/Grid';
32
- import EmptyStateImage from '../assets/emptystate.svg';
33
- import Dialog from '@material-ui/core/Dialog';
34
- import DialogTitle from '@material-ui/core/DialogTitle';
35
- import TextField from '@material-ui/core/TextField';
36
- import DialogActions from '@material-ui/core/DialogActions';
37
- import DialogContent from '@material-ui/core/DialogContent';
38
- import CircularProgress from '@material-ui/core/CircularProgress';
39
- import Select from '@material-ui/core/Select';
40
- import MenuItem from '@material-ui/core/MenuItem';
41
- import FormControl from '@material-ui/core/FormControl';
42
- import InputLabel from '@material-ui/core/InputLabel';
43
-
44
- class UnauthorizedError extends Error {
45
- }
46
- const splunkOnCallApiRef = createApiRef({
47
- id: "plugin.splunk-on-call.api"
48
- });
49
- class SplunkOnCallClient {
50
- constructor(config) {
51
- this.config = config;
52
- }
53
- static fromConfig(configApi, discoveryApi) {
54
- const eventsRestEndpoint = configApi.getOptionalString("splunkOnCall.eventsRestEndpoint") || null;
55
- return new SplunkOnCallClient({
56
- eventsRestEndpoint,
57
- discoveryApi
58
- });
59
- }
60
- async getIncidents() {
61
- const url = `${await this.config.discoveryApi.getBaseUrl(
62
- "proxy"
63
- )}/splunk-on-call/v1/incidents`;
64
- const { incidents } = await this.findByUrl(url);
65
- return incidents;
66
- }
67
- async getOnCallUsers() {
68
- const url = `${await this.config.discoveryApi.getBaseUrl(
69
- "proxy"
70
- )}/splunk-on-call/v1/oncall/current`;
71
- const { teamsOnCall } = await this.findByUrl(url);
72
- return teamsOnCall;
73
- }
74
- async getTeams() {
75
- const url = `${await this.config.discoveryApi.getBaseUrl(
76
- "proxy"
77
- )}/splunk-on-call/v1/team`;
78
- const teams = await this.findByUrl(url);
79
- return teams;
80
- }
81
- async getRoutingKeys() {
82
- const url = `${await this.config.discoveryApi.getBaseUrl(
83
- "proxy"
84
- )}/splunk-on-call/v1/org/routing-keys`;
85
- const { routingKeys } = await this.findByUrl(url);
86
- return routingKeys;
87
- }
88
- async getUsers() {
89
- const url = `${await this.config.discoveryApi.getBaseUrl(
90
- "proxy"
91
- )}/splunk-on-call/v2/user`;
92
- const { users } = await this.findByUrl(url);
93
- return users;
94
- }
95
- async getEscalationPolicies() {
96
- const url = `${await this.config.discoveryApi.getBaseUrl(
97
- "proxy"
98
- )}/splunk-on-call/v1/policies`;
99
- const { policies } = await this.findByUrl(url);
100
- return policies;
101
- }
102
- async incidentAction(options) {
103
- const {
104
- routingKey,
105
- incidentType,
106
- incidentId,
107
- incidentDisplayName,
108
- incidentMessage,
109
- incidentStartTime
110
- } = options;
111
- const body = JSON.stringify({
112
- message_type: incidentType,
113
- ...incidentId ? { entity_id: incidentId } : {},
114
- ...incidentDisplayName ? { entity_display_name: incidentDisplayName } : {},
115
- ...incidentMessage ? { state_message: incidentMessage } : {},
116
- ...incidentStartTime ? { state_start_time: incidentStartTime } : {}
117
- });
118
- const request = {
119
- method: "POST",
120
- headers: {
121
- Accept: "application/json",
122
- "Content-Type": "application/json"
123
- },
124
- body
125
- };
126
- const url = `${this.config.eventsRestEndpoint}/${routingKey}`;
127
- return this.request(url, request);
128
- }
129
- async findByUrl(url) {
130
- const options = {
131
- method: "GET",
132
- headers: {
133
- "Content-Type": "application/json"
134
- }
135
- };
136
- const response = await this.request(url, options);
137
- return response.json();
138
- }
139
- async request(url, options) {
140
- const response = await fetch(url, options);
141
- if (response.status === 403) {
142
- throw new UnauthorizedError();
143
- }
144
- if (!response.ok) {
145
- const payload = await response.json();
146
- const errors = payload.errors.map((error) => error).join(" ");
147
- const message = `Request failed with ${response.status}, ${errors}`;
148
- throw new Error(message);
149
- }
150
- return response;
151
- }
152
- }
153
-
154
- const rootRouteRef = createRouteRef({ id: "splunk-on-call" });
155
- const splunkOnCallPlugin = createPlugin({
156
- id: "splunk-on-call",
157
- apis: [
158
- createApiFactory({
159
- api: splunkOnCallApiRef,
160
- deps: { discoveryApi: discoveryApiRef, configApi: configApiRef },
161
- factory: ({ configApi, discoveryApi }) => SplunkOnCallClient.fromConfig(configApi, discoveryApi)
162
- })
163
- ],
164
- routes: {
165
- root: rootRouteRef
166
- }
167
- });
168
- const SplunkOnCallPage = splunkOnCallPlugin.provide(
169
- createRoutableExtension({
170
- name: "SplunkOnCallPage",
171
- component: () => import('./SplunkOnCallPage-D9lfarwB.esm.js').then((m) => m.SplunkOnCallPage),
172
- mountPoint: rootRouteRef
173
- })
174
- );
175
- const EntitySplunkOnCallCard$2 = splunkOnCallPlugin.provide(
176
- createComponentExtension({
177
- name: "EntitySplunkOnCallCard",
178
- component: {
179
- lazy: () => Promise.resolve().then(function () { return EntitySplunkOnCallCard$1; }).then(
180
- (m) => m.EntitySplunkOnCallCard
181
- )
182
- }
183
- })
184
- );
185
-
186
- const MissingApiKeyOrApiIdError = () => /* @__PURE__ */ React.createElement(
187
- EmptyState,
188
- {
189
- missing: "info",
190
- title: "Missing or invalid Splunk On-Call API key and/or API id",
191
- description: "The request to fetch data needs a valid api id and a valid api key. See README for more details.",
192
- action: /* @__PURE__ */ React.createElement(
193
- Button,
194
- {
195
- color: "primary",
196
- variant: "contained",
197
- href: "https://github.com/backstage/backstage/blob/master/plugins/splunk-on-call/README.md"
198
- },
199
- "Read More"
200
- )
201
- }
202
- );
203
-
204
- const useStyles$6 = makeStyles({
205
- denseListIcon: {
206
- marginRight: 0,
207
- display: "flex",
208
- flexDirection: "column",
209
- alignItems: "center",
210
- justifyContent: "center"
211
- }
212
- });
213
- const EscalationUsersEmptyState = () => {
214
- const classes = useStyles$6();
215
- return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement("div", { className: classes.denseListIcon }, /* @__PURE__ */ React.createElement(StatusWarning, null))), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Empty escalation policy" }));
216
- };
217
-
218
- const useStyles$5 = makeStyles({
219
- listItemPrimary: {
220
- fontWeight: "bold"
221
- }
222
- });
223
- const EscalationUser = ({ user }) => {
224
- const classes = useStyles$5();
225
- return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Avatar, { alt: "User" })), /* @__PURE__ */ React.createElement(
226
- ListItemText,
227
- {
228
- primary: /* @__PURE__ */ React.createElement(Typography, { className: classes.listItemPrimary }, user.firstName, " ", user.lastName),
229
- secondary: user.email
230
- }
231
- ), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(Tooltip, { title: "Send e-mail to user", placement: "top" }, /* @__PURE__ */ React.createElement(IconButton, { href: `mailto:${user.email}` }, /* @__PURE__ */ React.createElement(EmailIcon, { color: "primary" })))));
232
- };
233
-
234
- const useStyles$4 = makeStyles(
235
- (theme) => createStyles({
236
- root: {
237
- maxHeight: "400px",
238
- overflow: "auto"
239
- },
240
- subheader: {
241
- backgroundColor: theme.palette.background.paper
242
- },
243
- progress: {
244
- margin: theme.spacing(0, 2)
245
- }
246
- })
247
- );
248
- const EscalationPolicy = ({ users, team }) => {
249
- const classes = useStyles$4();
250
- const api = useApi(splunkOnCallApiRef);
251
- const {
252
- value: userNames,
253
- loading,
254
- error
255
- } = useAsync(async () => {
256
- const oncalls = await api.getOnCallUsers();
257
- const teamUsernames = oncalls.filter((oncall) => {
258
- var _a;
259
- return ((_a = oncall.team) == null ? void 0 : _a.name) === team;
260
- }).flatMap((oncall) => {
261
- var _a;
262
- return (_a = oncall.oncallNow) == null ? void 0 : _a.flatMap((oncallNow) => {
263
- var _a2;
264
- return (_a2 = oncallNow.users) == null ? void 0 : _a2.flatMap((user) => {
265
- var _a3;
266
- return (_a3 = user == null ? void 0 : user.onCalluser) == null ? void 0 : _a3.username;
267
- });
268
- });
269
- });
270
- return teamUsernames;
271
- });
272
- if (error) {
273
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, "Error encountered while fetching information. ", error.message);
274
- }
275
- if (!loading && !(userNames == null ? void 0 : userNames.length)) {
276
- return /* @__PURE__ */ React.createElement(EscalationUsersEmptyState, null);
277
- }
278
- return /* @__PURE__ */ React.createElement(
279
- List,
280
- {
281
- className: classes.root,
282
- dense: true,
283
- subheader: /* @__PURE__ */ React.createElement(ListSubheader, { className: classes.subheader }, "ON CALL")
284
- },
285
- loading ? /* @__PURE__ */ React.createElement(Progress, { className: classes.progress }) : userNames && userNames.map(
286
- (userName, index) => userName && userName in users && /* @__PURE__ */ React.createElement(EscalationUser, { key: index, user: users[userName] })
287
- )
288
- );
289
- };
290
-
291
- const useStyles$3 = makeStyles({
292
- denseListIcon: {
293
- marginRight: 0,
294
- display: "flex",
295
- flexDirection: "column",
296
- alignItems: "center",
297
- justifyContent: "center"
298
- },
299
- listItemPrimary: {
300
- fontWeight: "bold"
301
- },
302
- listItemIcon: {
303
- minWidth: "1em"
304
- },
305
- secondaryAction: {
306
- paddingRight: 48
307
- }
308
- });
309
- const IncidentPhaseStatus = ({
310
- currentPhase
311
- }) => {
312
- switch (currentPhase) {
313
- case "UNACKED":
314
- return /* @__PURE__ */ React.createElement(StatusError, null);
315
- case "ACKED":
316
- return /* @__PURE__ */ React.createElement(StatusWarning, null);
317
- default:
318
- return /* @__PURE__ */ React.createElement(StatusOK, null);
319
- }
320
- };
321
- const incidentPhaseTooltip = (currentPhase) => {
322
- switch (currentPhase) {
323
- case "UNACKED":
324
- return "Triggered";
325
- case "ACKED":
326
- return "Acknowledged";
327
- default:
328
- return "Resolved";
329
- }
330
- };
331
- const IncidentAction = ({
332
- currentPhase,
333
- incidentId,
334
- resolveAction,
335
- acknowledgeAction
336
- }) => {
337
- switch (currentPhase) {
338
- case "UNACKED":
339
- return /* @__PURE__ */ React.createElement(Tooltip, { title: "Acknowledge", placement: "top" }, /* @__PURE__ */ React.createElement(
340
- IconButton,
341
- {
342
- onClick: () => acknowledgeAction({ incidentId, incidentType: "ACKNOWLEDGEMENT" })
343
- },
344
- /* @__PURE__ */ React.createElement(DoneIcon, null)
345
- ));
346
- case "ACKED":
347
- return /* @__PURE__ */ React.createElement(Tooltip, { title: "Resolve", placement: "top" }, /* @__PURE__ */ React.createElement(
348
- IconButton,
349
- {
350
- onClick: () => resolveAction({ incidentId, incidentType: "RECOVERY" })
351
- },
352
- /* @__PURE__ */ React.createElement(DoneAllIcon, null)
353
- ));
354
- default:
355
- return /* @__PURE__ */ React.createElement(React.Fragment, null);
356
- }
357
- };
358
- const IncidentListItem = ({
359
- incident,
360
- readOnly,
361
- onIncidentAction,
362
- team
363
- }) => {
364
- var _a;
365
- const classes = useStyles$3();
366
- const duration = (/* @__PURE__ */ new Date()).getTime() - new Date(incident.startTime).getTime();
367
- const createdAt = DateTime.local().minus(Duration.fromMillis(duration)).toRelative({ locale: "en" });
368
- const alertApi = useApi(alertApiRef);
369
- const api = useApi(splunkOnCallApiRef);
370
- const hasBeenManuallyTriggered = (_a = incident.monitorName) == null ? void 0 : _a.includes("vouser-");
371
- const source = () => {
372
- var _a2;
373
- if (hasBeenManuallyTriggered) {
374
- return (_a2 = incident.monitorName) == null ? void 0 : _a2.replace("vouser-", "");
375
- }
376
- if (incident.monitorType === "API") {
377
- return "{ REST }";
378
- }
379
- return incident.monitorName;
380
- };
381
- const [{ value: resolveValue, error: resolveError }, handleResolveIncident] = useAsyncFn(
382
- async ({ incidentId, incidentType }) => await api.incidentAction({
383
- routingKey: team,
384
- incidentType,
385
- incidentId
386
- })
387
- );
388
- const [
389
- { value: acknowledgeValue, error: acknowledgeError },
390
- handleAcknowledgeIncident
391
- ] = useAsyncFn(
392
- async ({ incidentId, incidentType }) => await api.incidentAction({
393
- routingKey: team,
394
- incidentType,
395
- incidentId
396
- })
397
- );
398
- useEffect(() => {
399
- if (acknowledgeValue) {
400
- alertApi.post({
401
- message: `Incident successfully acknowledged`
402
- });
403
- }
404
- if (resolveValue) {
405
- alertApi.post({
406
- message: `Incident successfully resolved`
407
- });
408
- }
409
- if (resolveValue || acknowledgeValue) {
410
- onIncidentAction();
411
- }
412
- }, [acknowledgeValue, resolveValue, alertApi, onIncidentAction]);
413
- if (acknowledgeError) {
414
- alertApi.post({
415
- message: `Failed to acknowledge incident. ${acknowledgeError.message}`,
416
- severity: "error"
417
- });
418
- }
419
- if (resolveError) {
420
- alertApi.post({
421
- message: `Failed to resolve incident. ${resolveError.message}`,
422
- severity: "error"
423
- });
424
- }
425
- return /* @__PURE__ */ React.createElement(ListItem, { dense: true, key: incident.entityId }, /* @__PURE__ */ React.createElement(ListItemIcon, { className: classes.listItemIcon }, /* @__PURE__ */ React.createElement(
426
- Tooltip,
427
- {
428
- title: incidentPhaseTooltip(incident.currentPhase),
429
- placement: "top"
430
- },
431
- /* @__PURE__ */ React.createElement("div", { className: classes.denseListIcon }, /* @__PURE__ */ React.createElement(IncidentPhaseStatus, { currentPhase: incident.currentPhase }))
432
- )), /* @__PURE__ */ React.createElement(
433
- ListItemText,
434
- {
435
- primary: incident.entityDisplayName,
436
- primaryTypographyProps: {
437
- variant: "body1",
438
- className: classes.listItemPrimary
439
- },
440
- secondary: /* @__PURE__ */ React.createElement(Typography, { noWrap: true, variant: "body2", color: "textSecondary" }, "#", incident.incidentNumber, " - Created ", createdAt, " ", source() && `by ${source()}`)
441
- }
442
- ), incident.incidentLink && incident.incidentNumber && /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, !readOnly && /* @__PURE__ */ React.createElement(
443
- IncidentAction,
444
- {
445
- currentPhase: incident.currentPhase || "",
446
- incidentId: incident.entityId,
447
- resolveAction: handleResolveIncident,
448
- acknowledgeAction: handleAcknowledgeIncident
449
- }
450
- ), /* @__PURE__ */ React.createElement(Tooltip, { title: "View in Splunk On-Call", placement: "top" }, /* @__PURE__ */ React.createElement(
451
- IconButton,
452
- {
453
- href: incident.incidentLink,
454
- target: "_blank",
455
- rel: "noopener noreferrer",
456
- color: "primary"
457
- },
458
- /* @__PURE__ */ React.createElement(OpenInBrowserIcon, null)
459
- ))));
460
- };
461
-
462
- const IncidentsEmptyState = () => {
463
- return /* @__PURE__ */ React.createElement(
464
- Grid,
465
- {
466
- container: true,
467
- justifyContent: "center",
468
- direction: "column",
469
- alignItems: "center"
470
- },
471
- /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Nice! No incidents found!")),
472
- /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(
473
- "img",
474
- {
475
- src: EmptyStateImage,
476
- alt: "EmptyState",
477
- "data-testid": "emptyStateImg"
478
- }
479
- ))
480
- );
481
- };
482
-
483
- const useStyles$2 = makeStyles(
484
- (theme) => createStyles({
485
- root: {
486
- maxHeight: "400px",
487
- overflow: "auto"
488
- },
489
- subheader: {
490
- backgroundColor: theme.palette.background.paper
491
- },
492
- progress: {
493
- margin: theme.spacing(0, 2)
494
- }
495
- })
496
- );
497
- const Incidents = ({ readOnly, refreshIncidents, team }) => {
498
- const classes = useStyles$2();
499
- const api = useApi(splunkOnCallApiRef);
500
- const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn(
501
- async () => {
502
- var _a;
503
- await new Promise((resolve) => setTimeout(resolve, 2e3));
504
- const allIncidents = await api.getIncidents();
505
- const teams = await api.getTeams();
506
- const teamSlug = (_a = teams.find((teamValue) => teamValue.name === team)) == null ? void 0 : _a.slug;
507
- const filteredIncidents = teamSlug ? allIncidents.filter(
508
- (incident) => {
509
- var _a2;
510
- return (_a2 = incident.pagedTeams) == null ? void 0 : _a2.includes(teamSlug);
511
- }
512
- ) : [];
513
- return filteredIncidents;
514
- }
515
- );
516
- useEffect(() => {
517
- getIncidents();
518
- }, [refreshIncidents, getIncidents]);
519
- if (error) {
520
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, "Error encountered while fetching information. ", error.message);
521
- }
522
- if (!loading && !(incidents == null ? void 0 : incidents.length)) {
523
- return /* @__PURE__ */ React.createElement(IncidentsEmptyState, null);
524
- }
525
- return /* @__PURE__ */ React.createElement(
526
- List,
527
- {
528
- className: classes.root,
529
- dense: true,
530
- subheader: /* @__PURE__ */ React.createElement(ListSubheader, { className: classes.subheader }, "CRITICAL INCIDENTS")
531
- },
532
- loading ? /* @__PURE__ */ React.createElement(Progress, { className: classes.progress }) : incidents.map((incident, index) => /* @__PURE__ */ React.createElement(
533
- IncidentListItem,
534
- {
535
- onIncidentAction: () => getIncidents(),
536
- key: index,
537
- team,
538
- incident,
539
- readOnly
540
- }
541
- ))
542
- );
543
- };
544
-
545
- const useStyles$1 = makeStyles(
546
- (theme) => createStyles({
547
- chips: {
548
- display: "flex",
549
- flexWrap: "wrap"
550
- },
551
- chip: {
552
- margin: 2
553
- },
554
- formControl: {
555
- margin: theme.spacing(1),
556
- display: "flex",
557
- flexDirection: "row",
558
- alignItems: "center",
559
- minWidth: `calc(100% - ${theme.spacing(2)}px)`
560
- },
561
- formHeader: {
562
- width: "50%"
563
- },
564
- incidentType: {
565
- width: "90%"
566
- },
567
- targets: {
568
- display: "flex",
569
- flexDirection: "column",
570
- width: "100%"
571
- }
572
- })
573
- );
574
- const TriggerDialog = ({
575
- routingKey,
576
- showDialog,
577
- handleDialog,
578
- onIncidentCreated
579
- }) => {
580
- const alertApi = useApi(alertApiRef);
581
- const api = useApi(splunkOnCallApiRef);
582
- const classes = useStyles$1();
583
- const [incidentType, setIncidentType] = useState("");
584
- const [incidentId, setIncidentId] = useState();
585
- const [incidentDisplayName, setIncidentDisplayName] = useState("");
586
- const [incidentMessage, setIncidentMessage] = useState("");
587
- const [incidentStartTime, setIncidentStartTime] = useState();
588
- const [
589
- { value, loading: triggerLoading, error: triggerError },
590
- handleTriggerAlarm
591
- ] = useAsyncFn(
592
- async (params) => await api.incidentAction(params)
593
- );
594
- const handleIncidentType = (event) => {
595
- setIncidentType(event.target.value);
596
- };
597
- const handleIncidentId = (event) => {
598
- setIncidentId(event.target.value);
599
- };
600
- const handleIncidentDisplayName = (event) => {
601
- setIncidentDisplayName(event.target.value);
602
- };
603
- const handleIncidentMessage = (event) => {
604
- setIncidentMessage(event.target.value);
605
- };
606
- const handleIncidentStartTime = (event) => {
607
- const dateTime = new Date(event.target.value).getTime();
608
- const dateTimeInSeconds = Math.floor(dateTime / 1e3);
609
- setIncidentStartTime(dateTimeInSeconds);
610
- };
611
- useEffect(() => {
612
- if (value) {
613
- alertApi.post({
614
- message: `Alarm successfully triggered`
615
- });
616
- onIncidentCreated();
617
- handleDialog();
618
- }
619
- }, [value, alertApi, handleDialog, onIncidentCreated]);
620
- if (triggerError) {
621
- alertApi.post({
622
- message: `Failed to trigger alarm. ${triggerError.message}`,
623
- severity: "error"
624
- });
625
- }
626
- return /* @__PURE__ */ React.createElement(Dialog, { maxWidth: "md", open: showDialog, onClose: handleDialog, fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, "This action will trigger an incident"), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1", gutterBottom: true, align: "justify" }, "Created by: ", /* @__PURE__ */ React.createElement("b", null, `{ REST } Endpoint`)), /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", align: "justify" }, `If the issue you are seeing does not need urgent attention, please get in touch with the responsible team using their preferred communications channel. You can find information about the owner of this entity in the "About" card. If the issue is urgent, please don't hesitate to trigger the alert.`)), /* @__PURE__ */ React.createElement(
627
- Typography,
628
- {
629
- variant: "body1",
630
- style: { marginTop: "1em" },
631
- gutterBottom: true,
632
- align: "justify"
633
- },
634
- "Please describe the problem you want to report. Be as descriptive as possible. ",
635
- /* @__PURE__ */ React.createElement("br", null),
636
- "Note that only the ",
637
- /* @__PURE__ */ React.createElement("b", null, "Incident type"),
638
- ", ",
639
- /* @__PURE__ */ React.createElement("b", null, "Incident display name"),
640
- " ",
641
- "and the ",
642
- /* @__PURE__ */ React.createElement("b", null, "Incident message"),
643
- " fields are ",
644
- /* @__PURE__ */ React.createElement("b", null, "required"),
645
- "."
646
- ), /* @__PURE__ */ React.createElement(FormControl, { className: classes.formControl }, /* @__PURE__ */ React.createElement("div", { className: classes.formHeader }, /* @__PURE__ */ React.createElement(InputLabel, { id: "demo-simple-select-label" }, "Incident type"), /* @__PURE__ */ React.createElement(
647
- Select,
648
- {
649
- id: "incident-type",
650
- className: classes.incidentType,
651
- value: incidentType,
652
- onChange: handleIncidentType,
653
- inputProps: { "data-testid": "trigger-incident-type" }
654
- },
655
- /* @__PURE__ */ React.createElement(MenuItem, { value: "CRITICAL" }, "Critical"),
656
- /* @__PURE__ */ React.createElement(MenuItem, { value: "WARNING" }, "Warning"),
657
- /* @__PURE__ */ React.createElement(MenuItem, { value: "INFO" }, "Info")
658
- )), /* @__PURE__ */ React.createElement(
659
- TextField,
660
- {
661
- className: classes.formHeader,
662
- id: "datetime-local",
663
- label: "Incident start time",
664
- type: "datetime-local",
665
- onChange: handleIncidentStartTime,
666
- InputLabelProps: {
667
- shrink: true
668
- }
669
- }
670
- )), /* @__PURE__ */ React.createElement(
671
- TextField,
672
- {
673
- inputProps: { "data-testid": "trigger-incident-id" },
674
- id: "summary",
675
- fullWidth: true,
676
- margin: "normal",
677
- label: "Incident id",
678
- variant: "outlined",
679
- onChange: handleIncidentId
680
- }
681
- ), /* @__PURE__ */ React.createElement(
682
- TextField,
683
- {
684
- required: true,
685
- inputProps: { "data-testid": "trigger-incident-displayName" },
686
- id: "summary",
687
- fullWidth: true,
688
- margin: "normal",
689
- label: "Incident display name",
690
- variant: "outlined",
691
- onChange: handleIncidentDisplayName
692
- }
693
- ), /* @__PURE__ */ React.createElement(
694
- TextField,
695
- {
696
- required: true,
697
- inputProps: { "data-testid": "trigger-incident-message" },
698
- id: "details",
699
- multiline: true,
700
- fullWidth: true,
701
- minRows: "2",
702
- margin: "normal",
703
- label: "Incident message",
704
- variant: "outlined",
705
- onChange: handleIncidentMessage
706
- }
707
- )), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(
708
- Button,
709
- {
710
- "data-testid": "trigger-button",
711
- id: "trigger",
712
- color: "secondary",
713
- disabled: !incidentType.length || !incidentDisplayName || !incidentMessage || triggerLoading,
714
- variant: "contained",
715
- onClick: () => handleTriggerAlarm({
716
- routingKey,
717
- incidentType,
718
- incidentDisplayName,
719
- incidentMessage,
720
- ...incidentId ? { incidentId } : {},
721
- ...incidentStartTime ? { incidentStartTime } : {}
722
- }),
723
- endIcon: triggerLoading && /* @__PURE__ */ React.createElement(CircularProgress, { size: 16 })
724
- },
725
- "Trigger Incident"
726
- ), /* @__PURE__ */ React.createElement(Button, { id: "close", color: "primary", onClick: handleDialog }, "Close")));
727
- };
728
-
729
- const SPLUNK_ON_CALL_TEAM = "splunk.com/on-call-team";
730
- const SPLUNK_ON_CALL_ROUTING_KEY = "splunk.com/on-call-routing-key";
731
- const MissingAnnotation = () => /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Typography, null, "The Splunk On Call plugin requires setting either the", " ", /* @__PURE__ */ React.createElement("code", null, SPLUNK_ON_CALL_TEAM), " or the", " ", /* @__PURE__ */ React.createElement("code", null, SPLUNK_ON_CALL_ROUTING_KEY), " annotation."), /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { annotation: SPLUNK_ON_CALL_TEAM }));
732
- const InvalidAnnotation = ({
733
- teamName,
734
- routingKey
735
- }) => {
736
- let titleSuffix = "provided annotation";
737
- if (routingKey) {
738
- titleSuffix = `"${routingKey}" routing key`;
739
- }
740
- if (teamName) {
741
- titleSuffix = `"${teamName}" team name`;
742
- }
743
- return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Splunk On-Call" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
744
- EmptyState,
745
- {
746
- title: `Splunk On-Call API returned no record of teams associated with the ${titleSuffix}`,
747
- missing: "info",
748
- description: "Escalation Policy and incident information unavailable. Splunk On-Call requires a valid team name or routing key."
749
- }
750
- )));
751
- };
752
- const MissingEventsRestEndpoint = () => /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
753
- EmptyState,
754
- {
755
- title: "No Splunk On-Call REST endpoint available.",
756
- missing: "info",
757
- description: "You need to add a valid REST endpoint to your 'app-config.yaml' if you want to enable Splunk On-Call."
758
- }
759
- ));
760
- const isSplunkOnCallAvailable = (entity) => {
761
- var _a, _b;
762
- return Boolean((_a = entity.metadata.annotations) == null ? void 0 : _a[SPLUNK_ON_CALL_TEAM]) || Boolean((_b = entity.metadata.annotations) == null ? void 0 : _b[SPLUNK_ON_CALL_ROUTING_KEY]);
763
- };
764
- const useStyles = makeStyles({
765
- onCallCard: {
766
- marginBottom: "1em"
767
- }
768
- });
769
- const EntitySplunkOnCallCard = (props) => {
770
- const { readOnly } = props;
771
- const classes = useStyles();
772
- const config = useApi(configApiRef);
773
- const api = useApi(splunkOnCallApiRef);
774
- const { entity } = useEntity();
775
- const [showDialog, setShowDialog] = useState(false);
776
- const [refreshIncidents, setRefreshIncidents] = useState(false);
777
- const teamAnnotation = entity ? entity.metadata.annotations[SPLUNK_ON_CALL_TEAM] : void 0;
778
- const routingKeyAnnotation = entity ? entity.metadata.annotations[SPLUNK_ON_CALL_ROUTING_KEY] : void 0;
779
- const eventsRestEndpoint = config.getOptionalString("splunkOnCall.eventsRestEndpoint") || null;
780
- const handleRefresh = useCallback(() => {
781
- setRefreshIncidents((x) => !x);
782
- }, []);
783
- const handleDialog = useCallback(() => {
784
- setShowDialog((x) => !x);
785
- }, []);
786
- const {
787
- value: entityData,
788
- loading,
789
- error
790
- } = useAsync(async () => {
791
- const allUsers = await api.getUsers();
792
- const usersHashMap = allUsers.reduce(
793
- (map, obj) => {
794
- if (obj.username) {
795
- map[obj.username] = obj;
796
- }
797
- return map;
798
- },
799
- {}
800
- );
801
- const teams2 = await api.getTeams();
802
- let foundTeams = [
803
- teams2.find((teamValue) => teamValue.name === teamAnnotation)
804
- ].filter((team) => team !== void 0);
805
- let foundRoutingKey;
806
- if (routingKeyAnnotation) {
807
- const routingKeys = await api.getRoutingKeys();
808
- foundRoutingKey = routingKeys.find(
809
- (key) => key.routingKey === routingKeyAnnotation
810
- );
811
- }
812
- if (!foundTeams.length) {
813
- foundTeams = foundRoutingKey ? foundRoutingKey.targets.map((target) => {
814
- const teamUrlParts = target._teamUrl.split("/");
815
- const teamSlug = teamUrlParts[teamUrlParts.length - 1];
816
- return teams2.find((teamValue) => teamValue.slug === teamSlug);
817
- }).filter((team) => team !== void 0) : [];
818
- }
819
- return { usersHashMap, foundTeams, foundRoutingKey };
820
- });
821
- if (!teamAnnotation && !routingKeyAnnotation) {
822
- return /* @__PURE__ */ React.createElement(MissingAnnotation, null);
823
- }
824
- if (!eventsRestEndpoint) {
825
- return /* @__PURE__ */ React.createElement(MissingEventsRestEndpoint, null);
826
- }
827
- if (error instanceof UnauthorizedError) {
828
- return /* @__PURE__ */ React.createElement(MissingApiKeyOrApiIdError, null);
829
- }
830
- if (error) {
831
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, "Error encountered while fetching information. ", error.message);
832
- }
833
- if (loading) {
834
- return /* @__PURE__ */ React.createElement(Progress, null);
835
- }
836
- if (!(entityData == null ? void 0 : entityData.foundTeams) || !(entityData == null ? void 0 : entityData.foundTeams.length)) {
837
- return /* @__PURE__ */ React.createElement(
838
- InvalidAnnotation,
839
- {
840
- teamName: teamAnnotation,
841
- routingKey: routingKeyAnnotation
842
- }
843
- );
844
- }
845
- const triggerLink = {
846
- label: "Create Incident",
847
- onClick: handleDialog,
848
- color: "secondary",
849
- icon: /* @__PURE__ */ React.createElement(AlarmAddIcon, null)
850
- };
851
- const serviceLink = {
852
- label: "Portal",
853
- href: "https://portal.victorops.com/",
854
- icon: /* @__PURE__ */ React.createElement(WebIcon, null)
855
- };
856
- const teams = (entityData == null ? void 0 : entityData.foundTeams) || [];
857
- return /* @__PURE__ */ React.createElement(React.Fragment, null, teams.map((team, i) => {
858
- var _a, _b, _c;
859
- const teamName = (_a = team == null ? void 0 : team.name) != null ? _a : "";
860
- return /* @__PURE__ */ React.createElement(Card, { key: i, className: classes.onCallCard }, /* @__PURE__ */ React.createElement(
861
- CardHeader,
862
- {
863
- title: "Splunk On-Call",
864
- subheader: [
865
- /* @__PURE__ */ React.createElement(Typography, { key: "team_name" }, "Team: ", team && team.name ? team.name : ""),
866
- /* @__PURE__ */ React.createElement(
867
- HeaderIconLinkRow,
868
- {
869
- key: "incident_trigger",
870
- links: !readOnly ? [serviceLink, triggerLink] : [serviceLink]
871
- }
872
- )
873
- ]
874
- }
875
- ), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
876
- Incidents,
877
- {
878
- readOnly: readOnly || false,
879
- team: teamName,
880
- refreshIncidents
881
- }
882
- ), (entityData == null ? void 0 : entityData.usersHashMap) && team && /* @__PURE__ */ React.createElement(
883
- EscalationPolicy,
884
- {
885
- team: teamName,
886
- users: entityData == null ? void 0 : entityData.usersHashMap
887
- }
888
- ), /* @__PURE__ */ React.createElement(
889
- TriggerDialog,
890
- {
891
- routingKey: (_c = (_b = entityData == null ? void 0 : entityData.foundRoutingKey) == null ? void 0 : _b.routingKey) != null ? _c : teamName,
892
- showDialog,
893
- handleDialog,
894
- onIncidentCreated: handleRefresh
895
- }
896
- )));
897
- }));
898
- };
899
-
900
- var EntitySplunkOnCallCard$1 = /*#__PURE__*/Object.freeze({
901
- __proto__: null,
902
- EntitySplunkOnCallCard: EntitySplunkOnCallCard,
903
- InvalidAnnotation: InvalidAnnotation,
904
- MissingAnnotation: MissingAnnotation,
905
- MissingEventsRestEndpoint: MissingEventsRestEndpoint,
906
- SPLUNK_ON_CALL_ROUTING_KEY: SPLUNK_ON_CALL_ROUTING_KEY,
907
- SPLUNK_ON_CALL_TEAM: SPLUNK_ON_CALL_TEAM,
908
- isSplunkOnCallAvailable: isSplunkOnCallAvailable
909
- });
910
-
911
- export { EntitySplunkOnCallCard as E, SplunkOnCallPage as S, UnauthorizedError as U, EntitySplunkOnCallCard$2 as a, SplunkOnCallClient as b, splunkOnCallApiRef as c, isSplunkOnCallAvailable as i, splunkOnCallPlugin as s };
912
- //# sourceMappingURL=index-D8KSFXg5.esm.js.map