The new datetimepicker can be used to specify date ranges. We no longer need to define a start date and a range. This simplifies the code for zooming and shifting considerably.
269 lines
9.8 KiB
Java
269 lines
9.8 KiB
Java
package org.lucares.pdbui;
|
|
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.text.Collator;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.SortedSet;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.lucares.pdb.datastore.Proposal;
|
|
import org.lucares.pdb.plot.api.AxisScale;
|
|
import org.lucares.pdb.plot.api.Limit;
|
|
import org.lucares.pdb.plot.api.PlotSettings;
|
|
import org.lucares.pdbui.domain.Aggregate;
|
|
import org.lucares.pdbui.domain.AutocompleteProposal;
|
|
import org.lucares.pdbui.domain.AutocompleteProposalByValue;
|
|
import org.lucares.pdbui.domain.AutocompleteResponse;
|
|
import org.lucares.pdbui.domain.PlotRequest;
|
|
import org.lucares.pdbui.domain.PlotResponse;
|
|
import org.lucares.pdbui.domain.PlotResponseStats;
|
|
import org.lucares.performance.db.PerformanceDb;
|
|
import org.lucares.recommind.logs.InternalPlottingException;
|
|
import org.lucares.recommind.logs.NoDataPointsException;
|
|
import org.lucares.recommind.logs.PlotResult;
|
|
import org.lucares.recommind.logs.Plotter;
|
|
import org.lucares.utils.CollectionUtils;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.stereotype.Controller;
|
|
import org.springframework.util.StreamUtils;
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
import org.springframework.web.bind.annotation.PathVariable;
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
import org.springframework.web.bind.annotation.RequestMethod;
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
import org.springframework.web.bind.annotation.ResponseBody;
|
|
import org.springframework.web.servlet.ModelAndView;
|
|
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
|
|
|
import com.fasterxml.jackson.core.JsonParseException;
|
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
@Controller
|
|
@EnableAutoConfiguration
|
|
public class PdbController implements HardcodedValues, PropertyKeys {
|
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(PdbController.class);
|
|
|
|
private final Plotter plotter;
|
|
private final PerformanceDb db;
|
|
|
|
private final ReentrantLock plotterLock = new ReentrantLock();
|
|
|
|
@Value("${" + PRODUCTION_MODE + ":true}")
|
|
private boolean modeProduction;
|
|
|
|
public PdbController(final PerformanceDb db, final Plotter plotter) {
|
|
this.db = db;
|
|
this.plotter = plotter;
|
|
}
|
|
|
|
@GetMapping("/")
|
|
public ModelAndView index() {
|
|
final String view = "main";
|
|
final Map<String, Object> model = new HashMap<>();
|
|
// model.put("oldestValue",
|
|
// LocalDateTime.now().minusDays(7).format(DATE_FORMAT_BEGIN));
|
|
// model.put("latestValue", LocalDateTime.now().format(DATE_FORMAT_END));
|
|
model.put("isProduction", modeProduction);
|
|
return new ModelAndView(view, model);
|
|
}
|
|
|
|
@RequestMapping(path = "/plots", //
|
|
method = RequestMethod.GET, //
|
|
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
|
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
|
|
)
|
|
@ResponseBody
|
|
ResponseEntity<PlotResponse> createPlotGet(@RequestParam(name = "request") final String request)
|
|
throws InternalPlottingException, InterruptedException, JsonParseException, JsonMappingException,
|
|
IOException {
|
|
|
|
final ObjectMapper objectMapper = new ObjectMapper();
|
|
final PlotRequest plotRequest = objectMapper.readValue(request, PlotRequest.class);
|
|
|
|
return createPlot(plotRequest);
|
|
}
|
|
|
|
@RequestMapping(path = "/plots", //
|
|
method = RequestMethod.POST, //
|
|
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
|
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
|
|
)
|
|
@ResponseBody
|
|
ResponseEntity<PlotResponse> createPlot(@RequestBody final PlotRequest request)
|
|
throws InternalPlottingException, InterruptedException {
|
|
|
|
final PlotSettings plotSettings = PlotSettingsTransformer.toSettings(request);
|
|
if (StringUtils.isBlank(plotSettings.getQuery())) {
|
|
throw new BadRequest("The query must not be empty!");
|
|
}
|
|
|
|
// TODO the UI should cancel requests that are in flight before sending a plot
|
|
// request
|
|
if (plotterLock.tryLock(5, TimeUnit.SECONDS)) {
|
|
try {
|
|
final PlotResult result = plotter.plot(plotSettings);
|
|
|
|
final String imageUrl = WEB_IMAGE_OUTPUT_PATH + "/" + result.getImageName();
|
|
LOGGER.trace("image url: {}", imageUrl);
|
|
|
|
final String thumbnailUrl = result.getThumbnailPath() != null
|
|
? WEB_IMAGE_OUTPUT_PATH + "/" + result.getThumbnailName()
|
|
: "img/no-thumbnail.png";
|
|
|
|
final PlotResponseStats stats = PlotResponseStats.fromDataSeries(result.getDataSeries());
|
|
final PlotResponse plotResponse = new PlotResponse(stats, imageUrl, thumbnailUrl);
|
|
|
|
return ResponseEntity.ok().body(plotResponse);
|
|
} catch (final NoDataPointsException e) {
|
|
throw new NotFoundException(e);
|
|
} finally {
|
|
plotterLock.unlock();
|
|
}
|
|
|
|
} else {
|
|
throw new ServiceUnavailableException("Too many parallel requests!");
|
|
}
|
|
}
|
|
|
|
@RequestMapping(path = "/plots", //
|
|
method = RequestMethod.GET, //
|
|
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE //
|
|
)
|
|
StreamingResponseBody createPlotImage(@RequestParam(name = "query", defaultValue = "") final String query,
|
|
@RequestParam(name = "groupBy[]", defaultValue = "") final List<String> aGroupBy,
|
|
@RequestParam(name = "limitBy.number", defaultValue = "10") final int limit,
|
|
@RequestParam(name = "limitBy.selected", defaultValue = "NO_LIMIT") final Limit limitBy,
|
|
@RequestParam(name = "dateRange") final String dateRange,
|
|
@RequestParam(name = "axisScale", defaultValue = "LINEAR") final AxisScale axisScale,
|
|
@RequestParam(name = "aggregate", defaultValue = "NONE") final Aggregate aggregate,
|
|
@RequestParam(name = "keyOutside", defaultValue = "false") final boolean keyOutside,
|
|
@RequestParam(name = "width", defaultValue = "1920") final int hidth,
|
|
@RequestParam(name = "height", defaultValue = "1080") final int height) {
|
|
return (final OutputStream outputStream) -> {
|
|
|
|
if (StringUtils.isBlank(query)) {
|
|
throw new BadRequest("The query must not be empty!");
|
|
}
|
|
|
|
if (StringUtils.isBlank(dateRange)) {
|
|
throw new BadRequest("The parameter 'dateRange' must be set.");
|
|
}
|
|
|
|
final PlotSettings plotSettings = new PlotSettings();
|
|
plotSettings.setQuery(query);
|
|
plotSettings.setGroupBy(aGroupBy);
|
|
plotSettings.setHeight(height);
|
|
plotSettings.setWidth(hidth);
|
|
plotSettings.setLimit(limit);
|
|
plotSettings.setLimitBy(limitBy);
|
|
plotSettings.setDateRange(dateRange);
|
|
plotSettings.setYAxisScale(axisScale);
|
|
plotSettings.setAggregate(PlotSettingsTransformer.toAggregateInternal(aggregate));
|
|
plotSettings.setKeyOutside(keyOutside);
|
|
plotSettings.setGenerateThumbnail(false);
|
|
|
|
if (plotterLock.tryLock()) {
|
|
try {
|
|
final PlotResult result = plotter.plot(plotSettings);
|
|
|
|
try (FileInputStream in = new FileInputStream(result.getImagePath().toFile())) {
|
|
StreamUtils.copy(in, outputStream);
|
|
}
|
|
} catch (final NoDataPointsException e) {
|
|
throw new NotFoundException(e);
|
|
} catch (final InternalPlottingException e) {
|
|
throw new InternalServerError(e);
|
|
} finally {
|
|
plotterLock.unlock();
|
|
}
|
|
|
|
} else {
|
|
throw new ServiceUnavailableException("Too many parallel requests!");
|
|
}
|
|
};
|
|
}
|
|
|
|
@RequestMapping(path = "/autocomplete", //
|
|
method = RequestMethod.GET, //
|
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
|
|
)
|
|
@ResponseBody
|
|
AutocompleteResponse autocomplete(@RequestParam(name = "query") final String query,
|
|
@RequestParam(name = "caretIndex") final int caretIndex) {
|
|
|
|
final AutocompleteResponse result = new AutocompleteResponse();
|
|
final int zeroBasedCaretIndex = caretIndex - 1;
|
|
|
|
final List<Proposal> proposals = db.autocomplete(query, zeroBasedCaretIndex);
|
|
final List<Proposal> nonEmptyProposals = CollectionUtils.filter(proposals, p -> p.hasResults());
|
|
|
|
final List<AutocompleteProposal> autocompleteProposals = toAutocompleteProposals(nonEmptyProposals);
|
|
Collections.sort(autocompleteProposals, new AutocompleteProposalByValue());
|
|
|
|
result.setProposals(autocompleteProposals);
|
|
return result;
|
|
}
|
|
|
|
@RequestMapping(path = "/fields", //
|
|
method = RequestMethod.GET, //
|
|
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
|
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
|
|
)
|
|
@ResponseBody
|
|
List<String> fields() {
|
|
final List<String> fields = db.getFields();
|
|
|
|
fields.sort(Collator.getInstance(Locale.ENGLISH));
|
|
|
|
return fields;
|
|
}
|
|
|
|
@RequestMapping(path = "/fields/{fieldName}/values", //
|
|
method = RequestMethod.GET, //
|
|
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
|
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
|
|
)
|
|
@ResponseBody
|
|
SortedSet<String> fields(@PathVariable(name = "fieldName") final String fieldName,
|
|
@RequestParam(name = "query") final String query) {
|
|
|
|
final SortedSet<String> fields = db.getFieldsValues(query, fieldName);
|
|
|
|
return fields;
|
|
}
|
|
|
|
private List<AutocompleteProposal> toAutocompleteProposals(final List<Proposal> proposals) {
|
|
|
|
final List<AutocompleteProposal> result = new ArrayList<>();
|
|
|
|
for (final Proposal proposal : proposals) {
|
|
final AutocompleteProposal e = new AutocompleteProposal();
|
|
e.setValue(proposal.getProposedTag());
|
|
e.setNewQuery(proposal.getNewQuery());
|
|
e.setNewCaretPosition(proposal.getNewCaretPosition());
|
|
|
|
result.add(e);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
}
|