@journeyapps-labs/reactor-mod-data-browser 3.2.1 → 3.4.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 (126) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/@types/actions/schema-model/ViewHasManyAction.d.ts +12 -0
  3. package/dist/@types/core/AbstractConnection.d.ts +12 -0
  4. package/dist/@types/core/SchemaModelDefinition.d.ts +12 -5
  5. package/dist/@types/core/query/StandardModelFields.d.ts +4 -0
  6. package/dist/@types/core/query/query-simple/SimpleQueryFilterState.d.ts +4 -6
  7. package/dist/@types/core/query/widgets/BelongsToDisplayWidget.d.ts +1 -0
  8. package/dist/@types/core/query/widgets/ColumnDisplayWidget.d.ts +1 -0
  9. package/dist/@types/core/query/widgets/IDCellDisplayWidget.d.ts +5 -0
  10. package/dist/@types/core/query/widgets/PeekRelationshipButton.d.ts +1 -0
  11. package/dist/@types/core/query/widgets/SmartBelongsToDisplayWidget.d.ts +2 -0
  12. package/dist/@types/core/query/widgets/SmartColumnWidget.d.ts +8 -3
  13. package/dist/@types/core/query/widgets/SmartFilterWidget.d.ts +6 -0
  14. package/dist/@types/core/types/ManualConnection.d.ts +1 -0
  15. package/dist/@types/entities/ConnectionEntityDefinition.d.ts +2 -1
  16. package/dist/@types/entities/SchemaModelObjectEntityDefinition.d.ts +2 -1
  17. package/dist/@types/forms/types/belongs-to-filter.d.ts +10 -0
  18. package/dist/@types/forms/types/shared/type-handler.d.ts +1 -0
  19. package/dist/@types/forms/types/shared/ui.d.ts +12 -0
  20. package/dist/@types/panels/query/QueryPanelFactory.d.ts +2 -2
  21. package/dist/DataBrowserModule.js +2 -0
  22. package/dist/DataBrowserModule.js.map +1 -1
  23. package/dist/actions/schema-model/ViewHasManyAction.js +112 -0
  24. package/dist/actions/schema-model/ViewHasManyAction.js.map +1 -0
  25. package/dist/core/AbstractConnection.js +58 -5
  26. package/dist/core/AbstractConnection.js.map +1 -1
  27. package/dist/core/SchemaModelDefinition.js +70 -12
  28. package/dist/core/SchemaModelDefinition.js.map +1 -1
  29. package/dist/core/query/StandardModelFields.js +3 -0
  30. package/dist/core/query/StandardModelFields.js.map +1 -1
  31. package/dist/core/query/query-simple/SimpleQueryColumns.js +37 -12
  32. package/dist/core/query/query-simple/SimpleQueryColumns.js.map +1 -1
  33. package/dist/core/query/query-simple/SimpleQueryFilterState.js +35 -7
  34. package/dist/core/query/query-simple/SimpleQueryFilterState.js.map +1 -1
  35. package/dist/core/query/query-simple/SimpleQuerySortState.js +0 -1
  36. package/dist/core/query/query-simple/SimpleQuerySortState.js.map +1 -1
  37. package/dist/core/query/widgets/BelongsToDisplayWidget.js +1 -1
  38. package/dist/core/query/widgets/BelongsToDisplayWidget.js.map +1 -1
  39. package/dist/core/query/widgets/ColumnDisplayWidget.js +13 -2
  40. package/dist/core/query/widgets/ColumnDisplayWidget.js.map +1 -1
  41. package/dist/core/query/widgets/IDCellDisplayWidget.js +33 -0
  42. package/dist/core/query/widgets/IDCellDisplayWidget.js.map +1 -0
  43. package/dist/core/query/widgets/PeekRelationshipButton.js +23 -16
  44. package/dist/core/query/widgets/PeekRelationshipButton.js.map +1 -1
  45. package/dist/core/query/widgets/SmartBelongsToDisplayWidget.js +1 -1
  46. package/dist/core/query/widgets/SmartBelongsToDisplayWidget.js.map +1 -1
  47. package/dist/core/query/widgets/SmartColumnWidget.js +7 -14
  48. package/dist/core/query/widgets/SmartColumnWidget.js.map +1 -1
  49. package/dist/core/query/widgets/SmartFilterWidget.js +10 -7
  50. package/dist/core/query/widgets/SmartFilterWidget.js.map +1 -1
  51. package/dist/core/types/ManualConnection.js +3 -0
  52. package/dist/core/types/ManualConnection.js.map +1 -1
  53. package/dist/entities/ConnectionEntityDefinition.js +29 -6
  54. package/dist/entities/ConnectionEntityDefinition.js.map +1 -1
  55. package/dist/entities/SchemaModelObjectEntityDefinition.js +15 -14
  56. package/dist/entities/SchemaModelObjectEntityDefinition.js.map +1 -1
  57. package/dist/forms/types/attachment-handler.js +1 -0
  58. package/dist/forms/types/attachment-handler.js.map +1 -1
  59. package/dist/forms/types/belongs-to-filter.js +57 -0
  60. package/dist/forms/types/belongs-to-filter.js.map +1 -0
  61. package/dist/forms/types/boolean-handler.js +34 -1
  62. package/dist/forms/types/boolean-handler.js.map +1 -1
  63. package/dist/forms/types/date-handler.js +1 -0
  64. package/dist/forms/types/date-handler.js.map +1 -1
  65. package/dist/forms/types/image-handler.js +1 -0
  66. package/dist/forms/types/image-handler.js.map +1 -1
  67. package/dist/forms/types/location-handler.js +1 -0
  68. package/dist/forms/types/location-handler.js.map +1 -1
  69. package/dist/forms/types/multiple-choice-handler.js +1 -0
  70. package/dist/forms/types/multiple-choice-handler.js.map +1 -1
  71. package/dist/forms/types/multiple-choice-integer-handler.js +1 -0
  72. package/dist/forms/types/multiple-choice-integer-handler.js.map +1 -1
  73. package/dist/forms/types/number-handler.js +3 -2
  74. package/dist/forms/types/number-handler.js.map +1 -1
  75. package/dist/forms/types/single-choice-handler.js +1 -0
  76. package/dist/forms/types/single-choice-handler.js.map +1 -1
  77. package/dist/forms/types/single-choice-integer-handler.js +1 -0
  78. package/dist/forms/types/single-choice-integer-handler.js.map +1 -1
  79. package/dist/forms/types/text-handler.js +1 -0
  80. package/dist/forms/types/text-handler.js.map +1 -1
  81. package/dist/panels/model-json/ModelJsonPanelWidget.js +4 -3
  82. package/dist/panels/model-json/ModelJsonPanelWidget.js.map +1 -1
  83. package/dist/panels/query/QueryPanelFactory.js.map +1 -1
  84. package/dist/panels/query/table-controls/FilterControlsWidget.js +1 -0
  85. package/dist/panels/query/table-controls/FilterControlsWidget.js.map +1 -1
  86. package/dist/stores/ConnectionStore.js +15 -3
  87. package/dist/stores/ConnectionStore.js.map +1 -1
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/dist-module/bundle.js +51 -35
  90. package/dist-module/bundle.js.map +1 -1
  91. package/package.json +4 -4
  92. package/src/DataBrowserModule.ts +2 -0
  93. package/src/actions/schema-model/ViewHasManyAction.ts +114 -0
  94. package/src/core/AbstractConnection.ts +45 -2
  95. package/src/core/SchemaModelDefinition.ts +90 -14
  96. package/src/core/query/StandardModelFields.ts +8 -0
  97. package/src/core/query/query-simple/SimpleQueryColumns.tsx +50 -14
  98. package/src/core/query/query-simple/SimpleQueryFilterState.ts +42 -9
  99. package/src/core/query/query-simple/SimpleQuerySortState.ts +0 -1
  100. package/src/core/query/widgets/BelongsToDisplayWidget.tsx +2 -1
  101. package/src/core/query/widgets/ColumnDisplayWidget.tsx +17 -4
  102. package/src/core/query/widgets/IDCellDisplayWidget.tsx +46 -0
  103. package/src/core/query/widgets/PeekRelationshipButton.tsx +23 -16
  104. package/src/core/query/widgets/SmartBelongsToDisplayWidget.tsx +3 -0
  105. package/src/core/query/widgets/SmartColumnWidget.tsx +27 -23
  106. package/src/core/query/widgets/SmartFilterWidget.tsx +14 -5
  107. package/src/core/types/ManualConnection.ts +4 -0
  108. package/src/entities/ConnectionEntityDefinition.tsx +32 -4
  109. package/src/entities/SchemaModelObjectEntityDefinition.ts +17 -15
  110. package/src/forms/types/attachment-handler.tsx +1 -0
  111. package/src/forms/types/belongs-to-filter.tsx +84 -0
  112. package/src/forms/types/boolean-handler.tsx +46 -2
  113. package/src/forms/types/date-handler.tsx +1 -0
  114. package/src/forms/types/image-handler.tsx +1 -0
  115. package/src/forms/types/location-handler.tsx +1 -0
  116. package/src/forms/types/multiple-choice-handler.tsx +1 -0
  117. package/src/forms/types/multiple-choice-integer-handler.tsx +1 -0
  118. package/src/forms/types/number-handler.tsx +3 -2
  119. package/src/forms/types/shared/type-handler.ts +1 -0
  120. package/src/forms/types/single-choice-handler.tsx +1 -0
  121. package/src/forms/types/single-choice-integer-handler.tsx +1 -0
  122. package/src/forms/types/text-handler.tsx +1 -0
  123. package/src/panels/model-json/ModelJsonPanelWidget.tsx +8 -6
  124. package/src/panels/query/QueryPanelFactory.tsx +2 -2
  125. package/src/panels/query/table-controls/FilterControlsWidget.tsx +1 -0
  126. package/src/stores/ConnectionStore.ts +17 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeyapps-labs/reactor-mod-data-browser",
3
- "version": "3.2.1",
3
+ "version": "3.4.0",
4
4
  "main": "./dist/index.js",
5
5
  "typings": "./dist/@types/index",
6
6
  "publishConfig": {
@@ -21,8 +21,8 @@
21
21
  "@journeyapps-labs/common-sdk": "^1.0.3",
22
22
  "@journeyapps-labs/common-utils": "^1.0.1",
23
23
  "@journeyapps-labs/lib-reactor-data-layer": "1.0.10",
24
- "@journeyapps-labs/reactor-mod": "5.2.1",
25
- "@journeyapps-labs/reactor-mod-editor": "2.1.2",
24
+ "@journeyapps-labs/reactor-mod": "5.3.1",
25
+ "@journeyapps-labs/reactor-mod-editor": "2.2.1",
26
26
  "@journeyapps/db": "^8.1.1",
27
27
  "@journeyapps/parser-schema": "^8.2.5",
28
28
  "@projectstorm/react-workspaces-core": "4.2.3",
@@ -34,7 +34,7 @@
34
34
  "uuid": "^13.0.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@journeyapps-labs/lib-reactor-builder": "2.0.5",
37
+ "@journeyapps-labs/lib-reactor-builder": "3.0.1",
38
38
  "@types/async": "^3.2.25",
39
39
  "@types/lodash": "^4.17.24",
40
40
  "@types/react": "^19.2.14",
@@ -17,6 +17,7 @@ import { SchemaModelObjectEntityDefinition } from './entities/SchemaModelObjectE
17
17
  import { EditSchemaModelAction } from './actions/schema-model/EditSchemaModelAction';
18
18
  import { TypeEngine } from './forms/TypeEngine';
19
19
  import { ViewSchemaModelAsJsonAction } from './actions/schema-model/ViewSchemaModelAsJsonAction';
20
+ import { ViewHasManyAction } from './actions/schema-model/ViewHasManyAction';
20
21
  import { ModelJsonPanelFactory } from './panels/model-json/ModelJsonPanelFactory';
21
22
  import { SchemaModelIndexDefinition } from './entities/SchemaModelIndexDefinition';
22
23
  import { SavedQueryStore } from './stores/SavedQueryStore';
@@ -49,6 +50,7 @@ export class DataBrowserModule extends AbstractReactorModule {
49
50
  actionStore.registerAction(new CreateModelAction());
50
51
  actionStore.registerAction(new EditSchemaModelAction());
51
52
  actionStore.registerAction(new ViewSchemaModelAsJsonAction());
53
+ actionStore.registerAction(new ViewHasManyAction());
52
54
  actionStore.registerAction(new OpenSavedQueryAction());
53
55
  actionStore.registerAction(new RemoveSavedQueryAction());
54
56
 
@@ -0,0 +1,114 @@
1
+ import {
2
+ ActionStore,
3
+ ComboBoxItem,
4
+ ComboBoxStore2,
5
+ EntityAction,
6
+ EntityActionEvent,
7
+ NotificationStore,
8
+ NotificationType,
9
+ SimpleComboBoxDirective,
10
+ WorkspaceStore,
11
+ inject,
12
+ ioc
13
+ } from '@journeyapps-labs/reactor-mod';
14
+ import { Relationship } from '@journeyapps/parser-schema';
15
+ import { DataBrowserEntities } from '../../entities';
16
+ import { SchemaModelObject } from '../../core/SchemaModelObject';
17
+ import { QueryPanelModel } from '../../panels/query/QueryPanelFactory';
18
+ import { SimpleQuery } from '../../core/query/query-simple/SimpleQuery';
19
+ import { Condition, SimpleFilter, Statement } from '../../core/query/filters';
20
+
21
+ export class ViewHasManyAction extends EntityAction<SchemaModelObject> {
22
+ static ID = 'VIEW_HAS_MANY';
23
+
24
+ @inject(ComboBoxStore2)
25
+ accessor comboStore: ComboBoxStore2;
26
+
27
+ @inject(WorkspaceStore)
28
+ accessor workspaceStore: WorkspaceStore;
29
+
30
+ @inject(NotificationStore)
31
+ accessor notifications: NotificationStore;
32
+
33
+ constructor() {
34
+ super({
35
+ id: ViewHasManyAction.ID,
36
+ name: 'View has many',
37
+ icon: 'list',
38
+ target: DataBrowserEntities.SCHEMA_MODEL_OBJECT
39
+ });
40
+ }
41
+
42
+ async fireEvent(event: EntityActionEvent<SchemaModelObject>): Promise<any> {
43
+ const relationships = Object.values(event.targetEntity.definition.definition.hasMany || {});
44
+ if (relationships.length === 0) {
45
+ this.notifications.showNotification({
46
+ title: 'No relationships',
47
+ description: 'This record has no has-many relationships to view.',
48
+ type: NotificationType.ERROR
49
+ });
50
+ return;
51
+ }
52
+
53
+ const directive = await this.comboStore.show(
54
+ new SimpleComboBoxDirective({
55
+ title: 'View has many',
56
+ subtitle: event.targetEntity.data.display || event.targetEntity.id,
57
+ event: event.position,
58
+ items: relationships.map((relationship) => {
59
+ return {
60
+ key: relationship.foreignName || relationship.name,
61
+ title: relationship.foreignName || relationship.name,
62
+ subtitle: relationship.objectType.label || relationship.objectType.name,
63
+ action: async () => {}
64
+ } as ComboBoxItem;
65
+ })
66
+ })
67
+ );
68
+
69
+ const selected = directive.getSelectedItem();
70
+ if (!selected?.key) {
71
+ return;
72
+ }
73
+
74
+ const relationship = relationships.find((entry) => (entry.foreignName || entry.name) === selected.key);
75
+ if (!relationship) {
76
+ return;
77
+ }
78
+
79
+ await this.openRelationshipQuery(event.targetEntity, relationship);
80
+ }
81
+
82
+ private async openRelationshipQuery(parent: SchemaModelObject, relationship: Relationship) {
83
+ const definition = await parent.definition.connection.waitForSchemaModelDefinitionByName(
84
+ relationship.objectType.name
85
+ );
86
+ const variable = definition.getBelongsToIdVariableForRelationship(relationship.name);
87
+ if (!variable) {
88
+ this.notifications.showNotification({
89
+ title: 'Cannot open relationship',
90
+ description: `No belongs-to field found for ${relationship.name}.`,
91
+ type: NotificationType.ERROR
92
+ });
93
+ return;
94
+ }
95
+
96
+ variable.label = relationship.name;
97
+
98
+ const query = new SimpleQuery({
99
+ definition,
100
+ limit: 30
101
+ });
102
+
103
+ query.filterState.setFilter(
104
+ variable.name,
105
+ new SimpleFilter(variable, [new Statement(Condition.EQUALS, parent.id)])
106
+ );
107
+
108
+ this.workspaceStore.addModel(new QueryPanelModel(query));
109
+ }
110
+
111
+ static get() {
112
+ return ioc.get(ActionStore).getActionByID<ViewHasManyAction>(ViewHasManyAction.ID);
113
+ }
114
+ }
@@ -7,7 +7,7 @@ import { v4 } from 'uuid';
7
7
  import { BaseObserver } from '@journeyapps-labs/common-utils';
8
8
  import { Collection, LifecycleCollection } from '@journeyapps-labs/lib-reactor-data-layer';
9
9
  import { when } from 'mobx';
10
- import { observable } from 'mobx';
10
+ import { computed, observable } from 'mobx';
11
11
  import { EntityDescription } from '@journeyapps-labs/reactor-mod';
12
12
  import { V4BackendClient, V4Index, V4Indexes } from '@journeyapps-labs/client-backend-v4';
13
13
  import { SchemaModelObject } from './SchemaModelObject';
@@ -24,18 +24,27 @@ export interface AbstractConnectionListener {
24
24
  removed: () => any;
25
25
  }
26
26
 
27
+ export enum ConnectionOnlineState {
28
+ OFFLINE = 'offline',
29
+ LOADING = 'loading',
30
+ ONLINE = 'online'
31
+ }
32
+
27
33
  export abstract class AbstractConnection extends BaseObserver<AbstractConnectionListener> {
28
34
  id: string;
29
35
  @observable accessor color: string;
36
+ @observable accessor onlineState: ConnectionOnlineState;
30
37
 
31
38
  schema_models_collection: Collection<ObjectType>;
32
39
  schema_models: LifecycleCollection<ObjectType, SchemaModelDefinition>;
33
40
  private fetch_indexes_promise: Promise<V4Indexes['models']>;
41
+ private initialize_online_promise: Promise<void>;
34
42
 
35
43
  constructor(public factory: AbstractConnectionFactory) {
36
44
  super();
37
45
  this.id = v4();
38
46
  this.color = getDefaultConnectionColor(this.id);
47
+ this.onlineState = ConnectionOnlineState.OFFLINE;
39
48
  this.schema_models_collection = new Collection();
40
49
  this.schema_models = new LifecycleCollection({
41
50
  collection: this.schema_models_collection,
@@ -88,7 +97,16 @@ export abstract class AbstractConnection extends BaseObserver<AbstractConnection
88
97
  return this.schema_models.items.find((i) => i.definition.name === name);
89
98
  }
90
99
 
100
+ @computed get isOnline() {
101
+ return this.onlineState === ConnectionOnlineState.ONLINE;
102
+ }
103
+
104
+ @computed get isLoadingOnline() {
105
+ return this.onlineState === ConnectionOnlineState.LOADING;
106
+ }
107
+
91
108
  async waitForSchemaModelDefinitionByName(name: string) {
109
+ await this.ensureOnline();
92
110
  await when(() => !!this.getSchemaModelDefinitionByName(name));
93
111
  return this.getSchemaModelDefinitionByName(name);
94
112
  }
@@ -107,10 +125,35 @@ export abstract class AbstractConnection extends BaseObserver<AbstractConnection
107
125
  });
108
126
  }
109
127
 
110
- async init() {
128
+ async init() {}
129
+
130
+ protected async connectOnline() {
111
131
  await this.reload();
112
132
  }
113
133
 
134
+ async ensureOnline() {
135
+ if (this.isOnline) {
136
+ return;
137
+ }
138
+ if (!this.initialize_online_promise) {
139
+ this.initialize_online_promise = this.initializeOnline();
140
+ }
141
+ await this.initialize_online_promise;
142
+ }
143
+
144
+ private async initializeOnline() {
145
+ this.onlineState = ConnectionOnlineState.LOADING;
146
+ try {
147
+ await this.connectOnline();
148
+ this.onlineState = ConnectionOnlineState.ONLINE;
149
+ } finally {
150
+ if (this.onlineState === ConnectionOnlineState.LOADING) {
151
+ this.onlineState = ConnectionOnlineState.OFFLINE;
152
+ }
153
+ this.initialize_online_promise = null;
154
+ }
155
+ }
156
+
114
157
  protected async getSchemaModelDefinitions() {
115
158
  const schema = await this.getSchema();
116
159
  return _.map(schema.objects, (o) => {
@@ -1,6 +1,7 @@
1
1
  import { AbstractConnection } from './AbstractConnection';
2
2
  import { ObjectType } from '@journeyapps/parser-schema';
3
- import { Collection, JourneyAPIAdapter, Query } from '@journeyapps/db';
3
+ import { Collection, JourneyAPIAdapter, Query, Variable } from '@journeyapps/db';
4
+ import * as _ from 'lodash';
4
5
  import { SchemaModelObject } from './SchemaModelObject';
5
6
  import { LifecycleModel } from '@journeyapps-labs/lib-reactor-data-layer';
6
7
  import { BaseObserver } from '@journeyapps-labs/common-utils';
@@ -10,6 +11,7 @@ import { V4Index } from '@journeyapps-labs/client-backend-v4';
10
11
  import { action, observable } from 'mobx';
11
12
  import { IndexModel } from './IndexModel';
12
13
  import { TypeEngine } from '../forms/TypeEngine';
14
+ import { STANDARD_MODEL_FIELD_LABELS, StandardModelFields, idVariable } from './query/StandardModelFields';
13
15
 
14
16
  export interface SchemaModelDefinitionListener {
15
17
  resolved: (event: { object: SchemaModelObject }) => any;
@@ -20,6 +22,13 @@ export interface SchemaModelDefinitionOptions {
20
22
  connection: AbstractConnection;
21
23
  definition: ObjectType;
22
24
  }
25
+
26
+ export interface FilterableField {
27
+ key: string;
28
+ label: string;
29
+ group: 'Fields' | 'Belongs to';
30
+ }
31
+
23
32
  export class SchemaModelDefinition
24
33
  extends BaseObserver<SchemaModelDefinitionListener>
25
34
  implements LifecycleModel<ObjectType>
@@ -171,18 +180,85 @@ export class SchemaModelDefinition
171
180
  });
172
181
  }
173
182
 
174
- getFilterableFields(typeEngine: TypeEngine): { key: string; label: string }[] {
175
- return Object.values(this.definition.attributes)
176
- .map((attribute) => {
177
- const handler = typeEngine.getHandler(attribute.type);
178
- if (!handler?.setupFilter) {
179
- return null;
180
- }
181
- return {
182
- key: attribute.name,
183
- label: attribute.label || attribute.name
184
- };
185
- })
186
- .filter((value) => !!value);
183
+ getBelongsToIdVariableForRelationship(relationshipName: string): Variable | undefined {
184
+ const variable = _.find(_.values(this.definition.belongsToIdVars), (entry) => {
185
+ return entry.relationship === relationshipName;
186
+ });
187
+
188
+ if (!variable) {
189
+ return undefined;
190
+ }
191
+
192
+ const relationship = this.definition.belongsTo[relationshipName];
193
+ if (relationship) {
194
+ variable.label = relationship.name;
195
+ }
196
+
197
+ return variable;
198
+ }
199
+
200
+ getBelongsToRelationshipForField(field: string):
201
+ | {
202
+ variable: Variable;
203
+ relationship: ObjectType['belongsTo'][string];
204
+ }
205
+ | undefined {
206
+ const variable = _.find(_.values(this.definition.belongsToIdVars), (entry) => {
207
+ return entry.name === field;
208
+ });
209
+ if (!variable?.relationship) {
210
+ return undefined;
211
+ }
212
+
213
+ const relationship = this.definition.belongsTo[variable.relationship];
214
+ if (!relationship) {
215
+ return undefined;
216
+ }
217
+
218
+ variable.label = relationship.name;
219
+ return {
220
+ variable,
221
+ relationship
222
+ };
223
+ }
224
+
225
+ getFilterableFields(typeEngine: TypeEngine): FilterableField[] {
226
+ return [
227
+ ...(typeEngine.getHandler(idVariable.type)?.setupFilter
228
+ ? [
229
+ {
230
+ key: StandardModelFields.ID,
231
+ label: STANDARD_MODEL_FIELD_LABELS[StandardModelFields.ID],
232
+ group: 'Fields' as const
233
+ }
234
+ ]
235
+ : []),
236
+ ...Object.values(this.definition.belongsToIdVars)
237
+ .map((variable) => {
238
+ if (!variable?.name || !variable.relationship) {
239
+ return null;
240
+ }
241
+ const relationship = this.definition.belongsTo[variable.relationship];
242
+ return {
243
+ key: variable.name,
244
+ label: relationship?.name || variable.label || variable.name,
245
+ group: 'Belongs to' as const
246
+ };
247
+ })
248
+ .filter((value) => !!value),
249
+ ...Object.values(this.definition.attributes)
250
+ .map((attribute) => {
251
+ const handler = typeEngine.getHandler(attribute.type);
252
+ if (!handler?.setupFilter) {
253
+ return null;
254
+ }
255
+ return {
256
+ key: attribute.name,
257
+ label: attribute.label || attribute.name,
258
+ group: 'Fields' as const
259
+ };
260
+ })
261
+ .filter((value) => !!value)
262
+ ];
187
263
  }
188
264
  }
@@ -1,3 +1,5 @@
1
+ import { TextType, Variable } from '@journeyapps/db';
2
+
1
3
  export enum StandardModelFields {
2
4
  ID = 'id',
3
5
  UPDATED_AT = 'updated_at'
@@ -7,3 +9,9 @@ export const STANDARD_MODEL_FIELD_LABELS: Record<StandardModelFields, string> =
7
9
  [StandardModelFields.ID]: 'ID',
8
10
  [StandardModelFields.UPDATED_AT]: 'Updated at'
9
11
  };
12
+
13
+ export const idVariable = new Variable(StandardModelFields.ID, new TextType()) as Variable & {
14
+ label?: string;
15
+ };
16
+
17
+ idVariable.label = STANDARD_MODEL_FIELD_LABELS[StandardModelFields.ID];
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { TableColumn } from '@journeyapps-labs/reactor-mod';
2
+ import { ioc, TableColumn } from '@journeyapps-labs/reactor-mod';
3
3
  import * as _ from 'lodash';
4
4
  import { PageRow } from '../Page';
5
5
  import { CellDisplayWidget } from '../widgets/CellDisplayWidget';
@@ -7,12 +7,16 @@ import { SmartColumnWidget } from '../widgets/SmartColumnWidget';
7
7
  import { SmartCellDisplayWidget } from '../widgets/SmartCellDisplayWidget';
8
8
  import { SmartBelongsToDisplayWidget } from '../widgets/SmartBelongsToDisplayWidget';
9
9
  import { ColumnDisplayWidget } from '../widgets/ColumnDisplayWidget';
10
+ import { IDCellDisplayWidget } from '../widgets/IDCellDisplayWidget';
10
11
  import { SchemaModelDefinition } from '../../SchemaModelDefinition';
11
12
  import { AbstractConnection } from '../../AbstractConnection';
12
13
  import { SimpleQuerySort, SortDirection } from './SimpleQueryTypes';
13
14
  import { SimpleQuerySortState } from './SimpleQuerySortState';
14
15
  import { SimpleQueryFilterState } from './SimpleQueryFilterState';
15
- import { STANDARD_MODEL_FIELD_LABELS, StandardModelFields } from '../StandardModelFields';
16
+ import { STANDARD_MODEL_FIELD_LABELS, StandardModelFields, idVariable } from '../StandardModelFields';
17
+ import { TypeEngine } from '../../../forms/TypeEngine';
18
+ import { setupBelongsToFilter } from '../../../forms/types/belongs-to-filter';
19
+ import { Condition, SimpleFilter, Statement } from '../filters';
16
20
 
17
21
  export interface BuildSimpleQueryColumnsOptions {
18
22
  definition: SchemaModelDefinition;
@@ -27,27 +31,31 @@ export const buildSimpleQueryColumns = (options: BuildSimpleQueryColumnsOptions)
27
31
  if (!direction) {
28
32
  return label;
29
33
  }
30
- return `${label} ${direction === SortDirection.ASC ? '' : ''}`;
34
+ return `${label} ${direction === SortDirection.ASC ? '' : ''}`;
31
35
  };
32
36
 
33
37
  return [
34
38
  {
35
39
  key: StandardModelFields.ID,
36
40
  display: (
37
- <ColumnDisplayWidget
38
- label={getSortLabel(StandardModelFields.ID, STANDARD_MODEL_FIELD_LABELS[StandardModelFields.ID])}
39
- onClick={async () => {
40
- const sort = options.sortState.getSort(StandardModelFields.ID);
41
- if (!sort) {
42
- options.sortState.addSort(SimpleQuerySort.create(StandardModelFields.ID));
41
+ <SmartColumnWidget
42
+ variable={idVariable}
43
+ typeLabel={ioc.get(TypeEngine).getHandler(idVariable.type)?.getTypeLabel?.(idVariable.type)}
44
+ filter={options.filterState.getFilter(StandardModelFields.ID)}
45
+ filterChanged={async (filter) => {
46
+ if (!filter) {
47
+ options.filterState.getFilter(StandardModelFields.ID)?.delete();
43
48
  return;
44
49
  }
45
- sort.toggle();
50
+ options.filterState.setFilter(StandardModelFields.ID, filter);
46
51
  }}
47
52
  />
48
53
  ),
49
54
  noWrap: true,
50
- shrink: true
55
+ shrink: true,
56
+ accessor: (cell, row: PageRow) => {
57
+ return <IDCellDisplayWidget id={row.model.id} />;
58
+ }
51
59
  },
52
60
  {
53
61
  key: StandardModelFields.UPDATED_AT,
@@ -79,14 +87,41 @@ export const buildSimpleQueryColumns = (options: BuildSimpleQueryColumnsOptions)
79
87
  display: (
80
88
  <SmartColumnWidget
81
89
  variable={options.definition.definition.belongsToVars[a.relationship]}
82
- type={options.definition.definition.belongsTo[a.relationship].foreignType}
83
- filterChanged={(filter) => {}}
90
+ typeLabel={`Belongs To: ${options.definition.definition.belongsTo[a.relationship].foreignType.label}`}
91
+ filter={options.filterState.getFilter(a.name)}
92
+ setupFilter={async ({ filter }) => {
93
+ return await setupBelongsToFilter({
94
+ definition: options.definition,
95
+ relationship: options.definition.definition.belongsTo[a.relationship],
96
+ variable: a,
97
+ filter
98
+ });
99
+ }}
100
+ filterChanged={async (filter) => {
101
+ if (!filter) {
102
+ options.filterState.getFilter(a.name)?.delete();
103
+ return;
104
+ }
105
+ options.filterState.setFilter(a.name, filter);
106
+ }}
84
107
  />
85
108
  ),
86
109
  noWrap: true,
87
110
  shrink: true,
88
111
  accessor: (cell, row: PageRow) => {
89
- return <SmartBelongsToDisplayWidget variable_id={a} row={row} connection={options.connection} />;
112
+ return (
113
+ <SmartBelongsToDisplayWidget
114
+ variable_id={a}
115
+ row={row}
116
+ connection={options.connection}
117
+ filterBelongsTo={async (object) => {
118
+ options.filterState.setFilter(
119
+ a.name,
120
+ new SimpleFilter(a, [new Statement(Condition.EQUALS, object.id)])
121
+ );
122
+ }}
123
+ />
124
+ );
90
125
  }
91
126
  } as TableColumn;
92
127
  }),
@@ -96,6 +131,7 @@ export const buildSimpleQueryColumns = (options: BuildSimpleQueryColumnsOptions)
96
131
  display: (
97
132
  <SmartColumnWidget
98
133
  variable={a}
134
+ typeLabel={ioc.get(TypeEngine).getHandler(a.type)?.getTypeLabel?.(a.type)}
99
135
  filter={options.filterState.getFilter(a.name)}
100
136
  sortDirection={options.sortState.getSort(a.name)?.direction}
101
137
  onToggleSort={async () => {
@@ -1,9 +1,11 @@
1
1
  import { Variable } from '@journeyapps/db';
2
2
  import * as _ from 'lodash';
3
3
  import { BaseObserver } from '@journeyapps-labs/common-utils';
4
- import { SchemaModelDefinition } from '../../SchemaModelDefinition';
4
+ import { FilterableField, SchemaModelDefinition } from '../../SchemaModelDefinition';
5
5
  import { SerializedSimpleFilter, SimpleFilter } from '../filters';
6
6
  import { TypeEngine } from '../../../forms/TypeEngine';
7
+ import { setupBelongsToFilter } from '../../../forms/types/belongs-to-filter';
8
+ import { idVariable, StandardModelFields } from '../StandardModelFields';
7
9
 
8
10
  export interface SimpleQueryFilterStateListener {
9
11
  changed: () => any;
@@ -52,7 +54,7 @@ export class SimpleQueryFilterState extends BaseObserver<SimpleQueryFilterStateL
52
54
  return definitionChanged || hadFilters;
53
55
  }
54
56
 
55
- getFilterableFields(): { key: string; label: string }[] {
57
+ getFilterableFields(): FilterableField[] {
56
58
  if (!this.definition?.definition) {
57
59
  return [];
58
60
  }
@@ -69,7 +71,7 @@ export class SimpleQueryFilterState extends BaseObserver<SimpleQueryFilterStateL
69
71
  }
70
72
 
71
73
  getFilter(field: string): SimpleFilter | undefined {
72
- const variable = this.resolveAttribute(field);
74
+ const variable = this.resolveField(field);
73
75
  return variable ? this.simpleFilters.get(variable) : undefined;
74
76
  }
75
77
 
@@ -87,9 +89,7 @@ export class SimpleQueryFilterState extends BaseObserver<SimpleQueryFilterStateL
87
89
  if (!SimpleFilter.canDeserialize(filter)) {
88
90
  return;
89
91
  }
90
- const variable = _.find(_.values(definition.definition.attributes), (attribute) => {
91
- return attribute.name === filter.variable;
92
- });
92
+ const variable = this.resolveField(filter.variable);
93
93
  if (!variable) {
94
94
  return;
95
95
  }
@@ -98,7 +98,7 @@ export class SimpleQueryFilterState extends BaseObserver<SimpleQueryFilterStateL
98
98
  }
99
99
 
100
100
  setFilter(field: string, filter: SimpleFilter) {
101
- const variable = this.resolveAttribute(field);
101
+ const variable = this.resolveField(field);
102
102
  if (!variable) {
103
103
  return false;
104
104
  }
@@ -106,7 +106,21 @@ export class SimpleQueryFilterState extends BaseObserver<SimpleQueryFilterStateL
106
106
  }
107
107
 
108
108
  async setupFilterForField(field: string, position?: MouseEvent): Promise<boolean> {
109
- const variable = this.resolveAttribute(field);
109
+ const relationship = this.resolveBelongsToField(field);
110
+ if (relationship) {
111
+ const existing = this.simpleFilters.get(relationship.variable);
112
+ const nextFilter = await setupBelongsToFilter({
113
+ definition: this.definition,
114
+ relationship: relationship.relationship,
115
+ variable: relationship.variable,
116
+ filter: existing
117
+ });
118
+ if (!nextFilter) {
119
+ return false;
120
+ }
121
+ return this.addFilter(relationship.variable, nextFilter);
122
+ }
123
+ const variable = this.resolveField(field);
110
124
  if (!variable) {
111
125
  return false;
112
126
  }
@@ -149,12 +163,31 @@ export class SimpleQueryFilterState extends BaseObserver<SimpleQueryFilterStateL
149
163
  return true;
150
164
  }
151
165
 
152
- private resolveAttribute(field: string): Variable | undefined {
166
+ private resolveField(field: string): Variable | undefined {
153
167
  if (!this.definition?.definition) {
154
168
  return undefined;
155
169
  }
170
+ if (field === StandardModelFields.ID) {
171
+ return idVariable;
172
+ }
173
+ const relationship = this.resolveBelongsToField(field);
174
+ if (relationship) {
175
+ return relationship.variable;
176
+ }
156
177
  return _.find(_.values(this.definition.definition.attributes), (attribute) => {
157
178
  return attribute.name === field;
158
179
  });
159
180
  }
181
+
182
+ private resolveBelongsToField(field: string):
183
+ | {
184
+ variable: Variable;
185
+ relationship: any;
186
+ }
187
+ | undefined {
188
+ if (!this.definition?.definition) {
189
+ return undefined;
190
+ }
191
+ return this.definition.getBelongsToRelationshipForField(field);
192
+ }
160
193
  }
@@ -105,7 +105,6 @@ export class SimpleQuerySortState extends BaseObserver<SimpleQuerySortStateListe
105
105
  };
106
106
  });
107
107
  return [
108
- { key: StandardModelFields.ID, label: STANDARD_MODEL_FIELD_LABELS[StandardModelFields.ID] },
109
108
  {
110
109
  key: StandardModelFields.UPDATED_AT,
111
110
  label: STANDARD_MODEL_FIELD_LABELS[StandardModelFields.UPDATED_AT]
@@ -14,6 +14,7 @@ export interface BelongsToDisplayWidgetProps {
14
14
  id: string;
15
15
  connection: AbstractConnection;
16
16
  open: (object: SchemaModelObject) => any;
17
+ filterBelongsTo?: (object: SchemaModelObject) => any;
17
18
  className?: any;
18
19
  }
19
20
 
@@ -62,7 +63,7 @@ export const BelongsToDisplayWidget: React.FC<BelongsToDisplayWidgetProps> = obs
62
63
  <S.Container className={props.className}>
63
64
  {object.data.display}
64
65
  <S.Actions>
65
- <PeekRelationshipButton object={object} open={props.open} />
66
+ <PeekRelationshipButton object={object} open={props.open} filterBelongsTo={props.filterBelongsTo} />
66
67
  <TableButtonWidget
67
68
  icon="arrow-right"
68
69
  action={() => {