Skip to content

Commit 0dedcf9

Browse files
authored
Merge branch 'main' into copilot/alter-github-get-requests
2 parents 705c956 + 107b5f1 commit 0dedcf9

15 files changed

Lines changed: 181 additions & 78 deletions

File tree

frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@conorheffron/ironoc-frontend",
3-
"version": "9.1.8",
3+
"version": "9.1.9",
44
"private": false,
55
"license": "GPL-3.0-or-later",
66
"dependencies": {

frontend/src/components/RepoIssues.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ const RepoIssues = () => {
3535

3636
const [repoIssueList, setRepoIssueList] = useState([]);
3737
const [isLoading, setIsLoading] = useState(true);
38-
const [value, setValue] = useState('');
38+
const [usernameValue, setUsernameValue] = useState(id);
39+
const [repoValue, setRepoValue] = useState(repo);
3940

4041
useEffect(() => {
4142
const fetchIssues = async () => {
@@ -44,20 +45,27 @@ const RepoIssues = () => {
4445
const body = await response.json();
4546
setRepoIssueList(body);
4647
}
48+
setUsernameValue(id);
49+
setRepoValue(repo);
4750
setIsLoading(false);
4851
};
4952
fetchIssues();
5053
}, [id, repo]);
5154

52-
const handleChange = (event) => setValue(event.target.value);
55+
const handleUsernameChange = (event) => setUsernameValue(event.target.value);
56+
57+
const handleRepoChange = (event) => setRepoValue(event.target.value);
5358

5459
const onSubmit = (event) => {
5560
event.preventDefault();
56-
navigate(`/issues/${id}/${value}`, {
61+
const searchUsername = usernameValue.trim() || id;
62+
const searchRepo = repoValue.trim() || repo;
63+
64+
navigate(`/issues/${searchUsername}/${searchRepo}`, {
5765
replace: true,
5866
state: {
59-
id: id,
60-
repo: value,
67+
id: searchUsername,
68+
repo: searchRepo,
6169
},
6270
});
6371
navigate(0);
@@ -183,13 +191,19 @@ const RepoIssues = () => {
183191
<Container fluid={true}>
184192
<br />
185193
<InputGroup className="mb-3">
194+
<Form.Control
195+
placeholder="Enter GitHub User ID... Example: conorheffron"
196+
aria-label="Enter GitHub User ID..."
197+
type="text"
198+
value={usernameValue}
199+
onChange={handleUsernameChange}
200+
/>
186201
<Form.Control
187202
placeholder="Enter Project Name... Example: ironoc-db"
188203
aria-label="Enter Project Name..."
189-
aria-describedby="basic-addon2"
190204
type="text"
191-
value={value}
192-
onChange={handleChange}
205+
value={repoValue}
206+
onChange={handleRepoChange}
193207
/>
194208
<Button
195209
variant="outline-secondary"

frontend/src/components/__tests__/RepoIssues.test.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,17 +164,41 @@ describe('RepoIssues', () => {
164164
window.fetch = global.fetch;
165165
render(<RepoIssues />);
166166
await waitFor(() => expect(screen.queryByTestId('spinner')).not.toBeInTheDocument());
167-
const input = screen.getByTestId('form-control');
167+
const usernameInput = screen.getByRole('textbox', { name: /Enter GitHub User ID/i });
168+
const repoInput = screen.getByRole('textbox', { name: /Enter Project Name/i });
168169
const button = screen.getByText(/Search Issues/i);
169-
fireEvent.change(input, { target: { value: 'newrepo' } });
170+
fireEvent.change(usernameInput, { target: { value: 'other-user' } });
171+
fireEvent.change(repoInput, { target: { value: 'newrepo' } });
170172
fireEvent.click(button);
171-
expect(mockNavigate).toHaveBeenCalledWith('/issues/user/newrepo', {
173+
expect(mockNavigate).toHaveBeenCalledWith('/issues/other-user/newrepo', {
172174
replace: true,
173175
state: {
174-
id: 'user',
176+
id: 'other-user',
175177
repo: 'newrepo',
176178
},
177179
});
178180
expect(mockNavigate).toHaveBeenCalledWith(0);
179181
});
182+
183+
it('uses current route values when search fields are blank', async () => {
184+
useParams.mockReturnValue({ id: 'user', repo: 'repo' });
185+
global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve([]) }));
186+
window.fetch = global.fetch;
187+
render(<RepoIssues />);
188+
await waitFor(() => expect(screen.queryByTestId('spinner')).not.toBeInTheDocument());
189+
const usernameInput = screen.getByRole('textbox', { name: /Enter GitHub User ID/i });
190+
const repoInput = screen.getByRole('textbox', { name: /Enter Project Name/i });
191+
const button = screen.getByText(/Search Issues/i);
192+
fireEvent.change(usernameInput, { target: { value: '' } });
193+
fireEvent.change(repoInput, { target: { value: '' } });
194+
fireEvent.click(button);
195+
expect(mockNavigate).toHaveBeenCalledWith('/issues/user/repo', {
196+
replace: true,
197+
state: {
198+
id: 'user',
199+
repo: 'repo',
200+
},
201+
});
202+
expect(mockNavigate).toHaveBeenCalledWith(0);
203+
});
180204
});

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>conorheffron</groupId>
77
<artifactId>ironoc</artifactId>
8-
<version>9.1.8</version>
8+
<version>9.1.9</version>
99
<packaging>war</packaging>
1010

1111
<distributionManagement>

src/main/java/net/ironoc/portfolio/controller/CoffeeController.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import module java.base;
44

5-
import com.fasterxml.jackson.core.JsonProcessingException;
65
import com.fasterxml.jackson.databind.ObjectMapper;
76
import io.swagger.v3.oas.annotations.Operation;
87
import io.swagger.v3.oas.annotations.responses.ApiResponse;
98
import io.swagger.v3.oas.annotations.responses.ApiResponses;
109
import net.ironoc.portfolio.graph.BrewsResolver;
1110
import net.ironoc.portfolio.service.GraphQLClient;
11+
import net.ironoc.portfolio.exception.IronocJsonException;
1212
import net.ironoc.portfolio.logger.AbstractLogger;
1313
import net.ironoc.portfolio.domain.CoffeeDomain;
1414
import net.ironoc.portfolio.service.Coffees;
@@ -104,8 +104,12 @@ public ResponseEntity<List<CoffeeDomain>> getCoffeeDetailsGraphQl() {
104104
try {
105105
CoffeeDomain coffeeDomain = new ObjectMapper().convertValue(coffeeMap, CoffeeDomain.class);
106106
coffeeDomains.add(coffeeDomain);
107-
} catch (Exception e) {
108-
error("Error occurred mapping coffee domain object", e.getMessage());
107+
} catch (IllegalArgumentException e) {
108+
error("Error occurred mapping coffee domain object", e);
109+
throw new IronocJsonException(
110+
"Failed to map GraphQL coffee payload: " + e.getMessage(),
111+
e
112+
);
109113
}
110114
}
111115

@@ -114,10 +118,12 @@ public ResponseEntity<List<CoffeeDomain>> getCoffeeDetailsGraphQl() {
114118
coffeesCache.put(coffeeDomains);
115119

116120
return ResponseEntity.ok(coffeeDomains);
117-
} catch (JsonProcessingException e) {
121+
} catch (IronocJsonException e) {
122+
throw e;
123+
} catch (Exception e) {
118124
error("Unexpected exception occurred loading GraphQL query, msg={}", e.getMessage());
125+
throw new IronocJsonException("Unexpected exception occurred loading GraphQL query", e);
119126
}
120-
return ResponseEntity.ok(Collections.emptyList());
121127
} else {
122128
debug("Returning cached brews, cachedResults={}", cachedResults);
123129
return ResponseEntity.ok(cachedResults);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.ironoc.portfolio.exception;
2+
3+
import jakarta.servlet.http.HttpServletRequest;
4+
import org.springframework.http.HttpStatus;
5+
import org.springframework.http.ProblemDetail;
6+
import org.springframework.web.bind.annotation.ExceptionHandler;
7+
import org.springframework.web.bind.annotation.RestControllerAdvice;
8+
9+
@RestControllerAdvice
10+
public class IronocExceptionHandler {
11+
12+
@ExceptionHandler(IronocJsonException.class)
13+
public ProblemDetail handleIronocJsonException(IronocJsonException exception, HttpServletRequest request) {
14+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
15+
HttpStatus.INTERNAL_SERVER_ERROR,
16+
exception.getMessage()
17+
);
18+
problemDetail.setTitle("JSON processing failed");
19+
problemDetail.setProperty("path", request.getRequestURI());
20+
return problemDetail;
21+
}
22+
}

src/main/java/net/ironoc/portfolio/exception/IronocJsonException.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package net.ironoc.portfolio.exception;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
4-
5-
public class IronocJsonException extends JsonProcessingException {
3+
public class IronocJsonException extends RuntimeException {
64

75
public IronocJsonException(String message) {
86
super(message);

src/main/java/net/ironoc/portfolio/graph/BrewsResolver.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.fasterxml.jackson.core.type.TypeReference;
66
import com.fasterxml.jackson.databind.ObjectMapper;
77
import graphql.kickstart.tools.GraphQLQueryResolver;
8+
import net.ironoc.portfolio.exception.IronocJsonException;
89
import net.ironoc.portfolio.logger.AbstractLogger;
910
import org.springframework.core.io.ClassPathResource;
1011
import org.springframework.stereotype.Component;
@@ -19,11 +20,15 @@ public List<Map<String, Object>> getBrews() {
1920
try {
2021
// Load the JSON file from resources
2122
return objectMapper.readValue(
22-
new ClassPathResource(BREWS_JSON_FILE).getInputStream(),
23+
getBrewsInputStream(),
2324
new TypeReference<>() {});
2425
} catch (IOException e) {
2526
error("Failed to load Brews JSON", e);
27+
throw new IronocJsonException("Failed to load brews JSON", e);
2628
}
27-
return Collections.emptyList();
29+
}
30+
31+
protected InputStream getBrewsInputStream() throws IOException {
32+
return new ClassPathResource(BREWS_JSON_FILE).getInputStream();
2833
}
2934
}

src/main/java/net/ironoc/portfolio/graph/DonateItemsResolver.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import jakarta.annotation.PostConstruct;
99
import net.ironoc.portfolio.dto.Donate;
1010
import net.ironoc.portfolio.dto.DonateItemOrder;
11+
import net.ironoc.portfolio.exception.IronocJsonException;
1112
import net.ironoc.portfolio.logger.AbstractLogger;
1213
import org.springframework.core.io.ClassPathResource;
1314
import org.springframework.stereotype.Component;
@@ -39,6 +40,7 @@ public void loadDonateItems() {
3940
}
4041
} catch (IOException e) {
4142
error("Failed to load Donate items JSON", e);
43+
throw new IronocJsonException("Failed to load donate items JSON", e);
4244
}
4345
}
4446

0 commit comments

Comments
 (0)