Merge pull request #16 from RandellDawson/feat/create-search-component
[Feature] Finalized Search Component functionality
This commit is contained in:
committed by
mrugesh mohapatra
parent
db0fd603a5
commit
b228d426f6
42
dashboard-client/app/app/src/components/FilenameResults.js
Normal file
42
dashboard-client/app/app/src/components/FilenameResults.js
Normal 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;
|
@ -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}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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;
|
@ -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} /> }
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
16
dashboard-client/app/app/src/components/SearchOption.js
Normal file
16
dashboard-client/app/app/src/components/SearchOption.js
Normal 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;
|
@ -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;
|
||||||
|
4
dashboard-client/app/yarn.lock
Normal file
4
dashboard-client/app/yarn.lock
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user