diff --git a/package-lock.json b/package-lock.json
index 063312f..c309f56 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4310,6 +4310,15 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-csv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@types/react-csv/-/react-csv-1.1.3.tgz",
+ "integrity": "sha512-dkEdyRvRpygSnNg4cyzYWSUjukIQ5lAtXJwc7BqyUfzww/Cv2dcAFGYd+sWTFpGiDNZMVPp6vVPLcAPvJID8Kg==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-dom": {
"version": "18.2.6",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.6.tgz",
@@ -12687,6 +12696,11 @@
"react": ">= 16.3.0"
}
},
+ "node_modules/react-csv": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
+ "integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
+ },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -15207,6 +15221,7 @@
"monaco-editor": "^0.40.0",
"react": "^18.2.0",
"react-countup": "^6.4.2",
+ "react-csv": "^2.2.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-icons": "^4.10.1",
@@ -15220,6 +15235,7 @@
"@types/lodash-es": "^4.17.8",
"@types/md5": "^2.3.2",
"@types/react": "^18.0.37",
+ "@types/react-csv": "^1.1.3",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
diff --git a/packages/app/package.json b/packages/app/package.json
index 7680ea8..057a608 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -21,6 +21,7 @@
"monaco-editor": "^0.40.0",
"react": "^18.2.0",
"react-countup": "^6.4.2",
+ "react-csv": "^2.2.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-icons": "^4.10.1",
@@ -34,6 +35,7 @@
"@types/lodash-es": "^4.17.8",
"@types/md5": "^2.3.2",
"@types/react": "^18.0.37",
+ "@types/react-csv": "^1.1.3",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
diff --git a/packages/app/src/report/actions/init-report-action.ts b/packages/app/src/report/actions/init-report-action.ts
index 8cd278c..e56ec30 100644
--- a/packages/app/src/report/actions/init-report-action.ts
+++ b/packages/app/src/report/actions/init-report-action.ts
@@ -19,8 +19,6 @@ export const initProgress = async (
case AnalysisStatus.Created:
subscribeToReportProgress(requestId);
break;
- case AnalysisStatus.Completed:
- break;
case AnalysisStatus.NotFound:
navigate("/");
return;
diff --git a/packages/app/src/report/components/ExportToCSV.tsx b/packages/app/src/report/components/ExportToCSV.tsx
new file mode 100644
index 0000000..5a97d4e
--- /dev/null
+++ b/packages/app/src/report/components/ExportToCSV.tsx
@@ -0,0 +1,47 @@
+import { IconButton, Theme, Tooltip, Zoom } from "@mui/material";
+import { FC } from "react";
+import { CSVLink } from "react-csv";
+import { MdCloudDownload } from "react-icons/md";
+import { AnalysisStatus } from "shared-types";
+import { makeStyles } from "tss-react/mui";
+import { useReportStore } from "../stores/fe-report-store";
+
+export const ExportToCSV: FC = () => {
+ const { classes } = useStyles();
+ const { status, allIssues, requestId } = useReportStore();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const useStyles = makeStyles()((theme: Theme) => ({
+ exportToCSV: {
+ display: "inline-flex",
+ alignItems: "center",
+ gap: theme.spacing(1),
+ color: theme.palette.primary.main,
+ fontWeight: 400,
+ textDecoration: "none",
+ "&:hover": {
+ textDecoration: "underline",
+ },
+ },
+}));
diff --git a/packages/app/src/report/components/ReportHeader.tsx b/packages/app/src/report/components/ReportHeader.tsx
index e285412..2db0713 100644
--- a/packages/app/src/report/components/ReportHeader.tsx
+++ b/packages/app/src/report/components/ReportHeader.tsx
@@ -14,6 +14,7 @@ import { NavLink } from "react-router-dom";
import { makeStyles } from "tss-react/mui";
import { LanguageIcon } from "../../common/LanguageIcon";
import { useReportStore } from "../stores/fe-report-store";
+import { ExportToCSV } from "./ExportToCSV";
import { ReportHeaderSection } from "./ReportHeaderSection";
import { Score } from "./Score";
@@ -82,6 +83,7 @@ export const ReportHeader: FC = ({ ready }) => {
+
({
wordBreak: "keep-all",
},
footer: {
+ display: "flex",
+ alignItems: "center",
+ gap: theme.spacing(2),
paddingBlockStart: theme.spacing(1),
+ justifyContent: "space-between"
},
}));
diff --git a/packages/app/src/report/stores/fe-report-store.ts b/packages/app/src/report/stores/fe-report-store.ts
index c921b06..6af6e91 100644
--- a/packages/app/src/report/stores/fe-report-store.ts
+++ b/packages/app/src/report/stores/fe-report-store.ts
@@ -1,4 +1,4 @@
-import { AnalysisStatus, ReportState } from "shared-types";
+import { AnalysisStatus, Issue, ReportState } from "shared-types";
import { createStore, useStore } from "zustand";
import { ScoreColorKey } from "../fe-report-types";
import { resolveScoreColor } from "../utils/score-utils";
@@ -54,6 +54,17 @@ export const ReportStore = createStore((set, get) => ({
reset: () => {
set({ ...initialState });
},
+ allIssues: () => {
+ const { linters } = get();
+ return (linters || [])
+ .map((linter) =>
+ (linter.issues || []).map((issue) => ({
+ ...issue,
+ linter: linter.name,
+ }))
+ )
+ .flat();
+ },
}));
export const useReportStore = () => useStore(ReportStore);
@@ -66,6 +77,7 @@ type InitialState = Omit<
| "reset"
| "lintersCompleted"
| "progress"
+ | "allIssues"
>;
interface FeReportStoreState extends ReportState {
@@ -79,4 +91,5 @@ interface FeReportStoreState extends ReportState {
lintersCompleted(): number;
progress(): number;
reset(): void;
+ allIssues(): Issue[];
}