Web: use wrapper for the OpenAPI client to track requests

A wrapper for the generated `ApiClient` class tracks the number of running
queries. This makes it much simpler to show the "API calls pending" UI
element, regardless of which part of the webapp performs the queries.
This commit is contained in:
Sybren A. Stüvel 2022-04-22 11:46:33 +02:00
parent fc68e8a413
commit 4129ed11bb
7 changed files with 74 additions and 32 deletions

View File

@ -1,19 +1,19 @@
<template>
<header>{{ flamencoName }}</header>
<header class="right">
<api-spinner :numRunningQueries="numRunningQueries" />
<api-spinner />
version: {{ flamencoVersion }}
</header>
<div class="col-1">
<jobs-table ref="jobsTable" :apiClient="apiClient" @selectedJobChange="onSelectedJobChanged" />
<jobs-table ref="jobsTable" @selectedJobChange="onSelectedJobChanged" />
</div>
<div class="col-2">
<job-details :apiClient="apiClient" :jobData="selectedJob" />
<job-details :jobData="selectedJob" />
</div>
<div class="col-3">
<task-details :apiClient="apiClient" />
<task-details />
</div>
<footer>Footer
<footer>
<span class='notifications' v-if="notifs.last">{{ notifs.last.msg }}</span>
<update-listener ref="updateListener" :websocketURL="websocketURL" @jobUpdate="onSioJobUpdate"
@message="onChatMessage" @sioReconnected="onSIOReconnected" @sioDisconnected="onSIODisconnected" />
@ -25,6 +25,7 @@ import * as urls from '@/urls'
import * as API from '@/manager-api';
import { useJobs } from '@/stores/jobs';
import { useNotifs } from '@/stores/notifications';
import { apiClient } from '@/stores/api-query-count';
import ApiSpinner from '@/components/ApiSpinner.vue'
import JobsTable from '@/components/JobsTable.vue'
@ -41,7 +42,6 @@ export default {
ApiSpinner, JobsTable, JobDetails, TaskDetails, UpdateListener,
},
data: () => ({
apiClient: new API.ApiClient(urls.api()),
websocketURL: urls.ws(),
messages: [],
@ -50,8 +50,6 @@ export default {
flamencoName: DEFAULT_FLAMENCO_NAME,
flamencoVersion: DEFAULT_FLAMENCO_VERSION,
numRunningQueries: 0,
}),
mounted() {
window.app = this;
@ -68,8 +66,8 @@ export default {
return;
}
const jobsAPI = new API.JobsApi(this.apiClient);
this._wrap(jobsAPI.fetchJob(jobSummary.id))
const jobsAPI = new API.JobsApi(apiClient);
jobsAPI.fetchJob(jobSummary.id)
.then((job) => {
this.jobs.setSelectedJob(job);
// Forward the full job to Tabulator, so that that gets updated too.
@ -122,21 +120,12 @@ export default {
this.flamencoVersion = DEFAULT_FLAMENCO_VERSION;
},
fetchManagerInfo() {
const metaAPI = new API.MetaApi(this.apiClient);
const metaAPI = new API.MetaApi(apiClient);
metaAPI.getVersion().then((version) => {
this.flamencoName = version.name;
this.flamencoVersion = version.version;
})
},
// Wrap a Flamenco API promise, to keep track of how many queries are running.
// This is just a test to see how this works, not a final functional design.
_wrap(promise) {
this.numRunningQueries++;
return promise.finally(() => {
this.numRunningQueries--;
});
},
},
}
</script>

View File

@ -1,10 +1,14 @@
<template>
<span class="api-spinner" :class="{ running: numRunningQueries > 0 }">&#128008;</span>
<span class="api-spinner" :class="{ running: apiQueryCount.num > 0 }">&#128008;</span>
</template>
<script>
import { useAPIQueryCount } from '@/stores/api-query-count';
export default {
props: ["numRunningQueries"],
data: () => ({
apiQueryCount: useAPIQueryCount(),
}),
};
</script>

View File

@ -68,6 +68,7 @@
<script lang="js">
import * as datetime from "@/datetime";
import * as API from '@/manager-api';
import { apiClient } from '@/stores/api-query-count';
function objectEmpty(o) {
if (!o) return true;
@ -77,14 +78,13 @@ window.objectEmpty = objectEmpty;
export default {
props: [
"apiClient", // Flamenco Manager API client.
"jobData", // Job data to show.
],
data() {
return {
datetime: datetime, // So that the template can access it.
simpleSettings: null, // Object with filtered job settings, or null if there is no job.
jobsApi: new API.JobsApi(this.apiClient),
jobsApi: new API.JobsApi(apiClient),
jobType: null, // API.AvailableJobType object for the current job type.
jobTypeSettings: null, // Mapping from setting key to its definition in the job type.
showAllSettings: false,

View File

@ -9,11 +9,12 @@
import { TabulatorFull as Tabulator } from 'tabulator-tables';
import * as datetime from "@/datetime";
import * as API from '@/manager-api'
import { apiClient } from '@/stores/api-query-count';
import JobActionsBar from '@/components/JobActionsBar.vue'
export default {
emits: ["selectedJobChange"],
props: ["apiClient"],
components: {
JobActionsBar,
},
@ -71,10 +72,7 @@ export default {
tab.setSort(tab.getSorters()); // This triggers re-sorting.
},
fetchAllJobs() {
if (!this.apiClient) {
throw "no apiClient set on JobsTable component";
}
const jobsApi = new API.JobsApi(this.apiClient);
const jobsApi = new API.JobsApi(apiClient);
const jobsQuery = {};
jobsApi.queryJobs(jobsQuery).then(this.onJobsFetched, function (error) {
// TODO: error handling.
@ -127,6 +125,11 @@ export default {
.job-list-container {
font-family: 'Noto Mono', monospace;
font-size: smaller;
height: calc(100% - var(--action-bar-height));
height: calc(100% - var(--action-bar-height) - 2em);
}
.job-list {
outline: 2px solid lime;
outline-offset: -1px;
}
</style>

View File

@ -6,7 +6,6 @@
<script lang="js">
export default {
props: ["apiClient"],
data: () => {
return {
};

View File

@ -0,0 +1,46 @@
import { defineStore } from "pinia";
import * as API from "@/manager-api";
import * as urls from '@/urls'
/**
* Keep track of running API queries.
*/
export const useAPIQueryCount = defineStore("apiQueryCount", {
state: () => ({
/**
* Number of running queries.
*/
num: 0,
}),
actions: {
/**
* Track this promise, counting it as a query for the spinner.
* @param {Promise} promise
*/
async track(promise) {
this.num++;
try {
return await promise;
} finally {
this.num--;
}
},
},
});
export class CountingApiClient extends API.ApiClient {
callApi(path, httpMethod, pathParams, queryParams, headerParams, formParams,
bodyParam, authNames, contentTypes, accepts, returnType, apiBasePath ) {
const apiQueryCount = useAPIQueryCount();
apiQueryCount.num++;
return super
.callApi(path, httpMethod, pathParams, queryParams, headerParams, formParams,
bodyParam, authNames, contentTypes, accepts, returnType, apiBasePath)
.finally(() => {
apiQueryCount.num--;
});
}
}
export const apiClient = new CountingApiClient(urls.api());;

View File

@ -2,8 +2,9 @@ import { defineStore } from 'pinia'
import * as urls from '@/urls'
import * as API from '@/manager-api';
import { apiClient } from '@/stores/api-query-count';
const apiClient = new API.ApiClient(urls.api());
const jobsAPI = new API.JobsApi(apiClient);
// 'use' prefix is idiomatic for Pinia stores.