diff --git a/src/ur/serviceNow.ur b/src/ur/serviceNow.ur
index 5e3a38e..eb57311 100644
--- a/src/ur/serviceNow.ur
+++ b/src/ur/serviceNow.ur
@@ -18,47 +18,59 @@ type incident = {
Description : string
}
val _ : json incident = json_record {Description = "description"}
-
+
type result a = {
Result : a
}
fun json_result [a] (_ : json a) : json (result a) = json_record {Result = "result"}
-type tabl = {
+type table_name = {
Nam : string
}
-val _ : json tabl = json_record {Nam = "name"}
+val _ : json table_name = json_record {Nam = "name"}
type reference = {
Value : string
}
val _ : json reference = json_record {Value = "value"}
-type tabl' = {
- Id : string,
- Nam : string,
- Parent : option reference
+type rawColumn = {
+ Nam : option string,
+ Typ : option reference,
+ Dis : option string
}
-val _ : json tabl' = json_record_withOptional {Id = "sys_id", Nam = "name"}
- {Parent = "super_class"}
-type tabl'' = {
- Id : string,
- Nam : string
-}
-val _ : json tabl'' = json_record {Id = "sys_id", Nam = "name"}
+val _ : json rawColumn = json_record_withOptional {} {Nam = "element",
+ Typ = "internal_type",
+ Dis = "display"}
+
+(* Bools seem to come back from ServiceNow as strings that are either "true" or "false" *)
+fun unRawColumn (r : rawColumn) =
+ let fun stringToBool s =
+ if s = "true" then Some True
+ else if s = "false" then Some False
+ else None
+ val dis = (x <- Option.mp stringToBool r.Dis; x) in
+ name <- r.Nam;
+ typ <- r.Typ;
+ if name = "" then
+ None
+ else
+ return {Nam = name, Typ = typ.Value, Dis = dis}
+ end
type column = {
Nam : string,
Typ : string
}
-type column' = {
- Nam : option string,
- Typ : option reference
+type full_table = {
+ Columns : list column,
+ DisplayColumn : option string
}
-val _ : json column' = json_record_withOptional {} {Nam = "element",
- Typ = "internal_type"}
+
+(* This should probably be upstreamed. *)
+fun oAlt o1 o2 = case o1 of Some _ => o1 | None => o2
functor Make(M : AUTH) = struct
open M
@@ -70,92 +82,94 @@ functor Make(M : AUTH) = struct
| Some tok => return tok
val prefix =
- instance <- instance;
- return ("https://" ^ instance ^ ".service-now.com/api/now/")
+ instance <- instance;
+ return ("https://" ^ instance ^ ".service-now.com/api/now/")
fun logged [a] (_ : show a) (t : transaction a) =
v <- t;
debug ("ServiceNow response: " ^ show v);
return v
- fun api url =
+ fun api [a] (j : json a) (url : string) : transaction a =
tok <- token;
- prefix <- prefix;
+ prefix <- prefix;
debug ("ServiceNow GET: " ^ prefix ^ url);
- logged (WorldFfi.get (bless (prefix ^ url)) (WorldFfi.addHeader WorldFfi.emptyHeaders "Authorization" ("Bearer " ^ tok)) False)
+ raw <- logged (WorldFfi.get (bless (prefix ^ url)) (WorldFfi.addHeader WorldFfi.emptyHeaders "Authorization" ("Bearer " ^ tok)) False);
+ return (fromJson raw : result a).Result
structure Incidents = struct
- val list =
- s <- api "table/incident?sysparm_fields=description";
- return (fromJson s : result (list incident)).Result
+ val list = @@api [list incident] _ "table/incident?sysparm_fields=description"
end
structure Tables = struct
- val list =
- s <- api "table/sys_db_object?sysparm_fields=name";
- return (fromJson s : result (list tabl)).Result
-
- fun get tabl =
- s <- api ("table/sys_db_object?sysparm_fields=sys_id,name,super_class&sysparm_query=super_classISNOTEMPTY^name=" ^ Urls.urlencode tabl);
- raw <- return (fromJson s : result (list tabl')).Result;
- case raw of
- t :: [] => return (Some t)
- | [] => return None
- | _ => error Surprising multiple results when looking up table "{[tabl]}" in ServiceNow.
-
- fun getById tid =
- s <- api ("table/sys_db_object?sysparm_fields=sys_id,name&sysparm_query=sys_id=" ^ Urls.urlencode tid);
- raw <- return (fromJson s : result (list tabl'')).Result;
- case raw of
- t :: [] => return (Some t)
- | [] => return None
- | _ => error Surprising multiple results when looking up table #{[tid]} in ServiceNow.
-
- fun columnsWithoutInheritance tabl =
- s <- api ("table/sys_dictionary?sysparm_fields=element,internal_type&sysparm_query=name=" ^ Urls.urlencode tabl);
- raw <- return (fromJson s : result (list column')).Result;
- return (List.mapPartial (fn r =>
- name <- r.Nam;
- typ <- r.Typ;
- if name = "" then
- None
- else
- return {Nam = name, Typ = typ.Value}) raw)
-
- fun columns tabl =
- cs <- columnsWithoutInheritance tabl;
- t <- get tabl;
- case t of
- None => return cs
- | Some t =>
- case t.Parent of
- None => return cs
- | Some {Value = p} =>
- p <- getById p;
- case p of
- None => return cs
- | Some p =>
- cs' <- columns p.Nam;
- return (List.append cs cs')
+ val list = @@api [list table_name] _ "table/sys_db_object?sysparm_fields=name"
+
+ fun getParent tableName =
+ tableList <- @@api
+ [list {Parent : reference}]
+ (@json_list <| json_record {Parent = "super_class"})
+ ("table/sys_db_object?sysparm_fields=super_class&sysparm_query=super_classISNOTEMPTY^name=" ^ Urls.urlencode tableName);
+ case tableList of
+ [] => return None
+ | t :: [] =>
+ (parentTable <- @@api [list table_name] _ ("table/sys_db_object?sysparm_fields=name&sysparm_query=sys_id=" ^ Urls.urlencode t.Parent.Value);
+ case parentTable of
+ pName :: [] => return <| Some pName.Nam
+ | [] => error ServiceNow said that table #"{[t.Parent.Value]}" exists, but it weirdly has no name.
+ | _ => error Surprising multiple results when looking up table #"{[t.Parent.Value]}" in ServiceNow.)
+ | _ => error Surprising multiple results when looking up table "{[tableName]}" in ServiceNow.
+
+ fun columnsWithoutInheritance tableName =
+ raw <- @@api [list rawColumn] _ ("table/sys_dictionary?sysparm_fields=element,internal_type,display&sysparm_query=name=" ^ Urls.urlencode tableName);
+ return <| List.mapPartial unRawColumn raw
+
+ fun columnsWithDis tableName =
+ cs <- columnsWithoutInheritance tableName;
+ p <- getParent tableName;
+ case p of
+ None => return cs
+ | Some pName =>
+ cs' <- columnsWithDis pName;
+ return (cs `List.append` cs')
+ (* The order of appending matters here. A child table may have
+ a different display field than a parent, so we should search
+ through them in order from child to parent. See comment below
+ for more detail. *)
+
+ (* From https://docs.servicenow.com/bundle/tokyo-platform-administration/page/administer/field-administration/task/t_SelectTheDisplayValue.html
+ Reference fields look for the display value in the following order:
+ a. A field with display=true in the system dictionary on the lowest sub-table for extended tables.
+ b. A field with display=true in the system dictionary on the parent table.
+ c. A field named name or u_name.
+ d. The Created on field of the referenced record.
+ *)
+
+ fun columns tabl =
+ cs <- columnsWithDis tabl;
+ let val display =
+ List.find (fn x => x.Dis = Some True) cs `oAlt`
+ List.find (fn x => x.Nam = "name" || x.Nam = "u_name") cs `oAlt`
+ List.find (fn x => x.Nam = "sys_created_on") cs
+ in return {Columns = List.mp (fn x => x -- #Dis) cs,
+ DisplayColumn = Option.mp (fn x => x.Nam) display}
+ end
+
end
structure Table = struct
- fun list [ts] (fl : folder ts) (labels : $(map (fn _ => string) ts))
- (jsons : $(map json ts)) (tname : string) =
- fields <- return (@foldR [fn _ => string] [fn _ => string]
- (fn [nm ::_] [t ::_] [r ::_] [[nm] ~ r]
- (label : string) (acc : string) =>
- case acc of
- "" => label
- | _ => acc ^ "," ^ label)
- "" fl labels);
- s <- api ("table/" ^ Urls.urlencode tname ^ "?sysparm_fields=" ^ fields);
- v <- return (@fromJson
- (@json_result (@json_list
- (@json_record_withOptional ! _ {} {}
- fl jsons labels)))
- s : result (list $(map option ts)));
- return v.Result
+ fun list [ts] (fl : folder ts) (labels : $(map (fn _ => string) ts))
+ (jsons : $(map json ts)) (tname : string) =
+ fields <- return (@foldR [fn _ => string] [fn _ => string]
+ (fn [nm ::_] [t ::_] [r ::_] [[nm] ~ r]
+ (label : string) (acc : string) =>
+ case acc of
+ "" => label
+ | _ => acc ^ "," ^ label)
+ "" fl labels);
+ @@api
+ [list $(map option ts)]
+ (@json_list (@json_record_withOptional ! _ {} {} fl jsons labels))
+ ("table/" ^ Urls.urlencode tname ^ "?sysparm_fields=" ^ fields)
end
end
@@ -176,7 +190,7 @@ functor ThreeLeggedDyn(M : sig
val instance =
settings <- settings;
- return settings.Instance
+ return settings.Instance
table secrets : { Secret : int,
Token : string,
@@ -259,7 +273,7 @@ functor ThreeLeggedDyn(M : sig
end
functor ThreeLegged(M : sig
- val instance : string
+ val instance : string
val client_id : string
val client_secret : string
val https : bool
diff --git a/src/ur/serviceNow.urs b/src/ur/serviceNow.urs
index 62f20ae..965753c 100644
--- a/src/ur/serviceNow.urs
+++ b/src/ur/serviceNow.urs
@@ -11,7 +11,7 @@ signature AUTH = sig
end
functor ThreeLegged(M : sig
- val instance : string
+ val instance : string
val client_id : string
val client_secret : string
val https : bool
@@ -51,7 +51,7 @@ type incident = {
Description : string
}
-type tabl = {
+type table_name = {
Nam : string
}
@@ -60,22 +60,27 @@ type column = {
Typ : string
}
+type full_table = {
+ Columns : list column,
+ DisplayColumn : option string
+}
+
functor Make(M : AUTH) : sig
structure Incidents : sig
- val list : transaction (list incident)
+ val list : transaction (list incident)
end
structure Tables : sig
- val list : transaction (list tabl)
- val columns : string -> transaction (list column)
+ val list : transaction (list table_name)
+ val columns : string -> transaction full_table
end
structure Table : sig
- val list : ts ::: {Type}
- -> folder ts
- -> $(map (fn _ => string) ts) (* labels in JSON *)
- -> $(map Json.json ts)
- -> string (* table name *)
- -> transaction (list $(map option ts))
+ val list : ts ::: {Type}
+ -> folder ts
+ -> $(map (fn _ => string) ts) (* labels in JSON *)
+ -> $(map Json.json ts)
+ -> string (* table name *)
+ -> transaction (list $(map option ts))
end
end