From 8f6d27b6dce9177348ca8752fba0b7b0e46e1852 Mon Sep 17 00:00:00 2001 From: Arild Matsson Date: Fri, 13 Dec 2024 17:11:24 +0100 Subject: [PATCH] Use korpRequest in KwicProxy --- app/scripts/backend/base-proxy.ts | 9 +++- app/scripts/backend/common.ts | 16 +++++-- app/scripts/backend/kwic-proxy.ts | 46 +++++++++---------- app/scripts/backend/types/index.ts | 5 ++ app/scripts/components/results.ts | 1 + app/scripts/controllers/example_controller.ts | 4 +- app/scripts/controllers/kwic_controller.ts | 28 +++++------ app/scripts/util.ts | 2 +- 8 files changed, 63 insertions(+), 48 deletions(-) diff --git a/app/scripts/backend/base-proxy.ts b/app/scripts/backend/base-proxy.ts index f3d21450..9371bb4f 100644 --- a/app/scripts/backend/base-proxy.ts +++ b/app/scripts/backend/base-proxy.ts @@ -12,7 +12,9 @@ export default abstract class BaseProxy { progress: number total: number | null total_results: number | null + // TODO When Axios is used in all (abortable) subclasses, remove `pendingRequests` pendingRequests: JQuery.jqXHR[] + abortControllers: AbortController[] = [] constructor() { this.prev = "" @@ -43,15 +45,20 @@ export default abstract class BaseProxy { abort(): void { this.pendingRequests.forEach((req) => req.abort()) + this.abortControllers.forEach((controller) => controller.abort()) this.cleanup() } cleanup(): void { + this.abortControllers = [] this.prev = "" } hasPending(): boolean { - return _.some(_.map(this.pendingRequests, (req) => req.readyState !== 4 && req.readyState !== 0)) + return ( + _.some(_.map(this.pendingRequests, (req) => req.readyState !== 4 && req.readyState !== 0)) || + this.abortControllers.length > 0 + ) } /** Try to parse partial JSON data (of an in-progress HTTP response). */ diff --git a/app/scripts/backend/common.ts b/app/scripts/backend/common.ts index 15554e3d..a4bbda9f 100644 --- a/app/scripts/backend/common.ts +++ b/app/scripts/backend/common.ts @@ -3,16 +3,24 @@ import { axiosConfAddMethod } from "@/util" import { getAuthorizationHeader } from "@/components/auth/auth" import settings from "@/settings" import { API, ErrorMessage, Response, ResponseBase } from "./types" -import axios from "axios" +import axios, { AxiosProgressEvent } from "axios" + +type KorpRequestOptions = { + abortSignal?: AbortSignal + onProgress?: (event: AxiosProgressEvent) => void +} export async function korpRequest( endpoint: K, - params: API[K]["params"] + params: API[K]["params"], + options?: KorpRequestOptions ): Promise { const conf = axiosConfAddMethod({ - url: settings.korp_backend_url + "/" + endpoint, + url: korpEndpointUrl(endpoint), params, headers: getAuthorizationHeader(), + onDownloadProgress: options?.onProgress, + signal: options?.abortSignal, }) const response = await axios.request>(conf) const data = response.data @@ -25,6 +33,8 @@ export async function korpRequest( return data } +export const korpEndpointUrl = (endpoint: keyof API): string => settings.korp_backend_url + "/" + endpoint + export class KorpBackendError extends Error { constructor(public readonly message: string, public readonly details: string) { super(message) diff --git a/app/scripts/backend/kwic-proxy.ts b/app/scripts/backend/kwic-proxy.ts index 03fcdef0..035cd876 100644 --- a/app/scripts/backend/kwic-proxy.ts +++ b/app/scripts/backend/kwic-proxy.ts @@ -2,10 +2,10 @@ import _ from "lodash" import settings from "@/settings" import BaseProxy from "@/backend/base-proxy" -import { locationSearchGet, Factory, ajaxConfAddMethod } from "@/util" +import { locationSearchGet, Factory, buildUrl } from "@/util" import { ProgressReport, Response } from "./types" import { QueryParams, QueryResponse } from "./types/query" -import { AjaxSettings } from "@/jquery.types" +import { korpEndpointUrl, korpRequest } from "./common" export class KwicProxy extends BaseProxy { prevCQP?: string @@ -19,12 +19,12 @@ export class KwicProxy extends BaseProxy { this.prevParams = null } - makeRequest( + async makeRequest( options: KorpQueryRequestOptions, page: number | undefined, progressCallback: (data: ProgressReport) => void, kwicCallback: (data: Response) => void - ): JQuery.jqXHR> { + ): Promise { const self = this this.resetRequest() if (!kwicCallback) { @@ -90,38 +90,34 @@ export class KwicProxy extends BaseProxy { } this.prevParams = data - const ajaxSettings = { - url: settings.korp_backend_url + "/" + command, - data: data, - beforeSend(req, settings) { - self.addAuthorizationHeader(req) - self.prevUrl = settings.url - }, + this.prevUrl = buildUrl(korpEndpointUrl("query"), data) - success(data: QueryResponse, status, jqxhr) { - self.queryData = data.query_data - self.cleanup() - // Run the callback to show results, if not already done by the progress handler - if (!this.foundKwic) kwicCallback(data) - }, + const abortController = new AbortController() + this.abortControllers.push(abortController) + let foundKwic = false - progress(jqxhr, e: ProgressEvent) { + const request = korpRequest(command, data, { + abortSignal: abortController.signal, + onProgress(e) { // Calculate progress, used for progress bars - const progressObj = self.calcProgress(e) + const progressObj = self.calcProgress(e.event!) progressCallback(progressObj) // Show current page of results if they are available // The request may continue to count hits in the background if ("kwic" in progressObj.struct) { - this.foundKwic = true + foundKwic = true kwicCallback(progressObj.struct as QueryResponse) } }, - } satisfies AjaxSettings - - const def = $.ajax(ajaxConfAddMethod(ajaxSettings)) as JQuery.jqXHR> - this.pendingRequests.push(def) - return def + }) + + const result = await request + self.queryData = result.query_data + self.cleanup() + // Run the callback to show results, if not already done by the progress handler + if (!foundKwic) kwicCallback(result) + return result } } diff --git a/app/scripts/backend/types/index.ts b/app/scripts/backend/types/index.ts index 554116d0..1188fe88 100644 --- a/app/scripts/backend/types/index.ts +++ b/app/scripts/backend/types/index.ts @@ -41,4 +41,9 @@ export type API = { params: QueryParams response: QueryResponse } + relations_sentences: { + // The params actually differ slightly, but they are merged in QueryParams + params: QueryParams + response: QueryResponse + } } diff --git a/app/scripts/components/results.ts b/app/scripts/components/results.ts index ce1c8010..2dbc0808 100644 --- a/app/scripts/components/results.ts +++ b/app/scripts/components/results.ts @@ -281,6 +281,7 @@ angular.module("korpApp").component("results", { + diff --git a/app/scripts/controllers/example_controller.ts b/app/scripts/controllers/example_controller.ts index e0fc4873..a21f43ac 100644 --- a/app/scripts/controllers/example_controller.ts +++ b/app/scripts/controllers/example_controller.ts @@ -21,7 +21,7 @@ type ExampleCtrlScope = ScopeBase & { hitsPictureData?: any hitspictureClick?: (page: number) => void kwicTab: KwicTab - makeRequest: (isPaging?: boolean) => JQuery.jqXHR> + makeRequest: (isPaging?: boolean) => Promise onExampleProgress: (progressObj: ProgressReport, isPaging?: boolean) => void setupReadingWatch: () => void superRenderResult: (data: Response) => void @@ -145,7 +145,7 @@ class ExampleCtrl extends KwicCtrl { } ) - def.fail(() => { + def.catch(() => { $timeout(() => { // TODO it could be abort s.error = true diff --git a/app/scripts/controllers/kwic_controller.ts b/app/scripts/controllers/kwic_controller.ts index 097d5856..ae962ca1 100644 --- a/app/scripts/controllers/kwic_controller.ts +++ b/app/scripts/controllers/kwic_controller.ts @@ -1,6 +1,7 @@ /** @format */ import angular, { IController, ITimeoutService } from "angular" import _ from "lodash" +import { CanceledError } from "axios" import settings from "@/settings" import kwicProxyFactory, { type KwicProxy } from "@/backend/kwic-proxy" import { ApiKwic, Response, ProgressReport } from "@/backend/types" @@ -209,8 +210,9 @@ export class KwicCtrl implements IController { s.loading = true s.aborted = false - - s.ignoreAbort = Boolean(s.proxy.hasPending()) + // If this request aborts a previous request, flag to abort it silently + // TODO Aborting the new request should not also be silent + s.ignoreAbort = s.proxy.hasPending() const ajaxParams = s.buildQueryOptions(isPaging) @@ -220,27 +222,21 @@ export class KwicCtrl implements IController { (progressObj) => $timeout(() => s.onProgress(progressObj, isPaging)), (data) => $timeout(() => s.renderResult(data)) ) - req.done((data: Response) => { + + req.then((data: QueryResponse) => { $timeout(() => { s.loading = false s.renderCompleteResult(data, isPaging) }) }) - req.fail((jqXHR, status, errorThrown) => { - $timeout(() => { - console.log("kwic fail") - if (s.ignoreAbort) { - console.log("stats ignoreabort") - return - } + req.catch((err) => { + // If next request is already started, do nothing for this previous one + if (s.ignoreAbort) return + s.$applyAsync(() => { s.loading = false - - if (status === "abort") { - s.aborted = true - } else { - s.error = true - } + if (err instanceof CanceledError) s.aborted = true + else s.error = true }) }) } diff --git a/app/scripts/util.ts b/app/scripts/util.ts index 61ff5350..f0b3bd3d 100644 --- a/app/scripts/util.ts +++ b/app/scripts/util.ts @@ -454,7 +454,7 @@ export function fetchConfAddMethod(url: string, params: Record): { return { url: buildUrl(url, params), request: {} } } -export function axiosConfAddMethod(conf: AxiosRequestConfig & { url: string }): AxiosRequestConfig { +export function axiosConfAddMethod(conf: AxiosRequestConfig & { url: string }): AxiosRequestConfig & { url: string } { // Like $http, Axios uses `data` for POST but `params` for GET. conf.method = selectHttpMethod(conf.url, conf.params || {}) if (conf.method == "POST") {