Skip to content

Commit

Permalink
#100 Integrate GitHub POST issue API call
Browse files Browse the repository at this point in the history
  • Loading branch information
conorheffron committed Sep 30, 2024
1 parent 55052c5 commit 1fa4da8
Show file tree
Hide file tree
Showing 16 changed files with 285 additions and 11 deletions.
2 changes: 2 additions & 0 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import About from './About';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import RepoDetails from './RepoDetails';
import RepoIssues from './RepoIssues';
import CreateIssue from './CreateIssue';

class App extends Component {
render() {
Expand All @@ -16,6 +17,7 @@ class App extends Component {
<Route path='/projects' exact={true} component={RepoDetails}/>
<Route path='/issues' exact={true} component={RepoIssues}/>
<Route path='/issues/:repo' component={RepoIssues}/>
<Route path='/create-issue/:repo' component={CreateIssue}/>
<Route path="*" component={Home} />
</Switch>
</Router>
Expand Down
87 changes: 87 additions & 0 deletions frontend/src/CreateIssue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
import { Button, Container, Form, FormGroup, Input, Label } from 'reactstrap';
import AppNavbar from './AppNavbar';

class CreateIssue extends Component {

emptyItem = {
title: '',
body: '',
assignee: '',
labels: ''
};

constructor(props) {
super(props);
this.state = {
item: this.emptyItem
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

async componentDidMount() {
const issue = await (await fetch(`create-issue/${this.props.match.params.repo}`)).json();
this.setState({item: issue});
}

handleChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
let item = {...this.state.item};
item[name] = value;
this.setState({item});
}

async handleSubmit(event) {
event.preventDefault();
const {item} = this.state;
console.log(item);
await fetch(`/create-repo-issue/conorheffron/${this.props.match.params.repo}/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
});
this.props.history.push(`/issues/${this.props.match.params.repo}/`);
}

render() {
const {item} = this.state;
const title = <h2>Create Issue</h2>;

return <div>
<AppNavbar/><br /><br />
<Container fluid>
<br />
<h2>{title}</h2>
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="title">Title</Label>
<Input type="text" name="title" id="title" value={item.title || ''}
onChange={this.handleChange} autoComplete="title"/>
</FormGroup>
<FormGroup>
<Label for="body">Body</Label>
<Input type="text" name="body" id="body" value={item.body || ''}
onChange={this.handleChange} autoComplete="body"/>
</FormGroup>
<FormGroup>
<Label for="assignee">Assignee</Label>
<Input type="text" name="assignee" id="assignee" value={item.assignee || ''}
onChange={this.handleChange} autoComplete="assignee"/>
</FormGroup>
<br />
<FormGroup>
<Button color="primary" type="submit">Save</Button>{' '}
</FormGroup>
</Form>
</Container>
</div>
}
}
export default withRouter(CreateIssue);
4 changes: 3 additions & 1 deletion frontend/src/RepoIssues.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ class RepoIssues extends Component {
<td>{issue.body}</td>
</tr>
});

const repo = this.props.match.params.repo;
return (
<div>
<AppNavbar/><br /><br />
<Container fluid>
<br />
<div className="float-right">
<Button color="success" tag={Link} to="/create-issue">Create Issue</Button>
<Button color="success" tag={Link} to={`/create-issue/${this.props.match.params.repo}`} >Create Issue</Button>
</div>
<br />
<h3>GitHub Projects</h3>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/ironoc/portfolio/client/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public interface Client {

<T> List<T> callGitHubApi(String apiUri, String uri, Class<T> type, String httpMethod);

void postGitHubApi(String apiUri, String uri, String httpMethod, String jsonBody) throws Exception;

HttpsURLConnection createConn(String url, String baseUrl, String httpMethod) throws IOException;

InputStream readInputStream(HttpsURLConnection conn) throws IOException;
Expand Down
52 changes: 46 additions & 6 deletions src/main/java/com/ironoc/portfolio/client/GitClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;

import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -46,7 +42,7 @@ public GitClient(PropertyConfigI propertyConfig,

@Override
public <T> List<T> callGitHubApi(String apiUri, String uri, Class<T> type, String httpMethod) {
info("Triggering GET request: url={}", apiUri);
info("Triggering {} request: url={}", httpMethod, apiUri);
List<T> dtos = new ArrayList<>();
InputStream inputStream = null;
try {
Expand Down Expand Up @@ -77,6 +73,50 @@ public <T> List<T> callGitHubApi(String apiUri, String uri, Class<T> type, Strin
return dtos;
}

@Override
public void postGitHubApi(String apiUri, String uri, String httpMethod, String jsonBody) throws Exception {
info("Triggering {} request: url={}", httpMethod, apiUri);
URL urlBase = new URL(uri);
String base = urlBase.getProtocol() + "://" + urlBase.getHost();
if (!urlUtils.isValidURL(apiUri) || !apiUri.startsWith(base)) {
log.error("The url is not valid for GIT client connection, url={}", apiUri);
return;
}
URL apiUrlEndpoint = new URL(apiUri);
HttpsURLConnection conn = (HttpsURLConnection) apiUrlEndpoint.openConnection();
String token = secretManager.getGitSecret();
if (StringUtils.isBlank(token)) {
log.warn("GIT token not set, the lower request rate will apply");
} else {
// TODO generate token
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setRequestProperty("Accept", "application/vnd.github.raw+json");
conn.setRequestProperty("X-GitHub-Api-Version", "2022-11-28");
}
conn.setRequestMethod(httpMethod);
conn.setFollowRedirects(propertyConfig.getGitFollowRedirects());
conn.setConnectTimeout(propertyConfig.getGitTimeoutConnect());
conn.setReadTimeout(propertyConfig.getGitTimeoutRead());
conn.setInstanceFollowRedirects(propertyConfig.getGitInstanceFollowRedirects());

// conn.setDoInput(true);
conn.setDoOutput(true);
// conn.setRequestProperty("Accept", "application/json");
// conn.setRequestProperty("Content-Type", "application/json");
OutputStream os = conn.getOutputStream();
os.write(jsonBody.getBytes());
os.flush();

// InputStream inputStream = this.readInputStream(conn);

info("code={}, message={}", conn.getResponseCode(), conn.getResponseMessage());

InputStream errorStream = conn.getErrorStream();
String jsonResponse = this.convertInputStreamToString(errorStream);
info(jsonResponse);
this.closeConn(errorStream);
}

@Override
public HttpsURLConnection createConn(String url, String baseUrl, String httpMethod) throws IOException {
URL urlBase = new URL(baseUrl);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/ironoc/portfolio/config/Properties.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum Properties {

GIT_API_ENDPOINT_REPOS("com.ironoc.portfolio.github.api.endpoint.repos"),
GIT_API_ENDPOINT_ISSUES("com.ironoc.portfolio.github.api.endpoint.issues"),
GIT_API_ENDPOINT_CREATE_ISSUE("com.ironoc.portfolio.github.api.endpoint.create-issue"),
GIT_TIMEOUT_CONNECT ("com.ironoc.portfolio.github.timeout.connect"),
GIT_TIMEOUT_READ("com.ironoc.portfolio.github.timeout.read"),
GIT_INSTANCE_FOLLOW_REDIRECTS("com.ironoc.portfolio.github.instance-follow-redirects"),
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/PropertyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public String getGitApiEndpointIssues() {
return environment.getRequiredProperty(propertyKey.getGitApiEndpointIssues());
}

@Override
public String getGitApiEndpointCreateIssue() {
return environment.getRequiredProperty(propertyKey.getGitApiEndpointCreateIssue());
}

@Override
public Integer getGitTimeoutConnect() {
return Integer.valueOf(environment.getRequiredProperty(propertyKey.getGitTimeoutConnect()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public interface PropertyConfigI {
Boolean getGitFollowRedirects();

String getGitApiEndpointIssues();

String getGitApiEndpointCreateIssue();
}
5 changes: 5 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/PropertyKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public String getGitApiEndpointIssues() {
return Properties.GIT_API_ENDPOINT_ISSUES.getKey();
}

@Override
public String getGitApiEndpointCreateIssue() {
return Properties.GIT_API_ENDPOINT_CREATE_ISSUE.getKey();
}

@Override
public String getGitTimeoutConnect() {
return Properties.GIT_TIMEOUT_CONNECT.getKey();
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/PropertyKeyI.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public interface PropertyKeyI {
String getGitFollowRedirects();

String getGitApiEndpointIssues();

String getGitApiEndpointCreateIssue();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ironoc.portfolio.controller;

import com.ironoc.portfolio.domain.CreateIssueDomain;
import com.ironoc.portfolio.domain.RepositoryDetailDomain;
import com.ironoc.portfolio.domain.RepositoryIssueDomain;
import com.ironoc.portfolio.dto.RepositoryDetailDto;
Expand All @@ -17,6 +18,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

Expand Down Expand Up @@ -64,6 +67,34 @@ public ResponseEntity<List<RepositoryIssueDomain>> getIssuesByUsernameAndRepoPat
return getIssuesByUsernameAndRepo(request, username, repository);
}

@PostMapping(value = {"/create-repo-issue/{username}/{repository}/"}, consumes = MediaType.APPLICATION_JSON_VALUE,
produces= MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<RepositoryIssueDomain>> postIssue(@PathVariable(value = "username") String username,
@PathVariable(value = "repository") String repository,
@RequestBody CreateIssueDomain createIssue) {
// user & repo name validation (must contain only letters, numbers and/or dash chars)
String userId = "";
String repo = "";
if (!StringUtils.isNoneBlank(username, repository)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Collections.emptyList());
} else if (!StringUtils.isAlphanumericSpace(sanitizeValue(username)
.replaceAll("-", " "))) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Collections.emptyList());
} else if (!StringUtils.isAlphanumericSpace(sanitizeValue(repository)
.replaceAll("-", " "))) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Collections.emptyList());
} else {
List<String> pathVars = sanitizeValues(username, repository);
userId = pathVars.get(0);
repo = pathVars.get(1);
}

// TODO post and save issue
gitDetailsService.createIssue(userId, repo, createIssue);
return ResponseEntity.status(HttpStatus.OK)
.body(gitDetailsService.mapIssuesToResponse(Collections.emptyList()));
}

private ResponseEntity<List<RepositoryIssueDomain>> getIssuesByUsernameAndRepo(HttpServletRequest request,
String username,
String repository) {
Expand All @@ -83,6 +114,7 @@ private ResponseEntity<List<RepositoryIssueDomain>> getIssuesByUsernameAndRepo(H
userId = pathVars.get(0);
repo = pathVars.get(1);
}

info("Github list issues by username={} and repo={} for request, host={}, uri={}, user-agent={}",
userId, repo,
request.getHeader("host"),
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/ironoc/portfolio/domain/CreateIssueDomain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ironoc.portfolio.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CreateIssueDomain {

private String title;

private String body;

private String assignee;

private String labels;
}
32 changes: 32 additions & 0 deletions src/main/java/com/ironoc/portfolio/dto/CreateIssueDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.ironoc.portfolio.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.Map;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CreateIssueDto {

private String owner;

private String repo;

private String title;

private String body;

private List<String> assignees;

private List<String> labels;

private Integer milestone;

private Map<String, String> headers;
}
3 changes: 3 additions & 0 deletions src/main/java/com/ironoc/portfolio/service/GitDetails.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ironoc.portfolio.service;

import com.ironoc.portfolio.domain.CreateIssueDomain;
import com.ironoc.portfolio.domain.RepositoryDetailDomain;
import com.ironoc.portfolio.domain.RepositoryIssueDomain;
import com.ironoc.portfolio.dto.RepositoryDetailDto;
Expand All @@ -19,5 +20,7 @@ List<RepositoryDetailDto> mapResponseToRepositories(

List<RepositoryIssueDto> getIssues(String userId, String repo);

void createIssue(String userId, String repo, CreateIssueDomain createIssue);

List<RepositoryIssueDomain> mapIssuesToResponse(List<RepositoryIssueDto> repositoryIssueDtos);
}
Loading

0 comments on commit 1fa4da8

Please sign in to comment.