• 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.55
/frontend/src/js/components/helptips/MenderTooltip.tsx
1
// Copyright 2021 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 { useCallback, useEffect, useState } from 'react';
2✔
15
import type { ReactNode } from 'react';
2✔
16
import { useDispatch, useSelector } from 'react-redux';
2✔
17

2✔
18
import { Help as HelpIcon } from '@mui/icons-material';
2✔
19
import { Tooltip } from '@mui/material';
2✔
20
import { makeStyles, withStyles } from 'tss-react/mui';
2✔
21

2✔
22
import { MenderTooltipClickable, MenderTooltipClickableProps } from '@northern.tech/common-ui/helptips/MenderTooltip';
2✔
23
import type { Device } from '@northern.tech/store/api/types';
2✔
24
import { READ_STATES, TIMEOUTS } from '@northern.tech/store/constants';
2✔
25
import { getDeviceById, getTooltipsState } from '@northern.tech/store/selectors';
2✔
26
import { setAllTooltipsReadState, setTooltipReadState } from '@northern.tech/store/thunks';
2✔
27
import { useDebounce } from '@northern.tech/utils/debouncehook';
2✔
28
import { yes } from '@northern.tech/utils/helpers';
2✔
29

2✔
30
import type { HelpTooltipComponent } from './HelpTooltips';
2✔
31
import { HELPTOOLTIPS } from './HelpTooltips';
2✔
32

2✔
33
const useStyles = makeStyles()(theme => ({
81✔
34
  icon: {
2✔
35
    '&.read': {
2✔
36
      color: theme.palette.text.disabled
2✔
37
    }
2✔
38
  },
2✔
39
  iconAura: {
2✔
40
    position: 'absolute',
2✔
41
    top: -5,
2✔
42
    bottom: 0,
2✔
43
    left: -5,
2✔
44
    right: -5,
2✔
45
    border: `1px dashed ${theme.palette.primary.main}`,
2✔
46
    borderRadius: '50%',
2✔
47
    '&.read': {
2✔
48
      borderColor: theme.palette.text.disabled
2✔
49
    }
2✔
50
  }
2✔
51
}));
2✔
52

2✔
53
const iconWidth = 30;
63✔
54

2✔
55
export const OnboardingTooltip = withStyles(Tooltip, theme => ({
63✔
56
  arrow: {
2✔
57
    color: theme.palette.primary.main
2✔
58
  },
2✔
59
  tooltip: {
2✔
60
    backgroundColor: theme.palette.primary.main,
2✔
61
    boxShadow: theme.shadows[1],
2✔
62
    color: theme.palette.grey[500],
2✔
63
    fontSize: 14,
2✔
64
    maxWidth: 350,
2✔
65
    padding: '12px 18px',
2✔
66
    width: 350,
2✔
67
    '& a': {
2✔
68
      color: theme.palette.grey[500]
2✔
69
    },
2✔
70
    '&.MuiTooltip-tooltipPlacementTop': { marginLeft: iconWidth, marginBottom: 0, marginTop: `calc(${iconWidth} + ${theme.spacing(1.5)})` },
2✔
71
    '&.MuiTooltip-tooltipPlacementRight': { marginTop: iconWidth / 2 },
2✔
72
    '&.MuiTooltip-tooltipPlacementBottom': { marginLeft: iconWidth },
2✔
73
    '&.MuiTooltip-tooltipPlacementLeft': { marginTop: iconWidth / 2 }
2✔
74
  },
2✔
75
  popper: {
2✔
76
    opacity: 0.9
2✔
77
  }
2✔
78
}));
2✔
79

2✔
80
const tooltipStateStyleMap = {
63✔
81
  [READ_STATES.read]: 'read muted',
2✔
82
  default: ''
2✔
83
};
2✔
84

2✔
85
interface TooltipWrapperProps {
2✔
86
  content: ReactNode;
2✔
87
  onClose: () => void;
2✔
88
  onReadAll: () => void;
2✔
89
}
2✔
90

2✔
91
const TooltipWrapper = ({ content, onClose, onReadAll }: TooltipWrapperProps) => (
63✔
92
  <div>
2✔
93
    {content}
2✔
94
    <div className="flexbox space-between margin-top-small">
2✔
95
      <span className="link" onClick={onReadAll}>
2✔
96
        Mark all help tips as read
2✔
97
      </span>
2✔
98
      <span className="link" onClick={onClose}>
2✔
99
        Close
2✔
100
      </span>
2✔
101
    </div>
2✔
102
  </div>
2✔
103
);
2✔
104

2✔
105
export interface HelpTooltipProps {
2✔
106
  contentProps?: Record<string, unknown>;
2✔
107
  device?: Device; // TODO: use the UI Device type once it's available
2✔
108
  icon?: ReactNode;
2✔
109
  id: string;
2✔
110
  setAllTooltipsReadState: (state: keyof typeof READ_STATES) => void;
2✔
111
  setTooltipReadState: (args: { id: string; persist: boolean; readState: string }) => void;
2✔
112
  tooltip: Omit<HelpTooltipComponent, 'id' | 'isRelevant' | 'readState'> & {
2✔
113
    isRelevant: (props: { device?: Device }) => boolean;
2✔
114
    readState: keyof typeof READ_STATES;
2✔
115
  };
2✔
116
}
2✔
117

2✔
118
export const HelpTooltip = ({
63✔
119
  icon = undefined,
2✔
120
  id,
2✔
121
  contentProps = {},
2✔
122
  tooltip,
2✔
123
  device,
2✔
124
  setAllTooltipsReadState,
2✔
125
  setTooltipReadState,
2✔
126
  ...props
2✔
127
}: HelpTooltipProps & Omit<MenderTooltipClickableProps, 'children' | 'title'>) => {
2✔
128
  const [isOpen, setIsOpen] = useState(false);
965✔
129
  const debouncedIsOpen = useDebounce(isOpen, TIMEOUTS.threeSeconds);
965✔
130
  const { classes } = useStyles();
965✔
131
  const { Component, SpecialComponent, isRelevant, readState } = tooltip;
965✔
132

2✔
133
  useEffect(() => {
965✔
134
    if (!debouncedIsOpen) {
71!
135
      return;
71✔
136
    }
2✔
137
    setTooltipReadState({ id, persist: true, readState: READ_STATES.read });
2✔
138
  }, [debouncedIsOpen, id, setTooltipReadState]);
2✔
139

2✔
140
  const onReadAllClick = () => setAllTooltipsReadState({ readState: READ_STATES.read, tooltipIds: Object.keys(HELPTOOLTIPS) });
965✔
141

2✔
142
  const title = SpecialComponent ? (
965✔
143
    <SpecialComponent device={device} {...contentProps} />
2✔
144
  ) : (
2✔
UNCOV
145
    <TooltipWrapper content={<Component device={device} {...contentProps} />} onClose={() => setIsOpen(false)} onReadAll={onReadAllClick} />
2✔
146
  );
2✔
147

2✔
148
  if (!isRelevant({ device, ...contentProps })) {
965!
149
    return null;
2✔
150
  }
2✔
151

2✔
152
  const className = tooltipStateStyleMap[readState] ?? tooltipStateStyleMap.default;
965✔
153
  return (
965✔
154
    <MenderTooltipClickable className={isOpen ? 'muted' : ''} title={title} visibility={isOpen} onOpenChange={setIsOpen} {...props}>
2!
155
      <div className="relative">
2✔
156
        {icon || <HelpIcon className={`${classes.icon} ${className}`} color="primary" />}
2✔
157
        <div className={`${classes.iconAura} ${className}`} />
2✔
158
      </div>
2✔
159
    </MenderTooltipClickable>
2✔
160
  );
2✔
161
};
2✔
162

2✔
163
type MenderHelpTooltipProps = {
2✔
164
  contentProps?: Record<string, unknown>;
2✔
165
  id: string;
2✔
166
} & Omit<HelpTooltipProps, 'setAllTooltipsReadState' | 'setTooltipReadState' | 'tooltip'>;
2✔
167

2✔
168
export const MenderHelpTooltip = (props: MenderHelpTooltipProps) => {
63✔
169
  const { id, contentProps = {} } = props;
960✔
170
  const tooltipsById = useSelector(getTooltipsState);
960✔
171
  const dispatch = useDispatch();
960✔
172
  const device = useSelector(state => getDeviceById(state, contentProps.deviceId));
2,151✔
173
  const { readState = READ_STATES.unread } = tooltipsById[id] || {};
960✔
174
  const { Component, SpecialComponent, isRelevant = yes } = HELPTOOLTIPS[id];
960✔
175

2✔
176
  const onSetTooltipReadState = useCallback((...args) => dispatch(setTooltipReadState(...args)), [dispatch]);
960✔
177
  const onSetAllTooltipsReadState = state => dispatch(setAllTooltipsReadState(state));
960✔
178

2✔
179
  return (
960✔
180
    <HelpTooltip
2✔
181
      setAllTooltipsReadState={onSetAllTooltipsReadState}
2✔
182
      setTooltipReadState={onSetTooltipReadState}
2✔
183
      device={device}
2✔
184
      tooltip={{ Component, SpecialComponent, isRelevant, readState }}
2✔
185
      {...props}
2✔
186
    />
2✔
187
  );
2✔
188
};
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