• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-server / 10423

11 Nov 2025 04:53PM UTC coverage: 74.435% (-0.1%) from 74.562%
10423

push

gitlab-ci

web-flow
Merge pull request #1071 from mendersoftware/dependabot/npm_and_yarn/frontend/main/development-dependencies-92732187be

3868 of 5393 branches covered (71.72%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

176 existing lines in 95 files now uncovered.

64605 of 86597 relevant lines covered (74.6%)

7.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.49
/frontend/src/js/components/settings/webhooks/Management.tsx
1
// Copyright 2022 Northern.tech AS
2✔
2
//
2✔
3
//    Licensed under the Apache License, Version 2.0 (the "License");
2✔
4
//    you may not use this file except in compliance with the License.
2✔
5
//    You may obtain a copy of the License at
2✔
6
//
2✔
7
//        http://www.apache.org/licenses/LICENSE-2.0
2✔
8
//
2✔
9
//    Unless required by applicable law or agreed to in writing, software
2✔
10
//    distributed under the License is distributed on an "AS IS" BASIS,
2✔
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2✔
12
//    See the License for the specific language governing permissions and
2✔
13
//    limitations under the License.
2✔
14
import { ReactElement, useCallback, useMemo, useRef, useState } from 'react';
2✔
15
import { useDispatch, useSelector } from 'react-redux';
2✔
16

2✔
17
// material ui
2✔
18
import { Circle as CircleIcon } from '@mui/icons-material';
2✔
19
import { Button, Divider, Drawer, Slide } from '@mui/material';
2✔
20
import { makeStyles } from 'tss-react/mui';
2✔
21

2✔
22
import { TwoColumnData } from '@northern.tech/common-ui/ConfigurationObject';
2✔
23
import DetailsIndicator from '@northern.tech/common-ui/DetailsIndicator';
2✔
24
import { DrawerTitle } from '@northern.tech/common-ui/DrawerTitle';
2✔
25
import { ClassesOverrides } from '@northern.tech/common-ui/List';
2✔
26
import Time from '@northern.tech/common-ui/Time';
2✔
27
import actions from '@northern.tech/store/actions';
2✔
28
import { Event } from '@northern.tech/store/api/types';
2✔
29
import { EXTERNAL_PROVIDER, Webhook, emptyWebhook } from '@northern.tech/store/constants';
2✔
30
import { getTenantCapabilities, getWebhookEventInfo } from '@northern.tech/store/selectors';
2✔
31
import { getWebhookEvents } from '@northern.tech/store/thunks';
2✔
32

2✔
33
import WebhookActivity from './Activity';
2✔
34
import { availableScopes } from './Configuration';
2✔
35
import WebhookEventDetails from './EventDetails';
2✔
36

2✔
37
const { setSnackbar } = actions;
8✔
38

2✔
39
const useStyles = makeStyles()(theme => ({
8✔
40
  divider: { marginTop: theme.spacing(), marginBottom: theme.spacing() },
2✔
41
  statusIcon: { fontSize: 12, marginRight: theme.spacing() },
2✔
42
  twoColumnsMultiple: {
2✔
43
    gridTemplateColumns: 'max-content 1fr',
2✔
44
    marginBottom: theme.spacing(2),
2✔
45
    marginTop: theme.spacing(2),
2✔
46
    maxWidth: 'initial'
2✔
47
  },
2✔
48
  wrapper: { justifyContent: 'end' }
2✔
49
}));
2✔
50

2✔
51
const triggerMap = {
8✔
52
  'device-decommissioned': 'Device decommissioned',
2✔
53
  'device-provisioned': 'Device provisioned',
2✔
54
  'device-status-changed': 'Device status updated',
2✔
55
  'device-inventory-changed': 'Device inventory changed'
2✔
56
};
2✔
57

2✔
58
const DeliveryStatus = ({ entry, webhook = {}, classes }) => {
8✔
59
  const { delivery_statuses = [] } = entry;
17✔
60

2✔
61
  const status = useMemo(() => {
17✔
62
    const status = delivery_statuses.find(status => status.integration_id === webhook.id) ?? delivery_statuses[0];
10✔
63
    if (status) {
10✔
64
      return { code: status.status_code, signal: status.success ? 'green' : 'red' };
7!
65
    }
2✔
66
    return { code: 418, signal: 'disabled' };
5✔
67
    // eslint-disable-next-line react-hooks/exhaustive-deps
2✔
68
  }, [JSON.stringify(delivery_statuses), webhook.id]);
2✔
69

2✔
70
  return (
17✔
71
    <div className="flexbox center-aligned">
2✔
72
      <CircleIcon className={`${status.signal} ${classes.statusIcon}`} />
2✔
73
      <div className={status.code >= 400 ? 'muted' : ''}>{status.code}</div>
2✔
74
    </div>
2✔
75
  );
2✔
76
};
2✔
77

2✔
78
interface WebhookColumnRenderer extends ClassesOverrides {
2✔
79
  webhook: Webhook;
2✔
80
}
2✔
81

2✔
82
export type WebhookColumns = {
2✔
83
  key: string;
2✔
84
  render: (entry: Event, { webhook, classes }: WebhookColumnRenderer) => ReactElement;
2✔
85
  title: string;
2✔
86
}[];
2✔
87

2✔
88
const columns: WebhookColumns = [
8✔
89
  { key: 'created_ts', title: 'Time', render: entry => <Time value={entry.time} /> },
17✔
90
  { key: 'trigger', title: 'Event trigger', render: entry => <div className="trigger-type">{triggerMap[entry.type] ?? entry.type}</div> },
17✔
91
  { key: 'status', title: 'Status', render: (entry, { webhook, classes }) => <DeliveryStatus classes={classes} entry={entry} webhook={webhook} /> },
17✔
92
  { key: 'details', title: '', render: (_, { classes }) => <DetailsIndicator classes={classes} /> }
12✔
93
];
2✔
94

2✔
95
export const WebhookManagement = ({ onCancel, onRemove, webhook }) => {
8✔
96
  const [selectedEvent, setSelectedEvent] = useState<Event>();
9✔
97
  const { events, eventsTotal } = useSelector(getWebhookEventInfo);
9✔
98
  const { canDelta: canScopeWebhooks } = useSelector(getTenantCapabilities);
9✔
99
  const dispatch = useDispatch();
9✔
100
  const { classes } = useStyles();
9✔
101
  const containerRef = useRef();
9✔
102

2✔
103
  const dispatchedGetWebhookEvents = useCallback(options => dispatch(getWebhookEvents(options)), [dispatch]);
9✔
104
  const dispatchedSetSnackbar = useCallback(args => dispatch(setSnackbar(args)), [dispatch]);
9✔
105

2✔
106
  const { description, scopes = [], credentials = {} } = webhook ?? emptyWebhook;
9✔
107
  const {
2✔
108
    [EXTERNAL_PROVIDER.webhook.credentialsType]: { url = '', secret = '' }
2✔
109
  } = credentials;
9✔
110

2✔
111
  const webhookConfig = {
9✔
112
    'Destination URL': url,
2✔
113
    'Description': description,
2✔
114
    'Webhook events': scopes?.length
2!
115
      ? scopes.map(scope => availableScopes[scope].title).join(', ')
2✔
116
      : canScopeWebhooks
2!
117
        ? 'Backend information unclear'
2✔
118
        : availableScopes.deviceauth.title,
2✔
119
    'Secret': secret
2✔
120
  };
2✔
121

2✔
122
  const handleBack = () => setSelectedEvent();
9✔
123

2✔
124
  const onCancelClick = () => {
9✔
125
    setSelectedEvent();
3✔
126
    onCancel();
3✔
127
  };
2✔
128

2✔
129
  return (
9✔
130
    <Drawer anchor="right" open={!!webhook?.id} PaperProps={{ style: { minWidth: 750, width: '60vw' } }} onClose={onCancelClick}>
2✔
131
      <DrawerTitle
2✔
132
        title="Webhook details"
2✔
133
        preCloser={
2✔
UNCOV
134
          <Button className={selectedEvent ? 'muted' : ''} color="secondary" disabled={!!selectedEvent} onClick={() => onRemove(webhook)}>
2✔
135
            delete webhook
2✔
136
          </Button>
2✔
137
        }
2✔
138
        onClose={onCancelClick}
2✔
139
      />
2✔
140
      <Divider />
2✔
141
      <div className="relative" ref={containerRef}>
2✔
142
        <Slide in={!selectedEvent} container={containerRef.current} direction="right">
2✔
143
          <div className="absolute margin-top full-width" style={{ top: 0 }}>
2✔
144
            <h4>Settings</h4>
2✔
145
            <TwoColumnData className={classes.twoColumnsMultiple} config={webhookConfig} setSnackbar={dispatchedSetSnackbar} />
2✔
146
            <h4>Activity</h4>
2✔
147
            <WebhookActivity
2✔
148
              classes={classes}
2✔
149
              columns={columns}
2✔
150
              events={events}
2✔
151
              eventsTotal={eventsTotal}
2✔
152
              getWebhookEvents={dispatchedGetWebhookEvents}
2✔
153
              setSelectedEvent={setSelectedEvent}
2✔
154
              webhook={webhook}
2✔
155
            />
2✔
156
          </div>
2✔
157
        </Slide>
2✔
158
        <Slide in={!!selectedEvent} container={containerRef.current} direction="left">
2✔
159
          <div className="absolute margin-top full-width" style={{ top: 0 }}>
2✔
160
            <WebhookEventDetails
2✔
161
              classes={classes}
2✔
162
              columns={columns}
2✔
163
              entry={selectedEvent}
2✔
164
              onClickBack={handleBack}
2✔
165
              setSnackbar={setSnackbar}
2✔
166
              webhook={webhook}
2✔
167
            />
2✔
168
          </div>
2✔
169
        </Slide>
2✔
170
      </div>
2✔
171
    </Drawer>
2✔
172
  );
2✔
173
};
2✔
174

2✔
175
export default WebhookManagement;
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc