hippo-fw 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/.nvmrc +2 -0
  3. data/.travis.yml +1 -1
  4. data/client/hippo/access/login-dialog.jsx +3 -5
  5. data/client/hippo/boot.jsx +2 -2
  6. data/client/hippo/components/asset.jsx +0 -1
  7. data/client/hippo/components/data-list.jsx +16 -15
  8. data/client/hippo/components/data-list/data-list.scss +10 -0
  9. data/client/hippo/components/data-table.jsx +14 -14
  10. data/client/hippo/components/data-table/header-cell.jsx +0 -1
  11. data/client/hippo/components/data-table/table-styles.scss +7 -0
  12. data/client/hippo/components/date-time.jsx +19 -16
  13. data/client/hippo/components/date-time/calendar.jsx +5 -7
  14. data/client/hippo/components/date-time/date-time-drop.jsx +0 -2
  15. data/client/hippo/components/date-time/time.jsx +0 -1
  16. data/client/hippo/components/form.jsx +1 -1
  17. data/client/hippo/components/form/{model.js → api.js} +25 -7
  18. data/client/hippo/components/form/fields.jsx +12 -11
  19. data/client/hippo/components/form/fields/date-wrapper.jsx +1 -1
  20. data/client/hippo/components/form/fields/form-field.scss +9 -1
  21. data/client/hippo/components/form/fields/label.jsx +14 -0
  22. data/client/hippo/components/form/fields/select-wrapper.jsx +2 -3
  23. data/client/hippo/components/form/wrapper.jsx +1 -3
  24. data/client/hippo/components/icon.jsx +2 -69
  25. data/client/hippo/components/master-detail.jsx +0 -2
  26. data/client/hippo/components/network-activity-overlay.jsx +1 -4
  27. data/client/hippo/components/query-builder.jsx +15 -16
  28. data/client/hippo/components/record-finder.jsx +2 -5
  29. data/client/hippo/components/record-finder/query-layer.jsx +1 -5
  30. data/client/hippo/components/screen.jsx +7 -3
  31. data/client/hippo/components/text-editor.jsx +2 -8
  32. data/client/hippo/components/tool-tip.jsx +2 -3
  33. data/client/hippo/components/warning-notification.jsx +3 -3
  34. data/client/hippo/lib/date-range.js +28 -0
  35. data/client/hippo/lib/util.js +6 -0
  36. data/client/hippo/models/asset.js +6 -3
  37. data/client/hippo/models/config.js +1 -1
  38. data/client/hippo/models/pub_sub.js +12 -7
  39. data/client/hippo/models/pub_sub/channel.js +6 -4
  40. data/client/hippo/models/query.js +19 -4
  41. data/client/hippo/models/query/array-result.js +9 -8
  42. data/client/hippo/models/query/clause.js +21 -13
  43. data/client/hippo/models/query/field.js +7 -2
  44. data/client/hippo/models/query/info.js +7 -3
  45. data/client/hippo/models/sync.js +11 -14
  46. data/client/hippo/react/Root.jsx +1 -2
  47. data/client/hippo/react/{DefaultComponentNotFound.jsx → component-not-found.jsx} +1 -3
  48. data/client/hippo/screens/index.js +6 -2
  49. data/client/hippo/screens/system-settings.jsx +10 -6
  50. data/client/hippo/screens/system-settings/mailer-config.jsx +0 -2
  51. data/client/hippo/screens/system-settings/tenant.jsx +1 -4
  52. data/client/hippo/screens/user-management.jsx +0 -1
  53. data/client/hippo/screens/user-management/edit-form.jsx +1 -2
  54. data/client/hippo/workspace/index.jsx +21 -12
  55. data/client/hippo/workspace/menu-group.jsx +4 -7
  56. data/client/hippo/workspace/menu-option.jsx +1 -3
  57. data/client/hippo/workspace/menu.jsx +29 -11
  58. data/client/hippo/workspace/navbar.jsx +1 -2
  59. data/client/hippo/workspace/root-view.jsx +5 -2
  60. data/client/hippo/workspace/screen.jsx +2 -3
  61. data/client/hippo/workspace/styles.scss +17 -0
  62. data/command-reference-files/screen/client/appy-app/screens/ready-set-go.jsx +2 -1
  63. data/db/migrate/20170530120004_create_users.rb +1 -1
  64. data/hippo-fw.gemspec +3 -2
  65. data/lib/hippo/api/cable.rb +13 -13
  66. data/lib/hippo/api/controller_base.rb +1 -0
  67. data/lib/hippo/api/handlers/tenant.rb +1 -1
  68. data/lib/hippo/api/handlers/user_session.rb +2 -1
  69. data/lib/hippo/api/helper_methods.rb +4 -1
  70. data/lib/hippo/api/pub_sub.rb +7 -6
  71. data/lib/hippo/api/to_json.rb +1 -1
  72. data/lib/hippo/api/updates.rb +2 -0
  73. data/lib/hippo/command/console.rb +11 -3
  74. data/lib/hippo/command/jest.rb +2 -0
  75. data/lib/hippo/configuration.rb +4 -7
  76. data/lib/hippo/rake_tasks.rb +1 -1
  77. data/lib/hippo/screen.rb +31 -93
  78. data/lib/hippo/screen/definition.rb +76 -0
  79. data/lib/hippo/screen/group.rb +26 -0
  80. data/lib/hippo/spec_helper.rb +3 -0
  81. data/lib/hippo/system_settings.rb +6 -0
  82. data/lib/hippo/user.rb +11 -3
  83. data/lib/hippo/version.rb +1 -1
  84. data/package-lock.json +419 -533
  85. data/package.json +32 -30
  86. data/spec/client/access/login-dialog.spec.jsx +4 -5
  87. data/spec/client/components/__snapshots__/query-builder.spec.jsx.snap +1 -1
  88. data/spec/client/components/__snapshots__/record-finder.spec.jsx.snap +72 -0
  89. data/spec/client/components/asset.spec.jsx +2 -6
  90. data/spec/client/components/data-list.spec.jsx +2 -6
  91. data/spec/client/components/data-table.spec.jsx +3 -5
  92. data/spec/client/components/date-time.spec.jsx +1 -1
  93. data/spec/client/components/form.spec.jsx +2 -2
  94. data/spec/client/components/master-detail.spec.jsx +1 -2
  95. data/spec/client/components/network-activity-overlay.spec.jsx +2 -3
  96. data/spec/client/components/query-builder.spec.jsx +3 -6
  97. data/spec/client/components/record-finder.spec.jsx +4 -4
  98. data/spec/client/models/pub_sub.spec.js +3 -1
  99. data/spec/client/models/query.spec.js +4 -9
  100. data/spec/client/screens/system-settings-tenants.spec.jsx +1 -1
  101. data/spec/client/screens/system-settings.spec.jsx +1 -4
  102. data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +15 -20
  103. data/spec/client/workspace/menu.spec.jsx +1 -2
  104. data/spec/server/api/user_sessions_spec.rb +15 -0
  105. data/templates/client/screens/screen.jsx +2 -1
  106. data/views/hippo_root_view.erb +1 -0
  107. metadata +30 -52
  108. data/client/hippo/components/calendar/Calendar.jsx +0 -25
  109. data/client/hippo/components/calendar/index.js +0 -3
  110. data/client/hippo/components/calendar/styles.scss +0 -3
  111. data/client/hippo/components/shared/AssetsListing.jsx +0 -23
  112. data/client/hippo/components/shared/Checkbox.jsx +0 -49
  113. data/client/hippo/components/shared/CountBadge.jsx +0 -13
  114. data/client/hippo/components/shared/DateTime.jsx +0 -58
  115. data/client/hippo/components/shared/DisplayValue.jsx +0 -15
  116. data/client/hippo/components/shared/ErrorDisplay.jsx +0 -37
  117. data/client/hippo/components/shared/FieldMixin.jsx +0 -254
  118. data/client/hippo/components/shared/FieldSet.jsx +0 -52
  119. data/client/hippo/components/shared/FieldWrapper.jsx +0 -94
  120. data/client/hippo/components/shared/FormGroup.jsx +0 -41
  121. data/client/hippo/components/shared/GenericField.jsx +0 -7
  122. data/client/hippo/components/shared/IconButton.jsx +0 -13
  123. data/client/hippo/components/shared/ImageAsset.jsx +0 -78
  124. data/client/hippo/components/shared/IndeterminateCheckbox.jsx +0 -31
  125. data/client/hippo/components/shared/Input.jsx +0 -16
  126. data/client/hippo/components/shared/InputFieldMixin.jsx +0 -78
  127. data/client/hippo/components/shared/JobProgress.jsx +0 -46
  128. data/client/hippo/components/shared/NumberInput.jsx +0 -37
  129. data/client/hippo/components/shared/PanelHeader.jsx +0 -15
  130. data/client/hippo/components/shared/RadioField.jsx +0 -33
  131. data/client/hippo/components/shared/ResizeSensor.jsx +0 -18
  132. data/client/hippo/components/shared/ScreenWrapper.jsx +0 -17
  133. data/client/hippo/components/shared/TextArea.jsx +0 -19
  134. data/client/hippo/components/shared/Throbber.jsx +0 -8
  135. data/client/hippo/components/shared/ToggleField.jsx +0 -2
  136. data/client/hippo/components/shared/Tooltip.jsx +0 -23
  137. data/client/hippo/components/shared/fields.scss +0 -58
  138. data/client/hippo/components/shared/fieldset.scss +0 -27
  139. data/client/hippo/components/shared/image-asset.scss +0 -53
  140. data/client/hippo/components/shared/index.js +0 -5
  141. data/client/hippo/components/shared/overlay.scss +0 -83
  142. data/client/hippo/components/shared/resize-sensor.scss +0 -30
  143. data/client/hippo/components/shared/styles.scss +0 -64
  144. data/client/hippo/components/shared/throbber.scss +0 -53
  145. data/client/hippo/fonts/fontawesome-webfont.woff +0 -0
  146. data/client/hippo/fonts/fontawesome-webfont.woff2 +0 -0
  147. data/client/hippo/workspace/content.jsx +0 -22
  148. data/client/hippo/workspace/tabs.jsx +0 -60
  149. data/client/hippo/workspace/viewport.jsx +0 -82
  150. data/spec/client/screens/__snapshots__/tabs.spec.jsx.snap +0 -127
  151. data/spec/client/screens/tabs.spec.jsx +0 -36
@@ -1,12 +1,11 @@
1
- import React, { PureComponent } from 'react';
1
+ import React from 'react';
2
2
  import { Tooltip as Tippy } from 'react-tippy';
3
3
 
4
4
  import 'react-tippy/dist/tippy.css';
5
5
 
6
6
  // Use a wrapper component even though it doesn't really add any functionality
7
7
  // In the future we'll add a Manager wrapper so that multiple tooltips cannot be shown at once
8
- export default class ToolTip extends PureComponent {
9
-
8
+ export default class ToolTip extends React.PureComponent {
10
9
  render() {
11
10
  const { children, ...tipProps } = this.props;
12
11
  return (
@@ -1,14 +1,14 @@
1
- import React from 'react';
1
+ import React from 'react'; // eslint-disable-line no-unused-vars
2
2
  import PropTypes from 'prop-types';
3
3
  import Notification from 'grommet/components/Notification';
4
4
 
5
5
  export default function WarningNotification(props) {
6
- if (!props.message){ return null; }
6
+ if (!props.message) { return null; }
7
7
  return (
8
8
  <Notification message={props.message} size="small" status="warning" />
9
9
  );
10
10
  }
11
11
 
12
12
  WarningNotification.propTypes = {
13
- message: PropTypes.string
13
+ message: PropTypes.string,
14
14
  };
@@ -0,0 +1,28 @@
1
+ import { isString, isEmpty, map } from 'lodash';
2
+ import {
3
+ identifiedBy,
4
+ } from 'mobx-decorated-models';
5
+
6
+ @identifiedBy('date-range')
7
+ export default class DateRange {
8
+ constructor(range) {
9
+ if (isString(range) && !isEmpty(range)) {
10
+ [this.start, this.end] = map(range.split('...'), d => new Date(d));
11
+ } else if (range) {
12
+ this.start = range.start;
13
+ this.end = range.end;
14
+ }
15
+ }
16
+
17
+ toJSON() {
18
+ if (this.start && this.end) {
19
+ // this strange format is what PG user and is therefore what active record expects
20
+ return `[${this.start.toISOString()},${this.end.toISOString()})`;
21
+ }
22
+ return '';
23
+ }
24
+
25
+ static serialize(range) {
26
+ return (range ? range.toJSON() : '');
27
+ }
28
+ }
@@ -4,6 +4,8 @@ import {
4
4
  isNumber, isObject, isEmpty, isNil, isString, each,
5
5
  } from 'lodash';
6
6
  import pluralize from 'pluralize';
7
+ import classnames from 'classnames';
8
+ import { getColumnProps } from 'react-flexbox-grid';
7
9
 
8
10
  function lcDash(char, match, index) {
9
11
  return (0 === index ? '' : '_') + char.toLowerCase();
@@ -17,6 +19,10 @@ export function capitalize(str) {
17
19
  return str.charAt(0).toUpperCase() + str.slice(1);
18
20
  }
19
21
 
22
+ export function columnClasses(props, ...classes) {
23
+ return classnames(getColumnProps(props).className, ...classes);
24
+ }
25
+
20
26
  export function dasherize(str) {
21
27
  return trim(str)
22
28
  .replace(/([A-Z])/g, lcDash)
@@ -35,6 +35,10 @@ export default class Asset extends BaseModel {
35
35
  return Config.api_path + Config.assets_path_prefix;
36
36
  }
37
37
 
38
+ static urlForSize(data, size) {
39
+ const url = get(data, `${size}.id`);
40
+ return url ? `${Config.api_host}${this.syncUrl}/${url}` : null;
41
+ }
38
42
 
39
43
  @action.bound
40
44
  onOwnerChange({ newValue: owner }) {
@@ -93,9 +97,8 @@ export default class Asset extends BaseModel {
93
97
  return get(this, 'file.preview', this.urlFor('thumbnail'));
94
98
  }
95
99
 
96
- urlFor(type = 'original') {
97
- const url = get(this, `file_data.${type}.id`);
98
- return url ? `${Config.api_host}${this.constructor.syncUrl}/${url}` : null;
100
+ urlFor(size = 'original') {
101
+ return this.constructor.urlForSize(this.file_data, size);
99
102
  }
100
103
 
101
104
  save() {
@@ -16,7 +16,7 @@ export default class Config {
16
16
  @persist @observable product_name;
17
17
  @persist('list') @observable screen_ids = [];
18
18
  @persist @observable user_info;
19
-
19
+ @persist('object') @observable logo;
20
20
  @observable user;
21
21
  @observable isIntialized = false;
22
22
 
@@ -1,8 +1,7 @@
1
1
  import { Atom, when, reaction } from 'mobx';
2
2
  import ActionCable from 'actioncable';
3
3
  import invariant from 'invariant';
4
- import { invoke, mapValues } from 'lodash';
5
-
4
+ import { isEmpty, mapValues } from 'lodash';
6
5
  import User from '../user';
7
6
  import Config from '../config';
8
7
 
@@ -32,9 +31,11 @@ class PubSubMap {
32
31
 
33
32
  onChange(id, data) {
34
33
  const models = this.map[id];
35
- if (models) {
34
+ if (!isEmpty(models)) {
36
35
  const update = mapValues(data.update, '[1]');
37
- invoke(models, 'set', update);
36
+ for (let i = 0; i < models.length; i += 1) {
37
+ models[i].set(update);
38
+ }
38
39
  }
39
40
  }
40
41
 
@@ -43,7 +44,8 @@ class PubSubMap {
43
44
  if (!models.includes(model)) {
44
45
  models.push(model);
45
46
  if (1 === models.length) {
46
- PubSub.channel.subscribe(this.channelForId(id));
47
+ const channel = this.channelForId(id);
48
+ PubSub.channel.subscribe(channel);
47
49
  }
48
50
  }
49
51
  }
@@ -93,8 +95,10 @@ PubSub = {
93
95
 
94
96
  onLoginChange() {
95
97
  if (User.isLoggedIn) {
96
- const url = `${Config.api_host}${Config.api_path}/cable?token=${Config.access_token}`;
97
- PubSub.cable = ActionCable.createConsumer(url);
98
+ const host = Config.api_host.replace(/^http/, 'ws');
99
+ const url = `${host}${Config.api_path}/cable?token=${Config.access_token}`;
100
+ ActionCable.startDebugging();
101
+ PubSub.cable = new ActionCable.Consumer(url);
98
102
  PubSub.channel = new PubSubCableChannel(PubSub);
99
103
  } else if (PubSub.cable) {
100
104
  PubSub.cable.disconnect();
@@ -144,6 +148,7 @@ export class PubSubAtom {
144
148
  }
145
149
 
146
150
  export function observePubSub(...models) {
151
+ if (!PubSub.channel) { return; }
147
152
  for (let i = 0; i < models.length; i += 1) {
148
153
  const model = models[i];
149
154
  if (!model.$pubSub) {
@@ -1,5 +1,5 @@
1
1
  import { omit } from 'lodash';
2
-
2
+ import { action } from 'mobx';
3
3
  import { logger } from '../../lib/util';
4
4
  import { BaseModel } from '../base';
5
5
 
@@ -8,8 +8,9 @@ const CHANNEL_SPLITTER = new RegExp('^(.*):(.*)/([^/]+)$');
8
8
  export default class PubSubCableChannel {
9
9
  constructor(pub_sub) {
10
10
  this.channel = pub_sub.cable.subscriptions.create(
11
- 'pubsub', this,
11
+ 'Hippo::API::PubSub', this,
12
12
  );
13
+ this.channel.received = this.received;
13
14
  this.pub_sub = pub_sub;
14
15
  }
15
16
 
@@ -23,11 +24,12 @@ export default class PubSubCableChannel {
23
24
  this.channel.perform('off', { channel });
24
25
  }
25
26
 
27
+ @action.bound
26
28
  received(data) {
27
- const [channel, _, modelId, id] = Array.from(
29
+ const [_, __, modelId, id] = Array.from(
28
30
  data.channel.match(CHANNEL_SPLITTER),
29
31
  );
30
- logger.info(`[pubsub] change recvd for: ${channel}`);
32
+
31
33
  const model = BaseModel.findDerived(modelId);
32
34
  this.pub_sub.onModelChange(model, id, omit(data, 'channel'));
33
35
  }
@@ -1,5 +1,5 @@
1
1
  import { get, isString, find, extend, toPairs } from 'lodash';
2
- import { action, reaction, observe } from 'mobx';
2
+ import { action, reaction, observe, observable } from 'mobx';
3
3
  import {
4
4
  BaseModel, identifiedBy, field, belongsTo, hasMany, identifier, computed, session,
5
5
  } from './base';
@@ -30,7 +30,7 @@ export default class Query extends BaseModel {
30
30
  @session customSyncUrl;
31
31
 
32
32
  @session autoFetch = false;
33
-
33
+ @observable autoFetchDisposer;
34
34
  @field({ type: 'object' }) syncOptions = {};
35
35
 
36
36
  @hasMany({ model: Clause, inverseOf: 'query' }) clauses;
@@ -53,13 +53,17 @@ export default class Query extends BaseModel {
53
53
  [
54
54
  { id: 'like', label: 'Starts With', types: Types.LIKE_QUERY_TYPES },
55
55
  { id: 'eq', label: 'Equals' },
56
+ { id: 'contains', label: 'Contains', types: Types.LIKE_QUERY_TYPES },
56
57
  { id: 'lt', label: 'Less Than', types: Types.LESS_THAN_QUERY_TYPES },
57
58
  { id: 'gt', label: 'More Than', types: Types.LESS_THAN_QUERY_TYPES },
58
59
  ].forEach(op => this.operators.push(op));
59
60
  this.info = new Info(this);
60
61
  this.results = new ArrayResult({ query: this });
61
62
  if (0 === this.clauses.length) {
62
- this.clauses.push({ });
63
+ const ff = find(this.fields, { queryable: true });
64
+ if (ff) {
65
+ this.clauses.push({ field: ff, operator: ff.preferredOperator });
66
+ }
63
67
  }
64
68
  if (attrs.sort) {
65
69
  const [fieldId, dir] = toPairs(attrs.sort)[0];
@@ -80,7 +84,7 @@ export default class Query extends BaseModel {
80
84
  close() {
81
85
  if (this.autoFetchDisposer) {
82
86
  this.autoFetchDisposer();
83
- this.autoFetchDisposer = undefined;
87
+ this.autoFetchDisposer = null;
84
88
  }
85
89
  }
86
90
 
@@ -89,6 +93,15 @@ export default class Query extends BaseModel {
89
93
  return this.results.fetch({ start: 0 });
90
94
  }
91
95
 
96
+ get rows() {
97
+ return this.results.rows;
98
+ }
99
+
100
+ reset() {
101
+ this.clauses.clear();
102
+ this.results.reset();
103
+ }
104
+
92
105
  fetchSingle(query, queryOptions = {}) {
93
106
  const options = extend(queryOptions, this.syncOptions, { query });
94
107
  const { src: Src } = this;
@@ -102,10 +115,12 @@ export default class Query extends BaseModel {
102
115
  this._startAutoFetch();
103
116
  } else if (this.autoFetchDisposer) {
104
117
  this.autoFetchDisposer();
118
+ this.autoFetchDisposer = null;
105
119
  }
106
120
  }
107
121
 
108
122
  _startAutoFetch() {
123
+ if (this.autoFetchDisposer) { return; }
109
124
  this.autoFetchDisposer = reaction(
110
125
  () => this.fingerprint,
111
126
  this.fetch,
@@ -1,7 +1,7 @@
1
1
  import {
2
- isEmpty, isNil, extend, map, bindAll, omit, inRange, find, range,
2
+ isEmpty, isNil, extend, map, bindAll, inRange, find, range,
3
3
  } from 'lodash';
4
- import { reaction, observe } from 'mobx';
4
+ import { reaction, observe, toJS } from 'mobx';
5
5
 
6
6
  import Sync from '../sync';
7
7
  import Result from './result';
@@ -59,9 +59,10 @@ export default class ArrayResult extends Result {
59
59
  rowAsObject(index) {
60
60
  const row = this.rows[index];
61
61
  const obj = {};
62
- this.query.info.loadableFields.forEach(
63
- f => (obj[f.id] = (row[f.dataIndex] || f.defaultValue)),
64
- );
62
+ this.query.info.loadableFields.forEach((f) => {
63
+ const value = row[f.dataIndex] || f.defaultValue;
64
+ if (!isNil(value)) { obj[f.id] = value; }
65
+ });
65
66
  return obj;
66
67
  }
67
68
 
@@ -84,7 +85,7 @@ export default class ArrayResult extends Result {
84
85
  fetchModelForRow(index, syncOptions = {}) {
85
86
  const model = this.modelForRow(index);
86
87
  return model.isNew ? Promise.resolve(model) :
87
- model.fetch(extend({}, this.query.syncOptions, syncOptions));
88
+ model.fetch(extend({}, toJS(this.query.syncOptions), syncOptions));
88
89
  }
89
90
 
90
91
  onQuerySortChange() {
@@ -152,7 +153,7 @@ export default class ArrayResult extends Result {
152
153
  }
153
154
  });
154
155
 
155
- const options = extend({}, omit(this.query.syncOptions, 'include'), {
156
+ const options = extend({}, toJS(this.query.syncOptions), {
156
157
  start,
157
158
  limit,
158
159
  total_count: 't',
@@ -170,7 +171,7 @@ export default class ArrayResult extends Result {
170
171
  };
171
172
  }
172
173
 
173
- extend(options, omit(this.query.info.syncOptions, 'include'));
174
+ extend(options, toJS(this.query.info.syncOptions));
174
175
 
175
176
  this.syncInProgress = options;
176
177
 
@@ -1,26 +1,29 @@
1
- import { get, compact, first, filter, find, uniqueId } from 'lodash';
1
+ import { get, compact, first, filter, uniqueId } from 'lodash';
2
+ import { action, observe } from 'mobx';
2
3
  import {
3
- BaseModel, identifiedBy, autorun, belongsTo, computed, observable,
4
+ BaseModel, identifiedBy, belongsTo, computed, observable, session,
4
5
  } from '../base';
5
6
 
6
7
  @identifiedBy('hippo/query/clause')
7
8
  export default class Clause extends BaseModel {
8
9
  @observable id = uniqueId('clause');
9
-
10
- @observable value;
10
+ @session visible = true;
11
+ @session value;
11
12
  @belongsTo({ type: 'hippo/query' }) query;
12
13
 
13
14
  @belongsTo({ type: 'hippo/query/field' }) field;
14
15
  @belongsTo({ type: 'hippo/query/operator' }) operator;
15
16
 
16
- @computed get description() {
17
- return compact([get(this, 'field.title'), get(this, 'operator.id')]).join(' ');
18
- }
19
-
20
17
  constructor(attrs) {
21
18
  super(attrs);
22
- this.field = first(this.query.info.queryableFields);
23
- autorun(this._updateOperatorOnFieldChange.bind(this));
19
+ if (!this.field) {
20
+ this.field = first(this.query.info.queryableFields);
21
+ }
22
+ observe(this, 'field', this.updateOperatorOnFieldChange, true);
23
+ }
24
+
25
+ @computed get description() {
26
+ return compact([get(this, 'field.title'), get(this, 'operator.id')]).join(' ');
24
27
  }
25
28
 
26
29
  @computed get validOperators() {
@@ -37,15 +40,20 @@ export default class Clause extends BaseModel {
37
40
 
38
41
  toParam() {
39
42
  const param = {};
40
- const op = this.operator.id;
43
+ let op = this.operator.id;
41
44
  let value = this.value;
42
45
  if ('like' === op) { value += '%'; }
46
+ if ('contains' === op) {
47
+ op = 'like';
48
+ value = `%${value}%`;
49
+ }
43
50
  if ('n' === this.field.type) { value = parseFloat(value); }
44
51
  param[this.field.id] = 'eq' === op ? value : { op, value };
45
52
  return param;
46
53
  }
47
54
 
48
- _updateOperatorOnFieldChange() {
49
- this.operator = find(this.query.operators, o => o.isValidForField(this.field));
55
+ @action.bound
56
+ updateOperatorOnFieldChange() {
57
+ this.operator = this.field.preferredOperator;
50
58
  }
51
59
  }
@@ -18,7 +18,8 @@ export default class Field extends BaseModel {
18
18
  @session queryable = true;
19
19
  @session sortable = true;
20
20
  @session textAlign = 'left';
21
-
21
+ @session dataType = 'string'
22
+ @session cellRenderer;
22
23
  @session defaultValue;
23
24
  @session fetchIndex;
24
25
  @session sortBy;
@@ -38,7 +39,7 @@ export default class Field extends BaseModel {
38
39
  }
39
40
 
40
41
  @computed get queryType() {
41
- return get(this.query.src.propertyOptions[this.id], 'type', 'string');
42
+ return get(this.query.src.propertyOptions[this.id], 'type', this.dataType);
42
43
  }
43
44
 
44
45
  @computed get isNumeric() {
@@ -59,4 +60,8 @@ export default class Field extends BaseModel {
59
60
  }
60
61
  return null;
61
62
  }
63
+
64
+ @computed get preferredOperator() {
65
+ return this.query.operators.find(o => o.isValidForField(this));
66
+ }
62
67
  }
@@ -22,15 +22,19 @@ export default class Info {
22
22
  }
23
23
 
24
24
  @computed get visibleFields() {
25
- return filter(this.query.fields, f => f.visible);
25
+ return filter(this.query.fields, 'visible');
26
26
  }
27
27
 
28
28
  @computed get queryableFields() {
29
- return filter(this.query.fields, f => f.queryable);
29
+ return filter(this.query.fields, 'queryable');
30
+ }
31
+
32
+ @computed get visibleClauses() {
33
+ return filter(this.query.clauses, 'visible');
30
34
  }
31
35
 
32
36
  @computed get loadableFields() {
33
- return filter(this.query.fields, f => f.loadable);
37
+ return filter(this.query.fields, 'loadable');
34
38
  }
35
39
 
36
40
  @computed get identifierField() {
@@ -1,3 +1,5 @@
1
+ /* eslint no-param-reassign: 0 */
2
+
1
3
  import qs from 'qs';
2
4
  import 'whatwg-fetch'; // fetch polyfill
3
5
 
@@ -82,32 +84,27 @@ function perform(urlPrefix, defaultOptions = {}) {
82
84
  }
83
85
 
84
86
  const peformMobxyRequest = action('SyncforModel', (mobxObj, options = {}) => {
85
- mobxObj.syncInProgress = new SyncProgess(options); // eslint-disable-line no-param-reassign
87
+ mobxObj.syncInProgress = new SyncProgess(options);
86
88
  return perform(options.url || mobxObj.syncUrl, options)
87
89
  .then(action('syncSuccessHandler', (json) => {
88
- extend(mobxObj, {
89
- errors: json.errors,
90
- syncData: json.data,
91
- lastServerMessage: json.message,
92
- });
90
+ mobxObj.syncData = json.data;
91
+ mobxObj.errors = json.errors;
92
+ mobxObj.lastServerMessage = json.message;
93
+
93
94
  // sometimes the polyfill (or fetch iteself) sets a `:success` property?
94
95
  if (false === json[':success']) { merge(mobxObj, { errors: { fetch: json[':message'] } }); }
95
96
  return json;
96
97
  }))
97
98
  .then(action('whenComplete', json => invokeWhenComplete(json, mobxObj.syncInProgress)))
98
99
  .then(action('syncDoneHandler', () => {
99
- extend(mobxObj, {
100
- syncInProgress: undefined,
101
- });
100
+ mobxObj.syncInProgress = undefined;
102
101
  return mobxObj;
103
102
  }))
104
103
  .catch(action('syncErrorHandler', (e) => {
105
104
  logger.warn(e);
106
- extend(mobxObj, {
107
- errors: { network: e },
108
- syncInProgress: undefined,
109
- lastServerMessage: e.toString(),
110
- });
105
+ mobxObj.errors = { network: e };
106
+ mobxObj.syncInProgress = undefined;
107
+ mobxObj.lastServerMessage = e.toString();
111
108
  return {};
112
109
  }));
113
110
  });