Hi
I have a table need select some row and get values of all rows, when add row="true" to my table qaction didn't work, and the table is empty but when delete this parameter row="true" table work ok
<QAction id="106"name="services"encoding="csharp" row="true" triggers="105"> - table no work
<QAction id="106"name="services"encoding="csharp" triggers="105"> - table work
this table is trigger by timer
Thanks
Hi Joshua,
- Here is QAction, this populate my table :
using System;
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
using Skyline.DataMiner.Scripting;
public class TimeoutWebClient : WebClient
{
publicint Timeout { get; set; }
publicTimeoutWebClient(int timeout)
{
Timeout = timeout;
}
protectedoverride WebRequest GetWebRequest(Uri address)
{
var request = base.GetWebRequest(address);
if (request != null)
{
request.Timeout = Timeout;
if (request is HttpWebRequest httpRequest)
{
httpRequest.ReadWriteTimeout = Timeout;
}
}
return request;
}
}
public class QAction
{
publicclassServicio
{
[JsonProperty("UID")]publicstring Uid { get; set; }
[JsonProperty("Name")]publicstring Name { get; set; }
[JsonProperty("Device")]public DeviceInfo Device { get; set; }
[JsonProperty("State")]public StateInfo State { get; set; }
}
publicclassDeviceInfo
{
[JsonProperty("Template")]public TemplateInfo Template { get; set; }
}
publicclassTemplateInfo
{
[JsonProperty("GlobalConfiguration")]public GlobalConfigurationInfo GlobalConfiguration { get; set; }
}
publicclassStateInfo
{
[JsonProperty("State")]publicstring Value { get; set; }
}
publicclassGlobalConfigurationInfo
{
[JsonProperty("EnablePeriodicChunking")]publicbool EnablePeriodicChunking { get; set; }
}
publicstaticvoid Run(SLProtocol protocol)
{
try
{
string ip = protocol.GetParameter(15) asstring;
string token = protocol.GetParameter(Parameter.token) asstring;
protocol.Log("Inicio del QAction", LogType.DebugInfo);
if (string.IsNullOrEmpty(ip) || string.IsNullOrEmpty(token))
{
protocol.Log("IP o token vacío", LogType.Error);
return;
}
protocol.Log($"IP: {ip} | Token: {token}", LogType.DebugInfo);
string url = $"http://{ip}/api/v1/servicesmngt/services";
var client = new TimeoutWebClient(180000);
client.Headers[HttpRequestHeader.ContentType] = "application/json";
client.Headers.Add("token", token);
protocol.Log($"Realizando solicitud GET a: {url}", LogType.DebugInfo);
string json = client.DownloadString(url);
protocol.Log($"Respuesta recibida: {json.Length} caracteres", LogType.DebugInfo);
List<Servicio> servicios = null;
string trimmed = json.TrimStart();
if (trimmed.StartsWith("["))
{
protocol.Log("Detectado JSON tipo array", LogType.DebugInfo);
servicios = JsonConvert.DeserializeObject<List<Servicio>>(json);
}
elseif (trimmed.StartsWith("{"))
{
protocol.Log("Detectado JSON tipo objeto único", LogType.DebugInfo);
var single = JsonConvert.DeserializeObject<Servicio>(json);
servicios = new List<Servicio> { single };
}
else
{
protocol.Log("Formato JSON no reconocido", LogType.Error);
return;
}
object result = protocol.ClearAllKeys(1100);
int resultCode = Convert.ToInt32(result);
if (resultCode == 0)
{
protocol.Log("Tabla 1100 limpiada correctamente.", LogType.DebugInfo);
}
elseif (resultCode == -1)
{
protocol.Log("Tabla 1100 ya estaba vacía.", LogType.DebugInfo);
}
else
{
protocol.Log($"Error al limpiar la tabla 1100. Código: {resultCode}", LogType.Error);
}
if (servicios == null || servicios.Count == 0)
{
protocol.Log("No se encontraron servicios válidos.", LogType.DebugInfo);
return;
}
var primaryKeys = new List<object>();
var uuids = new List<object>();
var names = new List<object>();
var states = new List<object>();
var chunks = new List<object>();
foreach (var s in servicios)
{
string chunking = "Unknown";
if (s.Device?.Template?.GlobalConfiguration != null)
{
chunking = s.Device?.Template?.GlobalConfiguration?.EnablePeriodicChunking == true ? "Enable" : "Disable";
}
string state = s.State?.Value ?? "Unknown";
protocol.Log($"Servicio: UID={s.Uid} | Name={s.Name} | Chunking={chunking} | State={state}", LogType.DebugInfo);
primaryKeys.Add(s.Uid);
uuids.Add(s.Uid);
names.Add(s.Name);
states.Add(state);
chunks.Add(chunking);
}
object[] columnInfo = newobject[]
{
Parameter.Servicetable.tablePid,
Parameter.Servicetable.Pid.servicetableuuid,
Parameter.Servicetable.Pid.servicetableservice,
Parameter.Servicetable.Pid.servicetablestateservice,
Parameter.Servicetable.Pid.servicetablechunk,
};
object[] columnValues = newobject[]
{
primaryKeys.ToArray(),
uuids.ToArray(),
names.ToArray(),
states.ToArray(),
chunks.ToArray(),
};
protocol.NotifyProtocol(220, columnInfo, columnValues);
protocol.Log("Tabla 1100 actualizada correctamente", LogType.Information);
}
catch (WebException ex)
{
protocol.Log("WebException: " + ex.Message, LogType.Error);
}
catch (Exception ex)
{
protocol.Log("Exception: " + ex.Message, LogType.Error);
}
}
}
2.Table no populate after add row, al column of my table are read and write.
3. 105 is a param virtual

Hi Joshua,
Thanks for clarify about row and tables, I have a doubt about my case, need 2 QAction m, one for populate and another for changes ??
Please help me with this question
Thanks
Hi Jose,
Yes, in your case it’s best to use two separate QActions:
One QAction to populate the table — This one is triggered after the session or when the response parameter changes. It will parse the full response and fill in all the rows of the table.
Another QAction for changes to the table rows — This one should have row="true" and will be triggered when someone edits a specific row. You can use this to update or sync changes based on the row's primary key (RowKey()).
Hi Jose,
Thanks for sharing the code. I think I understand what you're trying to achieve here.
Instead of using a QAction to call the HTTP REST API and fill the table, I recommend using a Session. A Session can be set up to automatically call the URL, and then save the response into a parameter. Here is our documentation that helps explain how to do so https://docs.dataminer.services/develop/devguide/Connector/ConnectionsHttpImplementing.html.
The advantage of using a Session is that it supports built-in timeout and retry handling, which makes the connection more stable. These features are not available when calling the API directly from a QAction.
After the Session saves the response to the parameter, you can then:
Trigger a QAction when that response parameter changes, or
Trigger the QAction after the Session group runs
Inside that QAction, you can parse the response and fill table 1100, just like you’re doing now.
Also, about row="true": this is usually used for edits. So when a row in the table is changed (e.g. the user updates something), the QAction will be triggered only for that row. That’s different from what you’re trying to do here, which is to refresh all the data from an external API on a timer. The trigger for the QAction should then be a column of the table.