mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:27:07 +00:00
Merge branch '215-flag-unflag-qc-results-as-accepted' into 'devel'
Resolve "Flag / unflag QC results as accepted" Closes #215 See merge request wgp/dougal/software!28
This commit is contained in:
135
lib/www/client/source/src/components/qc-acceptance.vue
Normal file
135
lib/www/client/source/src/components/qc-acceptance.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
|
||||
<v-hover v-slot:default="{hover}" v-if="!isEmpty(item)">
|
||||
<span>
|
||||
<v-btn v-if="!isAccepted(item)"
|
||||
:class="{'text--disabled': !hover}"
|
||||
icon
|
||||
small
|
||||
color="primary"
|
||||
:title="isMultiple(item) ? 'Accept all' : 'Accept'"
|
||||
@click.stop="accept(item)">
|
||||
<v-icon small :color="isAccepted(item) ? 'green' : ''">
|
||||
{{ isMultiple(item) ? 'mdi-check-all' : 'mdi-check' }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-if="someAccepted(item)"
|
||||
:class="{'text--disabled': !hover}"
|
||||
icon
|
||||
small
|
||||
color="primary"
|
||||
:title="isMultiple(item) ? 'Restore all' : 'Restore'"
|
||||
@click.stop="unaccept(item)">
|
||||
<v-icon small>
|
||||
{{ isMultiple(item) ? 'mdi-restore' : 'mdi-restore' }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</span>
|
||||
</v-hover>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'DougalQcAcceptance',
|
||||
|
||||
props: {
|
||||
item: { type: Object }
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
isAccepted (item) {
|
||||
if (item._children) {
|
||||
return item._children.every(child => this.isAccepted(child));
|
||||
}
|
||||
|
||||
if (item.labels) {
|
||||
return item.labels.includes("QCAccepted");
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
someAccepted (item) {
|
||||
if (item._children) {
|
||||
return item._children.some(child => this.someAccepted(child));
|
||||
}
|
||||
|
||||
if (item.labels) {
|
||||
return item.labels.includes("QCAccepted");
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
isEmpty (item) {
|
||||
return item._children?.length === 0;
|
||||
},
|
||||
|
||||
isMultiple (item) {
|
||||
return item._children?.length;
|
||||
},
|
||||
|
||||
action (action, item) {
|
||||
const items = [];
|
||||
|
||||
const iterate = (item) => {
|
||||
if (item._kind == "point") {
|
||||
|
||||
if (this.isAccepted(item)) {
|
||||
if (action == "unaccept") {
|
||||
items.push(item);
|
||||
}
|
||||
} else {
|
||||
if (action == "accept") {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (item._kind == "sequence" || item._kind == "test") {
|
||||
|
||||
if (item._children) {
|
||||
|
||||
for (const child of item._children) {
|
||||
iterate(child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (item._shots) {
|
||||
|
||||
for (const child of item._children) {
|
||||
iterate(child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
iterate(item);
|
||||
return items;
|
||||
},
|
||||
|
||||
accept (item) {
|
||||
const items = this.action('accept', item);
|
||||
if (items.length) {
|
||||
this.$emit('accept', items);
|
||||
}
|
||||
},
|
||||
|
||||
unaccept (item) {
|
||||
const items = this.action('unaccept', item);
|
||||
if (items.length) {
|
||||
this.$emit('unaccept', items);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -50,16 +50,6 @@
|
||||
<v-col col="12" sm="6">
|
||||
<p>QC checks done on {{updatedOn}}.</p>
|
||||
</v-col>
|
||||
<v-col class="text-right">
|
||||
<div v-if="isDirty">
|
||||
<v-btn @click="saveLabels" small color="primary" class="mx-2">
|
||||
Save <v-icon right>mdi-content-save</v-icon>
|
||||
</v-btn>
|
||||
<v-btn @click="getQCData" small color="warning" outlined class="mx-2">
|
||||
Cancel <v-icon right>mdi-restore-alert</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-treeview
|
||||
@@ -98,39 +88,11 @@
|
||||
{{label}}
|
||||
</v-chip>
|
||||
|
||||
<template v-if="!item.labels || !item.labels.includes('QCAccepted')">
|
||||
<v-hover v-slot:default="{hover}" v-if="writeaccess">
|
||||
<span v-if="item.children && item.children.length">
|
||||
<v-btn
|
||||
:class="{'text--disabled': !hover}"
|
||||
icon
|
||||
small
|
||||
color="primary"
|
||||
title="Accept all"
|
||||
@click.stop="accept(item)">
|
||||
<v-icon small :color="accepted(item) ? 'green' : ''">mdi-check-all</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:class="{'text--disabled': !hover}"
|
||||
icon
|
||||
small
|
||||
color="primary"
|
||||
title="Restore all"
|
||||
@click.stop="unaccept(item)">
|
||||
<v-icon small>mdi-restore</v-icon>
|
||||
</v-btn>
|
||||
</span>
|
||||
<v-btn v-else
|
||||
:class="{'text--disabled': !hover}"
|
||||
icon
|
||||
small
|
||||
color="primary"
|
||||
title="Accept this value"
|
||||
@click="accept(item)">
|
||||
<v-icon small :color="(item.children && item.children.length == 0)? 'green':''">mdi-check</v-icon>
|
||||
</v-btn>
|
||||
</v-hover>
|
||||
</template>
|
||||
<dougal-qc-acceptance v-if="writeaccess"
|
||||
:item="item"
|
||||
@accept="accept"
|
||||
@unaccept="unaccept"
|
||||
></dougal-qc-acceptance>
|
||||
|
||||
</div>
|
||||
<div :title="item.remarks" @dblclick.stop.prevent="toggleChildren(item)" v-else-if="item._kind=='sequence'">
|
||||
@@ -142,8 +104,21 @@
|
||||
v-text="itemCount(item)"
|
||||
>
|
||||
</v-chip>
|
||||
|
||||
<dougal-qc-acceptance v-if="writeaccess"
|
||||
:item="item"
|
||||
@accept="accept"
|
||||
@unaccept="unaccept"
|
||||
></dougal-qc-acceptance>
|
||||
|
||||
</div>
|
||||
<div class="text--secondary" v-else>
|
||||
<dougal-qc-acceptance v-if="writeaccess"
|
||||
:item="item"
|
||||
@accept="accept"
|
||||
@unaccept="unaccept"
|
||||
></dougal-qc-acceptance>
|
||||
|
||||
{{item._text}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -164,10 +139,15 @@
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { withParentProps } from '@/lib/utils';
|
||||
import DougalQcAcceptance from '@/components/qc-acceptance';
|
||||
|
||||
export default {
|
||||
name: "QC",
|
||||
|
||||
components: {
|
||||
DougalQcAcceptance
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
updatedOn: null,
|
||||
@@ -179,8 +159,7 @@ export default {
|
||||
selectedSequences: null,
|
||||
multiple: false,
|
||||
autoexpand: false,
|
||||
itemIndex: 0,
|
||||
isDirty: false
|
||||
itemIndex: 0
|
||||
}
|
||||
},
|
||||
|
||||
@@ -283,44 +262,26 @@ export default {
|
||||
return sum;
|
||||
},
|
||||
|
||||
accepted (item) {
|
||||
if (item._children) {
|
||||
return item._children.every(child => this.accepted(child));
|
||||
}
|
||||
async accept (items) {
|
||||
const url = `/project/${this.$route.params.project}/qc/results/accept`;
|
||||
await this.api([url, {
|
||||
method: "POST",
|
||||
body: items.map(i => i.id)
|
||||
}]);
|
||||
|
||||
if (item.labels) {
|
||||
return item.labels.includes("QCAccepted");
|
||||
}
|
||||
return false;
|
||||
// The open/closed state of the tree branches should stay the same, unless
|
||||
// the tree structure itself has changed in the meanwhile.
|
||||
await this.getQCData();
|
||||
},
|
||||
|
||||
accept (item) {
|
||||
if (item._children) {
|
||||
for (const child of item._children) {
|
||||
this.accept(child);
|
||||
}
|
||||
return;
|
||||
}
|
||||
async unaccept (items) {
|
||||
const url = `/project/${this.$route.params.project}/qc/results/unaccept`;
|
||||
await this.api([url, {
|
||||
method: "POST",
|
||||
body: items.map(i => i.id)
|
||||
}]);
|
||||
|
||||
if (!item.labels) {
|
||||
this.$set(item, "labels", []);
|
||||
}
|
||||
item.labels.includes("QCAccepted") || item.labels.push("QCAccepted");
|
||||
this.isDirty = true;
|
||||
},
|
||||
|
||||
unaccept (item) {
|
||||
if (item._children) {
|
||||
for (const child of item._children) {
|
||||
this.unaccept(child);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const i = item.labels.indexOf("QCAccepted");
|
||||
if (i != -1) {
|
||||
item.labels.splice(i, 1);
|
||||
this.isDirty = true;
|
||||
}
|
||||
await this.getQCData();
|
||||
},
|
||||
|
||||
async getQCLabels () {
|
||||
@@ -375,16 +336,6 @@ export default {
|
||||
await Promise.all(promises);
|
||||
},
|
||||
|
||||
async saveLabels () {
|
||||
const url = `/project/${this.$route.params.project}/meta`;
|
||||
|
||||
const res = await this.api([url, {
|
||||
method: "PUT",
|
||||
body: this.resultObjects.filter(r => typeof r.value !== "undefined")
|
||||
}]);
|
||||
this.isDirty = false;
|
||||
},
|
||||
|
||||
filterByText(item, queryText) {
|
||||
if (!queryText || !item) return item;
|
||||
|
||||
@@ -495,6 +446,7 @@ export default {
|
||||
const res = await this.api([url]);
|
||||
|
||||
if (res) {
|
||||
this.itemIndex = 0;
|
||||
this.items = res.map(i => this.transform(i)) || [];
|
||||
this.updatedOn = res.updatedOn;
|
||||
await this.getQCLabels();
|
||||
@@ -503,7 +455,6 @@ export default {
|
||||
this.updatedOn = null;
|
||||
}
|
||||
|
||||
this.isDirty = false;
|
||||
},
|
||||
|
||||
...mapActions(["api"])
|
||||
|
||||
@@ -8,7 +8,7 @@ const mw = require('./middleware');
|
||||
|
||||
const verbose = process.env.NODE_ENV != 'test';
|
||||
const app = express();
|
||||
app.locals.version = "0.3.0"; // API version
|
||||
app.locals.version = "0.3.1"; // API version
|
||||
|
||||
app.map = function(a, route){
|
||||
route = route || '';
|
||||
@@ -187,6 +187,14 @@ app.map({
|
||||
// Delete all QC results for :project
|
||||
delete: [ mw.auth.access.write, mw.qc.results.delete ],
|
||||
|
||||
'/accept': {
|
||||
post: [ mw.auth.access.write, mw.qc.results.accept ]
|
||||
},
|
||||
|
||||
'/unaccept': {
|
||||
post: [ mw.auth.access.write, mw.qc.results.unaccept ]
|
||||
},
|
||||
|
||||
'/sequence/:sequence': {
|
||||
// Get QC results for :project, :sequence
|
||||
get: [ mw.qc.results.get ],
|
||||
|
||||
16
lib/www/server/api/middleware/qc/results/accept.js
Normal file
16
lib/www/server/api/middleware/qc/results/accept.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
const { qc } = require('../../../../lib/db');
|
||||
|
||||
module.exports = async function (req, res, next) {
|
||||
|
||||
try {
|
||||
const payload = req.body;
|
||||
|
||||
await qc.results.accept(req.params.project, payload);
|
||||
res.status(204).send();
|
||||
next();
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
get: require('./get'),
|
||||
delete: require('./delete')
|
||||
delete: require('./delete'),
|
||||
accept: require('./accept'),
|
||||
unaccept: require('./unaccept')
|
||||
};
|
||||
|
||||
16
lib/www/server/api/middleware/qc/results/unaccept.js
Normal file
16
lib/www/server/api/middleware/qc/results/unaccept.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
const { qc } = require('../../../../lib/db');
|
||||
|
||||
module.exports = async function (req, res, next) {
|
||||
|
||||
try {
|
||||
const payload = req.body;
|
||||
|
||||
await qc.results.unaccept(req.params.project, payload);
|
||||
res.status(204).send();
|
||||
next();
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
};
|
||||
23
lib/www/server/lib/db/qc/results/accept.js
Normal file
23
lib/www/server/lib/db/qc/results/accept.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const { setSurvey } = require('../../connection');
|
||||
|
||||
async function accept (projectId, payload) {
|
||||
const client = await setSurvey(projectId);
|
||||
|
||||
const text = `
|
||||
UPDATE event_log_full
|
||||
SET
|
||||
labels = array_append(labels, 'QCAccepted')
|
||||
WHERE
|
||||
validity @> current_timestamp
|
||||
AND id = ANY($1)
|
||||
AND NOT ('QCAccepted' = ANY(labels));
|
||||
`;
|
||||
|
||||
const values = [ payload ];
|
||||
|
||||
await client.query(text, values);
|
||||
client.release();
|
||||
return;
|
||||
}
|
||||
|
||||
module.exports = accept;
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
module.exports = {
|
||||
get: require('./get'),
|
||||
delete: require('./delete')
|
||||
delete: require('./delete'),
|
||||
accept: require('./accept'),
|
||||
unaccept: require('./unaccept')
|
||||
};
|
||||
|
||||
22
lib/www/server/lib/db/qc/results/unaccept.js
Normal file
22
lib/www/server/lib/db/qc/results/unaccept.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const { setSurvey, transaction } = require('../../connection');
|
||||
|
||||
async function unaccept (projectId, payload) {
|
||||
const client = await setSurvey(projectId);
|
||||
|
||||
const text = `
|
||||
UPDATE event_log_full
|
||||
SET
|
||||
labels = array_remove(labels, 'QCAccepted')
|
||||
WHERE
|
||||
validity @> current_timestamp
|
||||
AND id = ANY($1);
|
||||
`;
|
||||
|
||||
const values = [ payload ];
|
||||
|
||||
await client.query(text, values);
|
||||
client.release();
|
||||
return;
|
||||
}
|
||||
|
||||
module.exports = unaccept;
|
||||
@@ -1,6 +1,6 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
version: 0.3.0
|
||||
version: 0.3.1
|
||||
title: Dougal API
|
||||
description: >
|
||||
Public API of the Dougal seismic production & data analysis tool.
|
||||
@@ -55,6 +55,9 @@ tags:
|
||||
-
|
||||
name: log
|
||||
description: Project events log
|
||||
-
|
||||
name: qc
|
||||
description: Project quality control definition and results
|
||||
-
|
||||
name: metadata
|
||||
description: Project items metadata
|
||||
@@ -1401,6 +1404,133 @@ paths:
|
||||
$ref: "#/components/responses/401"
|
||||
|
||||
|
||||
/project/{project}/qc/results:
|
||||
get:
|
||||
summary: Get QC results.
|
||||
tags: [ "qc" ]
|
||||
security:
|
||||
- BearerAuthGuest: []
|
||||
- CookieAuthGuest: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Project"
|
||||
responses:
|
||||
"200":
|
||||
description: Project QC results.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
description: |
|
||||
The returned object is a tree structure of QC tests and their results.
|
||||
"401":
|
||||
$ref: "#/components/responses/401"
|
||||
delete:
|
||||
summary: Delete all QC results.
|
||||
tags: [ "qc" ]
|
||||
security:
|
||||
- BearerAuthUser: []
|
||||
- BearerAuthUser: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Project"
|
||||
responses:
|
||||
"204":
|
||||
description: |
|
||||
All QC results for the project have been deleted.
|
||||
Note that unless the project has been archived, the QCs will be regenerated in the next run of the deferred tasks process.
|
||||
"401":
|
||||
$ref: "#/components/responses/401"
|
||||
|
||||
|
||||
/project/{project}/qc/results/accept:
|
||||
post:
|
||||
summary: Accept shotpoint QC results.
|
||||
tags: [ "qc" ]
|
||||
security:
|
||||
- BearerAuthUser: []
|
||||
- BearerAuthUser: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Project"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
description: Event ID of the QC result to mark as accepted.
|
||||
responses:
|
||||
"204":
|
||||
description: |
|
||||
The QC events referenced in the request body have been marked as accepted. These events will not be exported to Seis+JSON files or derived human-readable reports.
|
||||
"401":
|
||||
$ref: "#/components/responses/401"
|
||||
|
||||
/project/{project}/qc/results/unaccept:
|
||||
post:
|
||||
summary: Unaccept shotpoint QC results.
|
||||
tags: [ "qc" ]
|
||||
security:
|
||||
- BearerAuthUser: []
|
||||
- BearerAuthUser: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Project"
|
||||
responses:
|
||||
"204":
|
||||
description: |
|
||||
The QC events referenced in the request body are no longer marked as accepted.
|
||||
"401":
|
||||
$ref: "#/components/responses/401"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
description: Event ID of the QC result to no longer mark accepted.
|
||||
|
||||
|
||||
/project/{project}/qc/results/sequence/{sequence}:
|
||||
get:
|
||||
summary: Get sequence QC results.
|
||||
tags: [ "qc" ]
|
||||
security:
|
||||
- BearerAuthGuest: []
|
||||
- CookieAuthGuest: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Project"
|
||||
- $ref: "#/components/parameters/SequenceNumber"
|
||||
responses:
|
||||
"200":
|
||||
description: Sequence QC results.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
description: |
|
||||
The returned object is a tree structure of QC tests and their results for a specific sequence
|
||||
"401":
|
||||
$ref: "#/components/responses/401"
|
||||
delete:
|
||||
summary: Delete sequence QC results.
|
||||
tags: [ "qc" ]
|
||||
security:
|
||||
- BearerAuthUser: []
|
||||
- BearerAuthUser: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Project"
|
||||
- $ref: "#/components/parameters/SequenceNumber"
|
||||
responses:
|
||||
"204":
|
||||
description: |
|
||||
All QC results for the sequence have been deleted.
|
||||
Note that unless the project has been archived, the QCs will be regenerated in the next run of the deferred tasks process.
|
||||
"401":
|
||||
$ref: "#/components/responses/401"
|
||||
|
||||
|
||||
/project/{project}/configuration/{path}:
|
||||
get:
|
||||
summary: Get project configuration data.
|
||||
|
||||
Reference in New Issue
Block a user