trigger.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*!
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /* global document, CodeMirror, window, $ */
  20. let jsonForm;
  21. const objectFields = new Map();
  22. const recentConfigList = document.getElementById("recent_configs");
  23. /**
  24. * Update the generated JSON DagRun.conf JSON field if any field changed
  25. */
  26. function updateJSONconf() {
  27. const jsonStart = document.getElementById("json_start").value;
  28. const params = JSON.parse(jsonStart);
  29. const elements = document.getElementById("trigger_form");
  30. for (let i = 0; i < elements.length; i += 1) {
  31. if (elements[i].name && elements[i].name.startsWith("element_")) {
  32. const keyName = elements[i].name.substr(8);
  33. if (elements[i].type === "checkbox") {
  34. params[keyName] = elements[i].checked;
  35. } else if (
  36. elements[i].attributes.valuetype &&
  37. elements[i].attributes.valuetype.value === "array"
  38. ) {
  39. const lines = elements[i].value.split("\n");
  40. const values = [];
  41. for (let j = 0; j < lines.length; j += 1) {
  42. if (lines[j].trim().length > 0) {
  43. values[values.length] = lines[j].trim();
  44. }
  45. }
  46. params[keyName] = values.length === 0 ? null : values;
  47. } else if (
  48. elements[i].attributes.valuetype &&
  49. elements[i].attributes.valuetype.value === "multiselect"
  50. ) {
  51. const { options } = elements[i];
  52. const values = [];
  53. for (let j = 0; j < options.length; j += 1) {
  54. if (options[j].selected) {
  55. values[values.length] = options[j].value;
  56. }
  57. }
  58. params[keyName] = values.length === 0 ? null : values;
  59. } else if (
  60. elements[i].attributes.valuetype &&
  61. (elements[i].attributes.valuetype.value === "object" ||
  62. elements[i].attributes.valuetype.value === "advancedarray")
  63. ) {
  64. try {
  65. const textValue = objectFields.get(elements[i].name).getValue();
  66. if (textValue.length > 0) {
  67. const objValue = JSON.parse(textValue);
  68. params[keyName] = objValue;
  69. objectFields
  70. .get(elements[i].name)
  71. .setValue(JSON.stringify(objValue, null, 4));
  72. } else {
  73. params[keyName] = null;
  74. }
  75. } catch (e) {
  76. // ignore JSON parsing errors
  77. // we don't want to bother users during entry, error will be displayed before submit
  78. }
  79. } else if (elements[i].value.length === 0) {
  80. params[keyName] = null;
  81. } else if (Number.isNaN(elements[i].value)) {
  82. params[keyName] = elements[i].value;
  83. } else if (
  84. elements[i].attributes.valuetype &&
  85. elements[i].attributes.valuetype.value === "number"
  86. ) {
  87. params[keyName] = Number(elements[i].value);
  88. } else {
  89. params[keyName] = elements[i].value;
  90. }
  91. }
  92. }
  93. jsonForm.setValue(JSON.stringify(params, null, 4));
  94. }
  95. /**
  96. * If the user hits ENTER key inside an input, ensure JSON data is updated.
  97. */
  98. function handleEnter() {
  99. updateJSONconf();
  100. // somehow following is needed to enforce form is submitted correctly from CodeMirror
  101. document.getElementById("json").value = jsonForm.getValue();
  102. }
  103. /**
  104. * Track user changes in input fields, ensure JSON is updated when user presses enter
  105. * See https://github.com/apache/airflow/issues/42157
  106. */
  107. function enterInputField() {
  108. const form = document.getElementById("trigger_form");
  109. form.addEventListener("submit", handleEnter);
  110. }
  111. /**
  112. * Stop tracking user changes in input fields
  113. */
  114. function leaveInputField() {
  115. const form = document.getElementById("trigger_form");
  116. form.removeEventListener("submit", handleEnter);
  117. updateJSONconf();
  118. }
  119. /**
  120. * Initialize the form during load of the web page
  121. */
  122. function initForm() {
  123. const formSectionsElement = document.getElementById("form_sections");
  124. const formHasFields = formSectionsElement != null;
  125. // Initialize the Generated JSON form or JSON entry form
  126. const minHeight = 300;
  127. const maxHeight =
  128. (formHasFields ? window.innerHeight / 2 : window.innerHeight) - 550;
  129. const height = maxHeight > minHeight ? maxHeight : minHeight;
  130. jsonForm = CodeMirror.fromTextArea(document.getElementById("json"), {
  131. lineNumbers: true,
  132. mode: { name: "javascript", json: true },
  133. gutters: ["CodeMirror-lint-markers"],
  134. lint: true,
  135. indentUnit: 4,
  136. });
  137. jsonForm.setSize(null, height);
  138. if (formHasFields) {
  139. // Initialize jQuery and Chakra fields
  140. const elements = document.getElementById("trigger_form");
  141. for (let i = 0; i < elements.length; i += 1) {
  142. if (elements[i].name && elements[i].name.startsWith("element_")) {
  143. if (
  144. elements[i].attributes.valuetype &&
  145. (elements[i].attributes.valuetype.value === "object" ||
  146. elements[i].attributes.valuetype.value === "advancedarray")
  147. ) {
  148. // Apply JSON formatting and linting to all object fields in the form
  149. const field = CodeMirror.fromTextArea(elements[i], {
  150. lineNumbers: true,
  151. mode: { name: "javascript", json: true },
  152. gutters: ["CodeMirror-lint-markers"],
  153. lint: true,
  154. indentUnit: 4,
  155. });
  156. field.on("blur", updateJSONconf);
  157. objectFields.set(elements[i].name, field);
  158. } else if (elements[i].nodeName === "SELECT") {
  159. // Activate select2 multi select boxes
  160. const elementId = `#${elements[i].name}`;
  161. $(elementId).select2({
  162. placeholder: "Select Values",
  163. allowClear: true,
  164. });
  165. elements[i].addEventListener("blur", updateJSONconf);
  166. } else if (elements[i].type === "checkbox") {
  167. elements[i].addEventListener("change", updateJSONconf);
  168. } else {
  169. elements[i].addEventListener("focus", enterInputField);
  170. elements[i].addEventListener("blur", leaveInputField);
  171. }
  172. }
  173. }
  174. // Refreshes JSON entry box sizes when they toggle display
  175. const formSections = formSectionsElement.value.split(",");
  176. for (let i = 0; i < formSections.length; i += 1) {
  177. const toggleBlock = document.getElementById(`${formSections[i]}_toggle`);
  178. if (toggleBlock) {
  179. toggleBlock.addEventListener("click", () => {
  180. setTimeout(() => {
  181. objectFields.forEach((cm) => {
  182. cm.refresh();
  183. });
  184. }, 300);
  185. });
  186. }
  187. }
  188. // Validate JSON entry fields before submission
  189. elements.addEventListener("submit", (event) => {
  190. updateJSONconf();
  191. objectFields.forEach((cm) => {
  192. const textValue = cm.getValue();
  193. try {
  194. if (textValue.trim().length > 0) {
  195. JSON.parse(textValue);
  196. }
  197. } catch (ex) {
  198. // eslint-disable-next-line no-alert
  199. window.alert(`Invalid JSON entered, please correct:\n\n${textValue}`);
  200. cm.focus();
  201. event.preventDefault();
  202. }
  203. });
  204. });
  205. // Ensure layout is refreshed on generated JSON as well
  206. document
  207. .getElementById("generated_json_toggle")
  208. .addEventListener("click", () => {
  209. setTimeout(() => {
  210. jsonForm.refresh();
  211. }, 300);
  212. });
  213. // Update generated conf once
  214. setTimeout(updateJSONconf, 100);
  215. }
  216. }
  217. $(document).ready(() => {
  218. initForm();
  219. });
  220. window.updateJSONconf = updateJSONconf;
  221. function setRecentConfig(e) {
  222. const dropdownValue = e.target.value;
  223. let dropdownJson;
  224. let value;
  225. try {
  226. dropdownJson = JSON.parse(dropdownValue);
  227. value = JSON.stringify(dropdownJson, null, 4);
  228. } catch (err) {
  229. // eslint-disable-next-line no-console
  230. console.error(`config is not valid JSON format: ${dropdownValue}`);
  231. }
  232. // Form fields need to be populated from recent config
  233. const keys = Object.keys(dropdownJson);
  234. for (let i = 0; i < keys.length; i += 1) {
  235. const element = document.getElementById(`element_${keys[i]}`);
  236. if (element) {
  237. const newValue = dropdownJson[keys[i]];
  238. if (element.type === "checkbox") {
  239. element.checked = newValue;
  240. } else if (newValue === "" || newValue == null) {
  241. element.value = "";
  242. } else if (
  243. element.attributes.valuetype &&
  244. element.attributes.valuetype.value === "array"
  245. ) {
  246. element.value = newValue.join("\n");
  247. } else if (
  248. element.attributes.valuetype &&
  249. (element.attributes.valuetype.value === "object" ||
  250. element.attributes.valuetype.value === "advancedarray")
  251. ) {
  252. objectFields
  253. .get(`element_${keys[i]}`)
  254. .setValue(JSON.stringify(newValue, null, 4));
  255. } else if (element.nodeName === "SELECT") {
  256. $(`#${element.name}`).select2("val", [newValue]);
  257. } else {
  258. element.value = newValue;
  259. }
  260. }
  261. }
  262. // Populate JSON field
  263. jsonForm.setValue(value);
  264. }
  265. if (recentConfigList) {
  266. recentConfigList.addEventListener("change", setRecentConfig);
  267. }