diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index 91f29ceb1..0d155c639 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -40,6 +40,10 @@ jobs:
uses: gradle/gradle-build-action@915a66c096a03101667f9df2e56c9efef558b165 # v2
with:
gradle-version: release-candidate
+ - name: Setup Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '18.4'
- uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3
with:
distribution: 'temurin'
@@ -53,7 +57,7 @@ jobs:
cosign-release: 'v1.13.1'
# Build Quarkus
- name: Build
- run: cd ./github-bot/ && gradle assemble
+ run: cd ./github-bot/ && gradle assemble --stacktrace --info
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1
diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json
new file mode 100644
index 000000000..1dc716459
--- /dev/null
+++ b/frontend/.prettierrc.json
@@ -0,0 +1,6 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": true
+}
\ No newline at end of file
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a706f34fc..18d6a99a1 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,6 +11,7 @@
"@apollo/client": "3.7.17",
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
+ "@js-joda/core": "^5.5.3",
"@mui/icons-material": "5.14.1",
"@mui/lab": "5.0.0-alpha.137",
"@mui/material": "5.14.1",
@@ -38,7 +39,8 @@
"web-vitals": "3.4.0"
},
"devDependencies": {
- "@types/babel__core": "^7.1.20"
+ "@types/babel__core": "^7.1.20",
+ "prettier": "^3.0.0"
}
},
"node_modules/@adobe/css-tools": {
@@ -3804,6 +3806,11 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
+ "node_modules/@js-joda/core": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz",
+ "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ=="
+ },
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@@ -17274,6 +17281,21 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -23649,6 +23671,11 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
+ "@js-joda/core": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz",
+ "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ=="
+ },
"@leichtgewicht/ip-codec": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@@ -33151,6 +33178,12 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
+ "prettier": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+ "dev": true
+ },
"pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index fc5cf0fd1..1757a4254 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,6 +6,7 @@
"@apollo/client": "3.7.17",
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
+ "@js-joda/core": "^5.5.3",
"@mui/icons-material": "5.14.1",
"@mui/lab": "5.0.0-alpha.137",
"@mui/material": "5.14.1",
@@ -36,7 +37,8 @@
"start": "react-scripts --openssl-legacy-provider start",
"build": "react-scripts build",
"test": "react-scripts test",
- "eject": "react-scripts eject"
+ "eject": "react-scripts eject",
+ "prettier": "prettier --write \"src/**/*.tsx\""
},
"eslintConfig": {
"extends": [
@@ -57,6 +59,7 @@
]
},
"devDependencies": {
- "@types/babel__core": "^7.1.20"
+ "@types/babel__core": "^7.1.20",
+ "prettier": "^3.0.0"
}
}
diff --git a/frontend/src/Keycloak.tsx b/frontend/src/Keycloak.tsx
index 2c35405d2..04fd25d11 100644
--- a/frontend/src/Keycloak.tsx
+++ b/frontend/src/Keycloak.tsx
@@ -1,8 +1,8 @@
-import Keycloak from "keycloak-js";
+import Keycloak from 'keycloak-js';
const keycloak = new Keycloak({
- url: "https://auth.keksdose.xyz",
- realm: "laughing-train",
- clientId: "laughing-train",
+ url: 'https://auth.keksdose.xyz',
+ realm: 'laughing-train',
+ clientId: 'laughing-train',
});
-export default keycloak;
\ No newline at end of file
+export default keycloak;
diff --git a/frontend/src/PrivateRoute.tsx b/frontend/src/PrivateRoute.tsx
index 475558b8a..00972c847 100644
--- a/frontend/src/PrivateRoute.tsx
+++ b/frontend/src/PrivateRoute.tsx
@@ -1,13 +1,22 @@
-import { Alert, Typography } from "@mui/material";
-import { useKeycloak } from "@react-keycloak/web";
+import { Alert, Typography } from '@mui/material';
+import { useKeycloak } from '@react-keycloak/web';
-const PrivateRoute = (child: any ) => {
+const PrivateRoute = (child: any) => {
const { keycloak } = useKeycloak();
const isLoggedIn = keycloak.authenticated;
- return isLoggedIn ? child.children : <>
- You are not logged in. Please log in to access this page.
- >;
+ return isLoggedIn ? (
+ child.children
+ ) : (
+ <>
+
+ {' '}
+
+ You are not logged in. Please log in to access this page.
+
+
+ >
+ );
};
-export default PrivateRoute;
\ No newline at end of file
+export default PrivateRoute;
diff --git a/frontend/src/ProjectData.tsx b/frontend/src/ProjectData.tsx
index 2c9e4693f..cb146e85e 100644
--- a/frontend/src/ProjectData.tsx
+++ b/frontend/src/ProjectData.tsx
@@ -1,64 +1,70 @@
-import { gql} from '@apollo/client';
+import { gql } from '@apollo/client';
import { BadSmell } from './data/BadSmell';
import { Project } from './data/Project';
-
-
-
-
export const fetchProjectQuery = gql`
query getProjects {
- getProjects {
- projectName
- projectUrl
- commitHashes
- }
-}
+ getProjects {
+ projectName
+ projectUrl
+ commitHashes
+ commits {
+ analyzerStatuses {
+ analyzerName
+ commitHash
+ localDateTime
+ numberOfIssues
+ status
+ }
+ commitHash
+ }
+ }
+ }
`;
export const fetchAvailableRefactorings = gql`
query getAvailableRefactorings {
- availableRefactorings {
- ruleId {
- id
+ availableRefactorings {
+ ruleId {
+ id
+ }
}
- }
-}
+ }
`;
export const fetchBadSmellsforHashQuery = gql`
query getBadSmellsForHash($hash: String) {
- byCommitHash(commitHash: $hash) {
- identifier
- ruleID
- messageMarkdown
- snippet
- filePath
- position {
- startLine
+ byCommitHash(commitHash: $hash) {
+ identifier
+ ruleID
+ messageMarkdown
+ snippet
+ filePath
+ position {
+ startLine
+ }
}
}
-}
`;
export const addprojectQuery = gql`
mutation addProject($projectName: String!, $projectUrl: String!) {
- addProject(projectName: $projectName, projectUrl: $projectUrl) {
- projectName
- projectUrl
+ addProject(projectName: $projectName, projectUrl: $projectUrl) {
+ projectName
+ projectUrl
+ }
}
-}
`;
export const refactorQuery = gql`
mutation refactor($badSmellIdentifier: [String]) {
- refactor(badSmellIdentifier: $badSmellIdentifier)
-}
+ refactor(badSmellIdentifier: $badSmellIdentifier)
+ }
`;
export const loginQuery = gql`
- query login($notNeeded : String) {
- login(notNeeded : $notNeeded)
-}
+ query login($notNeeded: String) {
+ login(notNeeded: $notNeeded)
+ }
`;
-export function filterDuplicates(params:Project[]) {
+export function filterDuplicates(params: Project[]) {
return params;
}
export function filterDuplicateBadSmells(params: BadSmell[]) {
@@ -68,24 +74,41 @@ export function filterDuplicateBadSmells(params: BadSmell[]) {
params = params.filter((badSmell) => {
return badSmell.snippet != null;
});
- const ids = params.map(o => o.snippet)
- const filtered = params.filter(({ snippet }, index) => !ids.includes(snippet, index + 1));
+ const ids = params.map((o) => o.snippet);
+ const filtered = params.filter(
+ ({ snippet }, index) => !ids.includes(snippet, index + 1)
+ );
return filtered;
}
export const fetchProjectConfigQuery = gql`
query getProjectConfig($projectUrl: String!) {
- getProjectConfig(projectUrl: $projectUrl) {
- projectUrl
- sourceFolder
+ getProjectConfig(projectUrl: $projectUrl) {
+ projectUrl
+ sourceFolder
+ }
}
-}
`;
export const addProjectConfigQuery = gql`
mutation addProjectConfig($projectConfig: ProjectConfig!) {
- addProjectConfig(projectConfig: $projectConfig) {
- projectUrl
- sourceFolder
+ addProjectConfig(projectConfig: $projectConfig) {
+ projectUrl
+ sourceFolder
+ }
}
-}
-`;
\ No newline at end of file
+`;
+
+export const getGitHubCommitsQuery = gql`
+ query getGitHubCommitsForProject($projectName: String!) {
+ getGitHubCommitsForProject(projectName: $projectName) {
+ analyzerStatuses {
+ analyzerName
+ commitHash
+ localDateTime
+ numberOfIssues
+ status
+ }
+ commitHash
+ }
+ }
+`;
diff --git a/frontend/src/component/AddProjectCard.tsx b/frontend/src/component/AddProjectCard.tsx
index f102a56d9..8567a3f61 100644
--- a/frontend/src/component/AddProjectCard.tsx
+++ b/frontend/src/component/AddProjectCard.tsx
@@ -1,16 +1,24 @@
-import { Card, CardMedia, Typography } from "@mui/material";
-import React from "react";
-import { BiPlus } from "react-icons/bi";
-import { useNavigate } from "react-router";
+import { Card, CardMedia, Typography } from '@mui/material';
+import React from 'react';
+import { BiPlus } from 'react-icons/bi';
+import { useNavigate } from 'react-router';
export function AddProjectCard() {
const navigate = useNavigate();
return (
- navigate("/mutation/addproject")} sx={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
+ navigate('/mutation/addproject')}
+ sx={{
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
Add Project
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/component/BadSmellList.tsx b/frontend/src/component/BadSmellList.tsx
index 113df8802..5e47ad7ee 100644
--- a/frontend/src/component/BadSmellList.tsx
+++ b/frontend/src/component/BadSmellList.tsx
@@ -1,22 +1,30 @@
-
import { useQuery } from '@apollo/client';
-import { Accordion, AccordionDetails, AccordionSummary, Box, Card, CircularProgress, Divider, Link, Stack, Typography } from '@mui/material';
+import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
+ Box,
+ Card,
+ CircularProgress,
+ Divider,
+ Link,
+ Stack,
+ Typography,
+} from '@mui/material';
import { BadSmell } from '../data/BadSmell';
-import { Project } from "../data/Project";
+import { Project } from '../data/Project';
import { fetchBadSmellsforHashQuery } from '../ProjectData';
import JavaCodeBlock from './JavaCodeBlock';
import React, { useMemo, useState } from 'react';
import { StyledDivider } from './StyledDivider';
-
-
export default function BadSmellList(project: Project) {
- const [badSmellFilter] = useState([""]);
+ const [badSmellFilter] = useState(['']);
const { data, error, loading } = useQuery(fetchBadSmellsforHashQuery, {
variables: {
- hash: project.commitHashes[0]
- }
+ hash: project.commitHashes[0],
+ },
});
const filteredBadSmells = useMemo(() => {
if (!data) {
@@ -36,52 +44,69 @@ export default function BadSmellList(project: Project) {
console.error(error);
}
if (loading) {
- return
+ return ;
}
return (
- Bad Smells
+
+ Bad Smells
+
-
-
+
+
- Box>
+
);
}
-
-function BadSmellBlocks({ badSmells, project }: { badSmells: BadSmell[]; project: Project }) {
- return <>
- {CodeBlocks(badSmells, project)}
- >
+function BadSmellBlocks({
+ badSmells,
+ project,
+}: {
+ badSmells: BadSmell[];
+ project: Project;
+}) {
+ return <>{CodeBlocks(badSmells, project)}>;
}
function CodeBlocks(params: BadSmell[], project: Project) {
return Array.from(groupByRuleID(params)).map((badSmell) => {
return (
-
- {badSmell[0]} {badSmell[1].length}
+
+
+ {badSmell[0]} {badSmell[1].length}
+
-
-
+
+
{badSmell[1].map((badSmell) => {
return (
-
-
- {badSmell.messageMarkdown}
+
+
+
+ {badSmell.messageMarkdown}
+
-
+
);
})}
@@ -89,14 +114,20 @@ function CodeBlocks(params: BadSmell[], project: Project) {
- )
+ );
});
}
-
function createGithubLink(badSmell: BadSmell, project: Project) {
- return project.projectUrl + "/tree/" + project.commitHashes[0] + "/" + badSmell.filePath + "#L" + badSmell.position.startLine;
-
+ return (
+ project.projectUrl +
+ '/tree/' +
+ project.commitHashes[0] +
+ '/' +
+ badSmell.filePath +
+ '#L' +
+ badSmell.position.startLine
+ );
}
function groupByRuleID(projects: BadSmell[]): Map {
@@ -114,22 +145,45 @@ function groupByRuleID(projects: BadSmell[]): Map {
}
function BadSmellCardHeader(badSmell: BadSmell) {
- return (<>
-
- {badSmell.ruleID}
- {badSmell.identifier}
-
- >);
+ return (
+ <>
+
+
+ {badSmell.ruleID}
+
+
+ {badSmell.identifier}
+
+
+ >
+ );
}
-function BadSmellCardFooter({ badSmell, project }: { badSmell: BadSmell; project: Project; }) {
- return (<>
-
- In file {badSmell.filePath} at line {badSmell.position.startLine}
- {GitHubLink(badSmell, project)}
- >);
+function BadSmellCardFooter({
+ badSmell,
+ project,
+}: {
+ badSmell: BadSmell;
+ project: Project;
+}) {
+ return (
+ <>
+
+
+ In file {badSmell.filePath} at line {badSmell.position.startLine}
+
+ {GitHubLink(badSmell, project)}
+ >
+ );
}
function GitHubLink(badSmell: BadSmell, project: Project) {
- return See on GitHub;
+ return (
+
+ See on GitHub
+
+ );
}
-
diff --git a/frontend/src/component/BadSmellTree.tsx b/frontend/src/component/BadSmellTree.tsx
index 4d6ba8006..d732a190f 100644
--- a/frontend/src/component/BadSmellTree.tsx
+++ b/frontend/src/component/BadSmellTree.tsx
@@ -1,24 +1,36 @@
-import { Box, Button, Card, CardActionArea, Divider, Stack, Typography } from "@mui/material";
-import React from "react";
-import { BadSmell } from "../data/BadSmell";
+import {
+ Box,
+ Button,
+ Card,
+ CardActionArea,
+ Divider,
+ Stack,
+ Typography,
+} from '@mui/material';
+import React from 'react';
+import { BadSmell } from '../data/BadSmell';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import TreeView from '@mui/lab/TreeView';
import TreeItem from '@mui/lab/TreeItem';
-import JavaCodeBlock from "./JavaCodeBlock";
+import JavaCodeBlock from './JavaCodeBlock';
-export function BadSmellTreeView({ badSmell, selector }: { badSmell: BadSmell[]; selector: (selected: [string]) => void; }) {
+export function BadSmellTreeView({
+ badSmell,
+ selector,
+}: {
+ badSmell: BadSmell[];
+ selector: (selected: [string]) => void;
+}) {
const [expanded, setExpanded] = React.useState([]);
const handleToggle = (event: React.SyntheticEvent, nodeIds: string[]) => {
setExpanded(nodeIds);
};
-
-
const handleExpandClick = () => {
setExpanded((oldExpanded) =>
- oldExpanded.length === 0 ? ['1', '5', '6', '7'] : [],
+ oldExpanded.length === 0 ? ['1', '5', '6', '7'] : []
);
};
@@ -37,22 +49,38 @@ export function BadSmellTreeView({ badSmell, selector }: { badSmell: BadSmell[];
onNodeToggle={handleToggle}
multiSelect
>
- {convertBadSmellToTree(badSmell).map((temp) => printChildren(temp, selector))}
+ {convertBadSmellToTree(badSmell).map((temp) =>
+ printChildren(temp, selector)
+ )}
);
}
-function printChildren(node: TreeNode, setSelected : (selected: [string]) => void) {
+function printChildren(
+ node: TreeNode,
+ setSelected: (selected: [string]) => void
+) {
if (node.children.length === 0) {
- return
- {CodeBlocks(node.badSmell, setSelected)}
-
+ return (
+
+ {CodeBlocks(node.badSmell, setSelected)}
+
+ );
} else {
- return
- {node.children.map((child) => printChildren(child, setSelected))}
+ return (
+
+ {node.children.map((child) => printChildren(child, setSelected))}
+ );
}
-
}
type TreeNode = {
@@ -60,70 +88,102 @@ type TreeNode = {
label: string;
children: TreeNode[];
badSmell: BadSmell[];
-}
+};
// convert an array of badsmell with filePath to a filetree. Each node has his foldername as name and upper folders as parent.
function convertBadSmellToTree(badSmell: BadSmell[]): TreeNode[] {
const result: TreeNode[] = [];
badSmell.forEach((badSmell) => {
const filePath = badSmell.filePath;
- const folders = filePath.split("/");
+ const folders = filePath.split('/');
for (let index = 0; index < folders.length; index++) {
const element = folders[index];
- const parent = index === 0 ? "" : folders.slice(0, index).join("/");
- if (!result.some((temp) => temp.parent === parent && temp.label === element)) {
- result.push({ parent: parent, label: element, children: [], badSmell: [] });
+ const parent = index === 0 ? '' : folders.slice(0, index).join('/');
+ if (
+ !result.some((temp) => temp.parent === parent && temp.label === element)
+ ) {
+ result.push({
+ parent: parent,
+ label: element,
+ children: [],
+ badSmell: [],
+ });
}
}
});
result.forEach((temp) => {
- result.find((temp2) => temp2.parent + "/" + temp2.label === temp.parent)?.children.push(temp);
+ result
+ .find((temp2) => temp2.parent + '/' + temp2.label === temp.parent)
+ ?.children.push(temp);
result.find((temp2) => temp2.label === temp.parent)?.children.push(temp);
});
badSmell.forEach((badSmell) => {
const filePath = badSmell.filePath;
- result.find((temp) => temp.parent + "/" + temp.label === filePath)?.badSmell.push(badSmell);
+ result
+ .find((temp) => temp.parent + '/' + temp.label === filePath)
+ ?.badSmell.push(badSmell);
});
- return result.filter((temp) => temp.parent === "");
-
+ return result.filter((temp) => temp.parent === '');
}
-function CodeBlocks(params: BadSmell[], setSelected: (selected: [string]) => void) {
- return (
-
-
- {params.map((badSmell) => {
- return (
-
- setSelected([badSmell.identifier])}>
-
- {badSmell.messageMarkdown}
-
- CardActionArea>
-
-
- );
- })}
-
-
- );
+function CodeBlocks(
+ params: BadSmell[],
+ setSelected: (selected: [string]) => void
+) {
+ return (
+
+
+ {params.map((badSmell) => {
+ return (
+
+ setSelected([badSmell.identifier])}
+ >
+
+
+ {badSmell.messageMarkdown}
+
+
+
+
+
+ );
+ })}
+
+
+ );
}
function BadSmellCardHeader(badSmell: BadSmell) {
- return (<>
-
- {badSmell.ruleID}
- {badSmell.identifier}
- >);
+ return (
+ <>
+
+
+ {badSmell.ruleID}
+
+
+ {badSmell.identifier}
+
+ {' '}
+ >
+ );
}
function BlackDivider() {
- return ();
+ return (
+
+ );
}
function OrangeDivider() {
- return ;
+ return ;
}
diff --git a/frontend/src/component/DashBoardCard.tsx b/frontend/src/component/DashBoardCard.tsx
index 6999cfa87..f75ca6d1c 100644
--- a/frontend/src/component/DashBoardCard.tsx
+++ b/frontend/src/component/DashBoardCard.tsx
@@ -1,22 +1,37 @@
-import { CardActionArea, Card, CardMedia, CardContent, Typography } from "@mui/material";
-import Avatar from "react-avatar";
-import { Project } from "../data/Project";
-import React from "react";
-import { useNavigate } from "react-router";
+import {
+ CardActionArea,
+ Card,
+ CardMedia,
+ CardContent,
+ Typography,
+} from '@mui/material';
+import Avatar from 'react-avatar';
+import { Project } from '../data/Project';
+import React from 'react';
+import { useNavigate } from 'react-router';
export function DashBoardCard(project: Project) {
const navigate = useNavigate();
return (
navigate(toLink(project))}>
-
+
-
-
+
+
{project.projectName}
- {urlToGitHubHandle(project.projectUrl)}
+
+ {urlToGitHubHandle(project.projectUrl)}
+
@@ -24,13 +39,13 @@ export function DashBoardCard(project: Project) {
}
function urlToGitHubHandle(params: string) {
if (params === undefined) {
- return "";
+ return '';
}
- let url = params.split("/");
+ let url = params.split('/');
// the first pop return the projectname
- url.pop()
+ url.pop();
return url.pop();
}
function toLink(project: Project): string {
- return "/resultview/" + project.projectName;
-}
\ No newline at end of file
+ return '/resultview/' + project.projectName;
+}
diff --git a/frontend/src/component/DashBoardItem.tsx b/frontend/src/component/DashBoardItem.tsx
index 9b8af593a..3299f0e16 100644
--- a/frontend/src/component/DashBoardItem.tsx
+++ b/frontend/src/component/DashBoardItem.tsx
@@ -1,36 +1,44 @@
-import { Card, CardActionArea, Typography } from "@mui/material";
-import { Box } from "@mui/system";
-import Avatar from "react-avatar";
-import { useNavigate } from "react-router";
-import { Project } from "../data/Project";
+import { Card, CardActionArea, Typography } from '@mui/material';
+import { Box } from '@mui/system';
+import Avatar from 'react-avatar';
+import { useNavigate } from 'react-router';
+import { Project } from '../data/Project';
function DashBoardItem(project: Project) {
const navigate = useNavigate();
- return <>
-
- navigate(toLink(project))}>
-
-
- {project.projectName} Typography>
- {project.projectUrl} Typography>
-
-
- {project.commitHashes.length} Commits Typography>
-
-
-
- >
+ return (
+ <>
+
+ navigate(toLink(project))}>
+
+
+
+ {project.projectName}{' '}
+
+
+ {project.projectUrl}{' '}
+
+
+
+
+ {project.commitHashes.length} Commits
+
+
+
+
+ >
+ );
}
function toLink(project: Project): string {
- return "/resultview/" + project.projectName;
+ return '/resultview/' + project.projectName;
}
function urlToGitHubHandle(params: string) {
if (params === undefined) {
- return "";
+ return '';
}
- let url = params.split("/");
+ let url = params.split('/');
// the first pop return the projectname
- url.pop()
+ url.pop();
return url.pop();
}
-export default DashBoardItem;
\ No newline at end of file
+export default DashBoardItem;
diff --git a/frontend/src/component/HashSelector.tsx b/frontend/src/component/HashSelector.tsx
index 4f82132a6..2809f7567 100644
--- a/frontend/src/component/HashSelector.tsx
+++ b/frontend/src/component/HashSelector.tsx
@@ -1,7 +1,14 @@
-import { Button, FormControl, InputLabel, MenuItem, Select, Typography } from "@mui/material";
-import React, { useState } from "react";
-import { useNavigate } from "react-router";
-import { Project } from "../data/Project";
+import {
+ Button,
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ Typography,
+} from '@mui/material';
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router';
+import { Project } from '../data/Project';
function HashSelector(project: Project) {
const navigate = useNavigate();
@@ -12,7 +19,9 @@ function HashSelector(project: Project) {
Choose a hash to view the results
-
+
Commit Hash
-
- >;
+ return (
+ <>
+ navigate(toLink(project))}
+ >
+
+
+
+
+
+ {urlToGitHubHandle(project.projectUrl)}
+
+ {project.projectName}
+
+ }
+ >
+ See on GitHub
+
+
+
+
+
+
+ >
+ );
}
function urlToGitHubHandle(params: string) {
if (params === undefined) {
- return "";
+ return '';
}
- let url = params.split("/");
+ let url = params.split('/');
// the first pop return the projectname
- url.pop()
+ url.pop();
return url.pop();
}
export default ProjectCard;
function toLink(project: Project): string {
- return "/resultview/" + project.projectName;
-}
\ No newline at end of file
+ return '/resultview/' + project.projectName;
+}
diff --git a/frontend/src/component/ProjectList.tsx b/frontend/src/component/ProjectList.tsx
index 1422cc21b..f5cfce599 100644
--- a/frontend/src/component/ProjectList.tsx
+++ b/frontend/src/component/ProjectList.tsx
@@ -1,13 +1,10 @@
-import { useQuery } from "@apollo/client";
-import { Stack } from "@mui/material";
-import { fetchProjectQuery } from "../ProjectData";
-import { Project } from "../data/Project";
-import DashBoardItem from "./DashBoardItem";
-import React, { useMemo } from "react";
-
+import { useQuery } from '@apollo/client';
+import { fetchProjectQuery } from '../ProjectData';
+import { Project } from '../data/Project';
+import React, { useMemo } from 'react';
+import ProjectTable from './ProjectTable';
export function ProjectList({ filter }: { filter: string }) {
-
const { data, loading, error } = useQuery(fetchProjectQuery);
const filteredProjects = useMemo(() => {
@@ -19,22 +16,11 @@ export function ProjectList({ filter }: { filter: string }) {
});
}, [data, filter]);
-
-
if (error) {
console.error(error);
}
if (loading) {
return
Loading...
;
}
-
- return
- {filteredProjects.map((project : Project) => {
- return (
-
- );
- })}
- ;
-}
\ No newline at end of file
+ return ;
+}
diff --git a/frontend/src/component/ProjectTable.tsx b/frontend/src/component/ProjectTable.tsx
new file mode 100644
index 000000000..403286dfb
--- /dev/null
+++ b/frontend/src/component/ProjectTable.tsx
@@ -0,0 +1,186 @@
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+} from '@mui/material';
+import { Project } from '../data/Project';
+import { AnalyzerStatus } from '../data/AnalyzerStatus';
+import { useState } from 'react';
+import {
+ DateTimeFormatter,
+ Duration,
+ LocalDateTime,
+ TemporalAccessor,
+ ZoneOffset,
+} from '@js-joda/core';
+
+interface ProjectTableProps {
+ projects: Project[];
+}
+
+function ProjectTable(props: ProjectTableProps) {
+ const { projects } = props;
+ const [sortConfig, setSortConfig] = useState<{
+ key: string;
+ direction: string;
+ }>({ key: '', direction: '' });
+
+ const sortedProjects = projects.sort((a, b) => {
+ const aStatus = getSpoonAnalyzerResult(a);
+ const bStatus = getSpoonAnalyzerResult(b);
+ if (aStatus === undefined && bStatus !== undefined) {
+ return 1;
+ }
+ if (aStatus !== undefined && bStatus === undefined) {
+ return -1;
+ }
+ if (sortConfig.direction === 'ascending') {
+ if (sortConfig.key === 'projectUrl') {
+ return a[sortConfig.key] > b[sortConfig.key] ? 1 : -1;
+ }
+ if (sortConfig.key === 'analyzerDate') {
+ if (aStatus && bStatus) {
+ return aStatus.localDateTime > bStatus.localDateTime ? 1 : -1;
+ }
+ return 0;
+ }
+ if (sortConfig.key === 'resultCount') {
+ if (aStatus && bStatus) {
+ return aStatus.numberOfIssues > bStatus.numberOfIssues ? 1 : -1;
+ }
+ return 0;
+ }
+ }
+ if (sortConfig.direction === 'descending') {
+ if (sortConfig.key === 'projectUrl') {
+ return a[sortConfig.key] < b[sortConfig.key] ? 1 : -1;
+ }
+ if (sortConfig.key === 'analyzerDate') {
+ if (aStatus && bStatus) {
+ return aStatus.localDateTime < bStatus.localDateTime ? 1 : -1;
+ }
+ return 0;
+ }
+ if (sortConfig.key === 'resultCount') {
+ if (aStatus && bStatus) {
+ return aStatus.numberOfIssues < bStatus.numberOfIssues ? 1 : -1;
+ }
+ return 0;
+ }
+ }
+ return 0;
+ });
+
+ const requestSort = (key: string) => {
+ let direction = 'ascending';
+ if (sortConfig.key === key && sortConfig.direction === 'ascending') {
+ direction = 'descending';
+ }
+ setSortConfig({ key, direction });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {sortedProjects.map((project) => tableCell(project))}
+
+
+
+ );
+}
+
+interface SortableTableCellProps {
+ label: string;
+ sortConfig: { key: string; direction: string };
+ requestSort: (key: string) => void;
+ sortKey: string;
+}
+
+function tableCell(project: Project) {
+ const status = getSpoonAnalyzerResult(project);
+ if (status === undefined) {
+ return <>>;
+ }
+ return (
+ (window.location.href = toLink(project))}
+ >
+ {project.projectUrl}
+ {toDateTime(status.localDateTime)}
+ {status.numberOfIssues}
+
+ );
+}
+
+function SortableTableCell(props: SortableTableCellProps) {
+ const { label, sortConfig, requestSort, sortKey } = props;
+ const isSorted = sortConfig.key === sortKey;
+ const direction = isSorted ? sortConfig.direction : 'none';
+
+ return (
+ requestSort(sortKey)}>
+ {label}
+ {isSorted && {direction === 'ascending' ? ' ▲' : ' ▼'}}
+
+ );
+}
+
+export default ProjectTable;
+
+function getSpoonAnalyzerResult(project: Project): AnalyzerStatus | undefined {
+ if (project.commits.length === 0) {
+ return undefined;
+ }
+ const lastCommit = project.commits[project.commits.length - 1];
+ if (
+ lastCommit.analyzerStatuses === undefined ||
+ lastCommit.analyzerStatuses === null
+ ) {
+ return undefined;
+ }
+ return lastCommit.analyzerStatuses.find(
+ (status) => status.analyzerName === 'spoon-analyzer'
+ );
+}
+
+function toDateTime(date: string) {
+ const formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+ const parsedDate: TemporalAccessor = formatter.parse(date);
+ const localDateTime = LocalDateTime.from(parsedDate);
+ const now = LocalDateTime.now(ZoneOffset.UTC);
+ const duration = Duration.between(localDateTime, now);
+ const hours = duration.toHours();
+ return {hours} hours ago;
+}
+
+function toLink(project: Project): string {
+ return '/resultview/' + project.projectName;
+}
diff --git a/frontend/src/component/StyledDivider.tsx b/frontend/src/component/StyledDivider.tsx
index 183a11d27..3f5dcf7e5 100644
--- a/frontend/src/component/StyledDivider.tsx
+++ b/frontend/src/component/StyledDivider.tsx
@@ -1,13 +1,20 @@
-import { Divider as MuiDivider } from "@mui/material";
-import React from "react";
+import { Divider as MuiDivider } from '@mui/material';
+import React from 'react';
interface DividerProps {
thickness?: number;
color?: string;
- orientation?: "horizontal" | "vertical";
+ orientation?: 'horizontal' | 'vertical';
}
-export function StyledDivider({ thickness = 1, color = "grey", orientation = "horizontal" }: DividerProps) {
- const sxProps = orientation === "horizontal" ? { borderBottomWidth: thickness, borderColor: color } : { borderRightWidth: thickness, borderColor: color };
+export function StyledDivider({
+ thickness = 1,
+ color = 'grey',
+ orientation = 'horizontal',
+}: DividerProps) {
+ const sxProps =
+ orientation === 'horizontal'
+ ? { borderBottomWidth: thickness, borderColor: color }
+ : { borderRightWidth: thickness, borderColor: color };
return ;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/data/AnalyzerStatus.tsx b/frontend/src/data/AnalyzerStatus.tsx
new file mode 100644
index 000000000..426f6abe7
--- /dev/null
+++ b/frontend/src/data/AnalyzerStatus.tsx
@@ -0,0 +1,9 @@
+export type AnalyzerStatus = {
+ analyzerName: string;
+ commitHash: string;
+ localDateTime: string;
+ numberOfIssues: number;
+ status: Status;
+};
+
+export type Status = 'SUCCESS' | 'FAILURE';
diff --git a/frontend/src/data/BadSmell.tsx b/frontend/src/data/BadSmell.tsx
index 1b818334a..58fc688d8 100644
--- a/frontend/src/data/BadSmell.tsx
+++ b/frontend/src/data/BadSmell.tsx
@@ -1,12 +1,12 @@
-import { Position } from "./Position"
+import { Position } from './Position';
export type BadSmell = {
- identifier: string
- name: string
- messageMarkdown: string
- snippet: string
- ruleID: string
- commitHashes: [string]
- filePath: string
- position: Position
-}
\ No newline at end of file
+ identifier: string;
+ name: string;
+ messageMarkdown: string;
+ snippet: string;
+ ruleID: string;
+ commitHashes: [string];
+ filePath: string;
+ position: Position;
+};
diff --git a/frontend/src/data/GitHubCommit.tsx b/frontend/src/data/GitHubCommit.tsx
new file mode 100644
index 000000000..b3e0d614f
--- /dev/null
+++ b/frontend/src/data/GitHubCommit.tsx
@@ -0,0 +1,6 @@
+import { AnalyzerStatus } from './AnalyzerStatus';
+
+export type GitHubCommit = {
+ analyzerStatuses: AnalyzerStatus[];
+ commitHash: String;
+};
diff --git a/frontend/src/data/Position.tsx b/frontend/src/data/Position.tsx
index 1a2b33113..33b9c98d9 100644
--- a/frontend/src/data/Position.tsx
+++ b/frontend/src/data/Position.tsx
@@ -1,8 +1,8 @@
export type Position = {
- startLine: number
- endLine: number
- startColumn: number
- endColumn: number
- charOffset: number
- charLength: number
-}
\ No newline at end of file
+ startLine: number;
+ endLine: number;
+ startColumn: number;
+ endColumn: number;
+ charOffset: number;
+ charLength: number;
+};
diff --git a/frontend/src/data/Project.tsx b/frontend/src/data/Project.tsx
index 3d5439d31..326422c02 100644
--- a/frontend/src/data/Project.tsx
+++ b/frontend/src/data/Project.tsx
@@ -1,7 +1,8 @@
-
+import { GitHubCommit } from './GitHubCommit';
export type Project = {
projectName: string;
projectUrl: string;
commitHashes: string[];
-}
\ No newline at end of file
+ commits: GitHubCommit[];
+};
diff --git a/frontend/src/data/ProjectConfig.tsx b/frontend/src/data/ProjectConfig.tsx
index a90dc7297..534c3ea07 100644
--- a/frontend/src/data/ProjectConfig.tsx
+++ b/frontend/src/data/ProjectConfig.tsx
@@ -1,4 +1,4 @@
export type ProjectConfig = {
- projectUrl: string,
- sourceFolder: string
-}
\ No newline at end of file
+ projectUrl: string;
+ sourceFolder: string;
+};
diff --git a/frontend/src/data/RuleID.tsx b/frontend/src/data/RuleID.tsx
index 31b32b146..100be1fe6 100644
--- a/frontend/src/data/RuleID.tsx
+++ b/frontend/src/data/RuleID.tsx
@@ -1,3 +1,3 @@
export type RuleID = {
- ruleId: string
-}
\ No newline at end of file
+ ruleId: string;
+};
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index ff058636f..71befee3e 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -1,13 +1,15 @@
-import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache } from '@apollo/client';
+import {
+ ApolloClient,
+ ApolloProvider,
+ HttpLink,
+ InMemoryCache,
+} from '@apollo/client';
import { CssBaseline } from '@mui/material';
import { ThemeOptions, ThemeProvider, createTheme } from '@mui/material/styles';
import { ReactKeycloakProvider } from '@react-keycloak/web';
import React from 'react';
import ReactDOM from 'react-dom/client';
-import {
- RouterProvider,
- createBrowserRouter,
-} from "react-router-dom";
+import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import keycloak from './Keycloak';
import PrivateRoute from './PrivateRoute';
import { AddProjectView } from './pages/AddProjectView';
@@ -22,39 +24,47 @@ const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
-
const router = createBrowserRouter([
-
{
- path: "/statistics",
+ path: '/statistics',
element: ,
},
{
- path: "/",
+ path: '/',
element: } />,
errorElement: } />,
},
{
- path: "/mutation/addproject",
+ path: '/mutation/addproject',
element: ,
},
{
- path: "/mutation/refactor/:name/:hash",
- element: } />,
+ path: '/mutation/refactor/:name/:hash',
+ element: (
+
+ {' '}
+ } />
+
+ ),
},
{
- path: "/mutation/projectconfig/:projectUrl",
- element: ,
+ path: '/mutation/projectconfig/:projectUrl',
+ element: (
+
+
+
+ ),
},
{
- path: "resultview/:name",
+ path: 'resultview/:name',
element: } />,
children: [
{
- path: ":hash",
+ path: ':hash',
element: ,
- }],
- }
+ },
+ ],
+ },
]);
const client = new ApolloClient({
@@ -62,18 +72,17 @@ const client = new ApolloClient({
uri: 'https://laughing-train.keksdose.xyz/graphql',
}),
connectToDevTools: true,
- cache: new InMemoryCache({
- }),
+ cache: new InMemoryCache({}),
});
// https://bareynol.github.io/mui-theme-creator/#Card
const themeOptions: ThemeOptions = {
palette: {
- mode: "dark",
+ mode: 'dark',
primary: {
- main: "#1976d2",
+ main: '#1976d2',
},
secondary: {
- main: "#dc004e",
+ main: '#dc004e',
},
},
spacing: 8,
@@ -81,39 +90,30 @@ const themeOptions: ThemeOptions = {
MuiCard: {
styleOverrides: {
root: {
- background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
+ background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
},
},
},
MuiButton: {
styleOverrides: {
root: {
- background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
+ background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
},
},
- }
+ },
},
};
-const theme = createTheme(
- themeOptions
-
-
-
-);
-
-
+const theme = createTheme(themeOptions);
root.render(
-
-
+
-
);
diff --git a/frontend/src/pages/Aboutpage.tsx b/frontend/src/pages/Aboutpage.tsx
index cb8f1c3ac..ca116f66f 100644
--- a/frontend/src/pages/Aboutpage.tsx
+++ b/frontend/src/pages/Aboutpage.tsx
@@ -1,6 +1,6 @@
-import React from "react";
-import { Typography } from "@mui/material";
-import PageLayout from "./PageLayout";
+import React from 'react';
+import { Typography } from '@mui/material';
+import PageLayout from './PageLayout';
export default function AboutPage() {
return (
@@ -8,4 +8,4 @@ export default function AboutPage() {
About page content goes here
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/AddProjectView.tsx b/frontend/src/pages/AddProjectView.tsx
index e4e5b5381..208327b19 100644
--- a/frontend/src/pages/AddProjectView.tsx
+++ b/frontend/src/pages/AddProjectView.tsx
@@ -1,77 +1,100 @@
-import { useMutation } from "@apollo/client";
-import { Button, Divider, Paper, TextField, Typography } from "@mui/material";
-import React, { useState } from "react";
-import { useNavigate } from "react-router";
-import Headline from "../component/Headline";
-import { addprojectQuery } from "../ProjectData";
-
+import { useMutation } from '@apollo/client';
+import { Button, Divider, Paper, TextField, Typography } from '@mui/material';
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router';
+import Headline from '../component/Headline';
+import { addprojectQuery } from '../ProjectData';
export function AddProjectView() {
const nagivate = useNavigate();
- const [url, setUrl] = useState("");
- const [projectname, setProjectName] = useState("");
- const [owner, setOwner] = useState("");
+ const [url, setUrl] = useState('');
+ const [projectname, setProjectName] = useState('');
+ const [owner, setOwner] = useState('');
const onUserInput = (event: React.ChangeEvent) => {
setUrl(event.target.value);
const stringArray = urlToGitHubHandle(event.target.value);
setOwner(stringArray[1]);
setProjectName(stringArray[0]);
- }
+ };
+
+ const [addProject] = useMutation(addprojectQuery, {
+ onError: (error) => {
+ console.log(error);
+ },
+ onCompleted: (data) => {
+ console.log(data);
+ nagivate('/');
+ },
+ });
- const [addProject] = useMutation(
- addprojectQuery,
- {
- onError: (error) => { console.log(error) },
- onCompleted: (data) => {
- console.log(data);
- nagivate("/");
- }
- }
- )
-
return (
-
-
- Add Project
-
-
-
-
+
+ Add Project
+
+
+
Result of parsing:
ProjectOwner {owner}
ProjectName: {projectname}
-
- nagivate("/")}>Cancel
- {
- e.preventDefault();
- addProject({ variables: { projectName: projectname, projectUrl: url }
- }).then(() => { });
- }}>Save
+
+ nagivate('/')}
+ >
+ Cancel
+
+ {
+ e.preventDefault();
+ addProject({
+ variables: { projectName: projectname, projectUrl: url },
+ }).then(() => {});
+ }}
+ >
+ Save
+
- );
+
+ );
}
function urlToGitHubHandle(params: string): string[] {
const stringArray: string[] = [];
if (params === undefined) {
return [];
}
- let url = params.split("/");
+ let url = params.split('/');
// the first pop return the projectname
const owner = url.pop();
if (owner !== undefined) {
stringArray.push(owner);
} else {
- stringArray.push("");
+ stringArray.push('');
}
const name = url.pop();
if (name !== undefined) {
stringArray.push(name);
} else {
- stringArray.push("");
+ stringArray.push('');
}
return stringArray;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/DashBoard.tsx b/frontend/src/pages/DashBoard.tsx
index 7884a48d7..1f9962247 100644
--- a/frontend/src/pages/DashBoard.tsx
+++ b/frontend/src/pages/DashBoard.tsx
@@ -1,32 +1,44 @@
-import { Button, Grid, TextField, Typography } from "@mui/material";
-import React from "react";
-import { useNavigate } from "react-router";
-import { ProjectList } from "../component/ProjectList";
+import { Box, Button, Grid, TextField, Typography } from '@mui/material';
+import React from 'react';
+import { useNavigate } from 'react-router';
+import { ProjectList } from '../component/ProjectList';
export default function DashBoard() {
const navigate = useNavigate();
- const [filter, setFilter] = React.useState("");
+ const [filter, setFilter] = React.useState('');
return (
-
-
-
- Search
-
- setFilter(e.target.value)} />
+
+
+
+ Search
+
+ setFilter(e.target.value)}
+ />
-
-
+
+ navigate('/mutation/addproject')}
+ variant="contained"
+ sx={{ width: '100%' }}
+ >
+ Add Project
+
-
- navigate("/mutation/addproject")} variant="contained" >Add Project
+
+
+
+
+
+
-
+
+
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx
index d40c04f20..bbd437271 100644
--- a/frontend/src/pages/LandingPage.tsx
+++ b/frontend/src/pages/LandingPage.tsx
@@ -1,6 +1,6 @@
-import React from "react";
-import { Typography } from "@mui/material";
-import PageLayout from "./PageLayout";
+import React from 'react';
+import { Typography } from '@mui/material';
+import PageLayout from './PageLayout';
export default function LandingPage() {
return (
@@ -8,4 +8,4 @@ export default function LandingPage() {
Content goes here
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/PageLayout.tsx b/frontend/src/pages/PageLayout.tsx
index 21141fdfb..41037c955 100644
--- a/frontend/src/pages/PageLayout.tsx
+++ b/frontend/src/pages/PageLayout.tsx
@@ -1,7 +1,16 @@
-import React from "react";
-import { Grid, AppBar, Toolbar, List, ListItem, ListItemText, Box, Divider } from "@mui/material";
-import { LoginButton } from "../component/LoginButton";
-import { useNavigate } from "react-router";
+import React from 'react';
+import {
+ Grid,
+ AppBar,
+ Toolbar,
+ List,
+ ListItem,
+ ListItemText,
+ Box,
+ Divider,
+} from '@mui/material';
+import { LoginButton } from '../component/LoginButton';
+import { useNavigate } from 'react-router';
interface LogoProps {
src: string;
@@ -9,7 +18,9 @@ interface LogoProps {
}
function Logo({ src, alt }: LogoProps) {
- return ;
+ return (
+
+ );
}
interface NameProps {
@@ -17,24 +28,29 @@ interface NameProps {
}
function Name({ name }: NameProps) {
- return {name}
;
+ return (
+
+ {name}
+
+ );
}
-
function Navigation({ links }: NavigationProps) {
const navigate = useNavigate();
return (
{links.map((link) => (
<>
- navigate(link.href, { replace: true })}>
+ navigate(link.href, { replace: true })}
+ >
-
-
-
+
+
>
))}
-
+
);
}
@@ -44,7 +60,6 @@ interface PageLayoutProps {
interface Link {
name: string;
href: string;
-
}
interface NavigationProps {
links: Link[];
@@ -52,20 +67,35 @@ interface NavigationProps {
export default function PageLayout({ children }: PageLayoutProps) {
const navigationItems: Link[] = [
- { name: "Home", href: "/" },
- { name: "Statistics", href: "/statistics" }
+ { name: 'Home', href: '/' },
+ { name: 'Statistics', href: '/statistics' },
];
return (
-
+
-
-
-
+
+
+
@@ -80,4 +110,4 @@ export default function PageLayout({ children }: PageLayoutProps) {
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/ProjectConfigView.tsx b/frontend/src/pages/ProjectConfigView.tsx
index 6eecd43d6..a3aab2357 100644
--- a/frontend/src/pages/ProjectConfigView.tsx
+++ b/frontend/src/pages/ProjectConfigView.tsx
@@ -8,34 +8,44 @@ import { fetchProjectConfigQuery } from '../ProjectData';
export function ProjectConfigview() {
const { projectUrl } = useParams();
const encodedProjectUrl = toBase64(projectUrl);
- const [sourceFolder, setSourceFolder] = useState("")
- const { loading, error} = useQuery(fetchProjectConfigQuery, {
+ const [sourceFolder, setSourceFolder] = useState('');
+ const { loading, error } = useQuery(fetchProjectConfigQuery, {
variables: { projectUrl: encodedProjectUrl },
onCompleted: (data) => {
- setSourceFolder(data.getProjectConfig.sourceFolder)
- }
+ setSourceFolder(data.getProjectConfig.sourceFolder);
+ },
});
return (
-
- Project Config
-
- {loading && Loading...
}
- {error && Can not fetch data. Are you logged in?}
- setSourceFolder(e.target.value)} />
- {
- console.log(sourceFolder);
- }}>Save
+
+ Project Config
+
+ {loading && Loading...
}
+ {error && (
+ Can not fetch data. Are you logged in?
+ )}
+ setSourceFolder(e.target.value)}
+ />
+ {
+ console.log(sourceFolder);
+ }}
+ >
+ Save
+
);
}
function toBase64(str: string | undefined): string {
if (undefined === str) {
- return "";
+ return '';
}
return atob(str);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/RefactorView.tsx b/frontend/src/pages/RefactorView.tsx
index bd0ab7388..cbf98cb2a 100644
--- a/frontend/src/pages/RefactorView.tsx
+++ b/frontend/src/pages/RefactorView.tsx
@@ -1,117 +1,189 @@
-import { useMutation, useQuery } from "@apollo/client";
-import { Alert, Button, Chip, Stack, TextField, Typography } from "@mui/material";
+import { useMutation, useQuery } from '@apollo/client';
+import {
+ Alert,
+ Button,
+ Chip,
+ Stack,
+ TextField,
+ Typography,
+} from '@mui/material';
import Grid2 from '@mui/material/Unstable_Grid2'; // Grid version 2
-import React, { useMemo } from "react";
-import { useParams } from "react-router";
-import { BadSmellTreeView } from "../component/BadSmellTree";
-import { BadSmell } from "../data/BadSmell";
-import { fetchBadSmellsforHashQuery, refactorQuery } from "../ProjectData";
-
+import React, { useMemo } from 'react';
+import { useParams } from 'react-router';
+import { BadSmellTreeView } from '../component/BadSmellTree';
+import { BadSmell } from '../data/BadSmell';
+import { fetchBadSmellsforHashQuery, refactorQuery } from '../ProjectData';
export function RefactorView() {
const [selected, setSelected] = React.useState
([]);
- const [search, setSearch] = React.useState("");
+ const [search, setSearch] = React.useState('');
const handleSelect = (values: string[]) => {
-
if (values.length === 1) {
if (selected.includes(values[0])) {
- setSelected(selected.filter((identifier: string) => identifier !== values[0]));
+ setSelected(
+ selected.filter((identifier: string) => identifier !== values[0])
+ );
} else {
setSelected([...selected, values[0]]);
}
- }
- else {
+ } else {
const temp = [...selected, ...values];
setSelected(temp);
}
};
- const [refactorBadSmell] = useMutation(
- refactorQuery,
- {
- onError: (error) => { console.log(error) },
- onCompleted: (data) => {
- console.log(data);
- }
- }
- )
+ const [refactorBadSmell] = useMutation(refactorQuery, {
+ onError: (error) => {
+ console.log(error);
+ },
+ onCompleted: (data) => {
+ console.log(data);
+ },
+ });
// function combine 2 arrays:
// const arr1 = [1, 2, 3];
// const arr2 = [4, 5, 6];
// const arr3 = [...arr1, ...arr2];
const { hash, name } = useParams();
- const { data: dataBadSmell, error: errorBadSmell, loading: loadingBadSmell } = useQuery(fetchBadSmellsforHashQuery, {
+ const {
+ data: dataBadSmell,
+ error: errorBadSmell,
+ loading: loadingBadSmell,
+ } = useQuery(fetchBadSmellsforHashQuery, {
variables: {
- hash: hash
- }
+ hash: hash,
+ },
});
// eslint-disable-next-line react-hooks/exhaustive-deps
- let allBadsmells: BadSmell[] = (dataBadSmell !== undefined) ? dataBadSmell.byCommitHash : [];
- allBadsmells = useMemo(() =>
- allBadsmells.filter((badSmell: BadSmell) => {
- return badSmell.ruleID.toLowerCase().includes(search.toLowerCase()) || badSmell.messageMarkdown.toLowerCase().includes(search.toLowerCase());
- })
- , [allBadsmells, search])
- if (hash === "undefined") {
- return
-
Hash not found
-
Hash not found. Please go back an select a hash before
-
;
+ let allBadsmells: BadSmell[] =
+ dataBadSmell !== undefined ? dataBadSmell.byCommitHash : [];
+ allBadsmells = useMemo(
+ () =>
+ allBadsmells.filter((badSmell: BadSmell) => {
+ return (
+ badSmell.ruleID.toLowerCase().includes(search.toLowerCase()) ||
+ badSmell.messageMarkdown.toLowerCase().includes(search.toLowerCase())
+ );
+ }),
+ [allBadsmells, search]
+ );
+ if (hash === 'undefined') {
+ return (
+
+
Hash not found
+
+ Hash not found. Please go back an select a hash before
+
+
+ );
}
if (errorBadSmell) {
- return Error Loading ruleset. Are you logged in?
+ return (
+ Error Loading ruleset. Are you logged in?
+ );
}
if (loadingBadSmell) {
return Loading;
}
return (
-
- Refactor {name} at {hash}
+
+ Refactor {name} at {hash}
+
-
+
Search
- setSearch(e.target.value)} />
+ setSearch(e.target.value)}
+ />
-
+
+
+
-
- {
- refactorBadSmell({
- variables: {
- badSmellIdentifier: selected,
- }
- });
- }} >Refactor
+
+ {
+ refactorBadSmell({
+ variables: {
+ badSmellIdentifier: selected,
+ },
+ });
+ }}
+ >
+ Refactor
+
);
}
-function StackSelectedRule({ selected, handleSelect }: { selected: string[]; handleSelect: (value: [string]) => void; }) {
- return
- {selected.map((value : string) => {
- return handleSelect([value])} />;
- })}
- ;
+function StackSelectedRule({
+ selected,
+ handleSelect,
+}: {
+ selected: string[];
+ handleSelect: (value: [string]) => void;
+}) {
+ return (
+
+ {selected.map((value: string) => {
+ return (
+ handleSelect([value])}
+ />
+ );
+ })}
+
+ );
}
-function StackSelector({ dataBadSmell, handleSelect }: { dataBadSmell: any; handleSelect: (value: string[]) => void; }): JSX.Element {
+function StackSelector({
+ dataBadSmell,
+ handleSelect,
+}: {
+ dataBadSmell: any;
+ handleSelect: (value: string[]) => void;
+}): JSX.Element {
const badSmells: BadSmell[] = dataBadSmell.byCommitHash;
- return
- {listOfUniqueRules(badSmells).map((badSmell: BadSmell) => {
- return handleSelect(toRuleIDs(filterBadSmells(badSmells, e.currentTarget.innerText)))} />;
- })}
- ;
+ return (
+
+ {listOfUniqueRules(badSmells).map((badSmell: BadSmell) => {
+ return (
+
+ handleSelect(
+ toRuleIDs(filterBadSmells(badSmells, e.currentTarget.innerText))
+ )
+ }
+ />
+ );
+ })}
+
+ );
}
-function filterBadSmells(BadSmell : BadSmell[], ruleID : string) : BadSmell[]{
+function filterBadSmells(BadSmell: BadSmell[], ruleID: string): BadSmell[] {
return BadSmell.filter((inner: BadSmell) => {
return inner.ruleID === ruleID;
});
@@ -123,7 +195,9 @@ function toRuleIDs(badSmells: BadSmell[]): string[] {
}
function listOfUniqueRules(badSmells: BadSmell[]): BadSmell[] {
- const ids = badSmells.map(o => o.ruleID)
- const filtered = badSmells.filter(({ ruleID }, index) => !ids.includes(ruleID, index + 1));
+ const ids = badSmells.map((o) => o.ruleID);
+ const filtered = badSmells.filter(
+ ({ ruleID }, index) => !ids.includes(ruleID, index + 1)
+ );
return filtered;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/Resultview.tsx b/frontend/src/pages/Resultview.tsx
index 85dab7cfd..5c08ea3d3 100644
--- a/frontend/src/pages/Resultview.tsx
+++ b/frontend/src/pages/Resultview.tsx
@@ -1,82 +1,103 @@
-import { Button, CircularProgress, Divider, Grid, Stack, Typography } from "@mui/material";
-import React from "react";
-import ProjectCard from "../component/ProjectCard";
-import { useNavigate, useParams } from "react-router-dom";
-import { fetchProjectQuery } from "../ProjectData";
-import HashSelector from "../component/HashSelector";
-import { Project } from "../data/Project";
-import { useQuery } from "@apollo/client";
-import BadSmellList from "../component/BadSmellList";
+import {
+ Button,
+ CircularProgress,
+ Divider,
+ Grid,
+ Stack,
+ Typography,
+} from '@mui/material';
+import React from 'react';
+import ProjectCard from '../component/ProjectCard';
+import { useNavigate, useParams } from 'react-router-dom';
+import { fetchProjectQuery } from '../ProjectData';
+import HashSelector from '../component/HashSelector';
+import { Project } from '../data/Project';
+import { useQuery } from '@apollo/client';
+import BadSmellList from '../component/BadSmellList';
function Resultview() {
- let navigate = useNavigate();
- let params = useParams();
- const { data, loading, error } = useQuery(fetchProjectQuery);
- if (error) {
- console.error(error);
+ let navigate = useNavigate();
+ let params = useParams();
+ const { data, loading, error } = useQuery(fetchProjectQuery);
+ if (error) {
+ console.error(error);
+ }
+ console.log(params);
+ if (loading) {
+ return ;
+ }
+ const project: Project | undefined = data.getProjects.find(
+ (project: Project) => {
+ return project.projectName === params.name;
}
- console.log(params);
- if (loading) {
- return ;
- }
- const project: Project | undefined = data.getProjects.find((project: Project) => {
- return project.projectName === params.name;
- });
- if (!project) {
- return Project not found
;
- }
- return (
-
-
-
-
- {addHashIfPresent(params.hash)}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Refactor
-
- navigate(generateProjectConfigLink(project))}>
- Config
-
-
-
-
-
- );
+ );
+ if (!project) {
+ return Project not found
;
+ }
+ return (
+
+
+
+
+ {addHashIfPresent(params.hash)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refactor
+
+ navigate(generateProjectConfigLink(project))}
+ >
+ Config
+
+
+
+
+
+ );
}
export default Resultview;
-function projectWithSingleHash(project: Project, hash: string | undefined): Project {
- if (hash) {
- return {
- projectName: project.projectName,
- commitHashes: [hash],
- projectUrl: project.projectUrl
- }
- }
- return project;
+function projectWithSingleHash(
+ project: Project,
+ hash: string | undefined
+): Project {
+ if (hash) {
+ return {
+ projectName: project.projectName,
+ commitHashes: [hash],
+ projectUrl: project.projectUrl,
+ commits: project.commits,
+ };
+ }
+ return project;
}
function addHashIfPresent(hash: string | undefined) {
- if (hash) {
- return Current selected hash: {hash};
- }
- return No hash selected;
+ if (hash) {
+ return Current selected hash: {hash};
+ }
+ return No hash selected;
}
function toBase64(str: string) {
- return btoa(str);
+ return btoa(str);
}
function generateProjectConfigLink(project: Project) {
- return "/mutation/projectconfig/" + toBase64(project.projectUrl);
-}
\ No newline at end of file
+ return '/mutation/projectconfig/' + toBase64(project.projectUrl);
+}
diff --git a/frontend/src/pages/StatisticsPage.tsx b/frontend/src/pages/StatisticsPage.tsx
index 4aafa3b89..11ba80ff9 100644
--- a/frontend/src/pages/StatisticsPage.tsx
+++ b/frontend/src/pages/StatisticsPage.tsx
@@ -1,17 +1,32 @@
-import { useQuery } from "@apollo/client";
-import { fetchProjectQuery } from "../ProjectData";
-import PageLayout from "./PageLayout";
-import { Box, Card, CardContent, CardHeader, CircularProgress, Divider} from "@mui/material";
-import { Project } from "../data/Project";
-import Avatar from "react-avatar";
-import { FancyText } from "../styled/FancyText";
+import { useQuery } from '@apollo/client';
+import { fetchProjectQuery } from '../ProjectData';
+import PageLayout from './PageLayout';
+import {
+ Box,
+ Card,
+ CardContent,
+ CardHeader,
+ CircularProgress,
+ Divider,
+} from '@mui/material';
+import { Project } from '../data/Project';
+import Avatar from 'react-avatar';
+import { FancyText } from '../styled/FancyText';
function toCard(card: CardData[]) {
const { owner, url } = card[0];
return (
-
+
}
title={{owner}}
/>
@@ -26,7 +41,6 @@ type CardData = {
name: string;
url: string;
commitHashes: string[];
-
};
function Statistics({ projects }: { projects: CardData[] }) {
@@ -43,32 +57,39 @@ function Statistics({ projects }: { projects: CardData[] }) {
const numOwners = Object.keys(groupedProjects).length;
return (
-
- Statistics
-
+
+
+ Statistics
+
+
{numOwners} owners
{projects.length} projects
-
-
+
+
{Object.values(groupedProjects).map((card) => toCard(card))}
);
}
-
-
export function StatisticPage() {
const { loading, error, data } = useQuery(fetchProjectQuery);
- if (loading) return
}>;
+ if (loading) return
}>;
if (error) return
Error :(
;
const projects: CardData[] = data.getProjects.map((project: Project) => {
- const urlParts = project.projectUrl.split("/");
+ const urlParts = project.projectUrl.split('/');
const owner = urlParts[urlParts.length - 2];
const name = urlParts[urlParts.length - 1];
- const url = urlParts.join("/");
+ const url = urlParts.join('/');
const commitHashes = project.commitHashes;
return { owner, name, url, commitHashes } as CardData;
});
@@ -78,4 +99,4 @@ export function StatisticPage() {
);
-};
\ No newline at end of file
+}
diff --git a/frontend/src/styled/FancyText.tsx b/frontend/src/styled/FancyText.tsx
index eb5c1f3e6..f88287cb4 100644
--- a/frontend/src/styled/FancyText.tsx
+++ b/frontend/src/styled/FancyText.tsx
@@ -1,8 +1,8 @@
-import { Typography, styled } from "@mui/material";
+import { Typography, styled } from '@mui/material';
export const FancyText = styled(Typography)({
- fontFamily: "monospace",
- fontSize: "2rem",
- fontWeight: "bold",
- color: "#3f51b5",
-});
\ No newline at end of file
+ fontFamily: 'monospace',
+ fontSize: '2rem',
+ fontWeight: 'bold',
+ color: '#3f51b5',
+});