Merge pull request #16 from RandellDawson/feat/create-search-component

[Feature] Finalized Search Component functionality
This commit is contained in:
Honman Yau
2018-12-01 18:48:25 +11:00
committed by mrugesh mohapatra
parent db0fd603a5
commit b228d426f6
8 changed files with 154 additions and 39 deletions

View File

@ -0,0 +1,42 @@
import React from 'react';
import styled from 'styled-components';
const List = styled.div`
margin: 5px;
display: flex;
flex-wrap: wrap;
`;
const ListItem = styled.div`
padding: 0 5px;
`;
const FilenameResults = ({ searchValue, results }) => {
const elements = results.map((result) => {
const { filename, prs: prObjects } = result;
const prs = prObjects.map(({ number }, index) => {
const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`;
return <ListItem key={`${filename}-${index}`}>
<a href={prUrl} rel="noopener noreferrer" target="_blank">{number}</a>
</ListItem>;
});
return (
<div key={filename}>
{filename}
<List>
{prs}
</List>
</div>
);
});
return (
<div>
{results.length ? <h3>Results for: {searchValue}</h3> : null}
{elements}
</div>
);
};
export default FilenameResults;

View File

@ -8,7 +8,6 @@ const Container = styled.input`
const Input = React.forwardRef((props, ref) => ( const Input = React.forwardRef((props, ref) => (
<Container <Container
type="text" type="text"
placeholder="PR #"
onChange={props.onInputEvent} onChange={props.onInputEvent}
onKeyPress={props.onInputEvent} onKeyPress={props.onInputEvent}
value={props.value} value={props.value}

View File

@ -7,8 +7,24 @@ const Result = styled.div`
&:nth-child(odd) { &:nth-child(odd) {
background: #eee; background: #eee;
} }
padding: 3px;
`; `;
const List = styled.div`
margin: 5px;
display: flex;
flex-wrap: wrap;
`;
const ListItem = styled.a`
flex-basis: 33%;
overflow: hidden;
`;
const detailsStyle = { padding: '3px' };
const filenameTitle = { fontWeight: '600' };
class Pareto extends React.Component { class Pareto extends React.Component {
state = { state = {
data: [] data: []
@ -33,20 +49,22 @@ class Pareto extends React.Component {
render() { render() {
const { data } = this.state; const { data } = this.state;
const elements = data.map((entry) => { const elements = data.map((entry) => {
const { filename, count, prs } = entry; const { filename, count, prs } = entry;
const prsList = prs.reduce((html, { number, username }) => { const prsList = prs.map(({ number, username }) => {
const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`; const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`;
return html += ` return (
<a href=${prUrl} rel="noopener noreferrer" target="_blank">#${number} <span>${username}</span></a>, `; <ListItem href={prUrl} rel="noopener noreferrer" target="_blank">
}, ''); #{number} <span>({username})</span>
</ListItem>
)
});
return ( return (
<Result key={filename}> <Result key={filename}>
{filename}<br /> <span style={filenameTitle}>{filename}</span><br />
<details> <details style={detailsStyle}>
<summary># of PRs: {count}</summary> <summary># of PRs: {count}</summary>
<div dangerouslySetInnerHTML={{__html: prsList}} /> <List>{prsList}</List>
</details> </details>
</Result> </Result>
); );

View File

@ -1,20 +1,24 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
const List = styled.ul` const Container = styled.div`
margin: 5px; margin-bottom: 15px;
`; `;
const Results = ({ foundPRs }) => { const List = styled.ul`
const elements = foundPRs.map((foundPR) => { margin: 3px;
const { number, filenames, username } = foundPR; `;
const PrResults = ({ searchValue, results }) => {
const elements = results.map((result, idx) => {
const { number, filenames, username } = result;
const files = filenames.map((filename, index) => { const files = filenames.map((filename, index) => {
return <li key={`${number}-${index}`}>{filename}</li>; return <li key={`${number}-${index}`}>{filename}</li>;
}); });
const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}` const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`
return ( return (
<div key={number}> <Container key={`${number}-${idx}`}>
{!Number(number) {!Number(number)
? number ? number
: <> : <>
@ -25,15 +29,16 @@ const Results = ({ foundPRs }) => {
<List> <List>
{files} {files}
</List> </List>
</div> </Container>
); );
}); });
return ( return (
<div> <div>
{results.length ? <h3>Results for PR# {searchValue}</h3> : null}
{elements} {elements}
</div> </div>
); );
}; };
export default Results; export default PrResults;

View File

@ -1,45 +1,62 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import Input from './Input'; import Input from './Input';
import Results from './Results'; import PrResults from './PrResults';
import FilenameResults from './FilenameResults';
import SearchOption from './SearchOption';
class Search extends Component { class Search extends Component {
state = { state = {
number: '', searchValue: '',
foundPRs: [], selectedOption: 'pr',
results: []
}; };
clearObj = { number: '', foundPRs: [] }; clearObj = { searchValue: '', results: [] };
inputRef = React.createRef(); inputRef = React.createRef();
handleInputEvent = (event) => { handleInputEvent = (event) => {
const { type, key, target: { value } } = event; const { type, key, target: { value: searchValue } } = event;
if (type === 'change') { if (type === 'change') {
if (Number(value) || value === '') { if (this.state.selectedOption === 'pr'){
this.setState((prevState) => ({ number: value, foundPRs: [] })); if (Number(searchValue) || searchValue === '') {
this.setState((prevState) => ({ searchValue, results: [] }));
}
}
else {
this.setState((prevState) => ({ searchValue, results: [] }));
} }
} }
else if (type === 'keypress' && key === 'Enter') { else if (type === 'keypress' && key === 'Enter') {
this.searchPRs(value); this.searchPRs(searchValue);
} }
} }
handleButtonClick = () => { handleButtonClick = () => {
const { number } = this.state; const { searchValue } = this.state;
this.searchPRs(number); this.searchPRs(searchValue);
} }
searchPRs = (number) => { handleOptionChange = (changeEvent) => {
fetch(`https://pr-relations.glitch.me/pr/${number}`) const selectedOption = changeEvent.target.value;
this.setState((prevState) => ({ selectedOption, ...this.clearObj }));
}
searchPRs = (value) => {
const { selectedOption } = this.state;
const baseUrl = 'https://pr-relations.glitch.me/';
const fetchUrl = baseUrl + (selectedOption === 'pr' ? `pr/${value}` : `search/?value=${value}`);
fetch(fetchUrl)
.then((response) => response.json()) .then((response) => response.json())
.then(({ ok, foundPRs }) => { .then((response) => {
if (ok) { if (response.ok) {
if (!foundPRs.length) { const { results } = response;
foundPRs.push({ number: 'No PRs with matching files', filenames: [] }); const objArrName = selectedOption === 'pr' ? 'filenames' : 'prs';
if (!results.length) {
results.push({ searchValue: 'No matching results', [objArrName]: [] });
} }
this.setState((prevState) => ({ foundPRs })); this.setState((prevState) => ({ results }));
} }
else { else {
this.inputRef.current.focus(); this.inputRef.current.focus();
@ -51,13 +68,22 @@ class Search extends Component {
} }
render() { render() {
const { handleButtonClick, handleInputEvent, inputRef, state } = this; const { handleButtonClick, handleInputEvent, inputRef, handleOptionChange, state } = this;
const { number, foundPRs } = state; const { searchValue, results, selectedOption } = state;
return ( return (
<> <>
<Input ref={inputRef} value={number} onInputEvent={handleInputEvent} /> <div>
<SearchOption value="pr" onOptionChange={handleOptionChange} selectedOption={selectedOption}>
PR #
</SearchOption>
<SearchOption value="filename" onOptionChange={handleOptionChange} selectedOption={selectedOption}>
Filename
</SearchOption>
</div>
<Input ref={inputRef} value={searchValue} onInputEvent={handleInputEvent} />
<button onClick={handleButtonClick}>Search</button> <button onClick={handleButtonClick}>Search</button>
<Results foundPRs={foundPRs} /> {selectedOption === 'pr' && <PrResults searchValue={searchValue} results={results} /> }
{selectedOption === 'filename' && <FilenameResults searchValue={searchValue} results={results} /> }
</> </>
); );
} }

View File

@ -0,0 +1,16 @@
import React from 'react';
const SearchOption = ({ children, value, selectedOption, onOptionChange }) => (
<label>
<input
name="searchType"
type="radio"
value={value}
checked={selectedOption === value}
onChange={onOptionChange}
/>
{children}
</label>
);
export default SearchOption;

View File

@ -16,6 +16,11 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
a {
text-decoration: none;
color: blue;
}
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;

View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1