Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API with queue history in CSV #166

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions simplq/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@
<artifactId>mailapi</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.8</version>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down Expand Up @@ -173,9 +178,9 @@
</configuration>
<dependencies>
<dependency>
<groupId>postgresql</groupId>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901.jdbc4</version>
<version>42.2.24</version>
</dependency>
</dependencies>
</plugin>
Expand Down
32 changes: 21 additions & 11 deletions simplq/src/main/java/me/simplq/controller/QueueController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package me.simplq.controller;

import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import me.simplq.controller.model.queue.CreateQueueRequest;
import me.simplq.controller.model.queue.CreateQueueResponse;
Expand All @@ -13,6 +12,7 @@
import me.simplq.controller.model.queue.QueueStatusResponse;
import me.simplq.controller.model.queue.UpdateQueueStatusResponse;
import me.simplq.service.QueueService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -24,6 +24,8 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
@RequestMapping("/v1")
@RequiredArgsConstructor
Expand All @@ -33,7 +35,7 @@ public class QueueController {

@PostMapping(path = "/queue")
public ResponseEntity<CreateQueueResponse> createQueue(
@Valid @RequestBody CreateQueueRequest createQueueRequest) {
@Valid @RequestBody CreateQueueRequest createQueueRequest) {
return ResponseEntity.ok(queueService.createQueue(createQueueRequest));
}

Expand All @@ -44,34 +46,34 @@ public ResponseEntity<MyQueuesResponse> getMyQueues() {

@PatchMapping(path = "/queue/{queueId}")
public ResponseEntity<PatchQueueResponse> patchQueue(
@Valid @RequestBody PatchQueueRequest patchRequest, @PathVariable("queueId") String queueId) {
@Valid @RequestBody PatchQueueRequest patchRequest, @PathVariable("queueId") String queueId) {

return ResponseEntity.ok(queueService.patchQueue(queueId, patchRequest));
}

@GetMapping(path = "/queue/{queueId}")
public ResponseEntity<QueueDetailsResponse> getQueueDetails(
@PathVariable("queueId") String queueId) {
@PathVariable("queueId") String queueId) {
return ResponseEntity.ok(queueService.getQueueDetails(queueId));
}

@PostMapping(path = "/queue/{queueId}")
public ResponseEntity<UpdateQueueStatusResponse> pauseQueueRequest(
@Valid @RequestBody PauseQueueRequest pauseQueueRequest,
@PathVariable("queueId") String queueId) {
@Valid @RequestBody PauseQueueRequest pauseQueueRequest,
@PathVariable("queueId") String queueId) {
return ResponseEntity.ok(queueService.pauseQueue(pauseQueueRequest, queueId));
}

@DeleteMapping(path = "/queue/{queueId}")
public ResponseEntity<UpdateQueueStatusResponse> deleteQueueRequest(
@PathVariable("queueId") String queueId) {
@PathVariable("queueId") String queueId) {
return ResponseEntity.ok(queueService.deleteQueue(queueId));
}

@GetMapping(path = "/queue/status")
public ResponseEntity<QueueStatusResponse> getQueueStatus(
@RequestParam(required = false) String queueId,
@RequestParam(required = false) String queueName) {
@RequestParam(required = false) String queueId,
@RequestParam(required = false) String queueName) {
if (queueId != null) {
return ResponseEntity.ok(queueService.getQueueStatus(queueId));
} else if (queueName != null) {
Expand All @@ -81,9 +83,17 @@ public ResponseEntity<QueueStatusResponse> getQueueStatus(
}
}

@GetMapping(path = "/queue/{queueId}/events")
@GetMapping(path = "/queue/{queueId}/events", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<QueueEventsResponse> getQueueEvents(
@PathVariable("queueId") String queueId) {
@PathVariable("queueId") String queueId) {
return ResponseEntity.ok(queueService.getQueueEvents(queueId));
}

@GetMapping(path = "/queue/{queueId}/events", produces = "text/csv")
public ResponseEntity<QueueEventsResponse> getQueueEventsCsv(
@PathVariable("queueId") String queueId) {
return ResponseEntity.ok(queueService.getQueueEvents(queueId));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package me.simplq.controller.converters;

import me.simplq.controller.model.queue.QueueEventsResponse;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

@Component
public class QueueEventsResponseConverter extends AbstractHttpMessageConverter<QueueEventsResponse> {

private static final MediaType TEXT_CSV = new MediaType("text", "csv", StandardCharsets.UTF_8);

QueueEventsResponseConverter() {
super(TEXT_CSV);
}

@Override
protected boolean supports(Class<?> aClass) {
return QueueEventsResponse.class.equals(aClass);
}

@Override
protected QueueEventsResponse readInternal(Class<? extends QueueEventsResponse> aClass, HttpInputMessage httpInputMessage) throws HttpMessageNotReadableException {
throw new HttpMessageNotReadableException("Reading history from CSV not supported", httpInputMessage);
}

@Override
protected void writeInternal(QueueEventsResponse response, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
output.getHeaders().setContentType(TEXT_CSV);
output.getHeaders().setContentDisposition(
ContentDisposition.builder("attachment")
.filename(String.format("%s.history.csv", response.getQueueName()))
.build());
printEventsToCsv(response, output);
}

private void printEventsToCsv(QueueEventsResponse response, HttpOutputMessage output) throws IOException {
CSVPrinter csvPrinter = new CSVPrinter(new OutputStreamWriter(output.getBody()), CSVFormat.DEFAULT);
for (QueueEventsResponse.Event event : response.getEvents()) {
printEvent(csvPrinter, event);
}
csvPrinter.flush();
csvPrinter.close();
}

private void printEvent(CSVPrinter csvPrinter, QueueEventsResponse.Event event) throws IOException {
csvPrinter.printRecord(
event.getTokenNumber(),
event.getTokenName(),
event.getEventType(),
event.getEventTimestamp()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
package me.simplq.controller.model.queue;

import java.util.Date;
import java.util.List;
import lombok.Builder;
import lombok.Data;

import java.util.Date;
import java.util.List;

@Data
@Builder
public class QueueEventsResponse {
@Data
@Builder
public static class Event {
public enum EventType {
TOKEN_ADDED,
TOKEN_REMOVED
}
@Data
@Builder
public static class Event {
public enum EventType {
TOKEN_ADDED,
TOKEN_REMOVED
}

EventType eventType;
String tokenName;
Integer tokenNumber;
Date eventTimestamp;
}
EventType eventType;
String tokenName;
Integer tokenNumber;
Date eventTimestamp;
}

List<Event> events;
String queueId;
String queueName;
List<Event> events;
}
79 changes: 43 additions & 36 deletions simplq/src/main/java/me/simplq/service/QueueEventsService.java
Original file line number Diff line number Diff line change
@@ -1,56 +1,63 @@
package me.simplq.service;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import me.simplq.controller.model.queue.QueueDetailsResponse;
import me.simplq.controller.model.queue.QueueEventsResponse;
import me.simplq.controller.model.queue.QueueEventsResponse.Event;
import me.simplq.controller.model.queue.QueueEventsResponse.Event.EventType;
import org.springframework.stereotype.Component;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class QueueEventsService {

public QueueEventsResponse getQueueEvents(QueueDetailsResponse queueDetails) {
return QueueEventsResponse.builder()
.queueId(queueDetails.getQueueId())
.queueName(queueDetails.getQueueName())
.events(
getTokenEventsStream(queueDetails)
.sorted(Comparator.comparing(Event::getEventTimestamp).reversed())
.collect(Collectors.toList()))
.build();
}

private Stream<Event> getTokenEventsStream(QueueDetailsResponse queueDetails) {
// Make creation events for active tokens.
var activeTokenEventStream =
queueDetails.getTokens().stream()
.map(
token ->
Event.builder()
.eventType(EventType.TOKEN_ADDED)
.tokenName(token.getName())
.tokenNumber(token.getTokenNumber())
.eventTimestamp(token.getTokenCreationTimestamp())
.build());
queueDetails.getTokens().stream()
.map(
token ->
Event.builder()
.eventType(EventType.TOKEN_ADDED)
.tokenName(token.getName())
.tokenNumber(token.getTokenNumber())
.eventTimestamp(token.getTokenCreationTimestamp())
.build());

// Make creation and removal events for removed tokens.
var removedTokenEventStream =
queueDetails.getRemovedTokens().stream()
.map(
token ->
List.of(
Event.builder()
.eventType(EventType.TOKEN_ADDED)
.tokenName(token.getName())
.tokenNumber(token.getTokenNumber())
.eventTimestamp(token.getTokenCreationTimestamp())
.build(),
Event.builder()
.eventType(EventType.TOKEN_REMOVED)
.tokenName(token.getName())
.tokenNumber(token.getTokenNumber())
.eventTimestamp(token.getTokenDeletionTimestamp())
.build()))
.flatMap(List::stream);
queueDetails.getRemovedTokens().stream()
.map(
token ->
List.of(
Event.builder()
.eventType(EventType.TOKEN_ADDED)
.tokenName(token.getName())
.tokenNumber(token.getTokenNumber())
.eventTimestamp(token.getTokenCreationTimestamp())
.build(),
Event.builder()
.eventType(EventType.TOKEN_REMOVED)
.tokenName(token.getName())
.tokenNumber(token.getTokenNumber())
.eventTimestamp(token.getTokenDeletionTimestamp())
.build()))
.flatMap(List::stream);

return QueueEventsResponse.builder()
.events(
Stream.concat(activeTokenEventStream, removedTokenEventStream)
.sorted(Comparator.comparing(Event::getEventTimestamp).reversed())
.collect(Collectors.toList()))
.build();
return Stream.concat(activeTokenEventStream, removedTokenEventStream);
}
}
Loading