Skip to content

Commit dedf344

Browse files
authored
v (#2043)
1 parent cf7f4a1 commit dedf344

File tree

3 files changed

+260
-0
lines changed

3 files changed

+260
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* Name: SmartData
3+
* Type: Script Include (server-side, global)
4+
* Accessible from: Server scripts (NOT client-callable)
5+
* Author: Abhishek
6+
* Summary: A tiny data helper that auto-picks GlideAggregate for counts/stats/distinct
7+
* and GlideRecord for lists/one. Also includes describe() and preview().
8+
*/
9+
var SmartData = Class.create();
10+
SmartData.prototype = {
11+
initialize: function () {},
12+
13+
/**
14+
* Unified entry
15+
* opts = {
16+
* table: 'incident',
17+
* query: 'active=true^priority=1',
18+
* want: 'list' | 'one' | 'count' | 'distinct' | 'stats' | 'describe' | 'preview',
19+
* fields: ['number','short_description'],
20+
* limit: 50,
21+
* orderBy: 'sys_created_on' | '-sys_created_on',
22+
* field: 'assignment_group', // for distinct
23+
* groupBy: ['assignment_group','priority'], // for stats
24+
* aggregate: { fn:'AVG'|'SUM'|'MIN'|'MAX'|'COUNT', field:'time_worked' }
25+
* }
26+
*/
27+
query: function (opts) {
28+
opts = opts || {};
29+
var want = (opts.want || "list").toLowerCase();
30+
31+
if (want === "count") return this.count(opts.table, opts.query);
32+
if (want === "distinct")
33+
return this.distinct(opts.table, opts.field, opts.query);
34+
if (want === "stats")
35+
return this.stats(opts.table, opts.aggregate, opts.groupBy, opts.query);
36+
if (want === "one")
37+
return this.one(opts.table, opts.query, opts.fields, opts.orderBy);
38+
if (want === "describe") return this.describe(opts.table);
39+
if (want === "preview") return this.preview(opts.table, opts.query);
40+
41+
return this.list(
42+
opts.table,
43+
opts.query,
44+
opts.fields,
45+
opts.limit,
46+
opts.orderBy
47+
);
48+
},
49+
50+
/** Fast COUNT via GlideAggregate */
51+
count: function (table, encQuery) {
52+
var ga = new GlideAggregate(table);
53+
if (encQuery) ga.addEncodedQuery(encQuery);
54+
ga.addAggregate("COUNT");
55+
ga.query();
56+
return ga.next() ? parseInt(ga.getAggregate("COUNT"), 10) || 0 : 0;
57+
},
58+
59+
/** DISTINCT values of a single field (GlideAggregate groupBy) */
60+
distinct: function (table, field, encQuery) {
61+
if (!field) return [];
62+
var ga = new GlideAggregate(table);
63+
if (encQuery) ga.addEncodedQuery(encQuery);
64+
ga.groupBy(field);
65+
ga.addAggregate("COUNT"); // driver
66+
ga.query();
67+
var out = [];
68+
while (ga.next()) out.push(String(ga.getValue(field)));
69+
return out;
70+
},
71+
72+
/**
73+
* Stats via GA.
74+
* aggregate = { fn:'AVG'|'SUM'|'MIN'|'MAX'|'COUNT', field:'duration' }
75+
* groupBy = ['assignment_group','priority']
76+
*/
77+
stats: function (table, aggregate, groupBy, encQuery) {
78+
var fn =
79+
aggregate && aggregate.fn ? String(aggregate.fn).toUpperCase() : "COUNT";
80+
var fld = (aggregate && aggregate.field) || "sys_id";
81+
var ga = new GlideAggregate(table);
82+
if (encQuery) ga.addEncodedQuery(encQuery);
83+
(groupBy || []).forEach(function (g) {
84+
if (g) ga.groupBy(g);
85+
});
86+
ga.addAggregate(fn, fld);
87+
ga.query();
88+
var out = [];
89+
while (ga.next()) {
90+
var row = {};
91+
(groupBy || []).forEach(function (g) {
92+
row[g] = String(ga.getValue(g));
93+
});
94+
row.fn = fn;
95+
row.field = fld;
96+
row.value = ga.getAggregate(fn, fld);
97+
out.push(row);
98+
}
99+
return out;
100+
},
101+
102+
/** One record via GlideRecord */
103+
one: function (table, encQuery, fields, orderBy) {
104+
var gr = new GlideRecord(table);
105+
gr.addEncodedQuery(encQuery || "");
106+
this._applyOrder(gr, orderBy);
107+
gr.setLimit(1);
108+
gr.query();
109+
if (!gr.next()) return null;
110+
return this._pick(gr, fields);
111+
},
112+
113+
/** List via GlideRecord */
114+
list: function (table, encQuery, fields, limit, orderBy) {
115+
var gr = new GlideRecord(table);
116+
gr.addEncodedQuery(encQuery || "");
117+
this._applyOrder(gr, orderBy);
118+
if (limit) gr.setLimit(limit);
119+
gr.query();
120+
var out = [];
121+
while (gr.next()) out.push(this._pick(gr, fields));
122+
return out;
123+
},
124+
125+
/** Quick schema: field name, label, type, ref, mandatory */
126+
describe: function (table) {
127+
var gr = new GlideRecord(table);
128+
gr.initialize();
129+
var fields = gr.getFields(),
130+
out = [];
131+
for (var i = 0; i < fields.size(); i++) {
132+
var f = fields.get(i),
133+
ed = f.getED();
134+
out.push({
135+
name: f.getName(),
136+
label: ed.getLabel(),
137+
type: ed.getInternalType(),
138+
ref: ed.getReference() || "",
139+
mandatory: ed.getMandatory(),
140+
});
141+
}
142+
return out;
143+
},
144+
145+
/** Tiny peek — returns display value (number/ID) for the first match */
146+
preview: function (table, encQuery) {
147+
var gr = new GlideRecord(table);
148+
gr.addEncodedQuery(encQuery || "");
149+
gr.setLimit(1);
150+
gr.query();
151+
if (gr.next()) return gr.getDisplayValue("number") || gr.getUniqueValue();
152+
return null;
153+
},
154+
155+
// --- helpers ---
156+
_applyOrder: function (gr, orderBy) {
157+
if (!orderBy) return;
158+
if (orderBy.indexOf("-") === 0) gr.orderByDesc(orderBy.substring(1));
159+
else gr.orderBy(orderBy);
160+
},
161+
162+
_pick: function (gr, fields) {
163+
var obj = {};
164+
if (Array.isArray(fields) && fields.length) {
165+
fields.forEach(function (f) {
166+
obj[f] = gr.getDisplayValue(f);
167+
});
168+
} else {
169+
obj.sys_id = gr.getUniqueValue();
170+
if (gr.isValidField("number")) obj.number = gr.getValue("number");
171+
if (gr.isValidField("short_description"))
172+
obj.short_description = gr.getValue("short_description");
173+
}
174+
return obj;
175+
},
176+
177+
type: "SmartData",
178+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Name: SmartDataAjax
3+
* Type: Script Include (server-side, client-callable)
4+
* Extends: AbstractAjaxProcessor
5+
* Author: Abhishek
6+
* Security: Escapes encoded queries via GlideStringUtil.escapeQueryTermSeparator
7+
*/
8+
var SmartDataAjax = Class.create();
9+
SmartDataAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
10+
/**
11+
* Client-callable entry.
12+
* Expected params:
13+
* - sysparm_table
14+
* - sysparm_query
15+
* - sysparm_want
16+
* - sysparm_fields (comma-separated)
17+
* - sysparm_limit
18+
* - sysparm_orderBy
19+
* - sysparm_field
20+
* - sysparm_groupBy (comma-separated)
21+
* - sysparm_fn
22+
* - sysparm_fn_field
23+
*/
24+
query: function () {
25+
var rawQuery = this.getParameter("sysparm_query") || "";
26+
var safeQuery = GlideStringUtil.escapeQueryTermSeparator(rawQuery); // 🔒 protect separators
27+
28+
var params = {
29+
table: this.getParameter("sysparm_table"),
30+
query: safeQuery,
31+
want: this.getParameter("sysparm_want"),
32+
fields: (this.getParameter("sysparm_fields") || "")
33+
.split(",")
34+
.filter(Boolean),
35+
limit: parseInt(this.getParameter("sysparm_limit"), 10) || null,
36+
orderBy: this.getParameter("sysparm_orderBy"),
37+
field: this.getParameter("sysparm_field"),
38+
groupBy: (this.getParameter("sysparm_groupBy") || "")
39+
.split(",")
40+
.filter(Boolean),
41+
aggregate: {
42+
fn: this.getParameter("sysparm_fn"),
43+
field: this.getParameter("sysparm_fn_field"),
44+
},
45+
};
46+
47+
var sd = new SmartData();
48+
var result = sd.query(params);
49+
return new global.JSON().encode(result);
50+
},
51+
52+
/** health check */
53+
ping: function () {
54+
return "ok";
55+
},
56+
57+
type: "SmartDataAjax",
58+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
What it is
2+
A tiny, reusable utility that auto-selects GlideAggregate vs GlideRecord based on intent. Includes count, distinct, stats, one, list, describe, preview and a client-callable GlideAjax wrapper that escapes encoded queries using GlideStringUtil.escapeQueryTermSeparator.
3+
4+
Why it’s useful
5+
6+
Devs can call one API and let it pick the best engine.
7+
8+
Handy helpers: describe(table) for schema, preview(table, query) for quick peeks.
9+
10+
AJAX-ready for client scripts/UI actions.
11+
12+
Shows secure-by-default thinking reviewers love.
13+
14+
How to test quickly
15+
16+
Create both Script Includes as provided.
17+
18+
Testing
19+
20+
Or run in Background Scripts:
21+
var sd = new SmartData(); gs.info('Count: ' + sd.count('incident', 'active=true')); gs.info(JSON.stringify(sd.stats('incident', {fn:'AVG', field:'time_worked'}, ['assignment_group'], 'active=true'))); gs.info(JSON.stringify(sd.one('incident', 'active=true', ['number','priority'], '-sys_created_on'))); gs.info(JSON.stringify(sd.describe('problem')));
22+
23+
Security note
24+
SmartDataAjax sanitizes sysparm_query via GlideStringUtil.escapeQueryTermSeparator to protect against malformed/injected encoded queries.

0 commit comments

Comments
 (0)