beeswax.vm.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. function BeeswaxViewModel(server, query_id) {
  17. var self = this;
  18. var QUERY_DEFAULTS = {
  19. 'id': query_id,
  20. 'query': '',
  21. 'name': null,
  22. 'description': null,
  23. 'settings': [],
  24. 'fileResources': [],
  25. 'functions': [],
  26. 'parameters': [],
  27. 'isParameterized': true,
  28. 'email': false,
  29. 'errors': [],
  30. 'explain': false,
  31. 'results': {
  32. 'rows': [],
  33. 'columns': [],
  34. 'empty': true,
  35. 'explanation': null,
  36. 'url': null,
  37. 'save': {
  38. 'errors': null,
  39. 'type': 'table',
  40. 'path': null
  41. }
  42. },
  43. 'watch': {
  44. 'logs': [],
  45. 'url': null,
  46. 'errors': []
  47. },
  48. 'isRunning': false
  49. };
  50. self.server = ko.observable(server);
  51. self.databases = ko.observableArray();
  52. self.selectedDatabase = ko.observable(0);
  53. self.query = ko.mapping.fromJS(QUERY_DEFAULTS);
  54. self.hasMoreResults = ko.computed(function() {
  55. return self.query.results.url() != null;
  56. });
  57. self.query.results.save.saveTargetError = ko.computed(function() {
  58. return (self.query.results.save.errors() && 'save_target' in self.query.results.save.errors()) ? self.query.results.save.errors()['save_target'] : null;
  59. });
  60. self.query.results.save.targetTableError = ko.computed(function() {
  61. return (self.query.results.save.errors() && 'target_table' in self.query.results.save.errors()) ? self.query.results.save.errors()['target_table'] : null;
  62. });
  63. self.query.results.save.targetDirectoryError = ko.computed(function() {
  64. return (self.query.results.save.errors() && 'target_dir' in self.query.results.save.errors()) ? self.query.results.save.errors()['target_dir'] : null;
  65. });
  66. self.database = ko.computed({
  67. 'read': function() {
  68. if (self.databases()) {
  69. return self.databases()[self.selectedDatabase()];
  70. } else{
  71. return "";
  72. }
  73. },
  74. 'write': function(value) {
  75. if (value) {
  76. self.selectedDatabase(self.databases.indexOf(value));
  77. }
  78. },
  79. 'deferEvaluation': true
  80. });
  81. self.resetQuery = function() {
  82. ko.mapping.fromJS(QUERY_DEFAULTS, self.query);
  83. };
  84. self.updateDatabases = function(databases) {
  85. self.databases(databases);
  86. };
  87. self.updateQuery = function(design) {
  88. self.query.query(design.query);
  89. self.query.id(design.id);
  90. self.query.name(design.name);
  91. self.query.description(design.desc);
  92. self.database(design.database);
  93. self.query.isParameterized(design.is_parameterized);
  94. self.query.email(design.email_notify);
  95. self.query.settings.removeAll();
  96. self.query.fileResources.removeAll();
  97. self.query.functions.removeAll();
  98. $.each(design.settings, function(index, setting) {
  99. self.addSetting(setting.key, setting.value);
  100. });
  101. $.each(design.file_resources, function(index, file_resource) {
  102. self.addFileResources(file_resource.type, file_resource.path);
  103. });
  104. $.each(design.functions, function(index, _function) {
  105. self.addFunction(_function.name, _function.class_name);
  106. });
  107. };
  108. self.updateParameters = function(parameters) {
  109. self.query.parameters.removeAll();
  110. $.each(parameters, function(index, parameter) {
  111. self.addParameter(parameter.parameter, parameter.name, '');
  112. });
  113. };
  114. self.addParameter = function(parameter, name, value) {
  115. var obj = {
  116. 'parameter': ko.observable(parameter),
  117. 'name': ko.observable(name),
  118. 'value': ko.observable(value)
  119. };
  120. obj.parameter.subscribe(function() {
  121. self.query.parameters.valueHasMutated();
  122. });
  123. obj.name.subscribe(function() {
  124. self.query.parameters.valueHasMutated();
  125. });
  126. obj.value.subscribe(function() {
  127. self.query.parameters.valueHasMutated();
  128. });
  129. self.query.parameters.push(obj);
  130. };
  131. self.addSetting = function(key, value) {
  132. var obj = {
  133. 'key': ko.observable(key),
  134. 'value': ko.observable(value)
  135. };
  136. obj.key.subscribe(function() {
  137. self.query.settings.valueHasMutated();
  138. });
  139. obj.value.subscribe(function() {
  140. self.query.settings.valueHasMutated();
  141. });
  142. self.query.settings.push(obj);
  143. };
  144. self.removeSetting = function(index) {
  145. self.query.settings.splice(index, 1);
  146. };
  147. self.addFileResources = function(type, path) {
  148. var obj = {
  149. 'type': ko.observable(type),
  150. 'path': ko.observable(path)
  151. };
  152. obj.type.subscribe(function() {
  153. self.query.fileResources.valueHasMutated();
  154. });
  155. obj.path.subscribe(function() {
  156. self.query.fileResources.valueHasMutated();
  157. });
  158. self.query.fileResources.push(obj);
  159. };
  160. self.removeFileResources = function(index) {
  161. self.query.fileResources.splice(index, 1);
  162. };
  163. self.addFunction = function(name, class_name) {
  164. var obj = {
  165. 'name': ko.observable(name),
  166. 'class_name': ko.observable(class_name)
  167. };
  168. obj.name.subscribe(function() {
  169. self.query.functions.valueHasMutated();
  170. });
  171. obj.class_name.subscribe(function() {
  172. self.query.functions.valueHasMutated();
  173. });
  174. self.query.functions.push(obj);
  175. };
  176. self.removeFunction = function(index) {
  177. self.query.functions.splice(index, 1);
  178. };
  179. function getMultiFormData(prefix, arr, members) {
  180. var data = {};
  181. var index = 0;
  182. data[prefix + '-next_form_id'] = arr.length;
  183. ko.utils.arrayForEach(arr, function(obj) {
  184. $.each(members, function(i, member) {
  185. data[prefix + '-' + index + '-' + member] = obj[member]();
  186. });
  187. data[prefix + '-' + index + '-_exists'] = true;
  188. data[prefix + '-' + index++ + '-_deleted'] = false;
  189. });
  190. return data;
  191. }
  192. self.getSettingsFormData = function() {
  193. return getMultiFormData('settings', self.query.settings(), ['key', 'value']);
  194. };
  195. self.getFileResourcesFormData = function() {
  196. return getMultiFormData('file_resources', self.query.fileResources(), ['type', 'path']);
  197. };
  198. self.getFunctionsFormData = function() {
  199. return getMultiFormData('functions', self.query.functions(), ['name', 'class_name']);
  200. };
  201. self.getParametersFormData = function() {
  202. var data = {};
  203. $.each(self.query.parameters(), function(index, parameter) {
  204. data[parameter.parameter()] = parameter.value();
  205. });
  206. return data;
  207. };
  208. self.getOtherData = function() {
  209. var data = {
  210. 'query-email_notify': self.query.email(),
  211. 'query-is_parameterized': self.query.isParameterized()
  212. };
  213. return data;
  214. };
  215. var error_fn = function(jqXHR, status, errorThrown) {
  216. self.query.isRunning(false);
  217. try {
  218. $(document).trigger('server.error', $.parseJSON(jqXHR.responseText));
  219. } catch(e) {
  220. $(document).trigger('server.unmanageable_error', jqXHR.responseText);
  221. }
  222. };
  223. self.fetchDatabases = function() {
  224. var request = {
  225. url: '/' + self.server() + '/api/autocomplete',
  226. dataType: 'json',
  227. type: 'GET',
  228. success: function(data) {
  229. self.updateDatabases(data.databases);
  230. },
  231. error: error_fn
  232. };
  233. $.ajax(request);
  234. };
  235. self.fetchQuery = function() {
  236. $(document).trigger('fetch.query');
  237. var request = {
  238. url: '/' + self.server() + '/api/query/' + self.query.id() + '/get',
  239. dataType: 'json',
  240. type: 'GET',
  241. success: function(data) {
  242. self.updateQuery(data.design);
  243. $(document).trigger('fetched.query', data);
  244. },
  245. error: error_fn
  246. };
  247. $.ajax(request);
  248. };
  249. self.fetchParameters = function() {
  250. $(document).trigger('fetch.parameters');
  251. var data = {
  252. 'query-query': self.query.query(),
  253. 'query-database': self.database()
  254. };
  255. $.extend(data, self.getSettingsFormData());
  256. $.extend(data, self.getFileResourcesFormData());
  257. $.extend(data, self.getFunctionsFormData());
  258. $.extend(data, self.getParametersFormData());
  259. $.extend(data, self.getOtherData());
  260. var request = {
  261. url: '/' + self.server() + '/api/query/parameters',
  262. dataType: 'json',
  263. type: 'POST',
  264. success: function(data) {
  265. self.updateParameters(data.parameters);
  266. $(document).trigger('fetched.parameters');
  267. },
  268. error: error_fn,
  269. data: data
  270. };
  271. $.ajax(request);
  272. };
  273. self.explainQuery = function() {
  274. $(document).trigger('explain.query', data);
  275. self.query.explain(true);
  276. self.query.isRunning(true);
  277. var data = {
  278. 'query-query': self.query.query(),
  279. 'query-database': self.database()
  280. };
  281. $.extend(data, self.getSettingsFormData());
  282. $.extend(data, self.getFileResourcesFormData());
  283. $.extend(data, self.getFunctionsFormData());
  284. $.extend(data, self.getParametersFormData());
  285. $.extend(data, self.getOtherData());
  286. var request = {
  287. url: '/' + self.server() + '/api/query/execute/?explain=true',
  288. dataType: 'json',
  289. type: 'POST',
  290. success: function(data) {
  291. self.query.errors.removeAll();
  292. if (data.status == 0) {
  293. self.query.watch.logs.removeAll();
  294. self.query.results.rows.removeAll();
  295. self.query.results.columns.removeAll();
  296. self.query.results.explanation(data.explanation);
  297. } else {
  298. self.query.errors.push(data.message);
  299. $(document).trigger('error.query');
  300. }
  301. $(document).trigger('explained.query', data);
  302. self.query.isRunning(false);
  303. },
  304. error: error_fn,
  305. data: data
  306. };
  307. $.ajax(request);
  308. };
  309. self.executeQuery = function() {
  310. $(document).trigger('execute.query', data);
  311. self.query.explain(false);
  312. self.query.isRunning(true);
  313. self.query.errors.removeAll();
  314. var data = {
  315. 'query-query': self.query.query(),
  316. 'query-database': self.database()
  317. };
  318. $.extend(data, self.getSettingsFormData());
  319. $.extend(data, self.getFileResourcesFormData());
  320. $.extend(data, self.getFunctionsFormData());
  321. $.extend(data, self.getParametersFormData());
  322. $.extend(data, self.getOtherData());
  323. var request = {
  324. url: '/' + self.server() + '/api/query/execute/',
  325. dataType: 'json',
  326. type: 'POST',
  327. success: function(data) {
  328. self.query.errors.removeAll();
  329. if (data.status == 0) {
  330. self.query.id(data.id);
  331. self.query.results.url('/' + self.server() + '/results/' + self.query.id() + '/0?format=json');
  332. self.query.watch.url(data.watch_url);
  333. self.watchQueryLoop();
  334. } else {
  335. self.query.errors.push(data.message);
  336. self.query.isRunning(false);
  337. $(document).trigger('error.query');
  338. }
  339. $(document).trigger('executed.query', data);
  340. },
  341. error: error_fn,
  342. data: data
  343. };
  344. $.ajax(request);
  345. };
  346. self.watchQuery = function() {
  347. var data = {
  348. 'query-query': self.query.query(),
  349. 'query-database': self.database()
  350. };
  351. $.extend(data, self.getSettingsFormData());
  352. $.extend(data, self.getFileResourcesFormData());
  353. $.extend(data, self.getFunctionsFormData());
  354. $.extend(data, self.getParametersFormData());
  355. $.extend(data, self.getOtherData());
  356. var request = {
  357. url: self.query.watch.url(),
  358. dataType: 'json',
  359. type: 'POST',
  360. success: function(data) {
  361. $(document).trigger('watched.query', data);
  362. },
  363. error: function(jqXHR, status, errorThrown) {
  364. self.query.isRunning(false);
  365. try {
  366. var data = $.parseJSON(jqXHR.responseText);
  367. self.query.errors.push(data.error);
  368. } catch(e) {
  369. $(document).trigger('server.unmanageable_error', jqXHR.responseText);
  370. }
  371. },
  372. data: data
  373. };
  374. $.ajax(request);
  375. };
  376. self.watchQueryLoop = function(fn) {
  377. var TIMEOUT = 1000;
  378. var timer = null;
  379. self.query.watch.logs.removeAll();
  380. self.query.watch.errors.removeAll();
  381. self.query.results.rows.removeAll();
  382. self.query.results.columns.removeAll();
  383. var _fn = function() {
  384. $(document).one('watched.query', function(e, data) {
  385. if (data.isSuccess || data.isFailure) {
  386. clearTimeout(timer);
  387. self.query.isRunning(false);
  388. if (data.log) {
  389. self.query.watch.logs.push(data.log);
  390. // scroll logs
  391. }
  392. if (!data.isFailure) {
  393. $(document).trigger('stop_watch.query');
  394. if (fn) {
  395. fn(data);
  396. } else {
  397. self.fetchResults();
  398. }
  399. }
  400. } else {
  401. if (data.log) {
  402. self.query.watch.logs.push(data.log);
  403. // scroll logs
  404. }
  405. timer = setTimeout(_fn, TIMEOUT);
  406. }
  407. });
  408. self.watchQuery();
  409. };
  410. $(document).trigger('start_watch.query');
  411. timer = setTimeout(_fn, TIMEOUT);
  412. };
  413. self.fetchResults = function() {
  414. $(document).trigger('fetch.results');
  415. var request = {
  416. url: self.query.results.url(),
  417. dataType: 'json',
  418. type: 'GET',
  419. success: function(data) {
  420. self.query.isRunning(false);
  421. if (self.query.results.columns().length == 0){
  422. self.query.results.columns(data.columns);
  423. }
  424. self.query.results.rows.push.apply(self.query.results.rows, data.results);
  425. self.query.results.empty(self.query.results.rows().length == 0);
  426. if (data.has_more) {
  427. self.query.results.url(data.next_json_set);
  428. } else {
  429. self.query.results.url(null);
  430. }
  431. $(document).trigger('fetched.results', [data]);
  432. },
  433. error: error_fn
  434. };
  435. $.ajax(request);
  436. };
  437. self.saveQuery = function() {
  438. var self = this;
  439. if (self.query.query() && self.query.name()) {
  440. var data = {
  441. 'query-query': self.query.query(),
  442. 'query-database': self.database()
  443. };
  444. $.extend(data, self.getSettingsFormData());
  445. $.extend(data, self.getFileResourcesFormData());
  446. $.extend(data, self.getFunctionsFormData());
  447. $.extend(data, self.getParametersFormData());
  448. $.extend(data, self.getOtherData());
  449. data['saveform-name'] = self.query.name();
  450. data['saveform-desc'] = self.query.description();
  451. if (self.query.id() > 0) {
  452. data['query-id'] = self.query.id();
  453. }
  454. var url = '/' + self.server() + '/api/query/';
  455. if (self.query.id() && self.query.id() != -1) {
  456. url += self.query.id();
  457. }
  458. var request = {
  459. url: url,
  460. dataType: 'json',
  461. type: 'POST',
  462. success: function(data) {
  463. self.query.id(data.design_id);
  464. $(document).trigger('saved.query', [data.design_id]);
  465. },
  466. error: function() {
  467. $(document).trigger('error_save.query');
  468. },
  469. data: data
  470. };
  471. $.ajax(request);
  472. }
  473. };
  474. self.cancelQuery = function() {
  475. $(document).trigger('cancel.query');
  476. var url = '/' + self.server() + '/api/query/' + self.query.id() + '/cancel';
  477. $.post(url,
  478. function(response) {
  479. if (response['status'] != 0) {
  480. $(document).trigger('error_cancel.query', response['message']);
  481. } else {
  482. $(document).trigger('cancelled.query');
  483. }
  484. }
  485. );
  486. };
  487. self.closeQuery = function() {
  488. var self = this;
  489. if (self.query.id()) {
  490. var data = {};
  491. var url = '/' + self.server() + '/api/query/' + self.query.id() + '/close';
  492. var request = {
  493. url: url,
  494. dataType: 'json',
  495. type: 'POST',
  496. success: function(data) {
  497. if (data.status == 0) {
  498. $(document).trigger('closed.query', data);
  499. self.resetQuery();
  500. } else {
  501. $(document).trigger('error_close.query');
  502. }
  503. },
  504. error: function(data) {
  505. $(document).trigger('error_close.results');
  506. },
  507. data: data
  508. };
  509. $.ajax(request);
  510. }
  511. };
  512. self.saveResults = function() {
  513. var self = this;
  514. if (self.query.id()) {
  515. var data = {
  516. 'type': self.query.results.save.type(),
  517. 'path': self.query.results.save.path()
  518. };
  519. var url = '/' + self.server() + '/api/query/' + self.query.id() + '/results/save';
  520. var request = {
  521. url: url,
  522. dataType: 'json',
  523. type: 'POST',
  524. success: function(data) {
  525. if (data.status == 0) {
  526. self.query.results.save.errors(null);
  527. if (data.id) {
  528. // watch this ID.
  529. self.query.watch.url(data.watch_url);
  530. self.query.watch.logs.removeAll();
  531. self.watchQueryLoop(function() {
  532. window.location.href = "/filebrowser/view" + data.path;
  533. });
  534. } else {
  535. // redirect to metastore app.
  536. window.location.href = "/metastore";
  537. }
  538. $(document).trigger('saved.results', data);
  539. } else {
  540. self.query.results.save.errors(data.errors);
  541. $(document).trigger('error_save.results');
  542. }
  543. },
  544. error: function(data) {
  545. self.query.results.save.errors(data);
  546. $(document).trigger('error_save.results');
  547. },
  548. data: data
  549. };
  550. $.ajax(request);
  551. }
  552. };
  553. // Events
  554. // Remove watched query event that watchQueryLoop may be bound to.
  555. $(document).on('server.unmanageable_error', function() {
  556. $(document).off('watched.query');
  557. });
  558. }
  559. // For routie
  560. function showSection(section) {
  561. $('.section').hide();
  562. $('#' + section).show();
  563. $(window).scrollTop(0);
  564. }
  565. // File browser button
  566. function getFileBrowseButton(inputElement) {
  567. return $("<button>").addClass("btn").addClass("fileChooserBtn").text("..").click(function (e) {
  568. e.preventDefault();
  569. $("#filechooser").jHueFileChooser({
  570. initialPath: inputElement.val(),
  571. onFileChoose: function (filePath) {
  572. inputElement.val(filePath);
  573. inputElement.trigger("change");
  574. $("#chooseFile").modal("hide");
  575. },
  576. createFolder: false
  577. });
  578. $("#chooseFile").modal("show");
  579. });
  580. }