Skip to content

Commit

Permalink
Adding language detection for snippet code
Browse files Browse the repository at this point in the history
  • Loading branch information
itayox committed Jul 24, 2023
1 parent 1384960 commit 71dd5c3
Show file tree
Hide file tree
Showing 22 changed files with 701 additions and 25 deletions.
3 changes: 1 addition & 2 deletions cypress/e2e/02-analysis.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe("analysis", () => {
cy.visit("/");

// submit button disabled by default
cy.get(`[data-cy="snippet-submit"]`).should("be.disabled");
cy.get(`[data-cy="submit"]`).should("be.disabled");

submitSnippet();

Expand All @@ -15,4 +15,3 @@ describe("analysis", () => {
});
});
});

4 changes: 2 additions & 2 deletions cypress/e2e/utils/submit-snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ export const submitSnippet = () => {
cy.get(`[data-cy="snippet-input"]`).type(snippetCode, { delay: 1 });

// assert the button is enabled
cy.get(`[data-cy="snippet-submit"]`).should("be.visible");
cy.get(`[data-cy="submit"]`).should("be.visible");

// submit the snippet
cy.get(`[data-cy="snippet-submit"]`).click();
cy.get(`[data-cy="submit"]`).click();
};

const snippetCode = `
Expand Down
43 changes: 43 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
content="CodeTotal by OX Security, a megalinter UI"
/>
<link rel="icon" href="/favicon.png" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/devicons/[email protected]/devicon.min.css"
/>
<meta name="theme-color" content="#6837FF" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Total By OX Security</title>
Expand Down
3 changes: 3 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"@mui/material": "^5.13.5",
"axios": "^1.4.0",
"filesize": "^10.0.7",
"lodash-es": "^4.17.21",
"md5": "^2.3.0",
"monaco-editor": "^0.40.0",
"react": "^18.2.0",
"react-countup": "^6.4.2",
"react-dom": "^18.2.0",
Expand All @@ -29,6 +31,7 @@
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/lodash-es": "^4.17.8",
"@types/md5": "^2.3.2",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
Expand Down
9 changes: 7 additions & 2 deletions packages/app/src/analysis/actions/analysis-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export const startAnalysis = async (navigate: NavigateFunction) => {
};

const createRequestData = (): Analysis | undefined => {
const { inputType, repositoryURL, file, snippet } = AnalysisStore.getState();
const { inputType, repositoryURL, file, snippet, language } =
AnalysisStore.getState();
const sharedVariables = {
name: "Lint",
inputType,
Expand All @@ -55,6 +56,10 @@ const createRequestData = (): Analysis | undefined => {
case AnalysisType.File:
return { ...sharedVariables, file } as FileAnalysis;
case AnalysisType.Snippet:
return { ...sharedVariables, snippet } as SnippetAnalysis;
return {
...sharedVariables,
snippet,
language,
} as SnippetAnalysis;
}
};
56 changes: 43 additions & 13 deletions packages/app/src/analysis/components/CodeSnippetForm.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { TextField, Theme } from "@mui/material";
import { TextField, Theme, Typography } from "@mui/material";
import { FC, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { makeStyles } from "tss-react/mui";
import { LanguageIcon } from "../../common/LanguageIcon";
import { fetchDetect } from "../../common/utils/detect-lanauge-utils";
import { startAnalysis } from "../actions/analysis-actions";
import { AnalysisStore, useAnalysisStore } from "../stores/analysis-store";
import { SubmitButton } from "./SubmitButton";

export const CodeSnippetForm: FC = () => {
const { classes } = useStyles();
const { snippet, snippetEnabled, sending } = useAnalysisStore();
const { snippet, snippetEnabled, sending, language } = useAnalysisStore();
const navigate = useNavigate();

const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
AnalysisStore.setState({ snippet: e.target.value });
const snippet = e.target.value;
AnalysisStore.setState({ snippet });
fetchDetect(snippet);
}, []);

const handleSubmit = () => {
Expand All @@ -34,16 +38,31 @@ export const CodeSnippetForm: FC = () => {
"data-cy": "snippet-input",
}}
/>
<SubmitButton
variant="contained"
color="primary"
onClick={handleSubmit}
disabled={!snippetEnabled() || sending === "loading"}
loading={sending === "loading"}
data-cy="snippet-submit"
>
Check Snippet
</SubmitButton>
<div className={classes.footer}>
<div>
{language && language.name && (
<div className={classes.language}>
{language.icon ? (
<LanguageIcon language={language} />
) : (
<Typography variant="body2" color="text.secondary">
{language.name}
</Typography>
)}
</div>
)}
</div>
<SubmitButton
variant="contained"
color="primary"
onClick={handleSubmit}
disabled={!snippetEnabled() || sending === "loading"}
loading={sending === "loading"}
data-cy="submit"
>
Send Snippet
</SubmitButton>
</div>
</div>
);
};
Expand All @@ -56,4 +75,15 @@ const useStyles = makeStyles()((theme: Theme) => ({
gap: theme.spacing(2),
padding: theme.spacing(1),
},
language: {
display: "flex",
gap: theme.spacing(1),
alignSelf: "flex-start",
},
footer: {
display: "flex",
gap: theme.spacing(1),
alignSelf: "stretch",
justifyContent: "space-between",
},
}));
6 changes: 4 additions & 2 deletions packages/app/src/analysis/components/SubmitButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Button, ButtonProps, CircularProgress } from "@mui/material";
import { FC } from "react";
import { BiSend } from "react-icons/bi";

export const SubmitButton: FC<SubmitButtonProps> = ({ loading, ...props }) => {
return (
<Button
{...props}
endIcon={
endIcon={<BiSend />}
startIcon={
loading && (
<CircularProgress
color="inherit"
Expand All @@ -18,6 +20,6 @@ export const SubmitButton: FC<SubmitButtonProps> = ({ loading, ...props }) => {
);
};

interface SubmitButtonProps extends ButtonProps {
interface SubmitButtonProps extends Omit<ButtonProps, "startIcon" | "endIcon"> {
loading: boolean;
}
4 changes: 3 additions & 1 deletion packages/app/src/analysis/stores/analysis-store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnalysisType, OneOfValues } from "shared-types";
import { AnalysisType, OneOfValues, ProgrammingLanguage } from "shared-types";
import { useStore } from "zustand";
import { createStore } from "zustand/vanilla";

Expand All @@ -8,6 +8,7 @@ const initialState: AnalysisStoreState = {
file: undefined,
sending: "idle",
inputType: AnalysisType.Snippet,
language: undefined,
};

export const AnalysisStore = createStore<
Expand All @@ -24,6 +25,7 @@ interface AnalysisStoreState {
file?: File;
sending: OneOfValues<typeof AsyncState>;
inputType: OneOfValues<typeof AnalysisType>;
language?: ProgrammingLanguage;
}

interface AnalysisStoreFunctions {
Expand Down
57 changes: 57 additions & 0 deletions packages/app/src/common/LanguageIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Theme, Tooltip, useTheme } from "@mui/material";
import { FC } from "react";
import { ProgrammingLanguage } from "shared-types";
import { makeStyles } from "tss-react/mui";

export const LanguageIcon: FC<LanguageIconProps> = ({ language }) => {
const { classes, cx } = useStyles();
const theme = useTheme();
const isDarkmode = theme.palette.mode === "dark";
if (!language) {
return null;
}

// only is dark mode and only for "plain" type icons
// they require a background since when used with an img tag
// colors can't be applied through css and we end up with a black colored icon
const requiresBackground = isDarkmode && language.icon === "plain";

return (
<Tooltip title={language.name || ""} arrow placement="top">
<span
className={cx(
classes.languageIcon,
requiresBackground && classes.darkmode
)}
>
<img
src={`https://cdn.jsdelivr.net/gh/devicons/devicon/icons/${language.name?.toLowerCase()}/${language.name?.toLowerCase()}-${
language.icon
}.svg`}
className={classes.img}
alt="programming language icon"
/>
</span>
</Tooltip>
);
};
const useStyles = makeStyles()((theme: Theme) => ({
languageIcon: {
display: "inline-flex",
height: "2em",
width: "2em",
},
img: {
width: "100%",
height: "100%",
},
darkmode: {
backgroundColor: theme.palette.text.secondary,
borderRadius: "50%",
display: "inline-flex",
},
}));

interface LanguageIconProps {
language?: ProgrammingLanguage;
}
15 changes: 15 additions & 0 deletions packages/app/src/common/utils/detect-lanauge-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import axios from "axios";
import debounce from "lodash-es/debounce";
import { ProgrammingLanguage } from "shared-types";
import { AnalysisStore } from "../../analysis/stores/analysis-store";
import config from "../../config";

export const detect = async (snippet: string) => {
const res = await axios.post<ProgrammingLanguage | undefined>(
`http://${config.CODETOTAL_HTTP_HOST}:${config.CODETOTAL_HTTP_PORT}/detect`,
{ snippet }
);
AnalysisStore.setState({ language: res.data });
};

export const fetchDetect = debounce(detect, 1000);
Loading

0 comments on commit 71dd5c3

Please sign in to comment.