Repository: GEOLYTIX/xyz
Pull request #2054: Ping Util
Author: login: simon-leech
Labels: name: Feature
description: New feature requests or changes to the behaviour or look of existing application features.
Created: 2025-04-11T13:55:15Z | Merged: 2025-04-24T08:08:37Z

# Ping Utility

## Description

  This function will ping the database running a specific query.
  The query will return a response, and this response will be checked against the successCondition and errorCondition if provided.
A `responseType` is provided to ensure the condition is evaluated properly. 
If the value of the response is the successCondition, the promise resolves.
If the value of the response is failCondition the promise rejects. 
If a callback is provided, this is completed.
If callback is set to false, the ping utility will return before evaluating the condition (ie stop). 

``` js
mapp.utils.ping({
  query: 'location_field_value',
  responseType: 'text',
  successCondition: 'ok',
  failCondition: 'failed',
  queryparams: {
    id: true,
    field: 'model_status',
    table: 'location_table',
  },
  interval: 4000,
  callback: (response) => {
    console.log(response);
  },
});
```
## GitHub Issue

[Provide the link to the relevant GitHub issue it addresses.](https://github.com/GEOLYTIX/xyz/issues/1968)



Diff:
diff --git a/lib/ui/locations/entries/_entries.mjs b/lib/ui/locations/entries/_entries.mjs
index 700dbcaee6..d36245e420 100644
--- a/lib/ui/locations/entries/_entries.mjs
+++ b/lib/ui/locations/entries/_entries.mjs
@@ -15,6 +15,7 @@ Exports a collection of entry methods for location views.
 @requires /ui/locations/entries/numeric
 @requires /ui/locations/entries/pills
 @requires /ui/locations/entries/pin
+@requires /ui/locations/entries/ping
 @requires /ui/locations/entries/query_button
 @requires /ui/locations/entries/tab
 @requires /ui/locations/entries/text
@@ -68,6 +69,8 @@ import pills from './pills.mjs';
 
 import pin from './pin.mjs';
 
+import ping from './ping.mjs';
+
 import query_button from './query_button.mjs';
 
 import tab from './tab.mjs';
@@ -136,6 +139,7 @@ export default {
   numeric,
   pills,
   pin,
+  ping,
   query_button,
   report: link,
   tab,
diff --git a/lib/ui/locations/entries/ping.mjs b/lib/ui/locations/entries/ping.mjs
new file mode 100644
index 0000000000..b64ca1660d
--- /dev/null
+++ b/lib/ui/locations/entries/ping.mjs
@@ -0,0 +1,41 @@
+/**
+## /ui/locations/entries/ping
+
+The ping module exports an entry method to test the ping utility.
+
+@module /ui/locations/entries/ping
+*/
+
+/**
+@function ping
+
+@description
+The ping method creates a text field and initiates the ping utility with the entry object as params.
+
+The ping callback method will update a textarea input with the response.
+
+The ping will be cancelled by setting the callback to false.
+
+@param {infoj-entry} entry type:ping entry.
+
+@return {HTMLElement} The entry node containing a textarea input.
+*/
+export default function ping(entry) {
+  if (!entry.textarea) {
+    entry.textarea = mapp.utils.html.node`<textarea
+      class="val"
+      style="auto; min-height: 50px;">`;
+
+    entry.callback ??= async (response, entry) => {
+      entry.textarea.value = response;
+    };
+
+    mapp.utils.ping(entry);
+
+    entry.location.removeCallbacks.push(() => {
+      entry.callback = false;
+    });
+  }
+
+  return entry.textarea;
+}
diff --git a/lib/utils/_utils.mjs b/lib/utils/_utils.mjs
index 7b850c1f54..5ed423b08e 100644
--- a/lib/utils/_utils.mjs
+++ b/lib/utils/_utils.mjs
@@ -59,6 +59,8 @@ import olScript from './olScript.mjs';
 
 import paramString from './paramString.mjs';
 
+import ping from './ping.mjs';
+
 import { polygonIntersectFeatures } from './polygonIntersectFeatures.mjs';
 
 import promiseAll from './promiseAll.mjs';
@@ -116,6 +118,7 @@ export default {
   mobile,
   olScript,
   paramString,
+  ping,
   polygonIntersectFeatures,
   promiseAll,
   queryParams,
diff --git a/lib/utils/ping.mjs b/lib/utils/ping.mjs
new file mode 100644
index 0000000000..06cc36a01e
--- /dev/null
+++ b/lib/utils/ping.mjs
@@ -0,0 +1,103 @@
+/**
+## /utils/ping
+
+The ping utility module exports a method to repeat a ping query until a condition is met.
+
+@module /utils/ping
+*/
+
+/**
+@function ping 
+  
+@description
+The ping utility method executes the pingQuery method which will repeat at a given interval until a condition is met.
+
+The ping method will create a request url for the ping query from the provided queryparams object.
+
+``` js
+mapp.utils.ping({
+  query: 'location_field_value',
+  responseType: 'text',
+  successCondition: 'ok',
+  failCondition: 'failed',
+  queryparams: {
+    id: true,
+    field: 'model_status',
+    table: 'location_table',
+  },
+  interval: 4000,
+  callback: (response) => {
+    console.log(response);
+  },
+});
+```
+
+@param {Object} params The parameters object.
+@property {string} params.query The query template for the ping query.
+@property {Object} params.queryparams Parameters for the ping query.
+*/
+export default function ping(params) {
+  if (!params.query) {
+    console.warn(`A query parameter must be defined for the ping utility.`);
+    return;
+  }
+
+  // Default interval to 4 seconds
+  params.interval ??= 4000;
+
+  params.host ??= mapp.host;
+
+  const queryParams = mapp.utils.queryParams(params);
+
+  const paramString = mapp.utils.paramString(queryParams);
+
+  params.url = `${mapp.host}/api/query?${paramString}`;
+
+  pingQuery(params);
+}
+
+/**
+@function pingQuery 
+@async
+  
+@description
+The pingQuery method will call itself with a timeout until a condition is met.
+
+The method will shortcircuit if the query fails.
+
+The method will shortcircuit if the callback property is set to false.
+
+If the callback property is a function it will be called with the response and the params object itself as arguments.
+
+The method will shortcircuit if the reponse matches either the failCondition or the successCondition.
+
+@param {Object} params The parameters object.
+@property {string} params.responseType The expected responseType form the ping query.
+@property {string} params.url The request url for the ping query.
+@property {function} params.callback Method to be executed with the response and params object as arguments.
+*/
+async function pingQuery(params) {
+  const response = await mapp.utils.xhr(params);
+
+  if (response instanceof Error) {
+    return;
+  }
+
+  if (params.callback instanceof Function) {
+    params.callback(response, params);
+  }
+
+  if (params.callback === false) {
+    return;
+  }
+
+  if (params.failCondition === response) {
+    return;
+  }
+
+  if (params.successCondition === response) {
+    return;
+  }
+
+  setTimeout(() => pingQuery(params), params.interval);
+}
diff --git a/mod/workspace/templates/_queries.js b/mod/workspace/templates/_queries.js
index 28a18c322d..d4e1958d54 100644
--- a/mod/workspace/templates/_queries.js
+++ b/mod/workspace/templates/_queries.js
@@ -29,6 +29,7 @@ import locationsDelete from './locations_delete.js';
 import locationUpdate from './location_update.js';
 import sql_table_insert from './sql_table_insert.js';
 import locationCount from './location_count.js';
+import locationFieldValue from './location_field_value.js';
 import mvt from './mvt.js';
 import mvtGeom from './mvt_geom.js';
 
@@ -130,6 +131,11 @@ export default {
     template: locationCount,
     value_only: true,
   },
+  location_field_value: {
+    layer: true,
+    render: locationFieldValue,
+    value_only: true,
+  },
   mvt: {
     layer: true,
     render: mvt,
diff --git a/mod/workspace/templates/location_field_value.js b/mod/workspace/templates/location_field_value.js
new file mode 100644
index 0000000000..5ba6e0f677
--- /dev/null
+++ b/mod/workspace/templates/location_field_value.js
@@ -0,0 +1,11 @@
+/**
+### /workspace/templates/location_field_value
+
+The location_field_value query returns the value of a single field from a table where the location qID matches the provided id param.
+*/
+export default (_) => {
+  return `
+  SELECT \${field}
+  FROM \${table}
+  WHERE ${_.layer.qID} = %{id}`;
+};
diff --git a/tests/lib/ui/locations/entries/boolean.test.mjs b/tests/lib/ui/locations/entries/boolean.test.mjs
deleted file mode 100644
index 47e8304742..0000000000
--- a/tests/lib/ui/locations/entries/boolean.test.mjs
+++ /dev/null
@@ -1,23 +0,0 @@
-import { it, assertEqual } from 'codi';
-export async function booleanTest() {
-  it('Should return a checkbox', () => {
-    const entry = {
-      title: 'title',
-      field: 'booleanField',
-      inline: true,
-      value: false,
-    };
-    const checkboxElement = mapp.ui.locations.entries.boolean(entry);
-
-    assertEqual(
-      checkboxElement.className,
-      'link-with-img',
-      'The Checkbox element should have a class of link-with-img',
-    );
-    assertEqual(
-      checkboxElement.children[0].className,
-      'mask-icon close',
-      'The checbox should have a first child with a close class',
-    );
-  });
-}


Reviews:
user: login: RobAndrewHurst
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T10:11:54Z
user: login: dbauszus-glx
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T10:22:30Z
user: login: AlexanderGeere
body: This is working as expected. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T14:04:05Z
user: login: RobAndrewHurst
body: All happy with this!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T07:54:47Z

Review comments:
diff_hunk: @@ -1,273 +0,0 @@
-//Assigning mapp.utils.xhr to a property to restore original function with.
path: tests/lib/utils/ping.test.mjs
user: login: RobAndrewHurst
body: Why were these tests removed?
created_at: 2025-04-23T10:11:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,273 +0,0 @@
-//Assigning mapp.utils.xhr to a property to restore original function with.
path: tests/lib/utils/ping.test.mjs
user: login: dbauszus-glx
body: Because they didn't test the ping utility logic.
created_at: 2025-04-23T10:22:30Z
author_association: MEMBER

PR comments:
user: login: dbauszus-glx
created_at: 2025-04-11T14:45:44Z
body: @simon-leech 

params.query is required. The method should shortcircuit with a warning if the query property is undefined.

params.field and any property required for the query should be added to queryparams.

I do not understand the stages. The ping should terminate with an error thrown if the ping query errs.

I would just provide a property success condition. The response is compared to the success condition value. If the successCondition matches the response then the callback is called with the response as param.
author_association: MEMBER
user: login: cityremade
created_at: 2025-04-22T11:49:51Z
body: Most of the time we expect the status information to be updated by the model procedure. 
However at times it might be needed to update the field based on status value returned from ping. 
Could it be included here? updating the field based on returned status?
Or could it be a separate utility?
author_association: MEMBER
user: login: simon-leech
created_at: 2025-04-22T13:08:12Z
body: > Most of the time we expect the status information to be updated by the model procedure. However at times it might be needed to update the field based on status value returned from ping. Could it be included here? updating the field based on returned status? Or could it be a separate utility?

@cityremade I think this should be a separate utility if we think it's widely used enough to facilitate this.
The callback in the ping utility can return you the status, so you'll have access to it once the ping completes. So you could then pass this to a query for example in a custom plugin.
author_association: CONTRIBUTOR
user: login: cityremade
created_at: 2025-04-22T14:28:05Z
body: <img width="620" alt="Screenshot 2025-04-22 at 16 27 23" src="https://github.com/user-attachments/assets/97df8487-a36a-4716-abe6-cc13b142289a" />

I cannot get this working with a button where a click would ping a table. I am getting the above if I use my own query or the default one. The error is inside getTemplate().
author_association: MEMBER
user: login: dbauszus-glx
created_at: 2025-04-23T10:06:43Z
body: The ping utility method does not require to be async and return a method.

The async pingQuery method will be called in a timeout with the provided interval property.

The method will call the callback which can be used to cancel the execution.

The params object is provided to the XHR utility which automatically cancel a running query.

I created a simple ping entry method which shows how to cancel the ping execution when a location is removed.
author_association: MEMBER
user: login: sonarqubecloud[bot]
created_at: 2025-04-23T10:10:47Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=GEOLYTIX_xyz&pullRequest=2054) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=GEOLYTIX_xyz&pullRequest=2054&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=GEOLYTIX_xyz&pullRequest=2054&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=GEOLYTIX_xyz&pullRequest=2054&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=GEOLYTIX_xyz&pullRequest=2054&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=GEOLYTIX_xyz&pullRequest=2054&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=GEOLYTIX_xyz&pullRequest=2054)


author_association: NONE
user: login: simon-leech
created_at: 2025-04-24T08:08:24Z
body: I can't review this as its my PR - but i'm happy with the changes that have been made so all good to go! 🥳
author_association: CONTRIBUTOR