@fleetbase/ember-ui 0.3.11 → 0.3.12

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 (39) hide show
  1. package/addon/components/chart.js +10 -10
  2. package/addon/components/click-to-reveal.hbs +1 -1
  3. package/addon/components/click-to-reveal.js +5 -23
  4. package/addon/components/dashboard/create.hbs +5 -3
  5. package/addon/components/dashboard/widget-panel.hbs +17 -7
  6. package/addon/components/dashboard/widget-panel.js +61 -13
  7. package/addon/components/dashboard.js +0 -1
  8. package/addon/components/layout/sidebar/item.hbs +2 -2
  9. package/addon/components/lazy-engine-component.hbs +8 -0
  10. package/addon/components/lazy-engine-component.js +136 -0
  11. package/addon/components/load-engine.hbs +1 -0
  12. package/addon/components/load-engine.js +82 -0
  13. package/addon/components/modals/query-builder-computed-column-editor.js +102 -7
  14. package/addon/components/pill.js +0 -1
  15. package/addon/components/query-builder/computed-columns.js +0 -2
  16. package/addon/components/registry-yield.hbs +7 -1
  17. package/addon/components/registry-yield.js +107 -15
  18. package/addon/components/report-builder.js +2 -1
  19. package/addon/components/tab-navigation.hbs +78 -38
  20. package/addon/components/tab-navigation.js +22 -9
  21. package/addon/components/table.hbs +20 -13
  22. package/addon/helpers/avatar-url.js +9 -0
  23. package/addon/helpers/lazy-engine-component.js +116 -0
  24. package/addon/helpers/point-coordinates.js +10 -0
  25. package/addon/helpers/point-to-coordinates.js +32 -0
  26. package/addon/helpers/resolve-component.js +18 -0
  27. package/addon/helpers/set-object-kv.js +9 -0
  28. package/addon/helpers/unwrap-coordinates.js +249 -0
  29. package/addon/services/dashboard.js +39 -17
  30. package/addon/styles/components/table.css +57 -0
  31. package/app/components/lazy-engine-component.js +1 -0
  32. package/app/components/load-engine.js +1 -0
  33. package/app/helpers/avatar-url.js +1 -0
  34. package/app/helpers/lazy-engine-component.js +1 -0
  35. package/app/helpers/point-coordinates.js +1 -0
  36. package/app/helpers/point-to-coordinates.js +1 -0
  37. package/app/helpers/set-object-kv.js +1 -0
  38. package/app/helpers/unwrap-coordinates.js +1 -0
  39. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { action } from '@ember/object';
4
+ import { debug } from '@ember/debug';
4
5
  import Chart, { _adapters } from 'chart.js/auto';
5
6
  import {
6
7
  parse,
@@ -46,13 +47,7 @@ import {
46
47
 
47
48
  export default class ChartComponent extends Component {
48
49
  @tracked chart;
49
- @tracked isLoading;
50
-
51
- constructor() {
52
- super(...arguments);
53
-
54
- this.isLoading = this.args.isLoading === true;
55
- }
50
+ @tracked isLoading = this.args.isLoading;
56
51
 
57
52
  @action async renderChart(ctx) {
58
53
  const options = {
@@ -65,9 +60,14 @@ export default class ChartComponent extends Component {
65
60
  };
66
61
 
67
62
  if (typeof options.data.datasets === 'function') {
68
- this.isLoading = true;
69
- options.data.datasets = await options.data.datasets();
70
- this.isLoading = false;
63
+ try {
64
+ this.isLoading = true;
65
+ options.data.datasets = await options.data.datasets();
66
+ } catch (err) {
67
+ debug('Error loading Chart dataset: ' + err.message);
68
+ } finally {
69
+ this.isLoading = false;
70
+ }
71
71
  }
72
72
 
73
73
  this.useDateFns();
@@ -1,4 +1,4 @@
1
- <div class="relative click-to-reveal {{if this.isVisible 'is-visible' 'is-blurred'}}" {{on "click" (fn this.copy @value)}} ...attributes>
1
+ <div class="relative click-to-reveal {{this.wrapperClass}} {{if this.isVisible 'is-visible' 'is-blurred'}}" {{on "click" (fn this.copy @value)}} ...attributes>
2
2
  <span class="click-to-reveal--hidden-value">
3
3
  {{#if (has-block)}}
4
4
  {{yield}}
@@ -4,33 +4,11 @@ import { action, computed } from '@ember/object';
4
4
  import { later } from '@ember/runloop';
5
5
 
6
6
  export default class ClickToRevealComponent extends ClickToCopyComponent {
7
- /**
8
- * The visiblity state of the value
9
- *
10
- * @var {Boolean}
11
- */
12
7
  @tracked isVisible = false;
13
-
14
- /**
15
- * The loading state of the reveal process
16
- *
17
- * @var {Boolean}
18
- */
19
8
  @tracked isLoading = false;
20
-
21
- /**
22
- * The loading timing
23
- *
24
- * @var {Integer}
25
- */
26
9
  @tracked timeout = 600;
27
-
28
- /**
29
- * If click to copy should be enabled.
30
- *
31
- * @var {Boolean}
32
- */
33
10
  @tracked clickToCopy = false;
11
+ @tracked wrapperClass = this.args.wrapperClass ?? '';
34
12
 
35
13
  /**
36
14
  * Setup the component
@@ -47,6 +25,10 @@ export default class ClickToRevealComponent extends ClickToCopyComponent {
47
25
  if (column && column.cellComponentArgs) {
48
26
  this.clickToCopy = column.cellComponentArgs.clickToCopy ?? false;
49
27
  }
28
+
29
+ if (column && column.cellComponentArgs) {
30
+ this.wrapperClass = column.cellComponentArgs.wrapperClass ?? '';
31
+ }
50
32
  }
51
33
 
52
34
  /**
@@ -1,10 +1,12 @@
1
1
  <div class="fleetbase-dashboard-grid" ...attributes>
2
2
  <GridStack @options={{this.gridOptions}} @onChange={{this.onChangeGrid}}>
3
3
  {{#each @dashboard.widgets as |widget|}}
4
- {{#let (resolve-component widget.component) as |ResolvedComponent|}}
5
- {{#if ResolvedComponent}}
4
+ {{#let (resolve-component widget.component) as |componentDefinition|}}
5
+ {{#if componentDefinition}}
6
6
  <GridStackItem id={{widget.id}} @options={{spread-widget-options (hash id=widget.id options=widget.grid_options)}} class="relative">
7
- {{component ResolvedComponent widget=widget options=widget.options}}
7
+ <LazyEngineComponent @component={{componentDefinition}} as |resolvedComponent|>
8
+ {{component resolvedComponent widget=widget options=widget.options}}
9
+ </LazyEngineComponent>
8
10
 
9
11
  {{#if @isEdit}}
10
12
  <div class="absolute top-2 right-2">
@@ -1,25 +1,35 @@
1
1
  <Overlay @isOpen={{@isOpen}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width={{or this.width @width "400px"}}>
2
- <Overlay::Header @title={{t "component.dashboard-widget-panel.select-widgets"}} @hideStatusDot={{true}} @titleWrapperClass="leading-5">
2
+ <Overlay::Header @title={{t "dashboard-widget-panel.select-widgets"}} @hideStatusDot={{true}} @titleWrapperClass="leading-5 text-gray-800 dark:text-white">
3
3
  <:actions>
4
4
  <div class="flex flex-1 justify-end">
5
- <Button @type="default" @icon="times" @helpText={{t "component.dashboard-widget-panel.close-and-save"}} @onClick={{this.onPressClose}} />
5
+ <Button @type="default" @icon="times" @helpText={{t "dashboard-widget-panel.close-and-save"}} @onClick={{this.onPressClose}} />
6
6
  </div>
7
7
  </:actions>
8
8
  </Overlay::Header>
9
9
 
10
- <Overlay::Body @wrapperClass="new-service-rate-overlay-body px-4 space-y-4 pt-4">
10
+ <Overlay::Body @wrapperClass="new-service-rate-overlay-body px-1 space-y-4 pt-2">
11
+ <div>
12
+ <Input
13
+ @type="text"
14
+ @value={{this.searchQuery}}
15
+ placeholder={{or (t "dashboard-widget-panel.filter-widgets") "Filter widgets by keyword"}}
16
+ class="w-full form-input form-input-sm"
17
+ {{on "input" this.updateSearchQuery}}
18
+ />
19
+ </div>
20
+
11
21
  <div class="grid grid-cols-1 gap-4 text-xs dark:text-gray-100">
12
22
  {{#each this.availableWidgets as |widget|}}
13
23
  <div
14
24
  class="rounded-lg border border-gray-300 bg-white dark:border-gray-700 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-300 ease-in-out shadow-md px-4 py-2 cursor-pointer"
15
- {{on "click" (fn this.addWidgetToDashboard widget)}}
25
+ {{on "click" (perform this.addWidgetToDashboard widget)}}
16
26
  >
17
- <div class="flex flex-row items-center leading-6 mb-2.5">
18
- <div class="w-8 flex items-center justify-start">
27
+ <div class="flex flex-row items-center leading-7 mb-2">
28
+ <div class="w-7 flex items-center justify-start">
19
29
  <FaIcon @icon={{widget.icon}} class="text-lg text-gray-600 dark:text-gray-300" />
20
30
  </div>
21
31
  <p class="text-sm truncate font-semibold dark:text-gray-100 text-gray-800">
22
- {{t "component.dashboard-widget-panel.widget-name" widgetName=widget.name}}
32
+ {{t "dashboard-widget-panel.widget-name" widgetName=widget.name}}
23
33
  </p>
24
34
  </div>
25
35
  <div>
@@ -2,23 +2,72 @@ import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
+ import { task } from 'ember-concurrency';
6
+ import { ExtensionComponent } from '@fleetbase/ember-core/contracts';
5
7
 
6
8
  export default class DashboardWidgetPanelComponent extends Component {
7
- @service universe;
8
- @tracked availableWidgets = [];
9
+ @service('universe/widget-service') widgetService;
10
+ @service notifications;
11
+ @tracked defaultDashboardId = this.args.defaultDashboardId ?? 'dashboard';
9
12
  @tracked dashboard;
10
13
  @tracked isOpen = true;
11
- @service notifications;
14
+ @tracked searchQuery = '';
12
15
 
13
16
  /**
14
17
  * Constructs the component and applies initial state.
15
18
  */
16
19
  constructor(owner, { dashboard, defaultDashboardId = 'dashboard' }) {
17
20
  super(...arguments);
18
- this.availableWidgets = this.universe.getWidgets(defaultDashboardId);
21
+ this.defaultDashboardId = defaultDashboardId;
19
22
  this.dashboard = dashboard;
20
23
  }
21
24
 
25
+ /**
26
+ * Gets available widgets for the dashboard.
27
+ * Computed as a getter to ensure reactivity when widgets are registered.
28
+ * @returns {Array} Available widgets
29
+ */
30
+ get availableWidgets() {
31
+ const dashboardId = this.args.defaultDashboardId || this.defaultDashboardId || 'dashboard';
32
+ const widgets = this.widgetService.getWidgets(dashboardId);
33
+
34
+ // Filter widgets by search query
35
+ if (this.searchQuery && this.searchQuery.trim()) {
36
+ const query = this.searchQuery.toLowerCase().trim();
37
+ return widgets.filter((widget) => {
38
+ const name = (widget.name || '').toLowerCase();
39
+ const description = (widget.description || '').toLowerCase();
40
+ return name.includes(query) || description.includes(query);
41
+ });
42
+ }
43
+
44
+ return widgets;
45
+ }
46
+
47
+ /**
48
+ * Adds a new available widget to the current dashboard.
49
+ *
50
+ * @param {Component|Widget|string} widget
51
+ * @memberof DashboardWidgetPanelComponent
52
+ */
53
+ @task *addWidgetToDashboard(widget) {
54
+ // If widget is a component definition/class
55
+ if (typeof widget.component === 'function') {
56
+ widget.component = widget.component.name;
57
+ }
58
+
59
+ // If using extension component definition
60
+ if (widget.component instanceof ExtensionComponent) {
61
+ widget.component = widget.component.toString();
62
+ }
63
+
64
+ try {
65
+ yield this.dashboard.addWidget(widget);
66
+ } catch (err) {
67
+ this.notifications.serverError(err);
68
+ }
69
+ }
70
+
22
71
  /**
23
72
  * Sets the overlay context.
24
73
  *
@@ -33,15 +82,14 @@ export default class DashboardWidgetPanelComponent extends Component {
33
82
  }
34
83
  }
35
84
 
36
- @action addWidgetToDashboard(widget) {
37
- // If widget is a component definition/class
38
- if (typeof widget.component === 'function') {
39
- widget.component = widget.component.name;
40
- }
41
-
42
- this.args.dashboard.addWidget(widget).catch((error) => {
43
- this.notifications.serverError(error);
44
- });
85
+ /**
86
+ * Updates the search query
87
+ *
88
+ * @action
89
+ * @param {Event} event
90
+ */
91
+ @action updateSearchQuery(event) {
92
+ this.searchQuery = event.target.value;
45
93
  }
46
94
 
47
95
  /**
@@ -17,7 +17,6 @@ export default class DashboardComponent extends Component {
17
17
  @service modalsManager;
18
18
  @service fetch;
19
19
  @service dashboard;
20
- @service universe;
21
20
 
22
21
  /**
23
22
  * Creates an instance of DashboardComponent.
@@ -9,7 +9,7 @@
9
9
  >
10
10
  <div class="next-nav-item-icon-container {{@iconWrapperClass}}">
11
11
  {{#if @iconComponent}}
12
- {{component @iconComponent options=@iconComponentOptions}}
12
+ {{component (lazy-engine-component @iconComponent) options=@iconComponentOptions}}
13
13
  {{else if @icon}}
14
14
  <FaIcon @prefix={{@iconPrefix}} @icon={{@icon}} @size={{or @iconSize "xs"}} class={{@iconClass}} />
15
15
  {{/if}}
@@ -17,7 +17,7 @@
17
17
  <div class="truncate w-10/12 {{@itemWrapperClass}}">{{yield}}</div>
18
18
  <div class="next-nav-item-right-side {{@itemRightSideContainerClass}}">
19
19
  {{#if @rightSideComponent}}
20
- {{component @rightSideComponent context=@rightSideComponentContext}}
20
+ {{component (lazy-engine-component @rightSideComponent) context=@rightSideComponentContext}}
21
21
  {{else}}
22
22
  {{#if @rightSideStatus}}
23
23
  <span class="ml-2 {{@rightSideStatusContainerClass}}">
@@ -0,0 +1,8 @@
1
+ {{#if this.resolvedComponent}}
2
+ {{#if (has-block)}}
3
+ {{yield this.resolvedComponent this.params}}
4
+ {{else}}
5
+ {{component this.resolvedComponent params=this.params}}
6
+ {{/if}}
7
+ {{/if}}
8
+ <div {{did-update this.handleChange @component @params}} />
@@ -0,0 +1,136 @@
1
+ import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action } from '@ember/object';
5
+ import { assert } from '@ember/debug';
6
+ import { task } from 'ember-concurrency';
7
+ import { ExtensionComponent } from '@fleetbase/ember-core/contracts';
8
+
9
+ /**
10
+ * LazyEngineComponent
11
+ *
12
+ * A wrapper component that handles lazy loading of components from engines.
13
+ * This component takes an ExtensionComponent definition and:
14
+ * 1. Triggers lazy loading of the engine if not already loaded
15
+ * 2. Looks up the component from the loaded engine
16
+ * 3. Renders the component with all passed arguments
17
+ *
18
+ * This enables cross-engine component usage while preserving lazy loading.
19
+ *
20
+ * @class LazyEngineComponent
21
+ * @extends Component
22
+ *
23
+ * @example
24
+ * <LazyEngineComponent
25
+ * @componentDef={{this.menuItem.component}}
26
+ * @model={{@model}}
27
+ * @onChange={{@onChange}}
28
+ * />
29
+ */
30
+ export default class LazyEngineComponent extends Component {
31
+ @service('universe/extension-manager') extensionManager;
32
+ @tracked resolvedComponent = null;
33
+ @tracked component = this.args.component;
34
+ @tracked params = this.args.params ?? {};
35
+ @tracked error = null;
36
+
37
+ constructor() {
38
+ super(...arguments);
39
+ this.loadComponent.perform();
40
+ }
41
+
42
+ @action handleChange(el, [component, params = {}]) {
43
+ this.component = component;
44
+ this.params = params;
45
+ this.loadComponent.perform();
46
+ }
47
+
48
+ /**
49
+ * Load the component from the engine
50
+ *
51
+ * @method loadComponent
52
+ * @private
53
+ */
54
+ @task *loadComponent() {
55
+ let componentDef = this.component;
56
+
57
+ // Handle string form component definition
58
+ if (typeof componentDef === 'string' && componentDef.startsWith('#extension-component')) {
59
+ /* eslint-disable no-unused-vars */
60
+ const [_, engineName, componentPathOrName] = componentDef.split(':');
61
+ componentDef = new ExtensionComponent(engineName, componentPathOrName);
62
+ }
63
+
64
+ // Handle backward compatibility: if componentDef is already a class or string, use it directly
65
+ if (typeof componentDef === 'function' || typeof componentDef === 'string') {
66
+ this.resolvedComponent = componentDef;
67
+ return;
68
+ }
69
+
70
+ // Handle ExtensionComponent definitions
71
+ if (componentDef && componentDef.engine) {
72
+ try {
73
+ const { engine: engineName, path: componentPath, class: componentClass, isClass } = componentDef;
74
+
75
+ assert(`LazyEngineComponent requires an engine name in componentDef`, engineName);
76
+
77
+ // Ensure engine is loaded
78
+ const engineInstance = yield this.extensionManager.ensureEngineLoaded(engineName);
79
+ if (!engineInstance) {
80
+ throw new Error(`Failed to load engine '${engineName}'`);
81
+ }
82
+
83
+ // Handle component class (immediate)
84
+ if (isClass && componentClass) {
85
+ // Register component class to engine if not already registered
86
+ const dasherized = componentClass.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
87
+ const componentKey = `component:${dasherized}`;
88
+
89
+ if (!engineInstance.hasRegistration(componentKey)) {
90
+ engineInstance.register(componentKey, componentClass);
91
+ // Also register with original name
92
+ engineInstance.register(`component:${componentClass.name}`, componentClass);
93
+ }
94
+
95
+ this.resolvedComponent = componentClass;
96
+ return;
97
+ }
98
+
99
+ // Handle component path (lazy)
100
+ if (componentPath) {
101
+ // Clean the path and lookup the component
102
+ const cleanPath = componentPath.replace(/^components\//, '');
103
+
104
+ // First, check if the component is registered in the engine
105
+ const componentKey = `component:${cleanPath}`;
106
+ if (!engineInstance.hasRegistration(componentKey)) {
107
+ throw new Error(`Component '${cleanPath}' is not registered in engine '${engineName}'. ` + `Make sure the component exists and is properly exported.`);
108
+ }
109
+
110
+ // Lookup the component factory (not the instance)
111
+ const componentFactory = engineInstance.factoryFor(componentKey);
112
+ if (!componentFactory) {
113
+ throw new Error(`Component factory for '${cleanPath}' not found in engine '${engineName}'. ` + `The component may be registered but not properly defined.`);
114
+ }
115
+
116
+ // Get the component class from the factory
117
+ const resolvedClass = componentFactory.class;
118
+ if (!resolvedClass) {
119
+ throw new Error(`Component class for '${cleanPath}' is undefined in engine '${engineName}'.`);
120
+ }
121
+
122
+ this.resolvedComponent = resolvedClass;
123
+ return;
124
+ }
125
+
126
+ throw new Error('ExtensionComponent requires either a path or class');
127
+ } catch (e) {
128
+ console.error('LazyEngineComponent: Error loading component:', e);
129
+ this.error = e.message;
130
+ }
131
+ } else {
132
+ // Invalid component definition
133
+ this.error = 'Invalid component definition. Expected an ExtensionComponent with engine and path/class properties.';
134
+ }
135
+ }
136
+ }
@@ -0,0 +1 @@
1
+ {{yield this.engineInstance this.isLoading this.error}}
@@ -0,0 +1,82 @@
1
+ import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { assert, debug } from '@ember/debug';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ /**
8
+ * LoadEngine Component
9
+ *
10
+ * A component that handles lazy loading of engines and yields the engine instance.
11
+ * This provides a flexible pattern for working with engine components.
12
+ *
13
+ * @class LoadEngineComponent
14
+ * @extends Component
15
+ *
16
+ * @example Basic usage
17
+ * <LoadEngine @engineName="@fleetbase/fleetops-engine" as |engine|>
18
+ * {{#if engine}}
19
+ * {{component "admin/navigator-app" engineInstance=engine}}
20
+ * {{/if}}
21
+ * </LoadEngine>
22
+ *
23
+ * @example With loading and error states
24
+ * <LoadEngine @engineName="@fleetbase/fleetops-engine" as |engine isLoading error|>
25
+ * {{#if isLoading}}
26
+ * <LoadingSpinner />
27
+ * {{else if error}}
28
+ * <ErrorMessage @message={{error}} />
29
+ * {{else if engine}}
30
+ * {{component "admin/navigator-app" engineInstance=engine}}
31
+ * {{/if}}
32
+ * </LoadEngine>
33
+ */
34
+ export default class LoadEngineComponent extends Component {
35
+ @service('universe/extension-manager') extensionManager;
36
+ @tracked engineInstance = null;
37
+ @tracked error = null;
38
+
39
+ constructor() {
40
+ super(...arguments);
41
+ this.loadEngine.perform();
42
+ }
43
+
44
+ /**
45
+ * Load the engine
46
+ *
47
+ * @method loadEngine
48
+ * @private
49
+ */
50
+ @task *loadEngine() {
51
+ const { engineName } = this.args;
52
+
53
+ assert(`LoadEngine requires an @engineName argument`, engineName);
54
+
55
+ try {
56
+ debug(`[LoadEngine] Loading engine: ${engineName}`);
57
+
58
+ // Trigger lazy loading of the engine
59
+ const engine = yield this.extensionManager.ensureEngineLoaded(engineName);
60
+
61
+ if (!engine) {
62
+ throw new Error(`Failed to load engine '${engineName}'`);
63
+ }
64
+
65
+ debug(`[LoadEngine] Engine loaded successfully: ${engineName}`);
66
+ this.engineInstance = engine;
67
+ } catch (e) {
68
+ console.error('[LoadEngine] Error loading engine:', e);
69
+ this.error = e.message;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Whether the engine is currently loading
75
+ *
76
+ * @computed isLoading
77
+ * @returns {Boolean}
78
+ */
79
+ get isLoading() {
80
+ return this.loadEngine.isRunning;
81
+ }
82
+ }
@@ -46,22 +46,117 @@ export default class ModalsQueryBuilderComputedColumnEditorComponent extends Com
46
46
 
47
47
  get allowedFunctions() {
48
48
  return [
49
+ // Date/Time Functions
49
50
  'DATEDIFF',
50
51
  'DATE_ADD',
51
52
  'DATE_SUB',
53
+ 'NOW',
54
+ 'CURDATE',
55
+ 'CURTIME',
56
+ 'YEAR',
57
+ 'MONTH',
58
+ 'DAY',
59
+ 'HOUR',
60
+ 'MINUTE',
61
+ 'SECOND',
62
+ 'DATE_FORMAT',
63
+ 'LAST_DAY',
64
+ 'DAYOFWEEK',
65
+ 'DAYOFMONTH',
66
+ 'DAYOFYEAR',
67
+ 'WEEK',
68
+ 'WEEKDAY',
69
+ 'QUARTER',
70
+ 'TIMESTAMPDIFF',
71
+ 'TIMESTAMPADD',
72
+ 'FROM_UNIXTIME',
73
+ 'UNIX_TIMESTAMP',
74
+ 'STR_TO_DATE',
75
+ 'MAKEDATE',
76
+ 'ADDDATE',
77
+ 'SUBDATE',
78
+
79
+ // String Functions
52
80
  'CONCAT',
53
- 'COALESCE',
54
- 'IFNULL',
55
- 'CASE/WHEN/THEN/ELSE/END',
56
- 'LEAST',
57
- 'GREATEST',
58
- 'ROUND',
59
- 'ABS',
81
+ 'CONCAT_WS',
82
+ 'SUBSTRING',
83
+ 'SUBSTR',
84
+ 'REPLACE',
60
85
  'UPPER',
61
86
  'LOWER',
62
87
  'TRIM',
88
+ 'LTRIM',
89
+ 'RTRIM',
63
90
  'LENGTH',
91
+ 'CHAR_LENGTH',
92
+ 'LEFT',
93
+ 'RIGHT',
94
+ 'LPAD',
95
+ 'RPAD',
96
+ 'REVERSE',
97
+ 'LOCATE',
98
+ 'POSITION',
99
+ 'INSTR',
100
+ 'STRCMP',
101
+
102
+ // Numeric Functions
103
+ 'ROUND',
104
+ 'ABS',
105
+ 'CEIL',
106
+ 'CEILING',
107
+ 'FLOOR',
108
+ 'TRUNCATE',
109
+ 'MOD',
110
+ 'POW',
111
+ 'POWER',
112
+ 'SQRT',
113
+ 'SIGN',
114
+ 'RAND',
115
+ 'PI',
116
+ 'EXP',
117
+ 'LN',
118
+ 'LOG',
119
+ 'LOG10',
120
+ 'LOG2',
121
+ 'DEGREES',
122
+ 'RADIANS',
123
+ 'SIN',
124
+ 'COS',
125
+ 'TAN',
126
+ 'ASIN',
127
+ 'ACOS',
128
+ 'ATAN',
129
+ 'ATAN2',
130
+
131
+ // Conditional/Logic Functions
132
+ 'CASE',
133
+ 'WHEN',
134
+ 'THEN',
135
+ 'ELSE',
136
+ 'END',
137
+ 'IF',
138
+ 'IFNULL',
64
139
  'NULLIF',
140
+ 'COALESCE',
141
+
142
+ // Comparison Functions
143
+ 'LEAST',
144
+ 'GREATEST',
145
+
146
+ // Aggregate Functions
147
+ 'COUNT',
148
+ 'SUM',
149
+ 'AVG',
150
+ 'MIN',
151
+ 'MAX',
152
+ 'GROUP_CONCAT',
153
+
154
+ // Type Conversion
155
+ 'CAST',
156
+ 'CONVERT',
157
+
158
+ // Other Utility Functions
159
+ 'INTERVAL',
65
160
  ];
66
161
  }
67
162
 
@@ -19,7 +19,6 @@ export default class PillComponent extends Component {
19
19
  }
20
20
 
21
21
  @action handleClick() {
22
- console.log('handleClick called!', ...arguments);
23
22
  if (typeof this.args.onClick === 'function') {
24
23
  if (this.args.resource) {
25
24
  this.args.onClick(this.args.resource, ...arguments);
@@ -53,10 +53,8 @@ export default class QueryBuilderComputedColumnsComponent extends Component {
53
53
  modal.startLoading();
54
54
 
55
55
  const editorComponent = modal.getOption('modalComponentInstance');
56
- console.log('[editorComponent]', editorComponent);
57
56
  if (editorComponent) {
58
57
  const isValid = await editorComponent.validateExpression();
59
- console.log('[isValid]', isValid);
60
58
  if (isValid) {
61
59
  const computedColumn = editorComponent.save();
62
60
  this.saveComputedColumn(computedColumn);
@@ -1,3 +1,9 @@
1
1
  {{#each this.yieldables as |item|}}
2
- {{yield item @context}}
2
+ {{#if this.isComponent}}
3
+ <LazyEngineComponent @component={{item}} as |resolvedComponent|>
4
+ {{yield resolvedComponent @context}}
5
+ </LazyEngineComponent>
6
+ {{else}}
7
+ {{yield item @context}}
8
+ {{/if}}
3
9
  {{/each}}