diff --git a/server/.gitignore b/server/.gitignore index 549e00a..c23b742 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -31,3 +31,5 @@ build/ ### VS Code ### .vscode/ +.mvn* +mvnw* diff --git a/server/pom.xml b/server/pom.xml index 329f5fe..5a43217 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -11,7 +11,7 @@ com.github awrb 0.0.1-SNAPSHOT - awrb + solr ISI 2021 Lab 4 1.8 diff --git a/server/src/main/java/com/github/awrb/AwrbApplication.java b/server/src/main/java/com/github/awrb/SolrApplication.java similarity index 57% rename from server/src/main/java/com/github/awrb/AwrbApplication.java rename to server/src/main/java/com/github/awrb/SolrApplication.java index fcbe5e6..3c7c804 100644 --- a/server/src/main/java/com/github/awrb/AwrbApplication.java +++ b/server/src/main/java/com/github/awrb/SolrApplication.java @@ -2,12 +2,11 @@ package com.github.awrb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; @SpringBootApplication -public class AwrbApplication { +public class SolrApplication { public static void main(String[] args) { - SpringApplication.run(AwrbApplication.class, args); + SpringApplication.run(SolrApplication.class, args); } } diff --git a/server/src/main/java/com/github/awrb/solr/SolrFacade.java b/server/src/main/java/com/github/awrb/solr/SolrFacade.java index 80b2ee6..b44411a 100644 --- a/server/src/main/java/com/github/awrb/solr/SolrFacade.java +++ b/server/src/main/java/com/github/awrb/solr/SolrFacade.java @@ -15,7 +15,7 @@ public class SolrFacade { private SolrClient solrClient; - public SolrFacade(@Value("${solr.address") String solrAddress) { + public SolrFacade(@Value("${solr.address}") String solrAddress) { this.solrClient = new HttpSolrClient.Builder(solrAddress).build(); } diff --git a/server/src/main/java/com/github/awrb/solr/services/SolrService.java b/server/src/main/java/com/github/awrb/solr/services/SolrService.java index 33590fe..549c25f 100644 --- a/server/src/main/java/com/github/awrb/solr/services/SolrService.java +++ b/server/src/main/java/com/github/awrb/solr/services/SolrService.java @@ -1,11 +1,13 @@ package com.github.awrb.solr.services; import com.github.awrb.solr.SolrFacade; +import com.github.awrb.solr.services.data.SolrQueryParams; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -13,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @RestController +@CrossOrigin(origins = "http://localhost:3000") public class SolrService { private SolrFacade solrFacade; @@ -23,14 +26,7 @@ public class SolrService { } @GetMapping("/search") - public SolrDocumentList search(@RequestParam("q") String query) throws IOException, SolrServerException { - SolrParams solrParams = solrParamsFromQuery(query); - return solrFacade.query(solrParams).getResults(); - } - - private SolrParams solrParamsFromQuery(String query) { - ModifiableSolrParams params = new ModifiableSolrParams(); - params.set("q", query); - return params; + public SolrDocumentList search(SolrQueryParams params) throws IOException, SolrServerException { + return solrFacade.query(params.toSolrParams()).getResults(); } } diff --git a/server/src/main/java/com/github/awrb/solr/services/data/SolrQueryParams.java b/server/src/main/java/com/github/awrb/solr/services/data/SolrQueryParams.java new file mode 100644 index 0000000..5cc47a1 --- /dev/null +++ b/server/src/main/java/com/github/awrb/solr/services/data/SolrQueryParams.java @@ -0,0 +1,86 @@ +package com.github.awrb.solr.services.data; + +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.params.SolrParams; + +public class SolrQueryParams { + + private static final String MATCH_ALL = "*"; + + private static final int DEFAULT_ROW_COUNT = 10; + + private String query; + + private String reviewText = MATCH_ALL; + private String reviewerName = MATCH_ALL; + private String summary = MATCH_ALL; + private String asin = MATCH_ALL; + private Sort sort = Sort.DESC; + + private int rows = DEFAULT_ROW_COUNT; + + public void setQuery(String query) { + this.query = query; + } + + public void setReviewText(String reviewText) { + this.reviewText = reviewText; + } + + public void setRows(int rows) { + this.rows = rows; + } + + public void setReviewerName(String reviewerName) { + this.reviewerName = reviewerName; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public void setAsin(String asin) { + this.asin = asin; + } + + public void setSort(Sort sort) { + this.sort = sort; + } + + public SolrParams toSolrParams() { + ModifiableSolrParams params = new ModifiableSolrParams(); + params.set("q", buildQ()); + params.set("rows", rows); +// params.set("sort", "overall " + sort.label); + System.out.println(params.toQueryString()); + return params; + } + + private String buildQ() { + StringBuilder sb = new StringBuilder(); + sb.append("(reviewText:") + .append(reviewText) + .append(" reviewerName:") + .append(reviewerName) + .append(" summary:") + .append(summary) + .append(" asin:") + .append(asin) + .append(")"); + // The query string ends up looking like this: (reviewText:* reviewerName:* summary:*) + // OR between parameters + System.out.println(sb.toString()); + return sb.toString(); + + } + + public enum Sort { + ASC("asc"), DESC("desc"); + + private String label; + + Sort(String label) { + this.label = label; + } + } +} diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties index a17e2a1..12565a4 100644 --- a/server/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,2 +1,2 @@ server.servlet.context-path=/api -solr.address=http://localhost:8983/solr/techproducts +solr.address=http://localhost:8983/solr/reviews diff --git a/server/src/test/java/com/github/awrb/AwrbApplicationTests.java b/server/src/test/java/com/github/awrb/SolrApplicationTests.java similarity index 85% rename from server/src/test/java/com/github/awrb/AwrbApplicationTests.java rename to server/src/test/java/com/github/awrb/SolrApplicationTests.java index 591caeb..797583d 100644 --- a/server/src/test/java/com/github/awrb/AwrbApplicationTests.java +++ b/server/src/test/java/com/github/awrb/SolrApplicationTests.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class AwrbApplicationTests { +class SolrApplicationTests { @Test void contextLoads() { diff --git a/ui/solr/.gitignore b/ui/solr/.gitignore index 4d29575..9733a5e 100644 --- a/ui/solr/.gitignore +++ b/ui/solr/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +yarn.lock +package-lock.json diff --git a/ui/solr/package.json b/ui/solr/package.json index af59670..68611fa 100644 --- a/ui/solr/package.json +++ b/ui/solr/package.json @@ -3,13 +3,16 @@ "version": "0.1.0", "private": true, "dependencies": { - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "@material-ui/core": "4.11.3", + "@material-ui/icons": "4.11.2", + "@testing-library/jest-dom": "5.11.4", + "@testing-library/react": "11.1.0", + "@testing-library/user-event": "12.1.10", + "axios": "0.21.1", + "react": "17.0.1", + "react-dom": "17.0.1", "react-scripts": "4.0.3", - "web-vitals": "^1.0.1" + "web-vitals": "1.0.1" }, "scripts": { "start": "react-scripts start", diff --git a/ui/solr/src/api/solr.js b/ui/solr/src/api/solr.js new file mode 100644 index 0000000..15971e7 --- /dev/null +++ b/ui/solr/src/api/solr.js @@ -0,0 +1,11 @@ +import axios from "axios"; + +const solr = axios.create({ + baseURL: "http://localhost:8080/api/", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, +}); + +export default solr; diff --git a/ui/solr/src/components/App.js b/ui/solr/src/components/App.js new file mode 100644 index 0000000..96c0344 --- /dev/null +++ b/ui/solr/src/components/App.js @@ -0,0 +1,108 @@ +import React, { useState } from "react"; +import SearchBar from "./SearchBar"; +import SearchResult from "./SearchResult"; +import SortSelect from "./SortSelect"; +import solr from "../api/solr"; +import { List, CircularProgress, Grid } from "@material-ui/core"; +import { makeStyles } from "@material-ui/core/styles"; +import FilterSelect from "./FilterSelect"; +import Constants from "../constants"; +import PaginationSelect from "./PaginationSelect"; + +const useStyles = makeStyles({ + loader: { margin: 20 }, + select: { marginLeft: "3vw" }, +}); + +const App = () => { + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + const [filter, setFilter] = useState(Constants.REVIEW_TEXT); + const [term, setTerm] = useState(""); + const [sort, setSort] = useState(Constants.DESC); + const [rows, setRows] = useState(10); + + const classes = useStyles(); + + const getParams = () => { + const params = { + [Constants.REVIEWER_NAME]: "*", + [Constants.REVIEW_TEXT]: "*", + [Constants.SUMMARY]: "*", + [Constants.ASIN]: "*", + [Constants.ROWS]: rows, + [Constants.SORT]: sort, + }; + + if (filter === Constants.REVIEW_TEXT) { + params[Constants.REVIEW_TEXT] = term; + } else if (filter === Constants.REVIEWER_NAME) { + params[Constants.REVIEWER_NAME] = term; + } else if (filter === Constants.SUMMARY) { + params[Constants.SUMMARY] = term; + } else { + params[Constants.ASIN] = term; + } + + return params; + }; + + const onSubmit = async () => { + setLoading(true); + const response = await solr.get("/search", { params: getParams() }); + setResults(response.data); + setLoading(false); + }; + + const renderResults = () => { + return ( + + {results.map((result) => ( + + ))} + + ); + }; + + return ( + + + + setTerm(newTerm)} + onSubmit={onSubmit} + /> + + + setFilter(filter)} + /> + + + setSort(sort)} + value={sort} + className={classes.select} + /> + + + setRows(rows)} /> + + + {loading && } + {!loading && results.length > 0 && renderResults()} + + ); +}; + +export default App; diff --git a/ui/solr/src/components/FilterSelect.js b/ui/solr/src/components/FilterSelect.js new file mode 100644 index 0000000..5bc9e38 --- /dev/null +++ b/ui/solr/src/components/FilterSelect.js @@ -0,0 +1,28 @@ +import { InputLabel, MenuItem, Select } from "@material-ui/core"; +import React from "react"; +import Constants from "../constants"; + +const FilterSelect = ({ onChange, value }) => { + const handleChange = (event) => { + onChange(event.target.value); + }; + + return ( + + Filter By + + + ); +}; + +export default FilterSelect; diff --git a/ui/solr/src/components/PaginationSelect.js b/ui/solr/src/components/PaginationSelect.js new file mode 100644 index 0000000..5a932d2 --- /dev/null +++ b/ui/solr/src/components/PaginationSelect.js @@ -0,0 +1,27 @@ +import { InputLabel, MenuItem, Select } from "@material-ui/core"; +import React from "react"; + +const PaginationSelect = ({ onChange, value }) => { + const handleChange = (event) => { + onChange(event.target.value); + }; + + return ( + + Rows per page + + + ); +}; + +export default PaginationSelect; diff --git a/ui/solr/src/components/SearchBar.js b/ui/solr/src/components/SearchBar.js new file mode 100644 index 0000000..dd11c70 --- /dev/null +++ b/ui/solr/src/components/SearchBar.js @@ -0,0 +1,59 @@ +import React from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Paper from "@material-ui/core/Paper"; +import InputBase from "@material-ui/core/InputBase"; +import IconButton from "@material-ui/core/IconButton"; +import MenuIcon from "@material-ui/icons/Menu"; +import SearchIcon from "@material-ui/icons/Search"; + +const useStyles = makeStyles((theme) => ({ + root: { + padding: "2px 4px", + display: "flex", + alignItems: "center", + width: 400, + }, + input: { + marginLeft: theme.spacing(1), + flex: 1, + }, + iconButton: { + padding: 10, + }, +})); + +const SearchBar = ({ onSubmit, onChange, value }) => { + const classes = useStyles(); + + return ( + { + e.preventDefault(); + onSubmit(); + }} + component="form" + className={classes.root} + > + + + + + onChange(e.target.value)} + className={classes.input} + placeholder="Search" + inputProps={{ "aria-label": "search" }} + /> + + + + + ); +}; + +export default SearchBar; diff --git a/ui/solr/src/components/SearchResult.js b/ui/solr/src/components/SearchResult.js new file mode 100644 index 0000000..655a240 --- /dev/null +++ b/ui/solr/src/components/SearchResult.js @@ -0,0 +1,33 @@ +import { Divider, ListItem, Paper, Typography } from "@material-ui/core"; +import React from "react"; + +const SearchResult = ({ + key, + summary, + reviewText, + author, + time, + asin, + rating, +}) => { + return ( + + + {summary} + {reviewText} +
+ + {author} + + {time} + + {`Rating ${rating}`} + + {`ASIN ${asin}`} +
+
+
+ ); +}; + +export default SearchResult; diff --git a/ui/solr/src/components/SortSelect.js b/ui/solr/src/components/SortSelect.js new file mode 100644 index 0000000..7b57b55 --- /dev/null +++ b/ui/solr/src/components/SortSelect.js @@ -0,0 +1,26 @@ +import { InputLabel, MenuItem, Select } from "@material-ui/core"; +import React from "react"; +import Constants from "../constants"; + +const SortSelect = ({ onChange, value }) => { + const handleChange = (event) => { + onChange(event.target.value); + }; + + return ( + + Sort by rating + + + ); +}; + +export default SortSelect; diff --git a/ui/solr/src/constants.js b/ui/solr/src/constants.js new file mode 100644 index 0000000..8f09b0d --- /dev/null +++ b/ui/solr/src/constants.js @@ -0,0 +1,21 @@ +export const REVIEWER_NAME = "reviewerName"; +export const REVIEW_TEXT = "reviewText"; +export const SUMMARY = "summary"; +export const ASIN = "asin"; +export const ROWS = "rows"; +export const ASC = "ASC"; +export const DESC = "DESC"; +export const SORT = "sort"; + +export const CONSTANTS = { + REVIEWER_NAME, + REVIEW_TEXT, + SUMMARY, + ASIN, + ROWS, + DESC, + ASC, + SORT, +}; + +export default CONSTANTS; diff --git a/ui/solr/src/index.js b/ui/solr/src/index.js index ef2edf8..66bdb66 100644 --- a/ui/solr/src/index.js +++ b/ui/solr/src/index.js @@ -1,14 +1,13 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./components/App"; +import reportWebVitals from "./reportWebVitals"; ReactDOM.render( , - document.getElementById('root') + document.getElementById("root") ); // If you want to start measuring performance in your app, pass a function