@processmaker/screen-builder 2.89.0 → 2.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +5 -4
  2. package/dist/vue-form-builder.css +1 -1
  3. package/dist/vue-form-builder.es.js +7887 -7457
  4. package/dist/vue-form-builder.es.js.map +1 -1
  5. package/dist/vue-form-builder.umd.js +74 -57
  6. package/dist/vue-form-builder.umd.js.map +1 -1
  7. package/package.json +3 -3
  8. package/src/DataProvider.js +28 -8
  9. package/src/assets/icons/Bypass.svg +5 -0
  10. package/src/assets/icons/Unbypass.svg +5 -0
  11. package/src/components/accordions.js +1 -0
  12. package/src/components/computed-properties.vue +211 -110
  13. package/src/components/index.js +2 -0
  14. package/src/components/renderer/form-list-table.vue +6 -1
  15. package/src/components/renderer/form-requests.vue +8 -2
  16. package/src/components/renderer/form-tasks.vue +25 -22
  17. package/src/components/renderer/index.js +1 -0
  18. package/src/components/renderer/link-button.vue +30 -0
  19. package/src/components/sortable/Sortable.vue +95 -13
  20. package/src/components/sortable/sortable.scss +5 -0
  21. package/src/components/sortable/sortableList/SortableList.vue +103 -36
  22. package/src/components/sortable/sortableList/sortableList.scss +63 -22
  23. package/src/components/task.vue +256 -59
  24. package/src/components/vue-form-builder.vue +19 -10
  25. package/src/components/watchers-form.vue +4 -3
  26. package/src/components/watchers-list.vue +46 -100
  27. package/src/components/watchers-popup.vue +89 -16
  28. package/src/customLogs.js +26 -0
  29. package/src/form-builder-controls.js +42 -0
  30. package/src/main.js +26 -1
  31. package/src/mixins/ScreenBase.js +1 -0
  32. package/src/mixins/computedFields.js +25 -7
  33. package/src/mixins/extensions/ComputedFields.js +26 -5
  34. package/src/mixins/extensions/Watchers.js +4 -0
  35. package/src/mixins/watchers.js +5 -2
  36. package/src/stories/Sortable.stories.js +58 -11
@@ -1,67 +1,47 @@
1
1
  <template>
2
- <div>
3
- <b-row class="mb-3">
4
- <b-col>
5
- <basic-search v-model="filter" @submit="search">
6
- <template slot="buttons">
7
- <b-btn class="text-nowrap" variant="secondary" @click.stop="displayFormProperty" data-cy="watchers-add-watcher">
8
- <i class="fas fa-plus" />
9
- {{ $t('Watcher') }}
10
- </b-btn>
11
- </template>
12
- </basic-search>
13
- </b-col>
14
- </b-row>
15
-
16
- <div class="card card-body table-card watchers-list">
17
- <b-table
18
- :items="filtered"
19
- :fields="fields"
20
- :empty-text="$t('No Data Available')"
21
- data-cy="watchers-table"
22
- sort-icon-left
2
+ <Sortable
3
+ :fields="fields"
4
+ :items="value"
5
+ disable-key="byPass"
6
+ :inline-edit="false"
7
+ :data-test-actions="{
8
+ tableBox: { 'data-cy': 'watchers-table' },
9
+ btnNew: { 'data-cy': 'watchers-add-watcher' },
10
+ btnEdit: { 'data-cy': 'watchers-table-edit' },
11
+ btnDelete: { 'data-cy': 'watchers-table-remove' },
12
+ }"
13
+ @item-edit="editProperty"
14
+ @item-delete="deleteProperty"
15
+ @add-page="displayFormProperty"
16
+ @ordered="$emit('ordered', $event)"
17
+ :searchProperties= "searchProperties"
18
+ >
19
+ <template #options="{ item }">
20
+ <button
21
+ v-b-tooltip="{ customClass: 'bypass-btn-tooltip' }"
22
+ :title="item.byPass ? $t('Unbypass Watcher') : $t('Bypass Watcher')"
23
+ class="btn"
24
+ data-test="watchers-bypass"
25
+ @click.prevent="$emit('toggle-bypass', item.uid)"
23
26
  >
24
- <template #cell(actions)="{item}">
25
- <div class="actions">
26
- <div class="popout">
27
- <b-btn
28
- variant="link"
29
- @click="editProperty(item)"
30
- v-b-tooltip.hover
31
- :title="$t('Edit')"
32
- data-cy="watchers-table-edit"
33
- >
34
- <i class="fas fa-edit fa-lg fa-fw"/>
35
- </b-btn>
36
- <b-btn
37
- variant="link"
38
- @click="deleteProperty(item)"
39
- v-b-tooltip.hover
40
- :title="$t('Delete')"
41
- data-cy="watchers-table-remove"
42
- >
43
- <i class="fas fa-trash-alt fa-lg fa-fw"/>
44
- </b-btn>
45
- </div>
46
- </div>
47
- </template>
48
- </b-table>
49
- </div>
50
- <template slot="modal-footer">
51
- <span />
27
+ <img :src="getByPassIcon(item)" alt="Bypass" width="24" />
28
+ </button>
29
+ <div class="sortable-item-vr"></div>
52
30
  </template>
53
- </div>
31
+ </Sortable>
54
32
  </template>
55
33
 
56
34
  <script>
57
35
  import BasicSearch from './basic-search';
58
36
  import { FormInput, FormTextArea } from '@processmaker/vue-form-elements';
37
+ import Sortable from './sortable/Sortable.vue';
59
38
 
60
39
  export default {
61
40
  components: {
62
41
  BasicSearch,
63
42
  FormInput,
64
43
  FormTextArea,
44
+ Sortable,
65
45
  },
66
46
  props: {
67
47
  value: {
@@ -90,7 +70,6 @@ export default {
90
70
  {
91
71
  label: this.$t('Name'),
92
72
  key: 'name',
93
- tdClass: 'break-word',
94
73
  },
95
74
  {
96
75
  label: this.$t('Watching Variable'),
@@ -99,39 +78,16 @@ export default {
99
78
  {
100
79
  label: this.$t('Output Variable'),
101
80
  key: 'output_variable',
102
- tdClass: 'break-word',
103
81
  },
104
82
  {
105
83
  label: this.$t('Source'),
106
84
  key: 'script.title',
107
85
  },
108
- {
109
- key: 'actions',
110
- label: '',
111
- },
112
86
  ],
87
+ searchProperties: ['name', 'output_variable', 'watching', 'script.title'],
113
88
  };
114
89
  },
115
- computed: {
116
- filtered() {
117
- const list = this.getValuesWithOutputVarsNames(this.value);
118
-
119
- if (!this.filter) {
120
- return list;
121
- }
122
- const filtered = [];
123
- list.forEach(item => {
124
- if (Object.keys(item).find(key => typeof item[key] === 'string' ? item[key].indexOf(this.filter)>=0 : false)) {
125
- filtered.push(item);
126
- }
127
- });
128
- return filtered;
129
- },
130
- },
131
90
  methods: {
132
- search() {
133
-
134
- },
135
91
  displayFormProperty() {
136
92
  this.$emit('display-form');
137
93
  },
@@ -141,36 +97,26 @@ export default {
141
97
  deleteProperty(item) {
142
98
  this.$emit('delete-form', item);
143
99
  },
144
- getValuesWithOutputVarsNames(values) {
145
- let list = values.map(watcher => {
146
- let newItem = Object.assign({}, watcher);
147
- // If watcher is a data source, extract the output vars
148
- if (newItem.script && newItem.script.id && newItem.script.id.substr(0, 11) === 'data_source') {
149
- let scriptConfig = JSON.parse(newItem.script_configuration);
150
- let vars = (scriptConfig && scriptConfig.dataMapping)
151
- ? scriptConfig.dataMapping.map(mapping => mapping.key).join(', ')
152
- : '';
153
-
154
- // var names string won't have more than 50 characters to avoid distorting the UI
155
- const maxLen = 50;
156
- newItem.output_variable = vars.length > maxLen
157
- ? vars.substr(0, maxLen) + '...'
158
- : vars;
159
- }
160
- return newItem;
161
- });
162
- return list;
100
+ getByPassIcon(item) {
101
+ return new URL(
102
+ `../assets/icons/${item.byPass ? 'Unbypass' : 'Bypass'}.svg`,
103
+ import.meta.url,
104
+ ).href;
163
105
  },
164
106
  },
165
107
  };
166
108
  </script>
167
109
 
168
- <style>
169
- .watchers-list .break-word {
170
- word-break: break-word;
171
- }
110
+ <style lang="scss" scoped>
111
+ .bypass-btn-tooltip::v-deep {
112
+ & .tooltip-inner {
113
+ background-color: #EBEEF2 !important;
114
+ color: #444444 !important;
115
+ }
172
116
 
173
- .watchers-list .table {
174
- margin-bottom: 0;
117
+ & .arrow:before {
118
+ border-top-color: #EBEEF2 !important;
119
+ border-bottom-color: #EBEEF2 !important;
120
+ }
175
121
  }
176
122
  </style>
@@ -1,17 +1,41 @@
1
1
  <template>
2
2
  <b-modal
3
3
  ref="modal"
4
- size="lg"
4
+ :size="modalSize"
5
5
  id="watchers-popup"
6
- :title="$t('Watchers')"
7
6
  @hidden="displayList"
8
7
  hide-footer
9
8
  header-close-content="&times;"
10
9
  no-close-on-backdrop
11
10
  data-cy="watchers-modal"
12
11
  >
12
+ <template #modal-title>
13
+ {{ $t('Watchers') }}
14
+ <small class="d-block my-2 modal-subtitle">
15
+ {{ $t('Manage your active watchers for this screen') }}
16
+ </small>
17
+ </template>
13
18
  <template v-if="enableList">
14
- <watchers-list v-model="current" @display-form="displayForm" @edit-form="edit" @delete-form="confirmRemoval"/>
19
+ <watchers-list
20
+ v-model="current"
21
+ @display-form="displayForm"
22
+ @edit-form="edit"
23
+ @delete-form="confirmRemoval"
24
+ @toggle-bypass="toggleBypass"
25
+ @ordered="$emit('input', $event)"
26
+ />
27
+
28
+ <div class="d-flex justify-content-end mt-3 mr-1">
29
+ <div class="d-flex align-items-end">
30
+ <button
31
+ class="btn btn-secondary ml-3 text-uppercase"
32
+ data-cy="calcs-button-close"
33
+ @click="$refs.modal.hide()"
34
+ >
35
+ {{ $t("Done") }}
36
+ </button>
37
+ </div>
38
+ </div>
15
39
  </template>
16
40
  <template v-else>
17
41
  <required />
@@ -37,34 +61,75 @@ export default {
37
61
  WatchersList,
38
62
  WatchersForm,
39
63
  },
40
- props: ['value'],
64
+ props: {
65
+ value: {
66
+ type: Array,
67
+ required: true,
68
+ },
69
+ },
41
70
  data() {
42
71
  return {
43
72
  enableList: true,
44
73
  current: [],
45
74
  add: {
46
- uid:'',
47
- name:'',
48
- variable:'',
49
- script_id:'',
50
- script_key:'',
51
- input_data:'',
52
- script_configuration:'',
53
- synchronous:false,
75
+ uid: '',
76
+ name: '',
77
+ variable: '',
78
+ script_id: '',
79
+ script_key: '',
80
+ input_data: '',
81
+ script_configuration: '',
82
+ synchronous: false,
83
+ byPass: false,
54
84
  },
55
85
  };
56
86
  },
87
+ computed: {
88
+ modalSize() {
89
+ return "xl";
90
+ },
91
+ },
57
92
  watch: {
58
93
  value: {
59
94
  handler(value) {
60
- this.current = value;
95
+ this.current = this.getValuesWithOutputVarsNames(value);
61
96
  },
62
97
  },
63
- },
64
- computed: {
65
-
66
98
  },
67
99
  methods: {
100
+ getValuesWithOutputVarsNames(values) {
101
+ const list = values.map((watcher) => {
102
+ const newItem = { ...watcher };
103
+
104
+ if (!Object.hasOwn(newItem, 'byPass')) {
105
+ newItem.byPass = false;
106
+ }
107
+
108
+ // If watcher is a data source, extract the output vars
109
+ if (newItem?.script?.id?.substr(0, 11) === 'data_source') {
110
+ const scriptConfig = JSON.parse(newItem.script_configuration);
111
+ const vars = scriptConfig?.dataMapping
112
+ ? scriptConfig.dataMapping.map((mapping) => mapping.key).join(', ')
113
+ : '';
114
+
115
+ // var names string won't have more than 50 characters to avoid distorting the UI
116
+ const maxLen = 50;
117
+ newItem.output_variable =
118
+ vars.length > maxLen ? `${vars.substr(0, maxLen)}...` : vars;
119
+ }
120
+
121
+ return newItem;
122
+ });
123
+
124
+ return list;
125
+ },
126
+ toggleBypass(itemUid) {
127
+ this.current = this.current.map((item) =>
128
+ item.uid === itemUid ? { ...item, byPass: !item.byPass } : item,
129
+ );
130
+
131
+ this.$emit('input', this.current);
132
+ },
68
133
  show() {
69
134
  this.$refs.modal.show();
70
135
  },
@@ -118,3 +183,11 @@ export default {
118
183
  },
119
184
  };
120
185
  </script>
186
+
187
+ <style lang="scss" scoped>
188
+ .modal-subtitle {
189
+ color: #556271;
190
+ font-size: 1rem;
191
+ font-weight: 400;
192
+ }
193
+ </style>
@@ -0,0 +1,26 @@
1
+ export default class CustomLog {
2
+ static logWithStyle(icon, type, name, status, message, color) {
3
+ const baseColor = color === '255, 0, 0' ? 'red' : 'green';
4
+ const style = `background-color: rgba(${color}, 0.1); color: ${baseColor}; padding: 2px 4px; border-radius: 3px;`;
5
+ const transparentStyle = 'background-color: transparent';
6
+
7
+ const logPrefix = `%c${icon} %c${type} "${name}" has`;
8
+ const logStatus = `%c${status}`;
9
+
10
+ if (status === 'RUN') {
11
+ console.log(`${logPrefix} ${logStatus}`, style, transparentStyle, style);
12
+ } else if (status === 'FAILED') {
13
+ console.groupCollapsed(`${logPrefix} ${logStatus}`, style, transparentStyle, style);
14
+ console.log(`%c${message}`, style);
15
+ console.groupEnd();
16
+ }
17
+ }
18
+
19
+ static success(type, name) {
20
+ this.logWithStyle('\u2705', type, name, 'RUN', '', '0, 128, 0');
21
+ }
22
+
23
+ static error(type, name, message) {
24
+ this.logWithStyle('\u274C', type, name, 'FAILED', message, '255, 0, 0');
25
+ }
26
+ }
@@ -23,6 +23,7 @@ import {
23
23
  BWrapperComponent,
24
24
  } from '@processmaker/vue-form-elements';
25
25
  import { dataSourceValues } from '@/components/inspector/data-source-types';
26
+ import LinkButton from "./components/renderer/link-button.vue";
26
27
 
27
28
  import {
28
29
  bgcolorProperty,
@@ -990,5 +991,46 @@ export default [
990
991
  }
991
992
  ]
992
993
  }
994
+ },
995
+ {
996
+ editorComponent: LinkButton,
997
+ editorBinding: 'LinkButton',
998
+ rendererComponent: LinkButton,
999
+ rendererBinding: "LinkButton",
1000
+
1001
+ control: {
1002
+ popoverContent: "Add a URL where this link should redirect",
1003
+ group: 'Content Fields',
1004
+ order: 6.0,
1005
+ label: "Link URL",
1006
+ component: "LinkButton",
1007
+ "editor-component": "LinkButton",
1008
+ "editor-control": "LinkButton",
1009
+ config: {
1010
+ label: "New Link",
1011
+ icon: "fas fa-link",
1012
+ variant: "primary",
1013
+ event: "link",
1014
+ },
1015
+ inspector: [
1016
+ {
1017
+ type: 'FormInput',
1018
+ field: 'label',
1019
+ config: {
1020
+ label: 'Label',
1021
+ helper: 'The label describes the button\'s text',
1022
+ },
1023
+ },
1024
+ {
1025
+ type: 'FormInput',
1026
+ field: 'linkUrl',
1027
+ config: {
1028
+ label: 'Link URL',
1029
+ helper: 'Type here the URL link. Mustache syntax is supported.',
1030
+ },
1031
+ },
1032
+ buttonVariantStyleProperty
1033
+ ]
1034
+ }
993
1035
  }
994
1036
  ];
package/src/main.js CHANGED
@@ -270,8 +270,8 @@ window.Echo = {
270
270
  }
271
271
  };
272
272
 
273
+ // Setup axios
273
274
  window.axios = axios.create({
274
- baseURL: "/api/1.0/",
275
275
  adapter: cacheAdapterEnhancer(axios.getAdapter(axios.defaults.adapter), {
276
276
  enabledByDefault: window.ProcessMaker.screen.cacheEnabled,
277
277
  cacheFlag: "useCache",
@@ -282,6 +282,31 @@ window.axios = axios.create({
282
282
  })
283
283
  });
284
284
 
285
+ // Setup api versions
286
+ const apiVersionConfig = [
287
+ { version: "1.0", baseURL: "/api/1.0/" },
288
+ { version: "1.1", baseURL: "/api/1.1/" },
289
+ ];
290
+
291
+ window.axios.defaults.baseURL = apiVersionConfig[0].baseURL;
292
+ window.axios.interceptors.request.use((config) => {
293
+ if (typeof config.url !== "string" || !config.url) {
294
+ throw new Error("Invalid URL in the request configuration");
295
+ }
296
+
297
+ apiVersionConfig.forEach(({ version, baseURL }) => {
298
+ const versionPrefix = `/api/${version}/`;
299
+ if (config.url.startsWith(versionPrefix)) {
300
+ // eslint-disable-next-line no-param-reassign
301
+ config.baseURL = baseURL;
302
+ // eslint-disable-next-line no-param-reassign
303
+ config.url = config.url.replace(versionPrefix, "");
304
+ }
305
+ });
306
+
307
+ return config;
308
+ });
309
+
285
310
  const searchParams = new URLSearchParams(window.location.search);
286
311
 
287
312
  const scenario = searchParams?.get("scenario");
@@ -328,6 +328,7 @@ export default {
328
328
  debouncedValuesQueue.forEach((args) => {
329
329
  this.setValue(...args);
330
330
  });
331
+ debouncedValuesQueue.length = 0;
331
332
  }, 210);
332
333
  this.setValueDebounced = (...args) => {
333
334
  debouncedValuesQueue.push(args);
@@ -1,4 +1,5 @@
1
1
  import { Parser } from "expr-eval";
2
+ import CustomLog from '../customLogs';
2
3
 
3
4
  export default {
4
5
  methods: {
@@ -9,7 +10,6 @@ export default {
9
10
  // Monitor if variable belongs to data (defined variables) or
10
11
  // vdata (external variables)in this way the event is not
11
12
  // executed again when the variable is update
12
-
13
13
  const data = this.getDataReference(null, () => {
14
14
  throw new Error(
15
15
  "You are not allowed to set properties from inside an expression"
@@ -19,18 +19,36 @@ export default {
19
19
  if (type === "expression") {
20
20
  value = Parser.evaluate(expression, data);
21
21
  } else {
22
+ // Create a new function with the expression and bind the data context
22
23
  // eslint-disable-next-line no-new-func
23
- value = new Function(expression).bind(data)();
24
+ value = new Function(expression).bind(data);
25
+ return { result: value(), error: null };
24
26
  }
25
27
 
26
28
  if (value instanceof Date) {
27
29
  value = value.toISOString();
28
30
  }
29
-
30
- return value;
31
- } catch (e) {
32
- console.warn("There was a problem evaluating the expression", e);
31
+ return { result: value, error: null };
32
+ } catch (error) {
33
+ // Catch any errors and return them
34
+ return { result: null, error };
33
35
  }
34
- }
36
+ },
37
+ /**
38
+ * Logs an error message with a custom format.
39
+ * @param {string} name - The name of the calculation.
40
+ * @param {string} message - The error message.
41
+ */
42
+ customErrorLog(name, message) {
43
+ CustomLog.error('Calc', name, message);
44
+ },
45
+
46
+ /**
47
+ * Logs a success message with a custom format.
48
+ * @param {string} name - The name of the calculation.
49
+ */
50
+ customSuccessLog(name) {
51
+ CustomLog.success('Calc', name);
52
+ },
35
53
  }
36
54
  };
@@ -9,18 +9,39 @@ export default {
9
9
  * this.setValueAsync("calcProperty", value, this.vdata);
10
10
  * }
11
11
  */
12
- computedFields(screen, definition) {
12
+ computedFields(screen, definition, logsEnabled = true) {
13
13
  // For each computed field defined
14
14
  definition.computed.forEach((computed) => {
15
+ if (computed.byPass) {
16
+ // If the computed field has bypass set to true, skip it
17
+ return;
18
+ }
15
19
  const formula = JSON.stringify(computed.formula);
16
20
  const type = JSON.stringify(computed.type);
17
21
  const name = JSON.stringify(computed.property);
18
22
  const safeDotName = this.safeDotName(computed.property);
19
23
  const code = `
20
- let value = this.evaluateExpression(${formula}, ${type});
21
- value = this.addNonDefinedComputedAttributes(value);
22
- this.setValue(${name}, value, this.vdata);
23
- return value;`;
24
+ const evaluatedExpression = this.evaluateExpression(${formula}, ${type});
25
+ // Handle errors if any
26
+ if (evaluatedExpression.error) {
27
+ if (${logsEnabled}) {
28
+ this.customErrorLog(${name}, evaluatedExpression.error);
29
+ }
30
+ } else {
31
+ // Add non-defined computed attributes
32
+ const value = this.addNonDefinedComputedAttributes(evaluatedExpression.result);
33
+ // Set the value
34
+ this.setValue(${name}, value, this.vdata);
35
+
36
+ // Log the successful calculation if logging is enabled
37
+ if (${logsEnabled}) {
38
+ this.customSuccessLog(${name});
39
+ }
40
+
41
+ // Return the result
42
+ return value;
43
+ }
44
+ `;
24
45
  this.addComputed(screen, safeDotName, code, "");
25
46
  // required to enable reactivity of computed field
26
47
  this.addWatch(screen, safeDotName, "");
@@ -20,6 +20,10 @@ export default {
20
20
  if (definition.watchers) {
21
21
  screen.mixins.push(watchersMixin);
22
22
  definition.watchers.filter(this.filterWatchers).forEach((watcher) => {
23
+ if (watcher?.byPass) {
24
+ // If the watcher has bypass set to true, skip it
25
+ return;
26
+ }
23
27
  this.addMounted(screen, `
24
28
  this.$nextTick(() => this.$watch('vdata.${watcher.watching}', (newValue) => {
25
29
  if (typeof newValue !== 'undefined') {
@@ -1,5 +1,6 @@
1
1
  import Mustache from 'mustache';
2
2
  import _ from 'lodash';
3
+ import CustomLog from '../customLogs';
3
4
 
4
5
  const broadcastEvent = '.Illuminate\\\\Notifications\\\\Events\\\\BroadcastNotificationCreated';
5
6
 
@@ -44,7 +45,7 @@ export default {
44
45
  // Data Source
45
46
  const requestId = _.get(this.vdata, '_request.id', null);
46
47
  const params = { config: JSON.parse(config), data: this.vdata };
47
-
48
+
48
49
  this.$dataProvider.postDataSource(scriptId, requestId, params).then(response => {
49
50
  this.$emit('asyncWatcherCompleted');
50
51
  complete(response.data);
@@ -67,6 +68,7 @@ export default {
67
68
  });
68
69
  }
69
70
  }).then((response) => {
71
+ CustomLog.success('Watcher', watcher.name);
70
72
  // If watcher has an output variable and is a script
71
73
  if (watcher.output_variable && (watcher.script_key || '').length === 0) {
72
74
  this.setValue(watcher.output_variable, response, this.vdata);
@@ -94,6 +96,7 @@ export default {
94
96
  }
95
97
  return response;
96
98
  }).catch(error => {
99
+ CustomLog.error('Watcher', watcher.name, error.message);
97
100
  const message = _.get(error, 'response.data.message', error.message);
98
101
  if (watcher.synchronous) {
99
102
  this.$parent.$refs.watchersSynchronous.error(message);
@@ -135,4 +138,4 @@ export default {
135
138
  destroyed() {
136
139
  this.cleanEchoListeners();
137
140
  },
138
- };
141
+ };