• 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

99.11
/frontend/src/js/components/deployments/deployment-report/AiLogAnalysis.tsx
1
// Copyright 2025 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 { useRef, useState } from 'react';
2✔
15
import { useSelector } from 'react-redux';
2✔
16
import { Link } from 'react-router-dom';
2✔
17

2✔
18
import {
2✔
19
  AutoAwesomeOutlined as AutoAwesomeIcon,
2✔
20
  ContentCopyOutlined as CopyPasteIcon,
2✔
21
  ThumbDownOutlined as ThumbDownIcon,
2✔
22
  ThumbUpOutlined as ThumbUpIcon
2✔
23
} from '@mui/icons-material';
2✔
24
import { Alert, Button, IconButton, Slide, Typography, alpha } from '@mui/material';
2✔
25
import { makeStyles } from 'tss-react/mui';
2✔
26

2✔
27
import { SparkleAnimation } from '@northern.tech/common-ui/Sparkles';
2✔
28
import { getGlobalSettings, getUserRoles } from '@northern.tech/store/selectors';
2✔
29
import { useAppDispatch } from '@northern.tech/store/store';
2✔
30
import { generateDeploymentLogAnalysis, submitUserFeedback } from '@northern.tech/store/thunks';
2✔
31
import copy from 'copy-to-clipboard';
2✔
32
import dayjs from 'dayjs';
2✔
33
import duration from 'dayjs/plugin/duration.js';
2✔
34
import relativeTime from 'dayjs/plugin/relativeTime.js';
2✔
35
import { MarkdownToJSX } from 'markdown-to-jsx';
2✔
36
import MuiMarkdown, { defaultOverrides } from 'mui-markdown';
2✔
37

2✔
38
dayjs.extend(duration);
13✔
39
dayjs.extend(relativeTime);
13✔
40

2✔
41
const useStyles = makeStyles()(theme => ({
13✔
42
  alert: { display: 'inline-flex', marginBottom: theme.spacing(2) },
2✔
43
  analysisResult: {
2✔
44
    backgroundColor: alpha(theme.palette.secondary.light, 0.08),
2✔
45
    border: `1px solid ${theme.palette.secondary.light}`,
2✔
46
    borderRadius: theme.shape.borderRadius,
2✔
47
    padding: theme.spacing(2)
2✔
48
  }
2✔
49
}));
2✔
50

2✔
51
const markDownStyleOverrides: MarkdownToJSX.Overrides = {
13✔
52
  ...defaultOverrides,
2✔
53
  h1: { component: 'b' },
2✔
54
  h2: { component: 'b' },
2✔
55
  h3: { component: 'b' }
2✔
56
};
2✔
57

2✔
58
interface AiLogAnalysisProps {
2✔
59
  deployment: { devices?: Record<string, unknown>; id: string };
2✔
60
  deviceId: string;
2✔
61
}
2✔
62

2✔
63
const Header = () => (
13✔
64
  <div className="flexbox center-aligned margin-bottom-small">
12✔
65
    <AutoAwesomeIcon className="margin-right-small" />
2✔
66
    <Typography variant="h6">AI summary (experimental)</Typography>
2✔
67
  </div>
2✔
68
);
2✔
69

2✔
70
const FeedbackSection = ({ deploymentId, deviceId }) => {
13✔
71
  const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);
5✔
72
  const dispatch = useAppDispatch();
5✔
73
  const handleFeedback = isHelpful => {
5✔
74
    dispatch(submitUserFeedback({ formId: 'feat.ai', feedback: { useful: isHelpful, deployment_id: deploymentId, device_id: deviceId } }));
3✔
75
    setFeedbackSubmitted(true);
3✔
76
  };
2✔
77

2✔
78
  return (
5✔
79
    <div className="flexbox center-aligned">
2✔
80
      {feedbackSubmitted ? (
2✔
81
        <Typography variant="body2" color="text.secondary">
2✔
82
          Thank you for your feedback!
2✔
83
        </Typography>
2✔
84
      ) : (
2✔
85
        <>
2✔
86
          <Typography className="margin-right-small" variant="body2" color="text.secondary">
2✔
87
            Was this helpful?
2✔
88
          </Typography>
2✔
89
          <IconButton size="small" aria-label="thumbs-up" onClick={() => handleFeedback(true)}>
3✔
90
            <ThumbUpIcon fontSize="small" />
2✔
91
          </IconButton>
2✔
UNCOV
92
          <IconButton size="small" aria-label="thumbs-down" onClick={() => handleFeedback(false)}>
2✔
93
            <ThumbDownIcon fontSize="small" />
2✔
94
          </IconButton>
2✔
95
        </>
2✔
96
      )}
2✔
97
    </div>
2✔
98
  );
2✔
99
};
2✔
100

2✔
101
const AiNotEnabledNote = ({ className, isAdmin }) => (
13✔
102
  <Alert className={className} severity="info">
3✔
103
    AI features are not enabled for this organization.
2✔
104
    {isAdmin ? (
2!
105
      <>
2✔
106
        Go to the settings page to enable this feature.
2✔
107
        <Link className="margin-left-small" to="/settings/global-settings">
2✔
108
          Settings
2✔
109
        </Link>
2✔
110
      </>
2✔
111
    ) : (
2✔
112
      'Contact your admin to enable this feature.'
2✔
113
    )}
2✔
114
  </Alert>
2✔
115
);
2✔
116

2✔
117
export const AiLogAnalysis = ({ deployment, deviceId }: AiLogAnalysisProps) => {
13✔
118
  const { classes } = useStyles();
12✔
119
  const dispatch = useAppDispatch();
12✔
120
  const [isAnalyzing, setIsAnalyzing] = useState(false);
12✔
121
  const [analysisResult, setAnalysisResult] = useState('');
12✔
122
  const [analysisError, setAnalysisError] = useState<string | null>(null);
12✔
123
  const { aiFeatures = {} } = useSelector(getGlobalSettings);
12✔
124
  const { isAdmin } = useSelector(getUserRoles);
12✔
125

2✔
126
  const { enabled: isAiEnabled } = aiFeatures;
12✔
127
  const slideOutRef = useRef<HTMLDivElement | null>(null);
12✔
128

2✔
129
  const onGenerateAnalysisClick = async () => {
12✔
130
    setIsAnalyzing(true);
5✔
131
    setAnalysisError(null);
5✔
132
    try {
5✔
133
      const result = await dispatch(generateDeploymentLogAnalysis({ deploymentId: deployment.id, deviceId })).unwrap();
5✔
134
      setAnalysisResult(result);
4✔
135
      setIsAnalyzing(false);
4✔
136
    } catch (error) {
2✔
137
      console.error('Error generating analysis:', error);
3✔
138
      setIsAnalyzing(false);
3✔
139
      if (error.status === 429) {
3!
140
        const waitingTime = dayjs.duration(error.request.getResponseHeader('Retry-After'), 'seconds').humanize();
3✔
141
        setAnalysisError(`You have reached your limit of 50 AI requests per day. Please try again in about ${waitingTime}.`);
3✔
142
        return;
3✔
143
      }
2✔
144
      setAnalysisError('Failed to generate analysis. Please try again.');
2✔
145
    }
2✔
146
  };
2✔
147

2✔
UNCOV
148
  const onCopyAnalysisClick = () => copy(analysisResult);
2✔
149

2✔
150
  return (
12✔
151
    <div className="padding-top-small padding-bottom-small">
2✔
152
      <Header />
2✔
153
      <div className="flexbox center-aligned">
2✔
154
        <Button className="margin-right-small" disabled={!isAiEnabled || isAnalyzing} onClick={onGenerateAnalysisClick} variant="contained">
2✔
155
          {isAnalyzing ? 'Generating summary...' : 'Generate summary'}
2✔
156
        </Button>
2✔
157
      </div>
2✔
158
      <div className="margin-top-small">
2✔
159
        {!isAiEnabled && <AiNotEnabledNote className={classes.alert} isAdmin={isAdmin} />}
2✔
160
        {analysisError && (
2✔
161
          <Alert className={classes.alert} severity="error">
2✔
162
            {analysisError}
2✔
163
          </Alert>
2✔
164
        )}
2✔
165
      </div>
2✔
166
      <div ref={slideOutRef}>
2✔
167
        <Slide in={!!analysisResult || isAnalyzing} container={slideOutRef.current}>
2✔
168
          <div className={`fadeInSlow ${classes.analysisResult}`}>
2✔
169
            {!analysisResult ? (
2✔
170
              <div className="flexbox">
2✔
171
                <SparkleAnimation className="margin-right-x-small" />
2✔
172
                <Typography variant="body1" color="text.secondary">
2✔
173
                  Thinking...
2✔
174
                </Typography>
2✔
175
              </div>
2✔
176
            ) : (
2✔
177
              <>
2✔
178
                <>
2✔
179
                  <div className="flexbox space-between center-aligned">
2✔
180
                    <div className="flexbox center-aligned">
2✔
181
                      <AutoAwesomeIcon className="margin-right-small" />
2✔
182
                      Summary of Deployment Failure:
2✔
183
                    </div>
2✔
184
                    <IconButton onClick={onCopyAnalysisClick}>
2✔
185
                      <CopyPasteIcon />
2✔
186
                    </IconButton>
2✔
187
                  </div>
2✔
188
                  <div className="margin">
2✔
189
                    <MuiMarkdown overrides={markDownStyleOverrides}>{analysisResult}</MuiMarkdown>
2✔
190
                  </div>
2✔
191
                </>
2✔
192
                <div className="flexbox center-aligned space-between">
2✔
193
                  <FeedbackSection deploymentId={deployment.id} deviceId={deviceId} />
2✔
194
                  <Typography variant="body2" color="text.secondary">
2✔
195
                    AI-generated information can be inaccurate, so always verify it before taking action.
2✔
196
                  </Typography>
2✔
197
                </div>
2✔
198
              </>
2✔
199
            )}
2✔
200
          </div>
2✔
201
        </Slide>
2✔
202
      </div>
2✔
203
    </div>
2✔
204
  );
2✔
205
};
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