-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathweb_data_connector.nb
128 lines (100 loc) · 3.75 KB
/
web_data_connector.nb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
generateCols[data_List, headers_List : {}] :=
Module[{types, dtypes, DateTimeObject, firstRow},
firstRow = First@data;
types = {Real -> "tableau.dataTypeEnum.float",
Integer -> "tableau.dataTypeEnum.int",
String -> "tableau.dataTypeEnum.string",
DateObject -> "tableau.dataTypeEnum.date",
DateTimeObject -> "tableau.dataTypeEnum.datetime",
Symbol -> "tableau.dataTypeEnum.bool"
};
dtypes =
If[Head@# === DateObject,
If[Length@#[[1]] > 3, DateTimeObject, DateObject], Head@#] & /@
firstRow;
Return@StringRiffle[#, {"[", ",", "]"}] &@MapIndexed[
StringTemplate["{id: \"`id`\",dataType: `type`}"][<|"type" -> #1,
"id" -> headers[[#2[[1]]]]|>] &, # /. types & /@ dtypes]
]
generateHTML[data_, headers_List : {}] :=
Module[{tHeaders, tdata, firstRow, output},
tHeaders = headers;
firstRow = First@data;
(* convert data\datetime to javascript type *)
tdata =
data /. {d_DateObject :>
StringRiffle[d[[1]], {"new Date(", ",", ")"}], _Missing ->
"null"};
(* if headers length doesnt match data length,
generate header names *)
If[Length@firstRow > Length@tHeaders,
tHeaders = Array["C" <> ToString@# &, Length@firstRow]];
output = StringTemplate["<!DOCTYPE html>
<html lang=\"en\">
<head>
<title></title>
<meta http-equiv=\"Cache-Control\" content=\"no-store\" />
<meta charset=\"UTF-8\">
<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/\
jquery.min.js\" type=\"text/javascript\"></script>
<script src=\"https://connectors.tableau.com/libs/tableauwdc-2.3.\
latest.js\" type=\"text/javascript\"></script>
<script>
(function() {
var myConnector = tableau.makeConnector();
myConnector.getSchema = function(schemaCallback) {
var cols = `cols`;
var tableSchema = {
id: \"mathematica\",
columns: cols
};
schemaCallback([tableSchema]);
};
myConnector.getData = function(table, doneCallback) {
table.appendRows(`table`);
doneCallback();
};
tableau.registerConnector(myConnector);
$(document).ready(function () {
$(\"#submitButton\").click(function () {
tableau.connectionName = \"Mathematica\";
tableau.submit();
});
});
})();
</script>
</head>
<body>
<button id=\"submitButton\">Click here to load</button>
</body>
</html>"][<|"cols" -> generateCols[data, tHeaders],
"table" ->
ExportString[
AssociationThread[tHeaders[[;; Length@#]], #] & /@ tdata,
"JavaScriptExpression", "Compact" -> True]|>];
(* remove double quotes around javascript generated date type *)
Return@StringReplace[
output, {"\"new Date(" ~~ Shortest[d__] ~~ ")\"" :>
"new Date(" <> d <> ")", "\"null\"" -> "null"}];
]
setupTableauConnector[data_, options___] := Module[{headers, port},
{headers, port} =
Values[Association[options][[{Key["Headers"], Key["Port"]}]]];
If[MissingQ@headers, headers = {}];
If[MissingQ@port, port = 39000];
(* close any existing port runned by mathematica *)
Close@SelectFirst[Sockets[], #["DestinationPort"] == port &] //
Quiet;
Return[SocketListen[port, Function[{assoc},
Module[{client = assoc["SourceSocket"], answer, output,
evalData1, evalData2, success},
answer = generateHTML[data, headers];
output = "HTTP/1.1 200 OK" <>
"\r\nServer: Mathematica/" <> ToString@$VersionNumber <>
"\r\nContent-Type: text/html; charset=utf-8" <>
"\r\nDate: " <> DateString[TimeZone -> 0] <> " GMT" <>
"\r\nContent-Length: " <> ToString@StringLength@answer <>
"\r\nVary: Accept-Encoding\r\n\r\n" <> answer;
WriteString[client, output];
]
]]["Socket"]]];