@backstage-community/plugin-code-coverage 0.2.28 → 0.2.29
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.
- package/CHANGELOG.md +7 -0
- package/dist/api.esm.js +48 -0
- package/dist/api.esm.js.map +1 -0
- package/dist/components/CodeCoveragePage/CodeCoveragePage.esm.js +11 -0
- package/dist/components/CodeCoveragePage/CodeCoveragePage.esm.js.map +1 -0
- package/dist/components/CoverageHistoryChart/CoverageHistoryChart.esm.js +112 -0
- package/dist/components/CoverageHistoryChart/CoverageHistoryChart.esm.js.map +1 -0
- package/dist/components/FileExplorer/CodeRow.esm.js +78 -0
- package/dist/components/FileExplorer/CodeRow.esm.js.map +1 -0
- package/dist/components/FileExplorer/FileContent.esm.js +89 -0
- package/dist/components/FileExplorer/FileContent.esm.js.map +1 -0
- package/dist/components/FileExplorer/FileExplorer.esm.js +239 -0
- package/dist/components/FileExplorer/FileExplorer.esm.js.map +1 -0
- package/dist/components/FileExplorer/Highlighter.esm.js +30 -0
- package/dist/components/FileExplorer/Highlighter.esm.js.map +1 -0
- package/dist/components/Router.esm.js +17 -0
- package/dist/components/Router.esm.js.map +1 -0
- package/dist/index.esm.js +3 -618
- package/dist/index.esm.js.map +1 -1
- package/dist/plugin.esm.js +27 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +14 -9
package/dist/index.esm.js
CHANGED
|
@@ -1,622 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import Box from '@material-ui/core/Box';
|
|
4
|
-
import Card from '@material-ui/core/Card';
|
|
5
|
-
import CardContent from '@material-ui/core/CardContent';
|
|
6
|
-
import CardHeader from '@material-ui/core/CardHeader';
|
|
7
|
-
import Typography from '@material-ui/core/Typography';
|
|
8
|
-
import { makeStyles } from '@material-ui/core/styles';
|
|
9
|
-
import TrendingDownIcon from '@material-ui/icons/TrendingDown';
|
|
10
|
-
import TrendingFlatIcon from '@material-ui/icons/TrendingFlat';
|
|
11
|
-
import TrendingUpIcon from '@material-ui/icons/TrendingUp';
|
|
12
|
-
import Alert from '@material-ui/lab/Alert';
|
|
13
|
-
import useAsync from 'react-use/esm/useAsync';
|
|
14
|
-
import { ResponsiveContainer, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Line } from 'recharts';
|
|
15
|
-
import { stringifyEntityRef } from '@backstage/catalog-model';
|
|
16
|
-
import { ResponseError } from '@backstage/errors';
|
|
17
|
-
import { createApiRef, useApi, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
|
|
18
|
-
import { Progress, ResponseErrorPanel, Table, Page, Content, ContentHeader } from '@backstage/core-components';
|
|
19
|
-
import { DateTime } from 'luxon';
|
|
20
|
-
import Modal from '@material-ui/core/Modal';
|
|
21
|
-
import FolderIcon from '@material-ui/icons/Folder';
|
|
22
|
-
import FileOutlinedIcon from '@material-ui/icons/InsertDriveFileOutlined';
|
|
23
|
-
import Paper from '@material-ui/core/Paper';
|
|
24
|
-
import 'highlight.js/styles/mono-blue.css';
|
|
25
|
-
import { highlight } from 'highlight.js';
|
|
26
|
-
|
|
27
|
-
var __defProp = Object.defineProperty;
|
|
28
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
29
|
-
var __publicField = (obj, key, value) => {
|
|
30
|
-
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
31
|
-
return value;
|
|
32
|
-
};
|
|
33
|
-
const codeCoverageApiRef = createApiRef({
|
|
34
|
-
id: "plugin.code-coverage.service"
|
|
35
|
-
});
|
|
36
|
-
class CodeCoverageRestApi {
|
|
37
|
-
constructor(options) {
|
|
38
|
-
__publicField(this, "discoveryApi");
|
|
39
|
-
__publicField(this, "fetchApi");
|
|
40
|
-
this.discoveryApi = options.discoveryApi;
|
|
41
|
-
this.fetchApi = options.fetchApi;
|
|
42
|
-
}
|
|
43
|
-
async fetch(path) {
|
|
44
|
-
var _a;
|
|
45
|
-
const url = await this.discoveryApi.getBaseUrl("code-coverage");
|
|
46
|
-
const resp = await this.fetchApi.fetch(`${url}${path}`);
|
|
47
|
-
if (!resp.ok) {
|
|
48
|
-
throw await ResponseError.fromResponse(resp);
|
|
49
|
-
}
|
|
50
|
-
if ((_a = resp.headers.get("content-type")) == null ? void 0 : _a.includes("application/json")) {
|
|
51
|
-
return await resp.json();
|
|
52
|
-
}
|
|
53
|
-
return await resp.text();
|
|
54
|
-
}
|
|
55
|
-
async getCoverageForEntity(entityName) {
|
|
56
|
-
const entity = encodeURIComponent(stringifyEntityRef(entityName));
|
|
57
|
-
return await this.fetch(
|
|
58
|
-
`/report?entity=${entity}`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
async getFileContentFromEntity(entityName, filePath) {
|
|
62
|
-
const entity = encodeURIComponent(stringifyEntityRef(entityName));
|
|
63
|
-
return await this.fetch(
|
|
64
|
-
`/file-content?entity=${entity}&path=${encodeURI(filePath)}`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
async getCoverageHistoryForEntity(entityName, limit) {
|
|
68
|
-
const entity = encodeURIComponent(stringifyEntityRef(entityName));
|
|
69
|
-
const hasValidLimit = limit && limit > 0;
|
|
70
|
-
return await this.fetch(
|
|
71
|
-
`/history?entity=${entity}${hasValidLimit ? `&limit=${encodeURIComponent(String(limit))}` : ""}`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const useStyles$3 = makeStyles((theme) => ({
|
|
77
|
-
trendDown: {
|
|
78
|
-
color: theme.palette.status.warning
|
|
79
|
-
},
|
|
80
|
-
trendUp: {
|
|
81
|
-
color: theme.palette.status.ok
|
|
82
|
-
}
|
|
83
|
-
}));
|
|
84
|
-
const getTrendIcon = (trend, classes) => {
|
|
85
|
-
switch (true) {
|
|
86
|
-
case trend > 0:
|
|
87
|
-
return /* @__PURE__ */ React.createElement(TrendingUpIcon, { className: classes.trendUp });
|
|
88
|
-
case trend < 0:
|
|
89
|
-
return /* @__PURE__ */ React.createElement(TrendingDownIcon, { className: classes.trendDown });
|
|
90
|
-
case trend === 0:
|
|
91
|
-
default:
|
|
92
|
-
return /* @__PURE__ */ React.createElement(TrendingFlatIcon, null);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
function formatDateToHuman(timeStamp) {
|
|
96
|
-
return DateTime.fromMillis(Number(timeStamp)).toLocaleString(
|
|
97
|
-
DateTime.DATETIME_MED
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
const CoverageHistoryChart = () => {
|
|
101
|
-
const { entity } = useEntity();
|
|
102
|
-
const codeCoverageApi = useApi(codeCoverageApiRef);
|
|
103
|
-
const {
|
|
104
|
-
loading: loadingHistory,
|
|
105
|
-
error: errorHistory,
|
|
106
|
-
value: valueHistory
|
|
107
|
-
} = useAsync(
|
|
108
|
-
async () => await codeCoverageApi.getCoverageHistoryForEntity({
|
|
109
|
-
kind: entity.kind,
|
|
110
|
-
namespace: entity.metadata.namespace || "default",
|
|
111
|
-
name: entity.metadata.name
|
|
112
|
-
})
|
|
113
|
-
);
|
|
114
|
-
const classes = useStyles$3();
|
|
115
|
-
if (loadingHistory) {
|
|
116
|
-
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
117
|
-
}
|
|
118
|
-
if (errorHistory) {
|
|
119
|
-
return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error: errorHistory });
|
|
120
|
-
} else if (!valueHistory) {
|
|
121
|
-
return /* @__PURE__ */ React.createElement(Alert, { severity: "warning" }, "No history found.");
|
|
122
|
-
}
|
|
123
|
-
if (!valueHistory.history.length) {
|
|
124
|
-
return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "History" }), /* @__PURE__ */ React.createElement(CardContent, null, "No coverage history found"));
|
|
125
|
-
}
|
|
126
|
-
const [oldestCoverage] = valueHistory.history.slice(-1);
|
|
127
|
-
const latestCoverage = valueHistory.history[0];
|
|
128
|
-
const getTrendForCoverage = (type) => {
|
|
129
|
-
if (!oldestCoverage[type].percentage) {
|
|
130
|
-
return 0;
|
|
131
|
-
}
|
|
132
|
-
return (latestCoverage[type].percentage - oldestCoverage[type].percentage) / oldestCoverage[type].percentage * 100;
|
|
133
|
-
};
|
|
134
|
-
const lineTrend = getTrendForCoverage("line");
|
|
135
|
-
const branchTrend = getTrendForCoverage("branch");
|
|
136
|
-
return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "History" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Box, { px: 6, display: "flex" }, /* @__PURE__ */ React.createElement(Box, { display: "flex", mr: 4 }, getTrendIcon(lineTrend, classes), /* @__PURE__ */ React.createElement(Typography, null, "Current line: ", latestCoverage.line.percentage, "%", /* @__PURE__ */ React.createElement("br", null), "(", Math.floor(lineTrend), "% change over ", valueHistory.history.length, " ", "builds)")), /* @__PURE__ */ React.createElement(Box, { display: "flex" }, getTrendIcon(branchTrend, classes), /* @__PURE__ */ React.createElement(Typography, null, "Current branch: ", latestCoverage.branch.percentage, "%", /* @__PURE__ */ React.createElement("br", null), "(", Math.floor(branchTrend), "% change over", " ", valueHistory.history.length, " builds)"))), /* @__PURE__ */ React.createElement(ResponsiveContainer, { width: "100%", height: 300 }, /* @__PURE__ */ React.createElement(
|
|
137
|
-
LineChart,
|
|
138
|
-
{
|
|
139
|
-
data: valueHistory.history,
|
|
140
|
-
margin: { right: 48, top: 32 }
|
|
141
|
-
},
|
|
142
|
-
/* @__PURE__ */ React.createElement(CartesianGrid, { strokeDasharray: "3 3" }),
|
|
143
|
-
/* @__PURE__ */ React.createElement(
|
|
144
|
-
XAxis,
|
|
145
|
-
{
|
|
146
|
-
dataKey: "timestamp",
|
|
147
|
-
tickFormatter: formatDateToHuman,
|
|
148
|
-
reversed: true
|
|
149
|
-
}
|
|
150
|
-
),
|
|
151
|
-
/* @__PURE__ */ React.createElement(YAxis, { dataKey: "line.percentage" }),
|
|
152
|
-
/* @__PURE__ */ React.createElement(YAxis, { dataKey: "branch.percentage" }),
|
|
153
|
-
/* @__PURE__ */ React.createElement(Tooltip, { labelFormatter: formatDateToHuman }),
|
|
154
|
-
/* @__PURE__ */ React.createElement(Legend, null),
|
|
155
|
-
/* @__PURE__ */ React.createElement(
|
|
156
|
-
Line,
|
|
157
|
-
{
|
|
158
|
-
type: "monotone",
|
|
159
|
-
dataKey: "branch.percentage",
|
|
160
|
-
stroke: "#8884d8"
|
|
161
|
-
}
|
|
162
|
-
),
|
|
163
|
-
/* @__PURE__ */ React.createElement(Line, { type: "monotone", dataKey: "line.percentage", stroke: "#82ca9d" })
|
|
164
|
-
))));
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const useStyles$2 = makeStyles((theme) => ({
|
|
168
|
-
lineNumberCell: {
|
|
169
|
-
color: `${theme.palette.grey[500]}`,
|
|
170
|
-
fontSize: "90%",
|
|
171
|
-
borderRight: `1px solid ${theme.palette.grey[500]}`,
|
|
172
|
-
paddingRight: theme.spacing(1),
|
|
173
|
-
textAlign: "right"
|
|
174
|
-
},
|
|
175
|
-
hitCountCell: {
|
|
176
|
-
width: "50px",
|
|
177
|
-
borderRight: `1px solid ${theme.palette.grey[500]}`,
|
|
178
|
-
textAlign: "center",
|
|
179
|
-
color: theme.palette.common.white,
|
|
180
|
-
// need to enforce this color since it needs to stand out against colored background
|
|
181
|
-
paddingLeft: theme.spacing(1),
|
|
182
|
-
paddingRight: theme.spacing(1)
|
|
183
|
-
},
|
|
184
|
-
countRoundedRectangle: {
|
|
185
|
-
borderRadius: "45px",
|
|
186
|
-
fontSize: "90%",
|
|
187
|
-
padding: "1px 3px 1px 3px",
|
|
188
|
-
width: "50px"
|
|
189
|
-
},
|
|
190
|
-
hitCountRoundedRectangle: {
|
|
191
|
-
backgroundColor: `${theme.palette.success.main}`,
|
|
192
|
-
color: `${theme.palette.success.contrastText}`
|
|
193
|
-
},
|
|
194
|
-
notHitCountRoundedRectangle: {
|
|
195
|
-
backgroundColor: `${theme.palette.error.main}`,
|
|
196
|
-
color: `${theme.palette.error.contrastText}`
|
|
197
|
-
},
|
|
198
|
-
codeLine: {
|
|
199
|
-
paddingLeft: `${theme.spacing(1)}`,
|
|
200
|
-
whiteSpace: "pre",
|
|
201
|
-
fontSize: "90%"
|
|
202
|
-
},
|
|
203
|
-
hitCodeLine: {
|
|
204
|
-
backgroundColor: `${theme.palette.success.main}`,
|
|
205
|
-
color: `${theme.palette.success.contrastText}`
|
|
206
|
-
},
|
|
207
|
-
notHitCodeLine: {
|
|
208
|
-
backgroundColor: `${theme.palette.error.main}`,
|
|
209
|
-
color: `${theme.palette.error.contrastText}`
|
|
210
|
-
}
|
|
211
|
-
}));
|
|
212
|
-
const CodeRow = ({
|
|
213
|
-
lineNumber,
|
|
214
|
-
lineContent,
|
|
215
|
-
lineHits = null
|
|
216
|
-
}) => {
|
|
217
|
-
const classes = useStyles$2();
|
|
218
|
-
const hitCountRoundedRectangleClass = [classes.countRoundedRectangle];
|
|
219
|
-
const lineContentClass = [classes.codeLine];
|
|
220
|
-
let hitRoundedRectangle = null;
|
|
221
|
-
if (lineHits !== null) {
|
|
222
|
-
if (lineHits > 0) {
|
|
223
|
-
hitCountRoundedRectangleClass.push(classes.hitCountRoundedRectangle);
|
|
224
|
-
lineContentClass.push(classes.hitCodeLine);
|
|
225
|
-
} else {
|
|
226
|
-
hitCountRoundedRectangleClass.push(classes.notHitCountRoundedRectangle);
|
|
227
|
-
lineContentClass.push(classes.notHitCodeLine);
|
|
228
|
-
}
|
|
229
|
-
hitRoundedRectangle = /* @__PURE__ */ React.createElement("div", { className: hitCountRoundedRectangleClass.join(" ") }, lineHits);
|
|
230
|
-
}
|
|
231
|
-
return /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { className: classes.lineNumberCell }, lineNumber), /* @__PURE__ */ React.createElement("td", { className: classes.hitCountCell }, hitRoundedRectangle), /* @__PURE__ */ React.createElement(
|
|
232
|
-
"td",
|
|
233
|
-
{
|
|
234
|
-
className: lineContentClass.join(" "),
|
|
235
|
-
dangerouslySetInnerHTML: { __html: lineContent }
|
|
236
|
-
}
|
|
237
|
-
));
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const highlightLines = (fileExtension, lines) => {
|
|
241
|
-
const formattedLines = [];
|
|
242
|
-
let fileformat = fileExtension;
|
|
243
|
-
if (fileExtension === "m") {
|
|
244
|
-
fileformat = "objectivec";
|
|
245
|
-
}
|
|
246
|
-
if (fileExtension === "tsx") {
|
|
247
|
-
fileformat = "typescript";
|
|
248
|
-
}
|
|
249
|
-
if (fileExtension === "jsx") {
|
|
250
|
-
fileformat = "javascript";
|
|
251
|
-
}
|
|
252
|
-
if (fileExtension === "kt") {
|
|
253
|
-
fileformat = "kotlin";
|
|
254
|
-
}
|
|
255
|
-
lines.forEach((line) => {
|
|
256
|
-
const result = highlight(line, {
|
|
257
|
-
language: fileformat,
|
|
258
|
-
ignoreIllegals: true
|
|
259
|
-
});
|
|
260
|
-
formattedLines.push(result.value);
|
|
261
|
-
});
|
|
262
|
-
return formattedLines;
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const useStyles$1 = makeStyles((theme) => ({
|
|
266
|
-
paper: {
|
|
267
|
-
margin: "auto",
|
|
268
|
-
top: "2em",
|
|
269
|
-
width: "80%",
|
|
270
|
-
border: `2px solid ${theme.palette.common.black}`,
|
|
271
|
-
boxShadow: theme.shadows[5],
|
|
272
|
-
padding: theme.spacing(2, 4, 3),
|
|
273
|
-
overflow: "scroll"
|
|
274
|
-
},
|
|
275
|
-
coverageFileViewTable: {
|
|
276
|
-
borderSpacing: "0px",
|
|
277
|
-
width: "80%",
|
|
278
|
-
marginTop: theme.spacing(2)
|
|
279
|
-
}
|
|
280
|
-
}));
|
|
281
|
-
const FormattedLines = ({
|
|
282
|
-
highlightedLines,
|
|
283
|
-
lineHits
|
|
284
|
-
}) => {
|
|
285
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, highlightedLines.map((lineContent, idx) => {
|
|
286
|
-
const line = idx + 1;
|
|
287
|
-
return /* @__PURE__ */ React.createElement(
|
|
288
|
-
CodeRow,
|
|
289
|
-
{
|
|
290
|
-
key: line,
|
|
291
|
-
lineNumber: line,
|
|
292
|
-
lineContent,
|
|
293
|
-
lineHits: lineHits[line]
|
|
294
|
-
}
|
|
295
|
-
);
|
|
296
|
-
}));
|
|
297
|
-
};
|
|
298
|
-
const FileContent = ({ filename, coverage }) => {
|
|
299
|
-
const { entity } = useEntity();
|
|
300
|
-
const codeCoverageApi = useApi(codeCoverageApiRef);
|
|
301
|
-
const { loading, error, value } = useAsync(
|
|
302
|
-
async () => await codeCoverageApi.getFileContentFromEntity(
|
|
303
|
-
{
|
|
304
|
-
kind: entity.kind,
|
|
305
|
-
namespace: entity.metadata.namespace || "default",
|
|
306
|
-
name: entity.metadata.name
|
|
307
|
-
},
|
|
308
|
-
filename
|
|
309
|
-
),
|
|
310
|
-
[entity]
|
|
311
|
-
);
|
|
312
|
-
const classes = useStyles$1();
|
|
313
|
-
if (loading) {
|
|
314
|
-
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
315
|
-
}
|
|
316
|
-
if (error) {
|
|
317
|
-
return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error });
|
|
318
|
-
}
|
|
319
|
-
if (!value) {
|
|
320
|
-
return /* @__PURE__ */ React.createElement(Alert, { severity: "warning" }, "Unable to retrieve file content for ", filename);
|
|
321
|
-
}
|
|
322
|
-
const [language] = filename.split(".").slice(-1);
|
|
323
|
-
const highlightedLines = highlightLines(language, value.split("\n"));
|
|
324
|
-
const lineHits = Object.entries(coverage.lineHits).reduce(
|
|
325
|
-
(acc, next) => {
|
|
326
|
-
acc[next[0]] = next[1];
|
|
327
|
-
return acc;
|
|
328
|
-
},
|
|
329
|
-
{}
|
|
330
|
-
);
|
|
331
|
-
return /* @__PURE__ */ React.createElement(Paper, { variant: "outlined", className: classes.paper }, /* @__PURE__ */ React.createElement("table", { className: classes.coverageFileViewTable }, /* @__PURE__ */ React.createElement("tbody", null, /* @__PURE__ */ React.createElement(
|
|
332
|
-
FormattedLines,
|
|
333
|
-
{
|
|
334
|
-
highlightedLines,
|
|
335
|
-
lineHits
|
|
336
|
-
}
|
|
337
|
-
))));
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const useStyles = makeStyles((theme) => ({
|
|
341
|
-
container: {
|
|
342
|
-
marginTop: theme.spacing(2)
|
|
343
|
-
},
|
|
344
|
-
icon: {
|
|
345
|
-
marginRight: theme.spacing(1)
|
|
346
|
-
},
|
|
347
|
-
link: {
|
|
348
|
-
color: theme.palette.primary.main,
|
|
349
|
-
cursor: "pointer"
|
|
350
|
-
}
|
|
351
|
-
}));
|
|
352
|
-
const groupByPath = (files) => {
|
|
353
|
-
const acc = {};
|
|
354
|
-
files.forEach((file) => {
|
|
355
|
-
const filename = file.filename;
|
|
356
|
-
if (!file.filename)
|
|
357
|
-
return;
|
|
358
|
-
const pathArray = filename == null ? void 0 : filename.split("/").filter((el) => el !== "");
|
|
359
|
-
if (pathArray) {
|
|
360
|
-
if (!acc.hasOwnProperty(pathArray[0])) {
|
|
361
|
-
acc[pathArray[0]] = [];
|
|
362
|
-
}
|
|
363
|
-
acc[pathArray[0]].push(file);
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
return acc;
|
|
367
|
-
};
|
|
368
|
-
const removeVisitedPathGroup = (files, pathGroup) => {
|
|
369
|
-
return files.map((file) => {
|
|
370
|
-
var _a;
|
|
371
|
-
return {
|
|
372
|
-
...file,
|
|
373
|
-
filename: file.filename ? file.filename.substring(
|
|
374
|
-
((_a = file.filename) == null ? void 0 : _a.indexOf(pathGroup)) + pathGroup.length + 1
|
|
375
|
-
) : file.filename
|
|
376
|
-
};
|
|
377
|
-
});
|
|
378
|
-
};
|
|
379
|
-
const buildFileStructure = (row) => {
|
|
380
|
-
const dataGroupedByPath = groupByPath(row.files);
|
|
381
|
-
row.files = Object.keys(dataGroupedByPath).map((pathGroup) => {
|
|
382
|
-
return buildFileStructure({
|
|
383
|
-
path: pathGroup,
|
|
384
|
-
files: dataGroupedByPath.hasOwnProperty("files") ? removeVisitedPathGroup(dataGroupedByPath.files, pathGroup) : removeVisitedPathGroup(dataGroupedByPath[pathGroup], pathGroup),
|
|
385
|
-
coverage: dataGroupedByPath[pathGroup].reduce(
|
|
386
|
-
(acc, cur) => acc + cur.coverage,
|
|
387
|
-
0
|
|
388
|
-
) / dataGroupedByPath[pathGroup].length,
|
|
389
|
-
missing: dataGroupedByPath[pathGroup].reduce(
|
|
390
|
-
(acc, cur) => acc + cur.missing,
|
|
391
|
-
0
|
|
392
|
-
),
|
|
393
|
-
tracked: dataGroupedByPath[pathGroup].reduce(
|
|
394
|
-
(acc, cur) => acc + cur.tracked,
|
|
395
|
-
0
|
|
396
|
-
)
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
return row;
|
|
400
|
-
};
|
|
401
|
-
const formatInitialData = (value) => {
|
|
402
|
-
return buildFileStructure({
|
|
403
|
-
path: "",
|
|
404
|
-
coverage: value.aggregate.line.percentage,
|
|
405
|
-
missing: value.aggregate.line.missed,
|
|
406
|
-
tracked: value.aggregate.line.available,
|
|
407
|
-
files: value.files.map((fc) => {
|
|
408
|
-
return {
|
|
409
|
-
path: "",
|
|
410
|
-
filename: fc.filename,
|
|
411
|
-
coverage: Math.floor(
|
|
412
|
-
Object.values(fc.lineHits).filter((hits) => hits > 0).length / Object.values(fc.lineHits).length * 100
|
|
413
|
-
),
|
|
414
|
-
missing: Object.values(fc.lineHits).filter((hits) => !hits).length,
|
|
415
|
-
tracked: Object.values(fc.lineHits).length
|
|
416
|
-
};
|
|
417
|
-
})
|
|
418
|
-
});
|
|
419
|
-
};
|
|
420
|
-
const getObjectsAtPath = (curData, path) => {
|
|
421
|
-
var _a;
|
|
422
|
-
let data = curData == null ? void 0 : curData.files;
|
|
423
|
-
for (const fragment of path) {
|
|
424
|
-
data = (_a = data == null ? void 0 : data.find((d) => d.path === fragment)) == null ? void 0 : _a.files;
|
|
425
|
-
}
|
|
426
|
-
return data;
|
|
427
|
-
};
|
|
428
|
-
const FileExplorer = () => {
|
|
429
|
-
const styles = useStyles();
|
|
430
|
-
const { entity } = useEntity();
|
|
431
|
-
const [curData, setCurData] = useState();
|
|
432
|
-
const [tableData, setTableData] = useState();
|
|
433
|
-
const [curPath, setCurPath] = useState("");
|
|
434
|
-
const [modalOpen, setModalOpen] = useState(false);
|
|
435
|
-
const [curFile, setCurFile] = useState("");
|
|
436
|
-
const codeCoverageApi = useApi(codeCoverageApiRef);
|
|
437
|
-
const { loading, error, value } = useAsync(
|
|
438
|
-
async () => await codeCoverageApi.getCoverageForEntity({
|
|
439
|
-
kind: entity.kind,
|
|
440
|
-
namespace: entity.metadata.namespace || "default",
|
|
441
|
-
name: entity.metadata.name
|
|
442
|
-
})
|
|
443
|
-
);
|
|
444
|
-
useEffect(() => {
|
|
445
|
-
if (!value)
|
|
446
|
-
return;
|
|
447
|
-
const data = formatInitialData(value);
|
|
448
|
-
setCurData(data);
|
|
449
|
-
if (data.files)
|
|
450
|
-
setTableData(data.files);
|
|
451
|
-
}, [value]);
|
|
452
|
-
if (loading) {
|
|
453
|
-
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
454
|
-
} else if (error) {
|
|
455
|
-
return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error });
|
|
456
|
-
}
|
|
457
|
-
if (!value) {
|
|
458
|
-
return /* @__PURE__ */ React.createElement(Alert, { severity: "warning" }, "No code coverage found for ", humanizeEntityRef(entity));
|
|
459
|
-
}
|
|
460
|
-
const moveDownIntoPath = (path) => {
|
|
461
|
-
const nextPathData = tableData.find(
|
|
462
|
-
(d) => d.path === path
|
|
463
|
-
);
|
|
464
|
-
if (nextPathData && nextPathData.files) {
|
|
465
|
-
setTableData(nextPathData.files);
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
const moveUpIntoPath = (idx) => {
|
|
469
|
-
const path = curPath.split("/").slice(0, idx + 1);
|
|
470
|
-
setCurFile("");
|
|
471
|
-
setCurPath(path.join("/"));
|
|
472
|
-
setTableData(getObjectsAtPath(curData, path.slice(1)));
|
|
473
|
-
};
|
|
474
|
-
const columns = [
|
|
475
|
-
{
|
|
476
|
-
title: "Path",
|
|
477
|
-
type: "string",
|
|
478
|
-
field: "path",
|
|
479
|
-
render: (row) => {
|
|
480
|
-
var _a, _b;
|
|
481
|
-
return /* @__PURE__ */ React.createElement(
|
|
482
|
-
Box,
|
|
483
|
-
{
|
|
484
|
-
display: "flex",
|
|
485
|
-
alignItems: "center",
|
|
486
|
-
role: "button",
|
|
487
|
-
tabIndex: row.tableData.id,
|
|
488
|
-
className: styles.link,
|
|
489
|
-
onClick: () => {
|
|
490
|
-
var _a2;
|
|
491
|
-
if ((_a2 = row.files) == null ? void 0 : _a2.length) {
|
|
492
|
-
setCurPath(`${curPath}/${row.path}`);
|
|
493
|
-
moveDownIntoPath(row.path);
|
|
494
|
-
} else {
|
|
495
|
-
setCurFile(`${curPath.slice(1)}/${row.path}`);
|
|
496
|
-
setModalOpen(true);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
},
|
|
500
|
-
((_a = row.files) == null ? void 0 : _a.length) > 0 && /* @__PURE__ */ React.createElement(FolderIcon, { fontSize: "small", className: styles.icon }),
|
|
501
|
-
((_b = row.files) == null ? void 0 : _b.length) === 0 && /* @__PURE__ */ React.createElement(FileOutlinedIcon, { fontSize: "small", className: styles.icon }),
|
|
502
|
-
row.path
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
title: "Coverage",
|
|
508
|
-
type: "numeric",
|
|
509
|
-
field: "coverage",
|
|
510
|
-
render: (row) => `${row.coverage.toFixed(2)}%`
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
title: "Missing lines",
|
|
514
|
-
type: "numeric",
|
|
515
|
-
field: "missing"
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
title: "Tracked lines",
|
|
519
|
-
type: "numeric",
|
|
520
|
-
field: "tracked"
|
|
521
|
-
}
|
|
522
|
-
];
|
|
523
|
-
const pathArray = curPath.split("/");
|
|
524
|
-
const lastPathElementIndex = pathArray.length - 1;
|
|
525
|
-
const fileCoverage = value.files.find(
|
|
526
|
-
(f) => f.filename.endsWith(curFile)
|
|
527
|
-
);
|
|
528
|
-
if (!fileCoverage) {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
return /* @__PURE__ */ React.createElement(Box, { className: styles.container }, /* @__PURE__ */ React.createElement(
|
|
532
|
-
Table,
|
|
533
|
-
{
|
|
534
|
-
emptyContent: /* @__PURE__ */ React.createElement(React.Fragment, null, "No files found"),
|
|
535
|
-
data: tableData || [],
|
|
536
|
-
columns,
|
|
537
|
-
title: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, null, "Explore Files"), /* @__PURE__ */ React.createElement(
|
|
538
|
-
Box,
|
|
539
|
-
{
|
|
540
|
-
mt: 1,
|
|
541
|
-
style: {
|
|
542
|
-
fontSize: "0.875rem",
|
|
543
|
-
fontWeight: "normal",
|
|
544
|
-
display: "flex"
|
|
545
|
-
}
|
|
546
|
-
},
|
|
547
|
-
pathArray.map((pathElement, idx) => /* @__PURE__ */ React.createElement(Fragment, { key: pathElement || "root" }, /* @__PURE__ */ React.createElement(
|
|
548
|
-
"div",
|
|
549
|
-
{
|
|
550
|
-
role: "button",
|
|
551
|
-
tabIndex: idx,
|
|
552
|
-
className: idx !== lastPathElementIndex ? styles.link : void 0,
|
|
553
|
-
onKeyDown: () => moveUpIntoPath(idx),
|
|
554
|
-
onClick: () => moveUpIntoPath(idx)
|
|
555
|
-
},
|
|
556
|
-
pathElement || "root"
|
|
557
|
-
), idx !== lastPathElementIndex && /* @__PURE__ */ React.createElement("div", null, "\xA0/\xA0")))
|
|
558
|
-
))
|
|
559
|
-
}
|
|
560
|
-
), /* @__PURE__ */ React.createElement(
|
|
561
|
-
Modal,
|
|
562
|
-
{
|
|
563
|
-
open: modalOpen,
|
|
564
|
-
onClick: (event) => event.stopPropagation(),
|
|
565
|
-
onClose: () => setModalOpen(false),
|
|
566
|
-
style: { overflow: "scroll" }
|
|
567
|
-
},
|
|
568
|
-
/* @__PURE__ */ React.createElement(FileContent, { filename: curFile, coverage: fileCoverage })
|
|
569
|
-
));
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
const CodeCoveragePage = () => {
|
|
573
|
-
return /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "Code coverage" }), /* @__PURE__ */ React.createElement(CoverageHistoryChart, null), /* @__PURE__ */ React.createElement(FileExplorer, null)));
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
function isCodeCoverageAvailable(entity) {
|
|
577
|
-
var _a;
|
|
578
|
-
return Boolean((_a = entity.metadata.annotations) == null ? void 0 : _a["backstage.io/code-coverage"]);
|
|
579
|
-
}
|
|
580
|
-
const Router = () => {
|
|
581
|
-
const { entity } = useEntity();
|
|
582
|
-
if (!isCodeCoverageAvailable(entity)) {
|
|
583
|
-
return /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { annotation: "backstage.io/code-coverage" });
|
|
584
|
-
}
|
|
585
|
-
return /* @__PURE__ */ React.createElement(CodeCoveragePage, null);
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
var Router$1 = /*#__PURE__*/Object.freeze({
|
|
589
|
-
__proto__: null,
|
|
590
|
-
Router: Router,
|
|
591
|
-
isCodeCoverageAvailable: isCodeCoverageAvailable
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
const rootRouteRef = createRouteRef({
|
|
595
|
-
id: "code-coverage"
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
const codeCoveragePlugin = createPlugin({
|
|
599
|
-
id: "code-coverage",
|
|
600
|
-
routes: {
|
|
601
|
-
root: rootRouteRef
|
|
602
|
-
},
|
|
603
|
-
apis: [
|
|
604
|
-
createApiFactory({
|
|
605
|
-
api: codeCoverageApiRef,
|
|
606
|
-
deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
|
|
607
|
-
factory: ({ discoveryApi, fetchApi }) => new CodeCoverageRestApi({ discoveryApi, fetchApi })
|
|
608
|
-
})
|
|
609
|
-
]
|
|
610
|
-
});
|
|
611
|
-
const EntityCodeCoverageContent = codeCoveragePlugin.provide(
|
|
612
|
-
createRoutableExtension({
|
|
613
|
-
name: "EntityCodeCoverageContent",
|
|
614
|
-
component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.Router),
|
|
615
|
-
mountPoint: rootRouteRef
|
|
616
|
-
})
|
|
617
|
-
);
|
|
1
|
+
import { isCodeCoverageAvailable } from './components/Router.esm.js';
|
|
2
|
+
export { EntityCodeCoverageContent, codeCoveragePlugin } from './plugin.esm.js';
|
|
618
3
|
|
|
619
4
|
const isPluginApplicableToEntity = isCodeCoverageAvailable;
|
|
620
5
|
|
|
621
|
-
export {
|
|
6
|
+
export { isCodeCoverageAvailable, isPluginApplicableToEntity };
|
|
622
7
|
//# sourceMappingURL=index.esm.js.map
|