Skip to content

Commit

Permalink
Fix tag extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
itaisteinherz committed Feb 14, 2019
1 parent d9634ce commit 83c8924
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 52 deletions.
16 changes: 8 additions & 8 deletions client/components/SearchForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ class SearchForm extends React.Component {
}

onSearch = async value => {
if (this.state.isNewQuery) {
this.setState({isNewQuery: false});
}

try {
await this.props.search(value);
} catch (error) {
console.error("error", error);
}

if (this.state.isNewQuery) { // TODO: Check if conditionally updating the state even matters (compared to just overriding the current state).
this.setState({isNewQuery: false});
}
}

render() {
const {ranSearch = false, stemmedWords = [], updateTags} = this.props;
const {ranSearch = false, extractedTags = [], updateTags} = this.props;

return (
<div
Expand All @@ -61,7 +61,7 @@ class SearchForm extends React.Component {
onChange={this.handleChange}
/>
<div className="search_input__tag_select">
<SearchTag ranSearch={ranSearch} stemmedWords={stemmedWords} updateTags={updateTags} isNewQuery={this.state.isNewQuery}/>
<SearchTag ranSearch={ranSearch} extractedTags={extractedTags} updateTags={updateTags} isNewQuery={this.state.isNewQuery}/>
</div>
</div>
);
Expand All @@ -71,12 +71,12 @@ class SearchForm extends React.Component {
SearchForm.propTypes = {
search: PropTypes.func.isRequired,
ranSearch: PropTypes.bool,
stemmedWords: PropTypes.arrayOf(PropTypes.string),
extractedTags: PropTypes.arrayOf(PropTypes.string),
updateTags: PropTypes.func
};
SearchForm.defaultProps = {
ranSearch: false,
stemmedWords: [],
extractedTags: [],
updateTags: () => {}
};

Expand Down
17 changes: 5 additions & 12 deletions client/components/SearchTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,12 @@ class SearchTag extends React.Component {
}
}

const {tags} = this.state;
const {isNewQuery = true, stemmedWords = []} = this.props;

const tagNames = tags.map(tag => tag.name);

if (stemmedWords && isNewQuery && prevProps.stemmedWords !== stemmedWords) {
const uniqueTags = stemmedWords.filter(word => tagNames.includes(word));
const {isNewQuery = true, extractedTags = []} = this.props;

if (extractedTags && isNewQuery && prevProps.extractedTags !== extractedTags) { // TODO: Check if the last comparison even works, and if it doens't fix it.
// TODO: Find a solution better than updating the state of the component in `componentDidUpdate`.
this.setState({ // eslint-disable-line react/no-did-update-set-state
activeTags: [
...uniqueTags
]
activeTags: [...extractedTags]
});
}
}
Expand Down Expand Up @@ -114,13 +107,13 @@ class SearchTag extends React.Component {
SearchTag.propTypes = {
ranSearch: PropTypes.bool,
isNewQuery: PropTypes.bool,
stemmedWords: PropTypes.arrayOf(PropTypes.string),
extractedTags: PropTypes.arrayOf(PropTypes.string),
updateTags: PropTypes.func.isRequired
};
SearchTag.defaultProps = {
ranSearch: false,
isNewQuery: true,
stemmedWords: []
extractedTags: []
};

export default SearchTag;
2 changes: 1 addition & 1 deletion client/components/TagList.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TagList extends React.Component {
}

TagList.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string).isRequired
tags: PropTypes.arrayOf(PropTypes.object).isRequired
};

export default TagList;
8 changes: 4 additions & 4 deletions client/pages/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Search extends React.Component {
state = {
activeTags: [],
questions: [],
stemmedWords: [],
extractedTags: [],
ranSearch: false,
showPost: false
}
Expand All @@ -31,7 +31,7 @@ class Search extends React.Component {

this.setState({
questions: json.result,
stemmedWords: json.stemmedWords
extractedTags: json.extractedTags
});
} catch (error) {
console.error(error);
Expand All @@ -53,11 +53,11 @@ class Search extends React.Component {
}

render() { // TODO: Only show the option to post a new question once a user searches something, and hide it when the query text field changes.
const {questions, stemmedWords, ranSearch, showPost} = this.state;
const {questions, extractedTags, ranSearch, showPost} = this.state;

return (
<MainLayout authStateListener={this.updatePostQuestion}>
<SearchForm search={this.querySearch} ranSearch={ranSearch} stemmedWords={stemmedWords} updateTags={this.updateActiveTags}/>
<SearchForm search={this.querySearch} ranSearch={ranSearch} extractedTags={extractedTags} updateTags={this.updateActiveTags}/>
<QuestionList questions={questions} ranSearch={ranSearch && questions.length === 0}/> {/* TODO: Set the list to `loading` when searching a query. */}
{showPost ?
<div className="post__question">
Expand Down
2 changes: 1 addition & 1 deletion docs/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ paths:
email: "test@test.testa"
createdAt: '2019-02-06T09:47:27.482Z'
updatedAt: '2019-02-06T09:47:27.482Z'
stemmedWords: []
extractedTags: []
500:
description: Internal server error
examples:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev:client": "cd client && npm run dev",
"start:client": "cd client && npm run build && npm start",
"dev:server": "npm run start:server",
"start:server": "cd server && npm run compose:build && npm run compose:start",
"start:server": "cd server && npm run compose:start",
"start": "concurrently npm:start:*",
"dev": "concurrently npm:dev:*",
"install:client": "cd client && npm install",
Expand Down
10 changes: 6 additions & 4 deletions server/components/search/search-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const {Question, Reply} = require("../content/content-model");
const Tag = require("../tags/tag-model");
const Thread = require("../thread/thread-model");
const User = require("../users/user-model");
const {resolveTagNames} = require("../util/tag");
const {resolveTagNames, filterTags} = require("../util/tag");

exports.getQuestion = async (req, res) => {
const {q: query = "", tags} = req.query;
Expand All @@ -14,27 +14,29 @@ exports.getQuestion = async (req, res) => {
// the text with 'foo' AND 'bar' instead of 'foo' OR 'bar').
const queryString = query.split(" ").map(word => `"${decodeURIComponent(word)}"`).join(" ");// TODO: Escape the given input so that the user isn't able to run malicious queries on the database (such as `" (insert bad query here)`).
let queryTags = [];

if (tags) {
const parsedTags = tags.split(",").map(decodeURIComponent);
queryTags = await resolveTagNames(parsedTags);
}

try {
const queryData = await Question.find({$text: {$search: queryString}}).explain(true);
const stemmedWords = queryData[0].queryPlanner.winningPlan.inputStage.parsedTextQuery.terms;
const {terms} = queryData[0].queryPlanner.winningPlan.inputStage.parsedTextQuery;
const extractedTags = await filterTags(terms);

const result = await Question
.find({
$or: [
{$text: {$search: queryString}},
{tags: {$elemMatch: {$in: queryTags}}}
{tags: {$elemMatch: {$in: queryTags}}} // TODO: Check if we should search for both the given tags as well as the extracted ones.
]
})
.sort({createdAt: "desc"})
.limit(20)
.select("-__v");

res.status(200).json({result, stemmedWords});
res.status(200).json({result, extractedTags});
} catch (error) {
res.status(500).json(error);
}
Expand Down
15 changes: 3 additions & 12 deletions server/components/tags/tag-controllers.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
const {Question} = require("../content/content-model");
const {findTag, findTags} = require("../util/tag");
const Tag = require("./tag-model");

exports.getTags = async (req, res) => {
const {query} = req.params;
const findOptions = query ? [{
name: {
$regex: query,
$options: "i"
}
}] : [];

try {
const tags = await Tag
.find(...findOptions)
const tags = await (query ? findTags(query) : Tag.find())
.limit(5)
.select("-__v");

Expand All @@ -24,10 +18,7 @@ exports.getTags = async (req, res) => {

exports.getTagQuestions = async (req, res) => {
const {tagName} = req.params;
const tag = await Tag.findOne({name: {
$regex: tagName,
$options: "i"
}}).select("_id");
const tag = await findTag(tagName).select("_id");

if (!tag || !tag._id) {
res.status(404).json({error: "Couldn't find a tag matching the given query."});
Expand Down
39 changes: 32 additions & 7 deletions server/components/util/tag.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
const Tag = require("../tags/tag-model");

exports.findTag = tagName => {
return Tag.findOne({
name: {
$regex: tagName,
$options: "i"
}
});
};

exports.findTags = query => {
return Tag.find({
name: {
$regex: query,
$options: "i"
}
});
};

exports.resolveTagNames = async tags => {
const resolvedTags = await Promise.all(tags.map(async tag => {
try {
const tagDoc = await Tag.findOne({name: tag}).exec();
const tagDoc = await exports.findTag(tag);
return tagDoc ? tagDoc._id : false;
} catch (_) {
return false;
}
}));

/* (Commented out the creation of the tag since it has
to be submitted to approval.)
return resolvedTags.filter(Boolean);
};

const newTag = await new Tag({name: tag}).save();
return newTag._id;
*/
exports.filterTags = async words => {
const tags = await Promise.all(words.map(async word => {
try {
const tagDoc = await exports.findTag(word);

return tagDoc && tagDoc._id ? word : false;
} catch (_) {
return false;
}
}));

return resolvedTags.filter(Boolean);
return tags.filter(Boolean);
};
2 changes: 0 additions & 2 deletions server/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ const path = require("path");

const dotenv = require("dotenv");

console.log("env", process.env.NODE_ENV);

const stage = process.env.NODE_ENV || "production";
dotenv.config({
path: path.resolve(process.cwd(), `${stage === "production" ? "prod" : "dev"}.env`)
Expand Down

0 comments on commit 83c8924

Please sign in to comment.