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

Enabling User to have multiple questions #80

Open
wants to merge 1 commit 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.restservice.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfiguration {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

return mapper;
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
package com.example.restservice.controller;

import com.example.restservice.controller.model.queue.CreateQueueRequest;
import com.example.restservice.controller.model.queue.CreateQueueResponse;
import com.example.restservice.controller.model.queue.MyQueuesResponse;
import com.example.restservice.controller.model.queue.QueueDetailsResponse;
import com.example.restservice.controller.model.queue.QueueStatusResponse;
import com.example.restservice.controller.model.queue.*;
import com.example.restservice.dao.CustomQuestions;
import com.example.restservice.service.QueueService;
import javax.validation.Valid;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/v1")
Expand Down Expand Up @@ -54,4 +47,11 @@ public ResponseEntity<QueueStatusResponse> getQueueStatus(
return new ResponseEntity(HttpStatus.BAD_REQUEST); // Todo Give reason
}
}

@PostMapping(path = "/queue/{queueId}/custom-questions")
public ResponseEntity<CustomQuestions> createCustomQuestions(
@PathVariable("queueId") String queueId,
@RequestBody String customQuestions) throws JsonProcessingException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove throws parts

return ResponseEntity.ok(queueService.createCustomQuestions(queueId ,customQuestions));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.example.restservice.controller.model.queue;

import java.util.Date;
import com.example.restservice.dao.CustomQuestions;
import lombok.Data;

import java.util.Date;

@Data
public class QueueStatusResponse {

Expand All @@ -12,4 +14,5 @@ public class QueueStatusResponse {
final Long numberOfActiveTokens;
final Long totalNumberOfTokens;
final Date queueCreationTimestamp;
final CustomQuestions customQuestions;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.example.restservice.dao;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;
import java.util.UUID;

@Getter
@Setter
@NoArgsConstructor
public class CustomQuestions {

@JsonProperty("questions")
List<Question> questions;

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Question {
@JsonProperty("questionId")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we're using our own custom representation of the questions object. Then we'll need to write our own parsing library to generate the forms on UI.
Maybe we should try an existing library like
https://github.com/rjsf-team/react-jsonschema-form ?

private String questionId;
@JsonProperty("type")
private String type;
@JsonProperty("label")
private String label;
@JsonProperty("repeatable")
private Boolean repeatable;
@JsonProperty("subQuestions")
private List<SubQuestion> subQuestions;

public Question() {
if(this.questionId == null) {
this.questionId = UUID.randomUUID().toString();
}
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Option {
@JsonProperty("text")
private String text;
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class SubQuestion {
@JsonProperty("questionId")
private String questionId;
@JsonProperty("label")
private String label;
@JsonProperty("type")
private String type;
@JsonProperty("options")
private List<Option> options;

public SubQuestion() {
if(this.questionId == null) {
this.questionId = UUID.randomUUID().toString();
}
}
}
}
}
18 changes: 7 additions & 11 deletions complete/src/main/java/com/example/restservice/dao/Queue.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
package com.example.restservice.dao;

import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.util.Date;
import java.util.List;

@Entity
@Table(name = "queue")
@Getter
Expand All @@ -40,6 +33,9 @@ public class Queue {
@Temporal(TemporalType.TIMESTAMP)
Date queueCreationTimestamp;

@Column(length = 100000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use Text type here maybe. It is just a proposal

private String customQuestions;

public Queue(String queueName, String ownerId) {
this.queueName = queueName;
this.ownerId = ownerId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,32 @@
import com.example.restservice.controller.model.queue.MyQueuesResponse;
import com.example.restservice.controller.model.queue.QueueDetailsResponse;
import com.example.restservice.controller.model.queue.QueueStatusResponse;
import com.example.restservice.dao.CustomQuestions;
import com.example.restservice.dao.Queue;
import com.example.restservice.dao.QueueRepository;
import com.example.restservice.dao.Token;
import com.example.restservice.exceptions.SQAccessDeniedException;
import com.example.restservice.exceptions.SQInternalServerException;
import com.example.restservice.exceptions.SQInvalidRequestException;
import java.util.Comparator;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
import javax.transaction.Transactional;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import springfox.documentation.spring.web.json.Json;

@Service
@RequiredArgsConstructor
public class QueueService {

private final QueueRepository queueRepository;
private final LoggedInUserInfo loggedInUserInfo;
private final ObjectMapper mapper;

@Transactional
public CreateQueueResponse createQueue(CreateQueueRequest createQueueRequest) {
Expand Down Expand Up @@ -68,14 +75,25 @@ public QueueStatusResponse getQueueStatus(String queueId) {
.findById(queueId)
.map(
queue ->
new QueueStatusResponse(
queueId,
queue.getQueueName(),
queue.getTokens().stream()
.filter(user -> user.getStatus().equals(TokenStatus.WAITING))
.count(),
Long.valueOf(queue.getTokens().size()),
queue.getQueueCreationTimestamp()))
{
try {
CustomQuestions customQuestions = queue.getCustomQuestions() != null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have duplicate code starting with line +122

Copy link
Contributor

@chalx chalx Feb 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you can do flatMap and create a method with the following body.
Option.ofNullable(queue.getCustomQuestions()).map(this::deserializeQuestions).map(this::createQueueStatusResponse)
And the code will be ....flatMap(this::createResponse).orElseThrow... and you can use same code on the other method

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why do we store json? We can handle this using table relationship and we can avoid this complicated logic. Plus it adds a lot of computational overhead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. If you want to redo this as a table, with just textual questions and answers, we can do that. But we should think first about how we would display the user input along with each token in the admin page. Maybe some collapsable expansion panel would do the job.

That's the reason why I have been delaying this feature for a long time, till all the easy features are done and I get some real customers.

? mapper.readValue(queue.getCustomQuestions(), CustomQuestions.class)
: null;
return new QueueStatusResponse(
queueId,
queue.getQueueName(),
queue.getTokens().stream()
.filter(user -> user.getStatus().equals(TokenStatus.WAITING))
.count(),
Long.valueOf(queue.getTokens().size()),
queue.getQueueCreationTimestamp(),
customQuestions);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
})
.orElseThrow(SQInvalidRequestException::queueNotFoundException);
}

Expand All @@ -99,14 +117,46 @@ public QueueStatusResponse getQueueStatusByName(String queueName) {
.findByQueueName(queueName)
.map(
queue ->
new QueueStatusResponse(
queue.getQueueId(),
queue.getQueueName(),
queue.getTokens().stream()
.filter(user -> user.getStatus().equals(TokenStatus.WAITING))
.count(),
Long.valueOf(queue.getTokens().size()),
queue.getQueueCreationTimestamp()))
{
try {
CustomQuestions customQuestions = queue.getCustomQuestions() != null
? mapper.readValue(queue.getCustomQuestions(), CustomQuestions.class)
: null;
return new QueueStatusResponse(
queue.getQueueId(),
queue.getQueueName(),
queue.getTokens().stream()
.filter(user -> user.getStatus().equals(TokenStatus.WAITING))
.count(),
Long.valueOf(queue.getTokens().size()),
queue.getQueueCreationTimestamp(),
customQuestions);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
})
.orElseThrow(SQInvalidRequestException::queueNotFoundException);
}

@Transactional
public CustomQuestions createCustomQuestions(String queueId, String customQuestions) throws JsonProcessingException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the throws part

CustomQuestions customQuestionsObject = mapper.readValue(customQuestions, CustomQuestions.class);
var queue = queueRepository
.findById(queueId)
.map(
queue1 -> {
if (!queue1.getOwnerId().equals(loggedInUserInfo.getUserId())) {
throw new SQAccessDeniedException("You do not have access to this queue");
}
try {
queue1.setCustomQuestions(mapper.writeValueAsString(customQuestionsObject));
} catch (JsonProcessingException e) {
e.printStackTrace();
Copy link
Contributor

@chalx chalx Feb 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is better to be logged using a logger, for future debugging.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I think you should throw an 500 internal error here

}
return queueRepository.save(queue1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should be moved in try block, and return null instead.

})
.orElseThrow(SQInvalidRequestException::queueNotFoundException);
return customQuestionsObject;
}
}