mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:07:08 +00:00
Compare commits
63 Commits
b1344bebd8
...
v2025.34.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c96ada6b78 | ||
|
|
673c60a359 | ||
|
|
99e425270c | ||
|
|
63633715e2 | ||
|
|
8afac5c150 | ||
|
|
11168def68 | ||
|
|
0f477b8e65 | ||
|
|
03b00a4ea7 | ||
|
|
c5faa53bee | ||
|
|
46b2512530 | ||
|
|
db4c9a0235 | ||
|
|
1a12ea13ed | ||
|
|
81717c37f1 | ||
|
|
6377e8854c | ||
|
|
d3446d03bd | ||
|
|
a52f7811f2 | ||
|
|
ef2bd4888e | ||
|
|
8801442c92 | ||
|
|
30f65dbeaa | ||
|
|
c2f53ac150 | ||
|
|
4328fc4d2a | ||
|
|
2c2eb8fceb | ||
|
|
767c2f2cb1 | ||
|
|
57a73f7d1c | ||
|
|
9f299056d8 | ||
|
|
5d3c59867c | ||
|
|
76b8355ede | ||
|
|
76b55f514d | ||
|
|
4e1d3209df | ||
|
|
f21ff7ee38 | ||
|
|
2446b42785 | ||
|
|
196e772004 | ||
|
|
674d818fee | ||
|
|
5527576679 | ||
|
|
fe7c016dea | ||
|
|
b7543aa6c4 | ||
|
|
b48a060dc0 | ||
|
|
c0f9a2de5a | ||
|
|
32a9c7a5f2 | ||
|
|
f1f74080f6 | ||
|
|
c5eb8e45f1 | ||
|
|
caab968fd6 | ||
|
|
5f28d1be7b | ||
|
|
22c9537889 | ||
|
|
e95aaa7de7 | ||
|
|
4f44f5a10c | ||
|
|
0ba467d34c | ||
|
|
2b5b302e54 | ||
|
|
28938e27a9 | ||
|
|
97f96fdc1e | ||
|
|
1e3ce35f76 | ||
|
|
619a886781 | ||
|
|
c054e63325 | ||
|
|
fd94b3b6f4 | ||
|
|
7b67b4afc9 | ||
|
|
7c52ada922 | ||
|
|
9072bbe389 | ||
|
|
6639b7110b | ||
|
|
be6652b539 | ||
|
|
bf054d3902 | ||
|
|
2734870871 | ||
|
|
52f49e6799 | ||
|
|
30150a8728 |
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Maximum runtime in seconds before killing an overdue instance (e.g., 10 minutes)
|
||||
MAX_RUNTIME_SECONDS=$((15 * 60))
|
||||
|
||||
DOUGAL_ROOT=${DOUGAL_ROOT:-$(dirname "$0")/..}
|
||||
|
||||
@@ -80,8 +82,9 @@ function run () {
|
||||
# DESCRIPTION=""
|
||||
SERVICE="deferred_imports"
|
||||
|
||||
$BINDIR/send_alert.py -t "$TITLE" -s "$SERVICE" -l "critical" \
|
||||
-O "$(cat $STDOUTLOG)" -E "$(cat $STDERRLOG)"
|
||||
# Disable GitLab alerts. They're just not very practical
|
||||
# $BINDIR/send_alert.py -t "$TITLE" -s "$SERVICE" -l "critical" \
|
||||
# -O "$(cat $STDOUTLOG)" -E "$(cat $STDERRLOG)"
|
||||
|
||||
exit 2
|
||||
}
|
||||
@@ -97,14 +100,37 @@ function cleanup () {
|
||||
}
|
||||
|
||||
if [[ -f $LOCKFILE ]]; then
|
||||
PID=$(cat "$LOCKFILE")
|
||||
if pgrep -F "$LOCKFILE"; then
|
||||
print_warning $(printf "The previous process is still running (%d)" $PID)
|
||||
exit 1
|
||||
else
|
||||
rm "$LOCKFILE"
|
||||
print_warning $(printf "Previous process (%d) not found. Must have died unexpectedly" $PID)
|
||||
fi
|
||||
PID=$(cat "$LOCKFILE")
|
||||
if kill -0 "$PID" 2>/dev/null; then # Check if process is running
|
||||
# Get elapsed time in D-HH:MM:SS format and convert to seconds
|
||||
ELAPSED_STR=$(ps -p "$PID" -o etime= | tr -d '[:space:]')
|
||||
if [ -n "$ELAPSED_STR" ]; then
|
||||
# Convert D-HH:MM:SS to seconds
|
||||
ELAPSED_SECONDS=$(echo "$ELAPSED_STR" | awk -F'[-:]' '{
|
||||
seconds = 0
|
||||
if (NF == 4) { seconds += $1 * 86400 } # Days
|
||||
if (NF >= 3) { seconds += $NF-2 * 3600 } # Hours
|
||||
if (NF >= 2) { seconds += $NF-1 * 60 } # Minutes
|
||||
seconds += $NF # Seconds
|
||||
print seconds
|
||||
}')
|
||||
if [ "$ELAPSED_SECONDS" -gt "$MAX_RUNTIME_SECONDS" ]; then
|
||||
# Kill the overdue process (SIGTERM; use -9 for SIGKILL if needed)
|
||||
kill "$PID" 2>/dev/null
|
||||
print_warning $(printf "Killed overdue process (%d) that ran for %s (%d seconds)" "$PID" "$ELAPSED_STR" "$ELAPSED_SECONDS")
|
||||
rm "$LOCKFILE"
|
||||
else
|
||||
print_warning $(printf "Previous process is still running (%d) for %s (%d seconds)" "$PID" "$ELAPSED_STR" "$ELAPSED_SECONDS")
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_warning $(printf "Could not retrieve elapsed time for process (%d)" "$PID")
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
rm "$LOCKFILE"
|
||||
print_warning $(printf "Previous process (%d) not found. Must have died unexpectedly" "$PID")
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$$" > "$LOCKFILE" || {
|
||||
|
||||
89
bin/update_comparisons.js
Executable file
89
bin/update_comparisons.js
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/node
|
||||
|
||||
const cmp = require('../lib/www/server/lib/comparisons');
|
||||
|
||||
async function purgeComparisons () {
|
||||
const groups = await cmp.groups();
|
||||
const comparisons = await cmp.getGroup();
|
||||
|
||||
const pids = new Set(Object.values(groups).flat().map( p => p.pid ));
|
||||
const comparison_pids = new Set(comparisons.map( c => [ c.baseline_pid, c.monitor_pid ] ).flat());
|
||||
|
||||
for (const pid of comparison_pids) {
|
||||
if (!pids.has(pid)) {
|
||||
console.log(`${pid} no longer par of a group. Deleting comparisons`);
|
||||
|
||||
staleComps = comparisons.filter( c => c.baseline_pid == pid || c.monitor_pid == pid );
|
||||
for (c of staleComps) {
|
||||
console.log(`Deleting comparison ${c.baseline_pid} → ${c.monitor_pid}`);
|
||||
await cmp.remove(c.baseline_pid, c.monitor_pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function main () {
|
||||
|
||||
console.log("Looking for unreferenced comparisons to purge");
|
||||
await purgeComparisons();
|
||||
|
||||
console.log("Retrieving project groups");
|
||||
const groups = await cmp.groups();
|
||||
|
||||
if (!Object.keys(groups??{})?.length) {
|
||||
console.log("No groups found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.log(`Found ${Object.keys(groups)?.length} groups: ${Object.keys(groups).join(", ")}`);
|
||||
|
||||
for (const groupName of Object.keys(groups)) {
|
||||
const projects = groups[groupName];
|
||||
|
||||
console.log(`Fetching saved comparisons for ${groupName}`);
|
||||
|
||||
const comparisons = await cmp.getGroup(groupName);
|
||||
|
||||
if (!comparisons || !comparisons.length) {
|
||||
console.log(`No comparisons found for ${groupName}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if there are any projects that have been modified since last comparison
|
||||
// or if there are any pairs that are no longer part of the group
|
||||
|
||||
const outdated = comparisons.filter( c => {
|
||||
const baseline_tstamp = projects.find( p => p.pid === c.baseline_pid )?.tstamp;
|
||||
const monitor_tstamp = projects.find( p => p.pid === c.monitor_pid )?.tstamp;
|
||||
return (c.tstamp < baseline_tstamp) || (c.tstamp < monitor_tstamp) ||
|
||||
baseline_tstamp == null || monitor_tstamp == null;
|
||||
});
|
||||
|
||||
for (const comparison of outdated) {
|
||||
console.log(`Removing stale comparison: ${comparison.baseline_pid} → ${comparison.monitor_pid}`);
|
||||
await cmp.remove(comparison.baseline_pid, comparison.monitor_pid);
|
||||
}
|
||||
|
||||
if (projects?.length < 2) {
|
||||
console.log(`Group ${groupName} has less than two projects. No comparisons are possible`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Re-run the comparisons that are not in the database. They may
|
||||
// be missing either beacause they were not there to start with
|
||||
// or because we just removed them due to being stale
|
||||
|
||||
console.log(`Recalculating group ${groupName}`);
|
||||
await cmp.saveGroup(groupName);
|
||||
}
|
||||
|
||||
console.log("Comparisons update done");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
} else {
|
||||
module.exports = main;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
-- Add procedure to decimate old nav data
|
||||
--
|
||||
-- New schema version: 0.6.6
|
||||
--
|
||||
-- ATTENTION:
|
||||
--
|
||||
-- ENSURE YOU HAVE BACKED UP THE DATABASE BEFORE RUNNING THIS SCRIPT.
|
||||
--
|
||||
--
|
||||
-- NOTE: This upgrade affects the public schema only.
|
||||
-- NOTE: Each application starts a transaction, which must be committed
|
||||
-- or rolled back.
|
||||
--
|
||||
-- This adds a last_project_update(pid) function. It takes a project ID
|
||||
-- and returns the last known timestamp from that project. Timestamps
|
||||
-- are derived from multiple sources:
|
||||
--
|
||||
-- - raw_shots table
|
||||
-- - final_shots table
|
||||
-- - events_log_full table
|
||||
-- - info table where key = 'qc'
|
||||
-- - files table, from the hashes (which contain the file's mtime)
|
||||
-- - project configuration, looking for an _updatedOn property
|
||||
--
|
||||
-- To apply, run as the dougal user:
|
||||
--
|
||||
-- psql <<EOF
|
||||
-- \i $THIS_FILE
|
||||
-- COMMIT;
|
||||
-- EOF
|
||||
--
|
||||
-- NOTE: It can be applied multiple times without ill effect.
|
||||
--
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE pg_temp.show_notice (notice text) AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE '%', notice;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE pg_temp.upgrade_database () AS $outer$
|
||||
BEGIN
|
||||
|
||||
RAISE NOTICE 'Updating schema %', 'public';
|
||||
SET search_path TO public;
|
||||
|
||||
-- BEGIN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.last_project_update(p_pid text)
|
||||
RETURNS timestamp with time zone
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_last_ts timestamptz := NULL;
|
||||
v_current_ts timestamptz;
|
||||
v_current_str text;
|
||||
v_current_unix numeric;
|
||||
v_sid_rec record;
|
||||
BEGIN
|
||||
-- From raw_shots, final_shots, info, and files
|
||||
FOR v_sid_rec IN SELECT schema FROM public.projects WHERE pid = p_pid
|
||||
LOOP
|
||||
-- From raw_shots
|
||||
EXECUTE 'SELECT max(tstamp) FROM ' || v_sid_rec.schema || '.raw_shots' INTO v_current_ts;
|
||||
IF v_current_ts > v_last_ts OR v_last_ts IS NULL THEN
|
||||
v_last_ts := v_current_ts;
|
||||
END IF;
|
||||
|
||||
-- From final_shots
|
||||
EXECUTE 'SELECT max(tstamp) FROM ' || v_sid_rec.schema || '.final_shots' INTO v_current_ts;
|
||||
IF v_current_ts > v_last_ts OR v_last_ts IS NULL THEN
|
||||
v_last_ts := v_current_ts;
|
||||
END IF;
|
||||
|
||||
-- From info where key = 'qc'
|
||||
EXECUTE 'SELECT value->>''updatedOn'' FROM ' || v_sid_rec.schema || '.info WHERE key = ''qc''' INTO v_current_str;
|
||||
IF v_current_str IS NOT NULL THEN
|
||||
v_current_ts := v_current_str::timestamptz;
|
||||
IF v_current_ts > v_last_ts OR v_last_ts IS NULL THEN
|
||||
v_last_ts := v_current_ts;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- From files hash second part, only for valid colon-separated hashes
|
||||
EXECUTE 'SELECT max( split_part(hash, '':'', 2)::numeric ) FROM ' || v_sid_rec.schema || '.files WHERE hash ~ ''^[0-9]+:[0-9]+\\.[0-9]+:[0-9]+\\.[0-9]+:[0-9a-f]+$''' INTO v_current_unix;
|
||||
IF v_current_unix IS NOT NULL THEN
|
||||
v_current_ts := to_timestamp(v_current_unix);
|
||||
IF v_current_ts > v_last_ts OR v_last_ts IS NULL THEN
|
||||
v_last_ts := v_current_ts;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- From event_log_full
|
||||
EXECUTE 'SELECT max(tstamp) FROM ' || v_sid_rec.schema || '.event_log_full' INTO v_current_ts;
|
||||
IF v_current_ts > v_last_ts OR v_last_ts IS NULL THEN
|
||||
v_last_ts := v_current_ts;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- From projects.meta->_updatedOn
|
||||
SELECT (meta->>'_updatedOn')::timestamptz FROM public.projects WHERE pid = p_pid INTO v_current_ts;
|
||||
IF v_current_ts > v_last_ts OR v_last_ts IS NULL THEN
|
||||
v_last_ts := v_current_ts;
|
||||
END IF;
|
||||
|
||||
RETURN v_last_ts;
|
||||
END;
|
||||
$function$;
|
||||
|
||||
-- END
|
||||
|
||||
END;
|
||||
$outer$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE pg_temp.upgrade () AS $outer$
|
||||
DECLARE
|
||||
row RECORD;
|
||||
current_db_version TEXT;
|
||||
BEGIN
|
||||
|
||||
SELECT value->>'db_schema' INTO current_db_version FROM public.info WHERE key = 'version';
|
||||
|
||||
IF current_db_version >= '0.6.6' THEN
|
||||
RAISE EXCEPTION
|
||||
USING MESSAGE='Patch already applied';
|
||||
END IF;
|
||||
|
||||
IF current_db_version != '0.6.5' THEN
|
||||
RAISE EXCEPTION
|
||||
USING MESSAGE='Invalid database version: ' || current_db_version,
|
||||
HINT='Ensure all previous patches have been applied.';
|
||||
END IF;
|
||||
|
||||
CALL pg_temp.upgrade_database();
|
||||
END;
|
||||
$outer$ LANGUAGE plpgsql;
|
||||
|
||||
CALL pg_temp.upgrade();
|
||||
|
||||
CALL pg_temp.show_notice('Cleaning up');
|
||||
DROP PROCEDURE pg_temp.upgrade_database ();
|
||||
DROP PROCEDURE pg_temp.upgrade ();
|
||||
|
||||
CALL pg_temp.show_notice('Updating db_schema version');
|
||||
INSERT INTO public.info VALUES ('version', '{"db_schema": "0.6.6"}')
|
||||
ON CONFLICT (key) DO UPDATE
|
||||
SET value = public.info.value || '{"db_schema": "0.6.6"}' WHERE public.info.key = 'version';
|
||||
|
||||
|
||||
CALL pg_temp.show_notice('All done. You may now run "COMMIT;" to persist the changes');
|
||||
DROP PROCEDURE pg_temp.show_notice (notice text);
|
||||
|
||||
--
|
||||
--NOTE Run `COMMIT;` now if all went well
|
||||
--
|
||||
@@ -39,7 +39,8 @@ export default {
|
||||
default:
|
||||
return {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
displaylogo: false,
|
||||
responsive: true
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -48,7 +49,8 @@ export default {
|
||||
const base = {
|
||||
font: {
|
||||
color: this.$vuetify.theme.isDark ? "#fff" : undefined
|
||||
}
|
||||
},
|
||||
autosize: true
|
||||
};
|
||||
|
||||
switch (this.facet) {
|
||||
@@ -274,18 +276,25 @@ export default {
|
||||
replot () {
|
||||
if (this.plotted) {
|
||||
const ref = this.$refs.graph;
|
||||
Plotly.relayout(ref, {
|
||||
width: ref.clientWidth,
|
||||
height: ref.clientHeight
|
||||
});
|
||||
if (ref && ref.clientWidth > 0 && ref.clientHeight > 0) {
|
||||
Plotly.relayout(ref, {
|
||||
width: ref.clientWidth,
|
||||
height: ref.clientHeight
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graph);
|
||||
this.$nextTick( () => {
|
||||
if (this.items?.length) {
|
||||
this.plot();
|
||||
}
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graph);
|
||||
});
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
|
||||
@@ -36,7 +36,8 @@ export default {
|
||||
config () {
|
||||
return {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
displaylogo: false,
|
||||
responsive: true
|
||||
};
|
||||
},
|
||||
|
||||
@@ -53,7 +54,8 @@ export default {
|
||||
title: "Time (s)"
|
||||
},
|
||||
plot_bgcolor:"rgba(0,0,0,0)",
|
||||
paper_bgcolor:"rgba(0,0,0,0)"
|
||||
paper_bgcolor:"rgba(0,0,0,0)",
|
||||
autosize: true
|
||||
};
|
||||
},
|
||||
|
||||
@@ -154,10 +156,12 @@ export default {
|
||||
replot () {
|
||||
if (this.plotted) {
|
||||
const ref = this.$refs.graph;
|
||||
Plotly.relayout(ref, {
|
||||
width: ref.clientWidth,
|
||||
height: ref.clientHeight
|
||||
});
|
||||
if (ref && ref.clientWidth > 0 && ref.clientHeight > 0) {
|
||||
Plotly.relayout(ref, {
|
||||
width: ref.clientWidth,
|
||||
height: ref.clientHeight
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -190,8 +194,13 @@ export default {
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graph);
|
||||
this.$nextTick( () => {
|
||||
if (this.items?.length) {
|
||||
this.plot();
|
||||
}
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graph);
|
||||
});
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
|
||||
@@ -102,21 +102,48 @@
|
||||
class="my-1 mt-4"
|
||||
title="Fit view"
|
||||
@click="zoomReset"
|
||||
>mdi-magnify-scan</v-icon>
|
||||
>mdi-magnify-scan</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
class="my-1"
|
||||
title="Zoom in"
|
||||
@click="zoomIn"
|
||||
>mdi-magnify-plus-outline</v-icon>
|
||||
>mdi-magnify-plus-outline</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
class="my-1"
|
||||
title="Zoom out"
|
||||
@click="zoomOut"
|
||||
>mdi-magnify-minus-outline</v-icon>
|
||||
>mdi-magnify-minus-outline</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
class="my-1"
|
||||
title="Tilt out"
|
||||
@click="tiltOut"
|
||||
>mdi-axis-x-rotate-counterclockwise</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
class="my-1"
|
||||
title="Tilt in"
|
||||
@click="tiltIn"
|
||||
>mdi-axis-x-rotate-clockwise</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon v-if="bearing==0"
|
||||
class="my-1"
|
||||
title="Bin up"
|
||||
@click="setBearing('ζ')"
|
||||
>mdi-view-grid-outline</v-icon>
|
||||
<v-icon v-else
|
||||
class="my-1"
|
||||
title="North up"
|
||||
:style="`transform: rotate(${-bearing}deg);`"
|
||||
@click="setBearing(0)"
|
||||
>mdi-navigation</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
@@ -363,6 +390,7 @@ export default {
|
||||
//maxZoom: 18,
|
||||
maxPitch: 89
|
||||
},
|
||||
bearing: 0,
|
||||
|
||||
heatmapValue: "total_error",
|
||||
isFullscreen: false,
|
||||
@@ -407,6 +435,11 @@ export default {
|
||||
watch: {
|
||||
|
||||
baseline () {
|
||||
// We need the configuration of the baseline project so that
|
||||
// the "bin up" orientation control will work.
|
||||
if (this.baseline?.pid) {
|
||||
this.$store.dispatch('getProject', this.baseline.pid);
|
||||
}
|
||||
this.populateDataLayersAvailable();
|
||||
},
|
||||
|
||||
@@ -518,6 +551,41 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
tiltIn () {
|
||||
if (deck) {
|
||||
const viewState = deck.getViewports()[0];
|
||||
const initialViewState = {...this.viewStateDefaults, ...viewState};
|
||||
initialViewState.pitch -= 10;
|
||||
initialViewState.transitionDuration = 300;
|
||||
deck.setProps({initialViewState});
|
||||
}
|
||||
},
|
||||
|
||||
tiltOut () {
|
||||
if (deck) {
|
||||
const viewState = deck.getViewports()[0];
|
||||
const initialViewState = {...this.viewStateDefaults, ...viewState};
|
||||
initialViewState.pitch += 10;
|
||||
initialViewState.transitionDuration = 300;
|
||||
deck.setProps({initialViewState});
|
||||
}
|
||||
},
|
||||
|
||||
setBearing (bearing) {
|
||||
if (deck) {
|
||||
|
||||
if (bearing === 'ζ') {
|
||||
bearing = this.$store.getters.projectConfiguration?.binning?.theta ?? 0;
|
||||
}
|
||||
|
||||
const viewState = deck.getViewports()[0];
|
||||
const initialViewState = {...this.viewStateDefaults, ...viewState};
|
||||
initialViewState.bearing = (bearing + 360) % 360;
|
||||
initialViewState.transitionDuration = 300;
|
||||
deck.setProps({initialViewState});
|
||||
}
|
||||
},
|
||||
|
||||
toggleFullscreen() {
|
||||
const mapElement = document.getElementById('map-container');
|
||||
if (!this.isFullscreen) {
|
||||
@@ -745,7 +813,7 @@ export default {
|
||||
|
||||
|
||||
async initLayers (gl) {
|
||||
deck.onViewStateChange = this.updateURL;
|
||||
// Does nothing
|
||||
},
|
||||
|
||||
setViewState () {
|
||||
@@ -760,6 +828,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
viewStateUpdated ({viewState}) {
|
||||
this.bearing = viewState.bearing;
|
||||
},
|
||||
|
||||
checkWebGLSupport() {
|
||||
const canvas = document.createElement('canvas');
|
||||
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
||||
@@ -1182,7 +1254,8 @@ export default {
|
||||
layers: [],
|
||||
getTooltip: this.getTooltip,
|
||||
pickingRadius: 24,
|
||||
onWebGLInitialized: this.initLayers
|
||||
onWebGLInitialized: this.initLayers,
|
||||
onViewStateChange: this.viewStateUpdated,
|
||||
});
|
||||
//console.log("deck = ", deck);
|
||||
|
||||
@@ -1194,6 +1267,10 @@ export default {
|
||||
|
||||
this.layersPendingLoad.push(`${this.baseline.pid}-seqfl`);
|
||||
this.layersPendingLoad.push(`${this.baseline.pid}-seqfp`);
|
||||
|
||||
// We need the configuration of the baseline project so that
|
||||
// the "bin up" orientation control will work.
|
||||
this.$store.dispatch('getProject', this.baseline.pid);
|
||||
}
|
||||
|
||||
if (this.monitors) {
|
||||
|
||||
@@ -158,6 +158,7 @@ Vue.use(VueRouter)
|
||||
component: SequenceList
|
||||
},
|
||||
{
|
||||
name: "shotlog",
|
||||
path: "sequences/:sequence",
|
||||
component: SequenceSummary
|
||||
},
|
||||
|
||||
@@ -36,7 +36,7 @@ async function refreshEvents ({commit, dispatch, state, rootState}, [modifiedAft
|
||||
|
||||
/** Return a subset of events from state.events
|
||||
*/
|
||||
async function getEvents ({commit, dispatch, state}, [projectId, {sequence, date0, date1, sortBy, sortDesc, itemsPerPage, page, text, label}]) {
|
||||
async function getEvents ({commit, dispatch, state}, [projectId, {sequence, date0, date1, sortBy, sortDesc, itemsPerPage, page, text, label, excludeLabels}]) {
|
||||
let filteredEvents = [...state.events];
|
||||
|
||||
if (sortBy) {
|
||||
@@ -114,6 +114,10 @@ async function getEvents ({commit, dispatch, state}, [projectId, {sequence, date
|
||||
filteredEvents = filteredEvents.filter( event => event.labels?.includes(label) );
|
||||
}
|
||||
|
||||
if (excludeLabels) {
|
||||
filteredEvents = filteredEvents.filter( event => !excludeLabels?.some( label => event.labels?.includes(label) ) );
|
||||
}
|
||||
|
||||
const count = filteredEvents.length;
|
||||
|
||||
if (itemsPerPage && itemsPerPage > 0) {
|
||||
|
||||
@@ -5,6 +5,22 @@
|
||||
<v-card-title>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>
|
||||
<template v-if="$route.params.sequence">
|
||||
<v-btn icon small
|
||||
:disabled="sequenceIndex >= (sequences.length - 1)"
|
||||
:to="{name: 'logBySequence', params: { sequence: (sequences[sequences.length-1]||{}).sequence }}"
|
||||
title="Go to the first sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-double-left</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon small
|
||||
:disabled="sequenceIndex >= (sequences.length - 1)"
|
||||
:to="{name: 'logBySequence', params: { sequence: (sequences[sequenceIndex+1]||{}).sequence }}"
|
||||
title="Go to the previous sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-left</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span class="d-none d-lg-inline">
|
||||
{{
|
||||
$route.params.sequence
|
||||
@@ -31,18 +47,38 @@
|
||||
: ""
|
||||
}}
|
||||
</span>
|
||||
|
||||
<template v-if="$route.params.sequence">
|
||||
<v-btn icon small
|
||||
:disabled="sequenceIndex==0"
|
||||
:to="{name: 'logBySequence', params: { sequence: (sequences[sequenceIndex-1]||{}).sequence }}"
|
||||
title="Go to the next sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-right</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon small class="mr-1"
|
||||
:disabled="sequenceIndex==0"
|
||||
:to="{name: 'logBySequence', params: { sequence: (sequences[0]||{}).sequence }}"
|
||||
title="Go to the last sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-double-right</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<a v-if="$route.params.sequence"
|
||||
class="mr-3"
|
||||
:href="`/projects/${$route.params.project}/sequences/${$route.params.sequence}`"
|
||||
title="View the shotlog for this sequence"
|
||||
>
|
||||
<v-icon
|
||||
right
|
||||
color="teal"
|
||||
>mdi-format-list-numbered</v-icon>
|
||||
</a>
|
||||
|
||||
</v-toolbar-title>
|
||||
|
||||
<a v-if="$route.params.sequence"
|
||||
class="mr-3"
|
||||
:href="`/projects/${$route.params.project}/sequences/${$route.params.sequence}`"
|
||||
title="View the shotlog for this sequence"
|
||||
>
|
||||
<v-icon
|
||||
right
|
||||
color="teal"
|
||||
>mdi-format-list-numbered</v-icon>
|
||||
</a>
|
||||
|
||||
|
||||
<dougal-event-edit v-if="$parent.writeaccess()"
|
||||
v-model="eventDialog"
|
||||
@@ -494,17 +530,6 @@ export default {
|
||||
rows () {
|
||||
const rows = {};
|
||||
this.items
|
||||
.filter(i => {
|
||||
return !this.$route.params.sequence || (this.$route.params.sequence == i.sequence)
|
||||
})
|
||||
.filter(i => {
|
||||
for (const label of this.filterableLabels) {
|
||||
if (!this.shownLabels.includes(label) && i.labels.includes(label)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.forEach(i => {
|
||||
const key = (i.sequence && i.point) ? (i.sequence+"@"+i.point) : i.tstamp;
|
||||
if (!rows[key]) {
|
||||
@@ -535,6 +560,10 @@ export default {
|
||||
.sort( (a, b) => b[1]-a[1] );
|
||||
},
|
||||
|
||||
filteredLabels () {
|
||||
return this.filterableLabels.filter( label => !this.shownLabels.includes(label) );
|
||||
},
|
||||
|
||||
presetRemarks () {
|
||||
return this.projectConfiguration?.events?.presetRemarks ?? [];
|
||||
},
|
||||
@@ -547,7 +576,17 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
...mapGetters(['user', 'eventsLoading', 'online', 'sequence', 'line', 'point', 'position', 'timestamp', 'lineName', 'events', 'labels', 'userLabels', 'projectConfiguration']),
|
||||
sequenceIndex () {
|
||||
if ("sequence" in this.$route.params) {
|
||||
const index = this.sequences.findIndex( i => i.sequence == this.$route.params.sequence );
|
||||
if (index != -1) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
// return undefined
|
||||
},
|
||||
|
||||
...mapGetters(['user', 'eventsLoading', 'online', 'sequence', 'sequences', 'line', 'point', 'position', 'timestamp', 'lineName', 'events', 'labels', 'userLabels', 'projectConfiguration']),
|
||||
...mapState({projectSchema: state => state.project.projectSchema})
|
||||
|
||||
},
|
||||
@@ -555,6 +594,7 @@ export default {
|
||||
watch: {
|
||||
options: {
|
||||
async handler () {
|
||||
this.savePrefs(),
|
||||
await this.fetchEvents();
|
||||
},
|
||||
deep: true
|
||||
@@ -573,12 +613,19 @@ export default {
|
||||
},
|
||||
|
||||
filter (newVal, oldVal) {
|
||||
this.savePrefs();
|
||||
if (newVal?.toLowerCase() != oldVal?.toLowerCase()) {
|
||||
this.fetchEvents();
|
||||
}
|
||||
},
|
||||
|
||||
labelSearch () {
|
||||
this.savePrefs();
|
||||
this.fetchEvents();
|
||||
},
|
||||
|
||||
filteredLabels () {
|
||||
this.savePrefs()
|
||||
this.fetchEvents();
|
||||
},
|
||||
|
||||
@@ -587,7 +634,7 @@ export default {
|
||||
},
|
||||
|
||||
user (newVal, oldVal) {
|
||||
this.itemsPerPage = Number(localStorage.getItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`)) || 25;
|
||||
this.loadPrefs();
|
||||
}
|
||||
|
||||
},
|
||||
@@ -638,8 +685,10 @@ export default {
|
||||
|
||||
async fetchEvents (opts = {}) {
|
||||
const options = {
|
||||
sequence: this.$route.params.sequence,
|
||||
text: this.filter,
|
||||
label: this.labelSearch,
|
||||
excludeLabels: this.filteredLabels,
|
||||
...this.options
|
||||
};
|
||||
const res = await this.getEvents([this.$route.params.project, options]);
|
||||
@@ -877,10 +926,36 @@ export default {
|
||||
*/
|
||||
},
|
||||
|
||||
getPrefsKey () {
|
||||
return `dougal/prefs/${this.user?.name}/${this.$route.params.project}/Log/v1`;
|
||||
},
|
||||
|
||||
savePrefs () {
|
||||
const prefs = {
|
||||
shownLabels: this.shownLabels,
|
||||
labelSearch: this.labelSearch,
|
||||
filter: this.filter,
|
||||
options: this.options
|
||||
};
|
||||
localStorage.setItem(this.getPrefsKey(), JSON.stringify(prefs));
|
||||
},
|
||||
|
||||
loadPrefs () {
|
||||
const stored = localStorage.getItem(this.getPrefsKey());
|
||||
if (stored) {
|
||||
const prefs = JSON.parse(stored);
|
||||
if (prefs.shownLabels !== undefined) this.shownLabels = prefs.shownLabels;
|
||||
if (prefs.labelSearch !== undefined) this.labelSearch = prefs.labelSearch;
|
||||
if (prefs.filter !== undefined) this.filter = prefs.filter;
|
||||
if (prefs.options !== undefined) this.options = prefs.options;
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions(["api", "showSnack", "refreshEvents", "getEvents"])
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
this.loadPrefs();
|
||||
this.fetchEvents();
|
||||
|
||||
window.addEventListener('keyup', this.handleKeyboardEvent);
|
||||
|
||||
@@ -470,6 +470,33 @@
|
||||
@click="zoomOut"
|
||||
>mdi-magnify-minus-outline</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
class="my-1"
|
||||
title="Tilt out"
|
||||
@click="tiltOut"
|
||||
>mdi-axis-x-rotate-counterclockwise</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
class="my-1"
|
||||
title="Tilt in"
|
||||
@click="tiltIn"
|
||||
>mdi-axis-x-rotate-clockwise</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon v-if="bearing==0"
|
||||
class="my-1"
|
||||
title="Bin up"
|
||||
@click="setBearing('ζ')"
|
||||
>mdi-view-grid-outline</v-icon>
|
||||
<v-icon v-else
|
||||
class="my-1"
|
||||
title="North up"
|
||||
:style="`transform: rotate(${-bearing}deg);`"
|
||||
@click="setBearing(0)"
|
||||
>mdi-navigation</v-icon>
|
||||
</div>
|
||||
<div>
|
||||
<v-icon
|
||||
class="my-1"
|
||||
@@ -661,6 +688,7 @@ export default {
|
||||
//maxZoom: 18,
|
||||
maxPitch: 89
|
||||
},
|
||||
bearing: 0,
|
||||
|
||||
vesselPosition: null,
|
||||
vesselTrackLastRefresh: 0,
|
||||
@@ -977,6 +1005,41 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
tiltIn () {
|
||||
if (deck) {
|
||||
const viewState = deck.getViewports()[0];
|
||||
const initialViewState = {...this.viewStateDefaults, ...viewState};
|
||||
initialViewState.pitch -= 10;
|
||||
initialViewState.transitionDuration = 300;
|
||||
deck.setProps({initialViewState});
|
||||
}
|
||||
},
|
||||
|
||||
tiltOut () {
|
||||
if (deck) {
|
||||
const viewState = deck.getViewports()[0];
|
||||
const initialViewState = {...this.viewStateDefaults, ...viewState};
|
||||
initialViewState.pitch += 10;
|
||||
initialViewState.transitionDuration = 300;
|
||||
deck.setProps({initialViewState});
|
||||
}
|
||||
},
|
||||
|
||||
setBearing (bearing) {
|
||||
if (deck) {
|
||||
|
||||
if (bearing === 'ζ') {
|
||||
bearing = this.$store.getters.projectConfiguration?.binning?.theta ?? 0;
|
||||
}
|
||||
|
||||
const viewState = deck.getViewports()[0];
|
||||
const initialViewState = {...this.viewStateDefaults, ...viewState};
|
||||
initialViewState.bearing = (bearing + 360) % 360;
|
||||
initialViewState.transitionDuration = 300;
|
||||
deck.setProps({initialViewState});
|
||||
}
|
||||
},
|
||||
|
||||
toggleFullscreen() {
|
||||
const mapElement = document.getElementById('map-container');
|
||||
if (!this.isFullscreen) {
|
||||
@@ -1368,7 +1431,7 @@ export default {
|
||||
//console.log("SHOULD BE INITIALISING LAYERS HERE", gl);
|
||||
this.decodeURL();
|
||||
this.decodeURLHash();
|
||||
deck.onViewStateChange = this.updateURL;
|
||||
//deck.onViewStateChange = this.viewStateUpdated;
|
||||
},
|
||||
|
||||
setViewState () {
|
||||
@@ -1383,6 +1446,11 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
viewStateUpdated ({viewState}) {
|
||||
this.bearing = viewState.bearing;
|
||||
this.updateURL({viewState});
|
||||
},
|
||||
|
||||
updateURL ({viewState} = {}) {
|
||||
if (!viewState && deck?.viewManager) {
|
||||
viewState = deck.getViewports()[0];
|
||||
@@ -1709,7 +1777,8 @@ export default {
|
||||
layers: [],
|
||||
getTooltip: this.getTooltip,
|
||||
pickingRadius: 24,
|
||||
onWebGLInitialized: this.initLayers
|
||||
onWebGLInitialized: this.initLayers,
|
||||
onViewStateChange: this.viewStateUpdated,
|
||||
});
|
||||
|
||||
// Get fullscreen state
|
||||
|
||||
@@ -6,8 +6,42 @@
|
||||
<v-progress-linear indeterminate v-if="loading"></v-progress-linear>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>
|
||||
<template v-if="$route.params.sequence">
|
||||
<v-btn icon small
|
||||
:disabled="sequenceIndex >= (sequences.length - 1)"
|
||||
:to="{name: 'shotlog', params: { sequence: (sequences[sequences.length-1]||{}).sequence }}"
|
||||
title="Go to the first sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-double-left</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon small
|
||||
:disabled="sequenceIndex >= (sequences.length - 1)"
|
||||
:to="{name: 'shotlog', params: { sequence: (sequences[sequenceIndex+1]||{}).sequence }}"
|
||||
title="Go to the previous sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-left</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
Sequence {{sequenceNumber}}
|
||||
<small :class="statusColour" v-if="sequence">({{sequence.status}})</small>
|
||||
|
||||
<template v-if="$route.params.sequence">
|
||||
<v-btn icon small
|
||||
:disabled="sequenceIndex==0"
|
||||
:to="{name: 'shotlog', params: { sequence: (sequences[sequenceIndex-1]||{}).sequence }}"
|
||||
title="Go to the next sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-right</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon small class="mr-1"
|
||||
:disabled="sequenceIndex==0"
|
||||
:to="{name: 'shotlog', params: { sequence: (sequences[0]||{}).sequence }}"
|
||||
title="Go to the last sequence"
|
||||
>
|
||||
<v-icon dense>mdi-chevron-double-right</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-toolbar-title>
|
||||
|
||||
<a v-if="$route.params.sequence"
|
||||
@@ -352,6 +386,16 @@ export default {
|
||||
return this.sequences.find(i => i.sequence == this.sequenceNumber);
|
||||
},
|
||||
|
||||
sequenceIndex () {
|
||||
if ("sequence" in this.$route.params) {
|
||||
const index = this.sequences.findIndex( i => i.sequence == this.$route.params.sequence );
|
||||
if (index != -1) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
// return undefined
|
||||
},
|
||||
|
||||
remarks () {
|
||||
return this.sequence?.remarks || "Nil.";
|
||||
},
|
||||
|
||||
@@ -36,8 +36,16 @@ async function fetchErrors (pid) {
|
||||
}
|
||||
|
||||
|
||||
async function groupTimestamps (groupName) {
|
||||
const projects = await groups()?.[groupName];
|
||||
if (projects?.length) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function groups () {
|
||||
const projects = await db.project.get();
|
||||
const projects = await db.project.get({timestamps: true});
|
||||
const groupNames = [
|
||||
...projects
|
||||
.reduce( (acc, cur) => acc.add(...cur.groups), new Set() )
|
||||
@@ -46,19 +54,6 @@ async function groups () {
|
||||
return Object.fromEntries(groupNames.map( g => [g, projects.filter( p => p.groups.includes(g) )] ));
|
||||
}
|
||||
|
||||
/*
|
||||
async function compare (baselineProjectID, monitorProjectID) {
|
||||
console.log("Getting baseline", baselineProjectID);
|
||||
const baselineData = await db.sequence.get(baselineProjectID);
|
||||
console.log("Getting monitor", monitorProjectID);
|
||||
const monitorData = await db.sequence.get(monitorProjectID);
|
||||
console.log("Comparing");
|
||||
|
||||
const comparison = comparisonGeometricDifferences(baselineData, monitorData);
|
||||
return comparison;
|
||||
}
|
||||
*/
|
||||
|
||||
function geometric_differences (baseline, monitor) {
|
||||
|
||||
if (!baseline || !baseline.length) {
|
||||
@@ -160,67 +155,6 @@ async function save (baselineProjectID, monitorProjectID, bundle, meta) {
|
||||
}
|
||||
|
||||
|
||||
async function saveSample (baselineProjectID, monitorProjectID, opts = {}) {
|
||||
DEBUG("Not bothering to save samples. This feature will be removed.");
|
||||
}
|
||||
|
||||
/*
|
||||
async function saveSample (baselineProjectID, monitorProjectID, opts = {}) {
|
||||
let sample = opts.sample;
|
||||
let populationStats = opts.populationStats;
|
||||
let sampleStats = opts.sampleStats;
|
||||
|
||||
if (!sample?.length) {
|
||||
const sampleSize = opts.sampleSize ?? 2000;
|
||||
const record = await get(baselineProjectID, monitorProjectID);
|
||||
let data;
|
||||
|
||||
if (record) {
|
||||
data = record.data;
|
||||
} else {
|
||||
console.log("Full data not found in database");
|
||||
data = asBundle(await compare(baselineProjectID, monitorProjectID));
|
||||
}
|
||||
|
||||
sample = computeSample(data, opts);
|
||||
|
||||
if (!populationStats) {
|
||||
populationStats = stats(data);
|
||||
}
|
||||
}
|
||||
|
||||
const bundle = asBundle(sample, {type: 0x0c});
|
||||
|
||||
if (!sampleStats) {
|
||||
sampleStats = stats(bundle);
|
||||
}
|
||||
|
||||
meta = {tstamp: (new Date()), populationStats, sampleStats, ...(opts.meta??{})};
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
const text = `
|
||||
INSERT INTO comparisons.comparisons
|
||||
(type, baseline_pid, monitor_pid, data, meta)
|
||||
VALUES ('geometric_difference_sample', $1, $2, $3, $4)
|
||||
ON CONFLICT (type, baseline_pid, monitor_pid)
|
||||
DO UPDATE SET
|
||||
data = EXCLUDED.data,
|
||||
meta = EXCLUDED.meta;
|
||||
`;
|
||||
|
||||
const values = [ baselineProjectID, monitorProjectID, Buffer.from(bundle), meta ];
|
||||
const res = await client.query(text, values);
|
||||
return res.rowCount;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
async function get (baselineProjectID, monitorProjectID, type = 'geometric_difference') {
|
||||
|
||||
const client = await pool.connect();
|
||||
@@ -328,77 +262,6 @@ function stats (comparison) {
|
||||
}
|
||||
|
||||
|
||||
/** Compare two projects' errorfinal quantities.
|
||||
*
|
||||
* Assumes that the preplots are the same.
|
||||
* It is not a terribly efficient way of doing it, but considering
|
||||
* that this is, by and large only going to be done once every few
|
||||
* hours for an active prospect, and never for inactive ones, I
|
||||
* think and hope we can live with that.
|
||||
*
|
||||
* `baseline` and `monitor` are the result of calling
|
||||
* db.sequence.get(projectId) on each of the respective
|
||||
* projects.
|
||||
*/
|
||||
/*
|
||||
function comparisonGeometricDifferences (baseline, monitor) {
|
||||
|
||||
if (!baseline || !baseline.length) {
|
||||
throw new Error("No baseline data");
|
||||
}
|
||||
|
||||
if (!monitor || !monitor.length) {
|
||||
throw new Error("No monitor data");
|
||||
}
|
||||
|
||||
const comparison = []; // An array of { line, point, εi, εj, δts }; line + point may be repeated
|
||||
|
||||
for (const bp of baseline) {
|
||||
if (!bp.errorfinal) {
|
||||
console.log(`No final data for baseline point L${bp.line} S${bp.sequence} P${bp.point}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const monitor_points = monitor.filter( mp => mp.line === bp.line && mp.point === bp.point );
|
||||
for (const mp of monitor_points) {
|
||||
if (!mp.errorfinal) {
|
||||
console.log(`No final data for monitor point L${mp.line} S${mp.sequence} P${mp.point}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const line = bp.line;
|
||||
const point = bp.point;
|
||||
const baseSeq = bp.sequence;
|
||||
const monSeq = mp.sequence;
|
||||
const baseTStamp = bp.tstamp;
|
||||
const monTStamp = mp.tstamp;
|
||||
const δi = bp.errorfinal.coordinates[0] - mp.errorfinal.coordinates[0];
|
||||
const δj = bp.errorfinal.coordinates[1] - mp.errorfinal.coordinates[1];
|
||||
|
||||
const obj = {line, point, baseSeq, monSeq, baseTStamp, monTStamp, δi, δj};
|
||||
comparison.push(obj);
|
||||
// console.log(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return comparison.sort(sortFn);
|
||||
}
|
||||
|
||||
function sortComparison (comparison) {
|
||||
return comparison.sort( (a, b) => {
|
||||
if (a.line == b.line) {
|
||||
if (a.point == b.point) {
|
||||
return a.baseTStamp - b.baseTStamp;
|
||||
} else {
|
||||
return a.point - b.point;
|
||||
}
|
||||
} else {
|
||||
return a.line - b.line;
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
function sortFn (a, b) {
|
||||
if (a.line == b.line) {
|
||||
if (a.point == b.point) {
|
||||
@@ -454,12 +317,7 @@ async function saveGroup (group, opts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const isSaved = await save(baselineProjectID, monitorProjectID);
|
||||
if (isSaved) {
|
||||
await saveSample(baselineProjectID, monitorProjectID, opts.sampleOpts);
|
||||
} else {
|
||||
await remove(baselineProjectID, monitorProjectID);
|
||||
}
|
||||
await save(baselineProjectID, monitorProjectID);
|
||||
DEBUG("Saved comparison between %s and %s", baselineProjectID, monitorProjectID);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -469,88 +327,83 @@ async function saveGroup (group, opts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
async function getGroup (groupName, opts = {}) {
|
||||
|
||||
const group = (await groups())?.[groupName]?.map( i => i.pid)?.sort();
|
||||
|
||||
if (!group?.length) return;
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
|
||||
const text = `
|
||||
-- SQL query goes here
|
||||
`;
|
||||
if (groupName) {
|
||||
|
||||
const values = combinations(group, 2);
|
||||
const res = await client.query(text, values);
|
||||
if (!res.rows.length) {
|
||||
console.log("Comparison not found in database");
|
||||
return;
|
||||
}
|
||||
const group = (await groups())?.[groupName]?.map( i => i.pid)?.sort();
|
||||
|
||||
if (!group?.length || group?.length < 2) return;
|
||||
|
||||
|
||||
const pairs = combinations(group, 2);
|
||||
const flatValues = pairs.flat();
|
||||
const placeholders = [];
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
placeholders.push(`($${i * 2 + 1}, $${i * 2 + 2})`);
|
||||
}
|
||||
const inClause = placeholders.join(',');
|
||||
const selectFields = opts.returnData ? 'data, meta' : 'meta';
|
||||
|
||||
const text = `
|
||||
SELECT baseline_pid, monitor_pid, ${selectFields}
|
||||
FROM comparisons.comparisons
|
||||
WHERE type = 'geometric_difference'
|
||||
AND (baseline_pid, monitor_pid) IN (VALUES ${inClause})
|
||||
ORDER BY baseline_pid, monitor_pid
|
||||
`;
|
||||
|
||||
if (!placeholders) {
|
||||
console.log("No pairs found in group");
|
||||
return [];
|
||||
}
|
||||
|
||||
const res = await client.query(text, flatValues);
|
||||
if (!res.rows.length) {
|
||||
console.log("Comparison not found in database");
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.returnData) {
|
||||
return res.rows.map( row => ({
|
||||
...row,
|
||||
data: DougalBinaryBundle.clone(row.data),
|
||||
}));
|
||||
} else {
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
if (opts.returnData) {
|
||||
return res.rows.map( row => ({
|
||||
data: DougalBinaryBundle.clone(row.data),
|
||||
meta: row.meta
|
||||
});
|
||||
} else {
|
||||
return res.rows.map( row => row.meta );
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const selectFields = opts.returnData ? 'data, meta' : 'meta';
|
||||
|
||||
async function getGroup (groupName, opts = {}) {
|
||||
const text = `
|
||||
SELECT baseline_pid, monitor_pid, ${selectFields}
|
||||
FROM comparisons.comparisons
|
||||
WHERE type = 'geometric_difference'
|
||||
ORDER BY baseline_pid, monitor_pid
|
||||
`;
|
||||
|
||||
const group = (await groups())?.[groupName]?.map( i => i.pid)?.sort();
|
||||
const res = await client.query(text);
|
||||
if (!res.rows.length) {
|
||||
console.log("Comparison not found in database");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!group?.length) return;
|
||||
if (opts.returnData) {
|
||||
return res.rows.map( row => ({
|
||||
...row,
|
||||
data: DougalBinaryBundle.clone(row.data),
|
||||
}));
|
||||
} else {
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
|
||||
const pairs = combinations(group, 2);
|
||||
const flatValues = pairs.flat();
|
||||
const placeholders = [];
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
placeholders.push(`($${i * 2 + 1}, $${i * 2 + 2})`);
|
||||
}
|
||||
const inClause = placeholders.join(',');
|
||||
const selectFields = opts.returnData ? 'data, meta' : 'meta';
|
||||
|
||||
const text = `
|
||||
SELECT baseline_pid, monitor_pid, ${selectFields}
|
||||
FROM comparisons.comparisons
|
||||
WHERE type = 'geometric_difference'
|
||||
AND (baseline_pid, monitor_pid) IN (VALUES ${inClause})
|
||||
ORDER BY baseline_pid, monitor_pid
|
||||
`;
|
||||
|
||||
console.log(text);
|
||||
console.log(flatValues);
|
||||
|
||||
const res = await client.query(text, flatValues);
|
||||
if (!res.rows.length) {
|
||||
console.log("Comparison not found in database");
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.returnData) {
|
||||
return res.rows.map( row => ({
|
||||
...row,
|
||||
data: DougalBinaryBundle.clone(row.data),
|
||||
}));
|
||||
} else {
|
||||
return res.rows;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -567,12 +420,10 @@ module.exports = {
|
||||
get,
|
||||
save,
|
||||
getSample,
|
||||
saveSample,
|
||||
saveGroup,
|
||||
getGroup,
|
||||
remove,
|
||||
stats,
|
||||
// comparisonGeometricDifferences,
|
||||
asBundle,
|
||||
fromBundle
|
||||
};
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
const { setSurvey, pool } = require('../connection');
|
||||
|
||||
async function get () {
|
||||
async function get (opts = {}) {
|
||||
|
||||
const select = opts.timestamps
|
||||
? "last_project_update(pid) tstamp,"
|
||||
: "";
|
||||
|
||||
const text = `
|
||||
SELECT
|
||||
${select}
|
||||
pid,
|
||||
name,
|
||||
schema,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"api": "0.4.0"
|
||||
},
|
||||
"wanted": {
|
||||
"db_schema": "^0.6.5"
|
||||
"db_schema": "^0.6.6"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
||||
Reference in New Issue
Block a user