Compare commits

..

53 Commits

Author SHA1 Message Date
D. Berge
a8ff7f3b52 Fix indentation 2025-08-22 02:13:30 +02:00
D. Berge
15b62ff581 Fix typos 2025-08-22 02:13:15 +02:00
D. Berge
ade86be556 Replace tilt icons 2025-08-22 02:12:45 +02:00
D. Berge
53594416a7 Remove dead code 2025-08-22 02:04:59 +02:00
D. Berge
ff4b4a9c90 Add more view controls to group map 2025-08-22 02:04:42 +02:00
D. Berge
5842940d3b Add more view controls to map 2025-08-22 02:03:29 +02:00
D. Berge
df6f1b2d32 Add script to update comparison groups.
This should be run at regular intervals (via cron or so) to keep
the comparisons up to date.

It is not necessarily a good idea to run this as part of the
runner.sh script as it will delay other tasks trying to
update the active project every time.

Probably OK to put it on a cronjbo every 2‒24 hours. If two
copies are running concurrently that should not break anything
but it will increase the server load.
2025-08-22 00:04:46 +02:00
D. Berge
c39afc1f3c Return project timestamps 2025-08-22 00:04:21 +02:00
D. Berge
a68000eac6 Add option to return project timestamp 2025-08-22 00:02:05 +02:00
D. Berge
87aa78af00 Updated wanted db schema 2025-08-22 00:01:40 +02:00
D. Berge
3b9061aeae Add database upgrade file 44 2025-08-22 00:01:02 +02:00
D. Berge
57dae4c755 Clean up dead code 2025-08-22 00:00:01 +02:00
D. Berge
b1344bebd8 Update the required schema version.
This is necessary for the comparisons code to work.
2025-08-21 17:08:23 +02:00
D. Berge
3e91ccba8d Don't show monitor lines by default 2025-08-21 15:21:01 +02:00
D. Berge
fa0be9c0b7 Make loading indicator spin when 0% 2025-08-21 15:20:31 +02:00
D. Berge
dcbf5496f6 Remove unneded dependency 2025-08-21 15:10:45 +02:00
D. Berge
8007f46e37 Fix typo 2025-08-21 15:04:48 +02:00
D. Berge
4a7683cfd0 Add group map view 2025-08-21 14:58:53 +02:00
D. Berge
565a9d7e01 Add support for type 4 decoding 2025-08-21 14:58:53 +02:00
D. Berge
b07244c823 Fix component paths 2025-08-21 14:58:53 +02:00
D. Berge
c909edc41f Move components to subdirectory 2025-08-21 14:55:27 +02:00
D. Berge
41ef511123 Return type 4 sequence data 2025-08-21 14:52:50 +02:00
D. Berge
4196e9760b Add encoding type 4 to bundle 2025-08-21 14:51:49 +02:00
D. Berge
6b6f5ab511 Link from group summary to individual projects 2025-08-20 12:06:20 +02:00
D. Berge
7d8c78648d Don't request summaries in ProjectList.
Those will be populated directly by Vuex.
2025-08-20 12:05:44 +02:00
D. Berge
faf7e9c98f Try to improve responsiveness when refreshing project list 2025-08-20 12:05:05 +02:00
D. Berge
abf2709705 Expand groups router definition 2025-08-20 12:04:26 +02:00
D. Berge
f5dfafd85a Make event handler more specific 2025-08-20 12:03:53 +02:00
D. Berge
cf8b0937d9 Rework comparison components.
More focused on error ellipses.
2025-08-19 19:28:19 +02:00
D. Berge
d737f5d676 Refresh comparisons when notified of changes 2025-08-19 19:27:38 +02:00
D. Berge
5fe19da586 Add control to reset comparisons view 2025-08-19 19:27:03 +02:00
D. Berge
0af0cf4b42 Add overlays when loading / data error 2025-08-19 18:58:04 +02:00
D. Berge
ccb8205d26 Don't cache comparisons in the API 2025-08-19 18:55:31 +02:00
D. Berge
9b3fffdcfc Don't save comparison samples 2025-08-19 18:54:28 +02:00
D. Berge
dea1e9ee0d Add comparisons channel to notifications 2025-08-19 18:53:40 +02:00
D. Berge
d45ec767ec Add database upgrade file 43 2025-08-19 17:56:30 +02:00
D. Berge
67520ffc48 Add database upgrade file 42 2025-08-19 17:56:14 +02:00
D. Berge
22a296ba26 Add database upgrade file 41 2025-08-19 17:55:58 +02:00
D. Berge
f89435d80f Don't overwrite existing comparisons unless forced.
opts.overwrite = true will cause existing comparisons to be
recomputed.
2025-08-19 17:20:57 +02:00
D. Berge
a3f1dd490c Fix non-existent method 2025-08-19 17:20:03 +02:00
D. Berge
2fcfcb4f84 Add link to group comparison from project list 2025-08-18 16:39:20 +02:00
D. Berge
b60db7e7ef Add frontend route for 4D comparisons 2025-08-18 14:17:17 +02:00
D. Berge
4bb087fff7 Add 4D comparisons list Vue component 2025-08-18 14:16:23 +02:00
D. Berge
15af5effc3 Add 4D comparisons Vue component 2025-08-18 14:15:52 +02:00
D. Berge
b5c6d04e62 Add utilities for transforming duration objects 2025-08-18 14:15:14 +02:00
D. Berge
571c5a8bca Add Vue components for 4D comparisons 2025-08-18 14:14:34 +02:00
D. Berge
c45982829c Add set operations utilities 2025-08-18 14:11:56 +02:00
D. Berge
f3958b37b7 Add comparison API endpoints 2025-08-18 14:11:20 +02:00
D. Berge
58374adc68 Add two new bundle types.
Of which 0xa is not actually used and 0xc is used for geometric
comparison data ([ line, point, δi, δj ]).
2025-08-18 14:05:26 +02:00
D. Berge
32aea8a5ed Add comparison functions to server/lib 2025-08-18 13:53:43 +02:00
D. Berge
023b65285f Fix bug trying to get project info for undefined 2025-08-18 13:51:37 +02:00
D. Berge
a320962669 Add project group info to Vuex 2025-08-18 13:50:49 +02:00
D. Berge
0c0067b8d9 Add iterators 2025-08-18 13:48:49 +02:00
9 changed files with 81 additions and 314 deletions

View File

@@ -1,7 +1,5 @@
#!/bin/bash #!/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")/..} DOUGAL_ROOT=${DOUGAL_ROOT:-$(dirname "$0")/..}
@@ -82,9 +80,8 @@ function run () {
# DESCRIPTION="" # DESCRIPTION=""
SERVICE="deferred_imports" SERVICE="deferred_imports"
# Disable GitLab alerts. They're just not very practical $BINDIR/send_alert.py -t "$TITLE" -s "$SERVICE" -l "critical" \
# $BINDIR/send_alert.py -t "$TITLE" -s "$SERVICE" -l "critical" \ -O "$(cat $STDOUTLOG)" -E "$(cat $STDERRLOG)"
# -O "$(cat $STDOUTLOG)" -E "$(cat $STDERRLOG)"
exit 2 exit 2
} }
@@ -100,37 +97,14 @@ function cleanup () {
} }
if [[ -f $LOCKFILE ]]; then if [[ -f $LOCKFILE ]]; then
PID=$(cat "$LOCKFILE") PID=$(cat "$LOCKFILE")
if kill -0 "$PID" 2>/dev/null; then # Check if process is running if pgrep -F "$LOCKFILE"; then
# Get elapsed time in D-HH:MM:SS format and convert to seconds print_warning $(printf "The previous process is still running (%d)" $PID)
ELAPSED_STR=$(ps -p "$PID" -o etime= | tr -d '[:space:]') exit 1
if [ -n "$ELAPSED_STR" ]; then else
# Convert D-HH:MM:SS to seconds rm "$LOCKFILE"
ELAPSED_SECONDS=$(echo "$ELAPSED_STR" | awk -F'[-:]' '{ print_warning $(printf "Previous process (%d) not found. Must have died unexpectedly" $PID)
seconds = 0 fi
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 fi
echo "$$" > "$LOCKFILE" || { echo "$$" > "$LOCKFILE" || {

View File

@@ -2,32 +2,8 @@
const cmp = require('../lib/www/server/lib/comparisons'); 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 () { async function main () {
console.log("Looking for unreferenced comparisons to purge");
await purgeComparisons();
console.log("Retrieving project groups"); console.log("Retrieving project groups");
const groups = await cmp.groups(); const groups = await cmp.groups();
@@ -36,7 +12,7 @@ async function main () {
return 0; return 0;
} }
console.log(`Found ${Object.keys(groups)?.length} groups: ${Object.keys(groups).join(", ")}`); console.log(`Found ${groups.length} groups: ${Object.keys(groups).join(", ")}`);
for (const groupName of Object.keys(groups)) { for (const groupName of Object.keys(groups)) {
const projects = groups[groupName]; const projects = groups[groupName];
@@ -45,11 +21,6 @@ async function main () {
const comparisons = await cmp.getGroup(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 // 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 // or if there are any pairs that are no longer part of the group

View File

@@ -39,8 +39,7 @@ export default {
default: default:
return { return {
editable: false, editable: false,
displaylogo: false, displaylogo: false
responsive: true
}; };
} }
}, },
@@ -49,8 +48,7 @@ export default {
const base = { const base = {
font: { font: {
color: this.$vuetify.theme.isDark ? "#fff" : undefined color: this.$vuetify.theme.isDark ? "#fff" : undefined
}, }
autosize: true
}; };
switch (this.facet) { switch (this.facet) {
@@ -276,25 +274,18 @@ export default {
replot () { replot () {
if (this.plotted) { if (this.plotted) {
const ref = this.$refs.graph; const ref = this.$refs.graph;
if (ref && ref.clientWidth > 0 && ref.clientHeight > 0) { Plotly.relayout(ref, {
Plotly.relayout(ref, { width: ref.clientWidth,
width: ref.clientWidth, height: ref.clientHeight
height: ref.clientHeight });
});
}
} }
} }
}, },
mounted () { mounted () {
this.$nextTick( () => { this.resizeObserver = new ResizeObserver(this.replot)
if (this.items?.length) { this.resizeObserver.observe(this.$refs.graph);
this.plot();
}
this.resizeObserver = new ResizeObserver(this.replot)
this.resizeObserver.observe(this.$refs.graph);
});
}, },
beforeDestroy () { beforeDestroy () {

View File

@@ -36,8 +36,7 @@ export default {
config () { config () {
return { return {
editable: false, editable: false,
displaylogo: false, displaylogo: false
responsive: true
}; };
}, },
@@ -54,8 +53,7 @@ export default {
title: "Time (s)" title: "Time (s)"
}, },
plot_bgcolor:"rgba(0,0,0,0)", plot_bgcolor:"rgba(0,0,0,0)",
paper_bgcolor:"rgba(0,0,0,0)", paper_bgcolor:"rgba(0,0,0,0)"
autosize: true
}; };
}, },
@@ -156,12 +154,10 @@ export default {
replot () { replot () {
if (this.plotted) { if (this.plotted) {
const ref = this.$refs.graph; const ref = this.$refs.graph;
if (ref && ref.clientWidth > 0 && ref.clientHeight > 0) { Plotly.relayout(ref, {
Plotly.relayout(ref, { width: ref.clientWidth,
width: ref.clientWidth, height: ref.clientHeight
height: ref.clientHeight });
});
}
} }
}, },
@@ -194,13 +190,8 @@ export default {
}, },
mounted () { mounted () {
this.$nextTick( () => { this.resizeObserver = new ResizeObserver(this.replot)
if (this.items?.length) { this.resizeObserver.observe(this.$refs.graph);
this.plot();
}
this.resizeObserver = new ResizeObserver(this.replot)
this.resizeObserver.observe(this.$refs.graph);
});
}, },
beforeDestroy () { beforeDestroy () {

View File

@@ -158,7 +158,6 @@ Vue.use(VueRouter)
component: SequenceList component: SequenceList
}, },
{ {
name: "shotlog",
path: "sequences/:sequence", path: "sequences/:sequence",
component: SequenceSummary component: SequenceSummary
}, },

View File

@@ -36,7 +36,7 @@ async function refreshEvents ({commit, dispatch, state, rootState}, [modifiedAft
/** Return a subset of events from state.events /** Return a subset of events from state.events
*/ */
async function getEvents ({commit, dispatch, state}, [projectId, {sequence, date0, date1, sortBy, sortDesc, itemsPerPage, page, text, label, excludeLabels}]) { async function getEvents ({commit, dispatch, state}, [projectId, {sequence, date0, date1, sortBy, sortDesc, itemsPerPage, page, text, label}]) {
let filteredEvents = [...state.events]; let filteredEvents = [...state.events];
if (sortBy) { if (sortBy) {
@@ -114,10 +114,6 @@ async function getEvents ({commit, dispatch, state}, [projectId, {sequence, date
filteredEvents = filteredEvents.filter( event => event.labels?.includes(label) ); 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; const count = filteredEvents.length;
if (itemsPerPage && itemsPerPage > 0) { if (itemsPerPage && itemsPerPage > 0) {

View File

@@ -5,22 +5,6 @@
<v-card-title> <v-card-title>
<v-toolbar flat> <v-toolbar flat>
<v-toolbar-title> <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"> <span class="d-none d-lg-inline">
{{ {{
$route.params.sequence $route.params.sequence
@@ -47,38 +31,18 @@
: "" : ""
}} }}
</span> </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> </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()" <dougal-event-edit v-if="$parent.writeaccess()"
v-model="eventDialog" v-model="eventDialog"
@@ -530,6 +494,17 @@ export default {
rows () { rows () {
const rows = {}; const rows = {};
this.items 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 => { .forEach(i => {
const key = (i.sequence && i.point) ? (i.sequence+"@"+i.point) : i.tstamp; const key = (i.sequence && i.point) ? (i.sequence+"@"+i.point) : i.tstamp;
if (!rows[key]) { if (!rows[key]) {
@@ -560,10 +535,6 @@ export default {
.sort( (a, b) => b[1]-a[1] ); .sort( (a, b) => b[1]-a[1] );
}, },
filteredLabels () {
return this.filterableLabels.filter( label => !this.shownLabels.includes(label) );
},
presetRemarks () { presetRemarks () {
return this.projectConfiguration?.events?.presetRemarks ?? []; return this.projectConfiguration?.events?.presetRemarks ?? [];
}, },
@@ -576,17 +547,7 @@ export default {
} }
}, },
sequenceIndex () { ...mapGetters(['user', 'eventsLoading', 'online', 'sequence', 'line', 'point', 'position', 'timestamp', 'lineName', 'events', 'labels', 'userLabels', 'projectConfiguration']),
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}) ...mapState({projectSchema: state => state.project.projectSchema})
}, },
@@ -594,7 +555,6 @@ export default {
watch: { watch: {
options: { options: {
async handler () { async handler () {
this.savePrefs(),
await this.fetchEvents(); await this.fetchEvents();
}, },
deep: true deep: true
@@ -613,19 +573,12 @@ export default {
}, },
filter (newVal, oldVal) { filter (newVal, oldVal) {
this.savePrefs();
if (newVal?.toLowerCase() != oldVal?.toLowerCase()) { if (newVal?.toLowerCase() != oldVal?.toLowerCase()) {
this.fetchEvents(); this.fetchEvents();
} }
}, },
labelSearch () { labelSearch () {
this.savePrefs();
this.fetchEvents();
},
filteredLabels () {
this.savePrefs()
this.fetchEvents(); this.fetchEvents();
}, },
@@ -634,7 +587,7 @@ export default {
}, },
user (newVal, oldVal) { user (newVal, oldVal) {
this.loadPrefs(); this.itemsPerPage = Number(localStorage.getItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`)) || 25;
} }
}, },
@@ -685,10 +638,8 @@ export default {
async fetchEvents (opts = {}) { async fetchEvents (opts = {}) {
const options = { const options = {
sequence: this.$route.params.sequence,
text: this.filter, text: this.filter,
label: this.labelSearch, label: this.labelSearch,
excludeLabels: this.filteredLabels,
...this.options ...this.options
}; };
const res = await this.getEvents([this.$route.params.project, options]); const res = await this.getEvents([this.$route.params.project, options]);
@@ -926,36 +877,10 @@ 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"]) ...mapActions(["api", "showSnack", "refreshEvents", "getEvents"])
}, },
async mounted () { async mounted () {
this.loadPrefs();
this.fetchEvents(); this.fetchEvents();
window.addEventListener('keyup', this.handleKeyboardEvent); window.addEventListener('keyup', this.handleKeyboardEvent);

View File

@@ -6,42 +6,8 @@
<v-progress-linear indeterminate v-if="loading"></v-progress-linear> <v-progress-linear indeterminate v-if="loading"></v-progress-linear>
<v-toolbar flat> <v-toolbar flat>
<v-toolbar-title> <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}} Sequence {{sequenceNumber}}
<small :class="statusColour" v-if="sequence">({{sequence.status}})</small> <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> </v-toolbar-title>
<a v-if="$route.params.sequence" <a v-if="$route.params.sequence"
@@ -386,16 +352,6 @@ export default {
return this.sequences.find(i => i.sequence == this.sequenceNumber); 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 () { remarks () {
return this.sequence?.remarks || "Nil."; return this.sequence?.remarks || "Nil.";
}, },

View File

@@ -330,80 +330,44 @@ async function saveGroup (group, opts = {}) {
async function getGroup (groupName, 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(); const client = await pool.connect();
try { try {
if (groupName) { 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 group = (await groups())?.[groupName]?.map( i => i.pid)?.sort(); 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 (!group?.length || group?.length < 2) return; const res = await client.query(text, flatValues);
if (!res.rows.length) {
console.log("Comparison not found in database");
const pairs = combinations(group, 2); return;
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 => ({
...row,
data: DougalBinaryBundle.clone(row.data),
}));
} else { } else {
return res.rows;
const selectFields = opts.returnData ? 'data, meta' : 'meta';
const text = `
SELECT baseline_pid, monitor_pid, ${selectFields}
FROM comparisons.comparisons
WHERE type = 'geometric_difference'
ORDER BY baseline_pid, monitor_pid
`;
const res = await client.query(text);
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) { } catch (err) {
console.error(err); console.error(err);