Mercurial > gemma
changeset 2969:b92a8d088d8a unified_import
merge with default
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Tue, 02 Apr 2019 10:07:48 +0200 |
parents | 8b32574bed09 (current diff) 6f7b8755eb07 (diff) |
children | 149a8f81f99e |
files | client/src/components/ui/SpinnerOverlay.vue |
diffstat | 18 files changed, 328 insertions(+), 350 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/assets/application.scss Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/assets/application.scss Tue Apr 02 10:07:48 2019 +0200 @@ -197,12 +197,12 @@ select.form-control-sm.small { padding: 0.25rem 0.1rem; - font-size: 80%; + font-size: 0.75rem; } input.form-control-sm.small { padding: 0.25rem 0.2rem; - font-size: 80%; + font-size: 0.75rem; } .empty {
--- a/client/src/components/Bottlenecks.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/Bottlenecks.vue Tue Apr 02 10:07:48 2019 +0200 @@ -24,7 +24,7 @@ <UITableBody :data="filteredBottlenecks() | sortTable(sortColumn, sortDirection)" :maxHeight="(showSplitscreen ? 18 : 35) + 'rem'" - :active="openBottleneck" + :isActive="item => item === this.openBottleneck" > <template v-slot:row="{ item: bottleneck }"> <div class="table-cell truncate text-left" style="width: 230px">
--- a/client/src/components/importoverview/AdditionalLog.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importoverview/AdditionalLog.vue Tue Apr 02 10:07:48 2019 +0200 @@ -1,18 +1,12 @@ <template> <div :class="[ - 'additionallog', - 'd-flex', - 'flex-column', - 'text-left', - { - full: showAdditional === $options.NODETAILS, - split: showAdditional !== $options.NODETAILS - } + 'additionallog d-flex flex-column text-left', + { split: showAdditional } ]" > <div - class="d-flex flex-row" + class="d-flex flex-row px-2 border-top" v-for="(line, index) in details.entries" :key="index" > @@ -36,7 +30,7 @@ 'font-weight-bold': /warn|error/.test(line.kind) } ]" - >{{ line.time }}</span + >{{ line.time | dateTime }}</span > <span :class="[ @@ -53,6 +47,31 @@ </div> </template> +<style lang="sass" scoped> +.additionallog + max-height: 70vh + overflow-y: auto + &.split + max-height: 35vh + + > div + &:not(:first-child) + border-top-style: dashed !important + + &:hover + background-color: #fcfcfc + + .kind + width: 9% + + .time + width: 26% + + .message + width: 65% + word-wrap: break-word +</style> + <script> /* This is Free Software under GNU Affero General Public License v >= 3.0 * without warranty, see README.md and license for details. @@ -73,32 +92,6 @@ name: "additionallogs", computed: { ...mapState("imports", ["showAdditional", "details"]) - }, - NODETAILS: -1 + } }; </script> - -<style lang="scss" scoped> -.additionallog { - overflow-y: auto; -} - -.split { - max-height: 35vh; -} - -.full { - max-height: 70vh; -} - -.kind { - width: 9%; -} -.time { - width: 26%; -} -.message { - width: 65%; - word-wrap: break-word; -} -</style>
--- a/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue Tue Apr 02 10:07:48 2019 +0200 @@ -2,8 +2,8 @@ <div :class="{ diffs: true, - full: showLogs === $options.NODETAILS, - split: showLogs !== $options.NODETAILS + full: !showLogs, + split: showLogs }" > <div v-for="(result, index) in details.summary" :key="index"> @@ -92,6 +92,28 @@ </div> </template> +<style lang="sass" scoped> +.diffs + width: 100% + max-height: 20vh + overflow-y: auto + +.agmcode + width: 35% + +.agmdetailskeys + width: 33% + +.agmdetailsvalues + width: 33% + +.split + max-height: 35vh + +.full + max-height: 70vh +</style> + <script> /* This is Free Software under GNU Affero General Public License v >= 3.0 * without warranty, see README.md and license for details. @@ -108,14 +130,12 @@ */ import { mapState } from "vuex"; -const NODIFF = -1; - export default { name: "agmdetails", props: ["entry"], data() { return { - showDiff: NODIFF + showDiff: false }; }, computed: { @@ -123,41 +143,12 @@ }, methods: { toggleDiff(number) { - if (this.showDiff !== number || this.showDiff == NODIFF) { + if (this.showDiff !== number) { this.showDiff = number; } else { - this.showDiff = NODIFF; + this.showDiff = false; } } - }, - NODETAILS: -1 + } }; </script> - -<style lang="scss" scoped> -.diffs { - width: 615px; - max-height: 20vh; - overflow-y: auto; -} - -.agmcode { - width: 35%; -} - -.agmdetailskeys { - width: 33%; -} - -.agmdetailsvalues { - width: 33%; -} - -.split { - max-height: 35vh; -} - -.full { - max-height: 70vh; -} -</style>
--- a/client/src/components/importoverview/BottleneckDetail.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importoverview/BottleneckDetail.vue Tue Apr 02 10:07:48 2019 +0200 @@ -2,8 +2,8 @@ <div :class="{ bottleneckdetails: true, - full: showLogs === $options.NODETAILS, - split: showLogs !== $options.NODETAILS + full: !showLogs, + split: showLogs }" > <div @@ -30,9 +30,9 @@ fixed-width ></font-awesome-icon> </div> - <a @click="moveToBottleneck(index)" class="" href="#">{{ - bottleneck.properties.objnam - }}</a> + <a @click="moveToBottleneck(index)" href="#"> + {{ bottleneck.properties.objnam }} + </a> </div> <div class="ml-3 d-flex flex-row" v-if="showBottleneckDetail === index"> @@ -54,6 +54,18 @@ </div> </template> +<style lang="sass" scoped> +.bottleneckdetails + width: 100% + overflow-y: auto + +.split + max-height: 35vh + +.full + max-height: 70vh +</style> + <script> /* This is Free Software under GNU Affero General Public License v >= 3.0 * without warranty, see README.md and license for details. @@ -149,27 +161,11 @@ }, showBottleneckDetails(index) { if (index == this.showBottleneckDetail) { - this.showBottleneckDetail = NO_BOTTLENECK; + this.showBottleneckDetail = false; return; } this.showBottleneckDetail = index; } - }, - NODETAILS: -1 + } }; </script> - -<style lang="scss" scoped> -.bottleneckdetails { - width: 615px; - overflow-y: auto; -} - -.split { - max-height: 35vh; -} - -.full { - max-height: 70vh; -} -</style>
--- a/client/src/components/importoverview/Filters.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importoverview/Filters.vue Tue Apr 02 10:07:48 2019 +0200 @@ -1,18 +1,33 @@ <template> <div> - <button @click="setFilter('pending')" :class="pendingStyle"> + <button + @click="setFilter('pending')" + :class="'mr-1 btn btn-xs btn-' + (this.pending ? 'secondary' : 'light')" + > <translate>pending</translate> </button> - <button @click="setFilter('failed')" :class="failedStyle"> + <button + @click="setFilter('failed')" + :class="'mr-1 btn btn-xs btn-' + (this.failed ? 'secondary' : 'light')" + > <translate>failed</translate> </button> - <button @click="setFilter('accepted')" :class="acceptedStyle"> + <button + @click="setFilter('accepted')" + :class="'mr-1 btn btn-xs btn-' + (this.accepted ? 'secondary' : 'light')" + > <translate>accepted</translate> </button> - <button @click="setFilter('declined')" :class="declinedStyle"> + <button + @click="setFilter('declined')" + :class="'mr-1 btn btn-xs btn-' + (this.declined ? 'secondary' : 'light')" + > <translate>declined</translate> </button> - <button @click="setFilter('warning')" :class="warningStyle"> + <button + @click="setFilter('warning')" + :class="'btn btn-xs btn-' + (this.warning ? 'secondary' : 'light')" + > <translate>warning</translate> </button> </div> @@ -49,53 +64,7 @@ "accepted", "warning", "declined" - ]), - pendingStyle() { - return { - btn: true, - "btn-sm": true, - "btn-light": !this.pending, - "btn-secondary": this.pending - }; - }, - failedStyle() { - return { - "ml-2": true, - btn: true, - "btn-sm": true, - "btn-light": !this.failed, - "btn-secondary": this.failed - }; - }, - declinedStyle() { - return { - "ml-2": true, - btn: true, - "btn-sm": true, - "btn-light": !this.declined, - "btn-secondary": this.declined - }; - }, - acceptedStyle() { - return { - "ml-2": true, - btn: true, - "btn-sm": true, - "btn-light": !this.accepted, - "btn-secondary": this.accepted - }; - }, - warningStyle() { - return { - "ml-2": true, - btn: true, - "btn-sm": true, - "btn-light": !this.warning, - "btn-secondary": this.warning - }; - } + ]) } }; </script> - -<style lang="scss" scoped></style>
--- a/client/src/components/importoverview/ImportOverview.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importoverview/ImportOverview.vue Tue Apr 02 10:07:48 2019 +0200 @@ -8,64 +8,56 @@ /> <div class="position-relative"> <SpinnerOverlay v-if="loading" /> - <div class="p-2 mb-1 d-flex flex-row flex-fill justify-content-between"> + <div class="border-bottom p-2 d-flex justify-content-between"> <Filters></Filters> - <div> - <button - class="btn btn-sm btn-info" - :disabled="!reviewed.length" - @click="save" - > - <translate>Commit</translate> {{ reviewed.length }} - </button> - </div> + <button + class="btn btn-xs btn-info" + :disabled="!reviewed.length" + @click="save" + > + <translate>Commit</translate> {{ reviewed.length }} + </button> </div> <div - class="ml-2 mr-2 mb-2 datefilter d-flex flex-row justify-content-between" + class="p-2 d-flex align-items-center justify-content-between border-bottom" > - <div class="mr-3 my-auto pointer"> - <button - :disabled="!this.prev" - @click="earlier" - class="btn btn-sm btn-outline-light text-dark" - > - <translate>Earlier</translate> - <font-awesome-icon class="ml-2" icon="angle-left" /> - </button> - </div> - <div class="selected-interval my-auto"> - <span class="date">{{ interval[0] | dateTime }}</span> - <span class="ml-3 mr-3">-</span> - <span class="date">{{ interval[1] | dateTime }}</span> - </div> - <div class="ml-3 my-auto pointer"> - <button - :disabled="!this.next" - @click="later" - class="btn btn-sm btn-outline-light text-dark" - > - <font-awesome-icon class="mr-2" icon="angle-right" /><translate - >Later</translate - > - </button> - </div> - <div class="d-flex flex-row"> + <button + :disabled="!this.prev" + @click="earlier" + class="btn btn-xs btn-outline-secondary" + > + <font-awesome-icon icon="angle-left" fixed-width /> + <translate>Earlier</translate> + </button> + <div class="d-flex align-items-center small"> + {{ interval[0] | dateTime }} + <span class="mx-2">–</span> + {{ interval[1] | dateTime }} <select - class="my-auto btn btn-outline-light text-dark form-control interval-select" + style="width: 75px; height: 24px" + class="form-control form-control-sm small ml-2" v-model="selectedInterval" > - <option :value="$options.LAST_HOUR" - ><translate>Hour</translate></option - > + <option :value="$options.LAST_HOUR"> + <translate>Hour</translate> + </option> <option :value="$options.TODAY"><translate>Day</translate></option> - <option :value="$options.LAST_7_DAYS" - ><translate>7 days</translate></option - > + <option :value="$options.LAST_7_DAYS"> + <translate>7 days</translate> + </option> <option :value="$options.LAST_30_DAYS"> <translate>30 Days</translate> </option> </select> </div> + <button + :disabled="!this.next" + @click="later" + class="btn btn-xs btn-outline-secondary" + > + <translate>Later</translate> + <font-awesome-icon icon="angle-right" fixed-width /> + </button> </div> <UITableHeader :columns="[ @@ -85,16 +77,25 @@ --> <UITableBody :data="filteredImports() | sortTable(sortColumn, sortDirection)" + :isActive="item => item.id === this.show" maxHeight="73vh" > <template v-slot:row="{ item: entry }"> <LogEntry :entry="entry"></LogEntry> </template> + <template v-slot:expand="{ item: entry }"> + <LogDetail :entry="entry"></LogDetail> + </template> </UITableBody> </div> </div> </template> +<style lang="sass" scoped> +.spinner-overlay + top: 110px +</style> + <script> /* This is Free Software under GNU Affero General Public License v >= 3.0 * without warranty, see README.md and license for details. @@ -129,7 +130,8 @@ export default { components: { Filters: () => import("./Filters.vue"), - LogEntry: () => import("./LogEntry.vue") + LogEntry: () => import("./LogEntry.vue"), + LogDetail: () => import("./LogDetail.vue") }, mixins: [sortTable], LAST_HOUR: "lasthour", @@ -321,7 +323,7 @@ query: this.searchQuery }) .then(() => { - if (this.show != -1) { + if (this.show) { this.loadDetails(this.show) .then(response => { this.$store.commit("imports/setCurrentDetails", response.data); @@ -418,23 +420,12 @@ }, mounted() { const { id } = this.$route.params; - if (!id) { + if (id) { + this.showSingleRessource(id); + } else { this.$store.commit("application/searchQuery", ""); this.loadLogs(); - } else { - this.showSingleRessource(id); } } }; </script> - -<style lang="scss" scoped> -.date { - font-stretch: condensed; -} -.interval-select { - padding: 0px; - margin: 0px; - font-size: 80%; -} -</style>
--- a/client/src/components/importoverview/LogDetail.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importoverview/LogDetail.vue Tue Apr 02 10:07:48 2019 +0200 @@ -1,5 +1,5 @@ <template> - <div class="border-top"> + <div> <div class="d-flex fex-row" style="padding-left: 3px;" @@ -71,7 +71,7 @@ </div> <AdditionalLog v-if="entry.id === showLogs" - class="mx-4 pb-1 d-flex flex-row" + class="d-flex flex-row" ></AdditionalLog> </div> </template>
--- a/client/src/components/importoverview/LogEntry.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importoverview/LogEntry.vue Tue Apr 02 10:07:48 2019 +0200 @@ -1,81 +1,71 @@ <template> - <div> - <div class="row no-gutters text-left"> - <div - style="width: 79px;" - class="table-cell d-flex justify-content-between" + <div class="row w-100 no-gutters text-left"> + <div style="width: 79px;" class="table-cell d-flex justify-content-between"> + <font-awesome-icon + @click="toggleDetails" + :class="'pointer ' + (entry.id === show ? 'text-white' : 'text-info')" + :icon="entry.id === show ? 'angle-down' : 'angle-right'" + fixed-width + ></font-awesome-icon> + {{ entry.id }} + </div> + <div style="width: 53px;" class="table-cell center"> + {{ entry.kind.toUpperCase() }} + </div> + <div style="width: 138px;" class="table-cell center"> + {{ entry.enqueued | dateTime }} + </div> + <div style="width: 105px;" class="table-cell truncate"> + {{ entry.user }} + </div> + <div style="width: 105px;" class="table-cell truncate"> + {{ entry.signer }} + </div> + <div style="width: 72px;" class="table-cell center"> + <span v-if="entry.state === 'failed'" class="text-danger"> + {{ entry.state }} + </span> + <span v-else>{{ entry.state }}</span> + </div> + <div style="width: 44px;" class="table-cell center"> + <font-awesome-icon + v-if="entry.warnings" + class="text-warning" + icon="exclamation-triangle" + fixed-width + ></font-awesome-icon> + </div> + <div style="flex-grow: 1; padding: 0;" class="table-cell text-right"> + <button + :class="['action approved', { active: isApproved }]" + @click="toggleApproval($options.STATES.APPROVED)" + v-if="entry.state === 'pending'" > <font-awesome-icon - @click="toggleDetails" - class="my-auto text-info pointer" - :icon="entry.id === show ? 'angle-down' : 'angle-right'" - fixed-width + class="small pointer" + icon="check" ></font-awesome-icon> - {{ entry.id }} - </div> - <div style="width: 53px;" class="table-cell text-center"> - {{ entry.kind.toUpperCase() }} - </div> - <div style="width: 138px;" class="table-cell text-center"> - {{ entry.enqueued | dateTime }} - </div> - <div style="width: 105px;" class="table-cell truncate"> - {{ entry.user }} - </div> - <div style="width: 105px;" class="table-cell truncate"> - {{ entry.signer }} - </div> - <div style="width: 72px;" class="table-cell text-center"> - <span v-if="entry.state === 'failed'" class="text-danger">{{ - entry.state - }}</span> - <span v-else>{{ entry.state }}</span> - </div> - <div style="width: 44px;" class="table-cell text-center"> + </button> + <button + :class="['action rejected', { active: isRejected }]" + @click="toggleApproval($options.STATES.REJECTED)" + v-if="entry.state === 'pending'" + > <font-awesome-icon - v-if="entry.warnings" - class="text-warning" - icon="exclamation-triangle" - fixed-width + icon="times" + class="small pointer" ></font-awesome-icon> - </div> - <div style="flex-grow: 1; padding: 0;" class="table-cell text-right"> - <div v-if="entry.state === 'pending'"> - <button - :class="['actions approved', { active: isApproved }]" - @click="toggleApproval($options.STATES.APPROVED)" - > - <font-awesome-icon - class="small pointer" - icon="check" - ></font-awesome-icon> - </button> - <button - :class="['actions rejected', { active: isRejected }]" - @click="toggleApproval($options.STATES.REJECTED)" - > - <font-awesome-icon - icon="times" - class="small pointer" - ></font-awesome-icon> - </button> - </div> - </div> + </button> </div> - <LogDetail - :entry="entry" - :details="details" - v-if="show === entry.id" - ></LogDetail> </div> </template> <style lang="sass" scoped> -.actions +.action height: 100% width: 50% border: 0 - background: transparent + background: white outline: none &.approved color: green @@ -90,6 +80,11 @@ &:hover color: white background: red +.active + .action + background-color: #d2eaee + &.rejected + border-left: solid 1px rgba(255, 255, 255, 0.3) </style> <script> @@ -112,9 +107,7 @@ import { HTTP } from "@/lib/http.js"; export default { - components: { - LogDetail: () => import("./LogDetail.vue") - }, + STATES, props: ["entry"], computed: { ...mapState("imports", ["show"]),
--- a/client/src/components/importschedule/Importschedule.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/importschedule/Importschedule.vue Tue Apr 02 10:07:48 2019 +0200 @@ -32,13 +32,16 @@ /> <UITableBody :data="filteredSchedules() | sortTable(sortColumn, sortDirection)" + :isActive="item => currentSchedule && item.id === currentSchedule.id" > <template v-slot:row="{ item: schedule }"> - <div class="py-1 col-1">{{ schedule.id }}</div> - <div class="py-1 col-2">{{ schedule.kind.toUpperCase() }}</div> - <div class="py-1 col-2">{{ schedule.user }}</div> - <div class="py-1 col-2">{{ schedule.config.cron }}</div> - <div class="py-1 col-2 text-center"> + <div class="table-cell col-1">{{ schedule.id }}</div> + <div class="table-cell col-2"> + {{ schedule.kind.toUpperCase() }} + </div> + <div class="table-cell col-2">{{ schedule.user }}</div> + <div class="table-cell col-2">{{ schedule.config.cron }}</div> + <div class="table-cell col-2 text-center"> <font-awesome-icon v-if="schedule.config['send-email']" class="fa-fw mr-2" @@ -46,7 +49,7 @@ icon="check" ></font-awesome-icon> </div> - <div class="py-1 col text-right"> + <div class="table-cell col justify-content-end"> <button @click="editSchedule(schedule.id)" class="btn btn-xs btn-dark mr-1" @@ -141,7 +144,11 @@ }, computed: { ...mapState("application", ["showSidebar"]), - ...mapState("importschedule", ["schedules", "importScheduleDetailVisible"]), + ...mapState("importschedule", [ + "schedules", + "currentSchedule", + "importScheduleDetailVisible" + ]), importScheduleLabel() { return this.$gettext("Import Schedule"); },
--- a/client/src/components/ui/SpinnerOverlay.vue Mon Apr 01 15:12:49 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -<template> - <transition name="fade"> - <div class="spinner-overlay"> - <font-awesome-icon icon="spinner" spin /> - </div> - </transition> -</template> - -<style lang="sass"> -.spinner-overlay - background: rgba(255, 255, 255, 0.9) - position: absolute - z-index: 99 - top: 0 - right: 0 - bottom: 0 - left: 0 - display: flex - align-items: center - justify-content: center - color: #888 -</style>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UISpinnerOverlay.vue Tue Apr 02 10:07:48 2019 +0200 @@ -0,0 +1,38 @@ +<template> + <transition name="fade"> + <div class="spinner-overlay"> + <font-awesome-icon icon="spinner" spin /> + </div> + </transition> +</template> + +<style lang="sass"> +.spinner-overlay + background: rgba(255, 255, 255, 0.9) + position: absolute + z-index: 99 + top: 0 + right: 0 + bottom: 0 + left: 0 + display: flex + align-items: center + justify-content: center + color: #888 +</style> + +<script> +/* This is Free Software under GNU Affero General Public License v >= 3.0 + * without warranty, see README.md and license for details. + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * License-Filename: LICENSES/AGPL-3.0.txt + * + * Copyright (C) 2018 by via donau + * – Österreichische Wasserstraßen-Gesellschaft mbH + * Software engineering by Intevation GmbH + * + * Author(s): + * Markus Kottländer <markus.kottlaender@intevation.de> + */ +</script>
--- a/client/src/components/ui/UITableBody.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/ui/UITableBody.vue Tue Apr 02 10:07:48 2019 +0200 @@ -7,12 +7,12 @@ <div v-for="(item, index) in data" :key="key(index)" - :class="['row-container border-top', { active: active === item }]" + :class="['row-container border-top', { active: isActive(item) }]" > <div class="row mx-0"> <slot :item="item" :index="index" name="row"></slot> </div> - <div class="expand" v-if="active === item"> + <div class="expand" v-if="isActive(item)"> <slot :item="item" :index="index" name="expand"></slot> </div> </div> @@ -27,14 +27,12 @@ .row-container > .row &:hover - background-color: #fafafa + background-color: #fcfcfc .table-cell display: flex align-items: center padding: 1.5px 3px border-right: solid 1px #dee2e6 - &:hover - background-color: #f2f2f2 &:last-child border-right: none &.center @@ -77,8 +75,9 @@ type: String, default: "18rem" }, - active: { - type: [Object, Array] + isActive: { + type: Function, + default: () => false } }, methods: {
--- a/client/src/components/usermanagement/Usermanagement.vue Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/components/usermanagement/Usermanagement.vue Tue Apr 02 10:07:48 2019 +0200 @@ -15,8 +15,8 @@ /> <UITableBody :data="users | sortTable(sortColumn, sortDirection, page, pageSize)" + :isActive="item => item === currentUser" maxHeight="47rem" - :active="currentUser" > <template v-slot:row="{ item: user }"> <div
--- a/client/src/main.js Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/main.js Tue Apr 02 10:07:48 2019 +0200 @@ -33,7 +33,7 @@ import UIBoxHeader from "@/components/ui/UIBoxHeader"; import UITableHeader from "@/components/ui/UITableHeader"; import UITableBody from "@/components/ui/UITableBody"; -import SpinnerOverlay from "@/components/ui/SpinnerOverlay"; +import UISpinnerOverlay from "@/components/ui/UISpinnerOverlay"; // styles import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; @@ -179,7 +179,7 @@ Vue.component("UIBoxHeader", UIBoxHeader); Vue.component("UITableHeader", UITableHeader); Vue.component("UITableBody", UITableBody); -Vue.component("SpinnerOverlay", SpinnerOverlay); +Vue.component("UISpinnerOverlay", UISpinnerOverlay); // register global filters for (let name in filters) Vue.filter(name, filters[name]);
--- a/client/src/store/imports.js Mon Apr 01 15:12:49 2019 +0200 +++ b/client/src/store/imports.js Tue Apr 02 10:07:48 2019 +0200 @@ -17,16 +17,12 @@ import { equalTo as equalToFilter } from "ol/format/filter.js"; import { startOfHour, endOfHour } from "date-fns"; -/* eslint-disable no-unused-vars */ -/* eslint-disable no-unreachable */ const STATES = { NEEDSAPPROVAL: "pending", APPROVED: "accepted", REJECTED: "declined" }; -const NODETAILS = -1; - // initial state const init = () => { return { @@ -38,9 +34,9 @@ stretches: [], imports: [], reviewed: [], - show: NODETAILS, - showAdditional: NODETAILS, - showLogs: NODETAILS, + show: null, + showAdditional: null, + showLogs: null, details: [], startDate: startOfHour(new Date()), endDate: endOfHour(new Date()), @@ -158,19 +154,19 @@ state.show = id; }, hideDetails: state => { - state.show = NODETAILS; + state.show = null; }, showAdditionalInfoFor: (state, id) => { state.showAdditional = id; }, hideAdditionalInfo: state => { - state.showAdditional = NODETAILS; + state.showAdditional = false; }, showAdditionalLogsFor: (state, id) => { state.showLogs = id; }, hideAdditionalLogs: state => { - state.showLogs = NODETAILS; + state.showLogs = false; }, toggleApprove: (state, change) => { const { id, newStatus } = change; @@ -192,7 +188,7 @@ } }, actions: { - loadStretch({ commit }, name) { + loadStretch(context, name) { return new Promise((resolve, reject) => { getStretchFromWFS(equalToFilter("name", name)) .then(response => { @@ -219,7 +215,7 @@ }); }); }, - saveStretch({ commit }, stretch) { + saveStretch(context, stretch) { return new Promise((resolve, reject) => { HTTP.post("/imports/st", stretch, { headers: { "X-Gemma-Auth": localStorage.getItem("token") } @@ -271,7 +267,7 @@ }); }); }, - confirmReview({ state }, reviewResults) { + confirmReview(context, reviewResults) { return new Promise((resolve, reject) => { HTTP.patch("/imports", reviewResults, { headers: {
--- a/pkg/common/nashsutcliffe.go Mon Apr 01 15:12:49 2019 +0200 +++ b/pkg/common/nashsutcliffe.go Tue Apr 02 10:07:48 2019 +0200 @@ -14,6 +14,7 @@ package common import ( + "math" "sort" "time" ) @@ -24,6 +25,10 @@ Observed float64 } +func (m NSMeasurement) Valid() bool { + return !m.When.IsZero() && !math.IsNaN(m.Predicted) && !math.IsNaN(m.Observed) +} + func NashSutcliffeSort(measurements []NSMeasurement) { sort.Slice(measurements, func(i, j int) bool { return measurements[i].When.Before(measurements[j].When)
--- a/pkg/controllers/gauges.go Mon Apr 01 15:12:49 2019 +0200 +++ b/pkg/controllers/gauges.go Tue Apr 02 10:07:48 2019 +0200 @@ -18,6 +18,7 @@ "encoding/csv" "fmt" "log" + "math" "net/http" "sort" "strconv" @@ -36,13 +37,10 @@ const ( selectPredictedObserveredSQL = ` SELECT - a.measure_date AS measure_date, - a.water_level AS predicted, - b.water_level AS observed -FROM waterway.gauge_measurements a JOIN waterway.gauge_measurements b - ON a.fk_gauge_id = b.fk_gauge_id AND - a.measure_date = b.measure_date AND - a.predicted AND NOT b.predicted + measure_date, + predicted, + water_level +FROM waterway.gauge_measurements WHERE a.fk_gauge_id = ( $1::char(2), @@ -51,11 +49,10 @@ $4::char(5), $5::int ) AND - a.measure_date BETWEEN - $6::timestamp AND $6::timestamp - '72hours'::interval -ORDER BY a.measure_date + measure_date BETWEEN + $6::timestamp - '72hours'::interval AND $6::timestamp +ORDER BY measure_date, date_issue ` - selectWaterlevelsSQL = ` SELECT measure_date, @@ -457,21 +454,46 @@ var measurements []common.NSMeasurement + invalid := common.NSMeasurement{ + Predicted: math.NaN(), + Observed: math.NaN(), + } + current := invalid + for rows.Next() { - var m common.NSMeasurement + var ( + when time.Time + predicted bool + value float64 + ) if err = rows.Scan( - &m.When, - &m.Predicted, - &m.Observed, + &when, + &predicted, + &value, ); err != nil { return } - measurements = append(measurements, m) + if !when.Equal(current.When) { + if current.Valid() { + measurements = append(measurements, current) + } + current = invalid + } + if predicted { + current.Predicted = value + } else { + current.Observed = value + } } + if err = rows.Err(); err != nil { return } + if current.Valid() { + measurements = append(measurements, current) + } + type coeff struct { Value float64 `json:"value"` Samples int `json:"samples"`