Browse Source

basic range deletion

pull/25/head
Alexey Velikiy 3 years ago
parent
commit
a0b7d09b8a
  1. 18
      client/src/components/Graph.vue
  2. 87
      client/src/components/hastic_pod/index.ts
  3. 6
      client/src/services/segments.ts
  4. 22
      server/src/api/segments.rs
  5. 9
      server/src/services/data_service.rs

18
client/src/components/Graph.vue

@ -6,7 +6,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { HasticPod, TimeRange } from "./hastic_pod"; import { HasticPod, TimeRange } from "./hastic_pod";
import { getMetrics } from '../services/metrics.service'; import { getMetrics } from '../services/metrics.service';
import { getSegments, postSegment } from '../services/segments'; import { getSegments, postSegment, deleteSegment } from '../services/segments';
import { LineTimeSerie } from "@chartwerk/line-pod"; import { LineTimeSerie } from "@chartwerk/line-pod";
import { SegmentArray } from '@/types/segment_array'; import { SegmentArray } from '@/types/segment_array';
@ -58,6 +58,21 @@ async function addSegment(segment: Segment): Promise<SegmentId> {
} }
} }
// TODO: move to store
async function _deleteSegment(from: number, to: number): Promise<SegmentId> {
try {
const id = await deleteSegment(from, to);
return id;
} catch (e) {
this.$notify({
title: "Error during saving segment",
text: e,
type: 'error'
});
console.error(e);
}
}
export default defineComponent({ export default defineComponent({
name: 'Graph', name: 'Graph',
props: {}, props: {},
@ -70,6 +85,7 @@ export default defineComponent({
document.getElementById('chart'), document.getElementById('chart'),
resolveData.bind(this), resolveData.bind(this),
addSegment.bind(this), addSegment.bind(this),
_deleteSegment.bind(this),
s s
); );
pod.render(); pod.render();

87
client/src/components/hastic_pod/index.ts

@ -5,22 +5,29 @@ import { ANALYTIC_UNIT_COLORS } from "@/types/colors"
import { Segment, SegmentId } from "@/types/segment"; import { Segment, SegmentId } from "@/types/segment";
export type TimeRange = { from: number, to: number }; export type TimeRange = { from: number, to: number };
export type UpdateDataCallback = (range: TimeRange) => Promise<{ export type UpdateDataCallback = (range: TimeRange) => Promise<{
timeserie: LineTimeSerie[], timeserie: LineTimeSerie[],
segments: Segment[] segments: Segment[]
}>; }>;
export type CreateSegmentCallback = (segment: Segment) => Promise<SegmentId>; export type CreateSegmentCallback = (segment: Segment) => Promise<SegmentId>;
export type DeleteSegmentCallback = (from: number, to: number) => Promise<number>;
export class HasticPod extends LinePod { export class HasticPod extends LinePod {
private _udc: UpdateDataCallback; private _udc: UpdateDataCallback;
private _csc: CreateSegmentCallback; private _csc: CreateSegmentCallback;
private _dsc: DeleteSegmentCallback;
private _ctrlKeyIsDown: boolean; private _ctrlKeyIsDown: boolean;
private _ctrlBrush: boolean; private _dKeyIsDown: boolean;
private _labelBrush: boolean;
private _deleteBrush: boolean;
constructor( constructor(
el: HTMLElement, udc: UpdateDataCallback, csc: CreateSegmentCallback, el: HTMLElement,
udc: UpdateDataCallback,
csc: CreateSegmentCallback,
dsc: DeleteSegmentCallback,
private _segmentSet: SegmentsSet<Segment> private _segmentSet: SegmentsSet<Segment>
) { ) {
super(el, undefined, { super(el, undefined, {
@ -33,18 +40,25 @@ export class HasticPod extends LinePod {
}); });
this._csc = csc; this._csc = csc;
this._dsc = dsc;
this._ctrlKeyIsDown = false; this._ctrlKeyIsDown = false;
this._ctrlBrush = false; this._labelBrush = false;
window.addEventListener("keydown", e => { window.addEventListener("keydown", e => {
if(e.code == "ControlLeft") { if(e.code == "ControlLeft") {
this._ctrlKeyIsDown = true; this._ctrlKeyIsDown = true;
} }
if(e.code == 'KeyD') {
this._dKeyIsDown = true;
}
}); });
window.addEventListener("keyup", (e) => { window.addEventListener("keyup", (e) => {
if(e.code == "ControlLeft") { if(e.code == "ControlLeft") {
this._ctrlKeyIsDown = false; this._ctrlKeyIsDown = false;
} }
if(e.code == 'KeyD') {
this._dKeyIsDown = false;
}
}); });
this._udc = udc; this._udc = udc;
@ -63,19 +77,21 @@ export class HasticPod extends LinePod {
if(this.state?.xValueRange !== undefined) { if(this.state?.xValueRange !== undefined) {
[from, to] = this.state?.xValueRange; [from, to] = this.state?.xValueRange;
console.log('took from range'); console.log('took from range from state');
} else {
console.log('took from range from default');
} }
console.log(from + " ---- " + to); console.log(from + " ---- " + to);
this._udc({ from, to }) this._udc({ from, to })
.then(resp => { .then(resp => {
this.updateData(resp.timeserie); this.updateData(resp.timeserie);
this.updateSegments(resp.segments); this.updateSegments(resp.segments);
}) })
.catch(() => { /* set "error" message */ }) .catch(() => { /* set "error" message */ })
} }
protected addEvents(): void { protected addEvents(): void {
this.initBrush(); this.initBrush();
this.initPan(); this.initPan();
@ -88,11 +104,15 @@ export class HasticPod extends LinePod {
protected onBrushStart(): void { protected onBrushStart(): void {
if(this._ctrlKeyIsDown) { if(this._ctrlKeyIsDown) {
this._ctrlBrush = true; this._labelBrush = true;
this.svg.select('.selection').attr('fill', 'red') this.svg.select('.selection')
.attr('fill', 'red')
} else if (this._dKeyIsDown) {
this._deleteBrush = true;
this.svg.select('.selection')
.attr('fill', 'blue')
} }
// this.in
// TODO: move to state // TODO: move to state
this.isBrushing === true; this.isBrushing === true;
const selection = this.d3.event.selection; const selection = this.d3.event.selection;
@ -103,12 +123,12 @@ export class HasticPod extends LinePod {
} }
protected onBrushEnd(): void { protected onBrushEnd(): void {
if(!this._ctrlBrush) { if(!this._labelBrush && !this._deleteBrush) {
super.onBrushEnd(); super.onBrushEnd();
} else { } else {
const extent = this.d3.event.selection; const extent = this.d3.event.selection;
this.isBrushing === false; this.isBrushing === false;
this._ctrlBrush = false; this._labelBrush = false;
if(extent === undefined || extent === null || extent.length < 2) { if(extent === undefined || extent === null || extent.length < 2) {
return; return;
} }
@ -117,8 +137,13 @@ export class HasticPod extends LinePod {
const startTimestamp = this.xScale.invert(extent[0]); const startTimestamp = this.xScale.invert(extent[0]);
const endTimestamp = this.xScale.invert(extent[1]); const endTimestamp = this.xScale.invert(extent[1]);
this.addSegment(startTimestamp, endTimestamp); if(this._labelBrush) {
this.addSegment(startTimestamp, endTimestamp);
}
if(this._deleteBrush) {
this.deleteSegment(startTimestamp, endTimestamp);
}
} }
} }
@ -134,7 +159,7 @@ export class HasticPod extends LinePod {
} }
const segment = new Segment(id, from, to); const segment = new Segment(id, from, to);
//const storedId = //const storedId =
await this._csc(segment); await this._csc(segment);
this.fetchData(); this.fetchData();
// segment.id = storedId; // segment.id = storedId;
@ -143,6 +168,21 @@ export class HasticPod extends LinePod {
// this.renderSegment(segment); // this.renderSegment(segment);
} }
protected async deleteSegment(from: number, to: number) {
from = Math.floor(from);
to = Math.ceil(to);
if (from > to) {
const t = from;
from = to;
to = t;
}
await this._dsc(from, to);
this.fetchData();
}
protected renderSegments() { protected renderSegments() {
const segments = this._segmentSet.getSegments(); const segments = this._segmentSet.getSegments();
for (const s in segments) { for (const s in segments) {
@ -168,17 +208,12 @@ export class HasticPod extends LinePod {
} }
private async _updateRange(range: AxisRange[]) { private async _updateRange(range: AxisRange[]) {
console.log('update range'); // in assumption that range have been changed
console.log(range); this.fetchData();
const resp = await this._udc({ from: range[0][0], to: range[0][1] });
const options = { axis: { x: { range: range[0] } } };
this.updateData(resp.timeserie, options);
this.updateSegments(resp.segments);
} }
private _zoomOut({x, y}) { private _zoomOut({x, y}) {
console.log(`${x} -- ${y}`); this.fetchData();
} }
protected updateSegments(segments: Segment[]) { protected updateSegments(segments: Segment[]) {

6
client/src/services/segments.ts

@ -22,3 +22,9 @@ export async function postSegment(segment: Segment): Promise<SegmentId> {
const resp = await axios.post(SEGMENTS_API_URL, segment); const resp = await axios.post(SEGMENTS_API_URL, segment);
return resp['data']['id']; return resp['data']['id'];
} }
export async function deleteSegment(from: number, to: number): Promise<SegmentId> {
const uri = SEGMENTS_API_URL + `?from=${from}&to=${to}`;
const resp = await axios.delete(uri);
return resp['data']['count'];
}

22
server/src/api/segments.rs

@ -9,7 +9,7 @@ pub mod filters {
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
list(db.clone()).or(create(db.clone())) list(db.clone()).or(create(db.clone()))
// .or(update(db.clone())) // .or(update(db.clone()))
// .or(delete(db.clone())) .or(delete(db.clone()))
} }
/// GET /segments?from=3&to=5 /// GET /segments?from=3&to=5
@ -34,6 +34,17 @@ pub mod filters {
.and_then(handlers::create) .and_then(handlers::create)
} }
/// POST /segments with JSON body
pub fn delete(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("segments")
.and(warp::delete())
.and(warp::query::<ListOptions>())
.and(with_db(db))
.and_then(handlers::delete)
}
fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = std::convert::Infallible> + Clone { fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || db.clone()) warp::any().map(move || db.clone())
} }
@ -44,6 +55,7 @@ mod handlers {
use hastic::services::data_service::Segment; use hastic::services::data_service::Segment;
use super::models::{CreateResponse, Db, ListOptions}; use super::models::{CreateResponse, Db, ListOptions};
use crate::api;
use crate::api::BadQuery; use crate::api::BadQuery;
use crate::api::API; use crate::api::API;
@ -66,6 +78,14 @@ mod handlers {
} }
} }
} }
pub async fn delete(opts: ListOptions, db: Db) -> Result<impl warp::Reply, warp::Rejection> {
match db.read().delete_segments_in_range(opts.from, opts.to) {
Ok(count) => Ok(API::json(&api::Message{ message: count.to_string() })),
Err(e) => Err(warp::reject::custom(BadQuery)),
}
}
} }
mod models { mod models {

9
server/src/services/data_service.rs

@ -112,6 +112,15 @@ impl DataService {
Ok(res) Ok(res)
} }
pub fn delete_segments_in_range(&self, from: u64, to: u64) -> anyhow::Result<usize> {
let conn = self.connection.lock().unwrap();
let res = conn.execute(
"DELETE FROM segment where ?1 <= start AND end <= ?2",
params![from, to],
)?;
return Ok(res);
}
pub fn delete_segments(&self, ids: &Vec<SegmentId>) -> anyhow::Result<usize> { pub fn delete_segments(&self, ids: &Vec<SegmentId>) -> anyhow::Result<usize> {
if ids.len() == 0 { if ids.len() == 0 {
return Ok(0); return Ok(0);

Loading…
Cancel
Save