@projectcaluma/ember-core 9.0.2 → 10.0.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # [@projectcaluma/ember-core-v10.0.0](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-core-v9.2.0...@projectcaluma/ember-core-v10.0.0) (2021-11-25)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **resolver:** fix concurrency issues with resolver helper ([7f09834](https://github.com/projectcaluma/ember-caluma/commit/7f09834b07437ecd91c548601b81360176717481))
7
+
8
+
9
+ ### chore
10
+
11
+ * **deps:** update dependencies and drop support for node 10 ([51d6dee](https://github.com/projectcaluma/ember-caluma/commit/51d6deeda9811518622ba0cefd8d3876651dab4f))
12
+
13
+
14
+ ### BREAKING CHANGES
15
+
16
+ * **deps:** Remove support for node v10
@@ -7,7 +7,6 @@ export default {
7
7
  "WorkItem",
8
8
  "Flow",
9
9
  "DynamicOption",
10
- "CalculatedFloatQuestion",
11
10
  "Option",
12
11
  "TextQuestion",
13
12
  "StringAnswer",
@@ -30,13 +29,14 @@ export default {
30
29
  "StaticQuestion",
31
30
  "FileAnswer",
32
31
  "File",
32
+ "CalculatedFloatQuestion",
33
+ "ActionButtonQuestion",
33
34
  "SimpleTask",
34
35
  "CompleteWorkflowFormTask",
35
36
  "CompleteTaskFormTask",
36
37
  ],
37
38
  Task: ["SimpleTask", "CompleteWorkflowFormTask", "CompleteTaskFormTask"],
38
39
  Question: [
39
- "CalculatedFloatQuestion",
40
40
  "TextQuestion",
41
41
  "ChoiceQuestion",
42
42
  "MultipleChoiceQuestion",
@@ -50,6 +50,8 @@ export default {
50
50
  "FormQuestion",
51
51
  "FileQuestion",
52
52
  "StaticQuestion",
53
+ "CalculatedFloatQuestion",
54
+ "ActionButtonQuestion",
53
55
  ],
54
56
  Answer: [
55
57
  "StringAnswer",
@@ -60,4 +62,5 @@ export default {
60
62
  "TableAnswer",
61
63
  "FileAnswer",
62
64
  ],
65
+ DynamicQuestion: ["DynamicChoiceQuestion", "DynamicMultipleChoiceQuestion"],
63
66
  };
@@ -2,18 +2,19 @@ import { getOwner, setOwner } from "@ember/application";
2
2
  import { assert } from "@ember/debug";
3
3
  import { tracked } from "@glimmer/tracking";
4
4
  import { queryManager } from "ember-apollo-client";
5
- import gql from "graphql-tag";
5
+ import {
6
+ enqueueTask,
7
+ lastValue,
8
+ restartableTask,
9
+ task,
10
+ } from "ember-concurrency-decorators";
11
+ import { gql } from "graphql-tag";
6
12
 
7
13
  export default class BaseQuery {
8
14
  @queryManager apollo;
9
15
 
10
16
  @tracked items = [];
11
17
 
12
- @tracked totalCount = 0;
13
- @tracked hasNextPage = true;
14
-
15
- @tracked isLoading = true;
16
-
17
18
  constructor({
18
19
  pageSize = null,
19
20
  processNew = async (items) => items,
@@ -21,17 +22,13 @@ export default class BaseQuery {
21
22
  queryOptions = {},
22
23
  }) {
23
24
  this.pageSize = pageSize;
24
- this.cursor = null;
25
-
26
25
  this.processNew = processNew;
27
26
  this.processAll = processAll;
28
27
  this.queryOptions = queryOptions;
29
28
  }
30
29
 
31
30
  get query() {
32
- assert("`query` must be implemented on the model");
33
-
34
- return "";
31
+ return assert("`query` must be implemented on the model");
35
32
  }
36
33
 
37
34
  get pagination() {
@@ -52,51 +49,20 @@ export default class BaseQuery {
52
49
  return factory.class;
53
50
  }
54
51
 
55
- fetch({ filter = [], order = [], queryOptions = {} } = {}) {
56
- this.items = [];
57
- this.totalCount = 0;
58
- this.hasNextPage = true;
59
- this.cursor = null;
52
+ get isLoading() {
53
+ return this._fetch.isRunning || this._fetchMore.isRunning;
54
+ }
60
55
 
61
- this.filter = filter;
62
- this.order = order;
63
- this.queryOptions = { ...(this.queryOptions ?? {}), ...queryOptions };
56
+ get totalCount() {
57
+ return this._data?.[this.dataKey].totalCount;
58
+ }
64
59
 
65
- return this.fetchMore();
60
+ get hasNextPage() {
61
+ return this._data?.[this.dataKey].pageInfo.hasNextPage;
66
62
  }
67
63
 
68
- async fetchMore() {
69
- if (this.hasNextPage) {
70
- this.isLoading = true;
71
-
72
- const data = await this.apollo.query({
73
- query: gql`
74
- ${this.query}
75
- `,
76
- variables: {
77
- filter: this.filter,
78
- order: this.order,
79
- pageSize: this.pageSize,
80
- cursor: this.cursor,
81
- },
82
- fetchPolicy: "network-only",
83
- ...this.queryOptions,
84
- });
85
-
86
- this.cursor = data[this.dataKey].pageInfo.endCursor;
87
-
88
- this.hasNextPage = data[this.dataKey].pageInfo.hasNextPage;
89
- this.totalCount = data[this.dataKey].totalCount;
90
-
91
- const rawItems = data[this.dataKey].edges.map(({ node }) => node);
92
-
93
- this.items = await this.processAll([
94
- ...this.items,
95
- ...(await this.processNew(rawItems)),
96
- ]);
97
-
98
- this.isLoading = false;
99
- }
64
+ get cursor() {
65
+ return this._data?.[this.dataKey].pageInfo.endCursor;
100
66
  }
101
67
 
102
68
  get value() {
@@ -112,4 +78,59 @@ export default class BaseQuery {
112
78
  return instance;
113
79
  });
114
80
  }
81
+
82
+ fetch(...args) {
83
+ return this._fetch.perform(...args);
84
+ }
85
+
86
+ fetchMore(...args) {
87
+ return this._fetchMore.perform(...args);
88
+ }
89
+
90
+ @restartableTask
91
+ *_fetch({ filter = [], order = [], queryOptions = {} } = {}) {
92
+ yield this._fetchPage.cancelAll({ resetState: true });
93
+
94
+ this.items = [];
95
+
96
+ this.filter = filter;
97
+ this.order = order;
98
+ this.queryOptions = { ...(this.queryOptions ?? {}), ...queryOptions };
99
+
100
+ return yield this._fetchPage.linked().perform();
101
+ }
102
+
103
+ @enqueueTask
104
+ *_fetchMore() {
105
+ if (!this._data) return;
106
+
107
+ return yield this._fetchPage.linked().perform();
108
+ }
109
+
110
+ @lastValue("_fetchPage") _data;
111
+ @task
112
+ *_fetchPage() {
113
+ const data = yield this.apollo.query({
114
+ query: gql`
115
+ ${this.query}
116
+ `,
117
+ variables: {
118
+ filter: this.filter,
119
+ order: this.order,
120
+ pageSize: this.pageSize,
121
+ cursor: this.cursor,
122
+ },
123
+ fetchPolicy: "network-only",
124
+ ...this.queryOptions,
125
+ });
126
+
127
+ this.items = yield this.processAll([
128
+ ...this.items,
129
+ ...(yield this.processNew(
130
+ data[this.dataKey].edges.map(({ node }) => node)
131
+ )),
132
+ ]);
133
+
134
+ return data;
135
+ }
115
136
  }
@@ -0,0 +1,64 @@
1
+ import Helper from "@ember/component/helper";
2
+ import { assert } from "@ember/debug";
3
+ import { run } from "@ember/runloop";
4
+ import { inject as service } from "@ember/service";
5
+
6
+ export default class PrivateResolver extends Helper {
7
+ @service("-scheduler") scheduler;
8
+ @service calumaOptions;
9
+
10
+ get resolverType() {
11
+ return assert("`resolverType` needs to be defined");
12
+ }
13
+
14
+ _identifier = null;
15
+ _value = null;
16
+ _settled = false;
17
+
18
+ compute([identifiers]) {
19
+ const identifier = Array.isArray(identifiers)
20
+ ? identifiers[0]
21
+ : identifiers;
22
+
23
+ if (!identifier) return null;
24
+
25
+ // The parameter for the helper changed so we need to recompute and store
26
+ // the new parameter to remember it at the next computation
27
+ if (identifier !== this._identifier) {
28
+ this._settled = false;
29
+ this._identifier = identifier;
30
+ }
31
+
32
+ // The value is resolved, return the passed property (default is defined as
33
+ // `{type}NameProperty` on the caluma options service) of the value. This
34
+ // happens after the `this.recompute()` call in `resolve`
35
+ if (this._settled) {
36
+ return this._value?.[
37
+ this.calumaOptions[`${this.resolverType}NameProperty`]
38
+ ];
39
+ }
40
+
41
+ // Schedule a resolve batch to only trigger the resolve method in the caluma
42
+ // options service once. We pass a resolve function to run after the
43
+ // possibly asynchronous resolve method.
44
+ this.scheduler.resolveOnce(identifier, this.resolverType, (value) =>
45
+ run(this, "resolve", value)
46
+ );
47
+
48
+ // Return the default value (`null`) if the value is not computed yet
49
+ return this._value;
50
+ }
51
+
52
+ /**
53
+ * Resolver method to set the value to the resolved value and mark the helper
54
+ * as settled and then recompute it to display that value.
55
+ *
56
+ * @method resolve
57
+ * @param {*} value The resolved group value
58
+ */
59
+ resolve(value) {
60
+ this._value = value;
61
+ this._settled = true;
62
+ this.recompute();
63
+ }
64
+ }
@@ -0,0 +1,5 @@
1
+ import PrivateResolver from "@projectcaluma/ember-core/helpers/-resolver";
2
+
3
+ export default class GroupName extends PrivateResolver {
4
+ resolverType = "group";
5
+ }
@@ -0,0 +1,5 @@
1
+ import PrivateResolver from "@projectcaluma/ember-core/helpers/-resolver";
2
+
3
+ export default class UserName extends PrivateResolver {
4
+ resolverType = "user";
5
+ }
@@ -0,0 +1,77 @@
1
+ import { assert } from "@ember/debug";
2
+ import { once } from "@ember/runloop";
3
+ import Service, { inject as service } from "@ember/service";
4
+ import { camelize } from "@ember/string";
5
+ import { task } from "ember-concurrency";
6
+ import { pluralize } from "ember-inflector";
7
+
8
+ /**
9
+ * Decorator to define a type resolver in the scheduler service.
10
+ *
11
+ * @function typeResolver
12
+ * @param {"group"|"user"} type The type of the objects to resolve
13
+ * @returns {Function} The decorator function that returns an enqueued task to resolve the requested objects
14
+ */
15
+ function typeResolver(type) {
16
+ return task(function* () {
17
+ const identifiers = [...this[type].identifiers];
18
+ const callbacks = [...this[type].callbacks];
19
+
20
+ this[type] = undefined;
21
+
22
+ if (!identifiers.length) return;
23
+
24
+ const methodName = camelize(`resolve-${pluralize(type)}`);
25
+ const result = yield this.calumaOptions[methodName]?.(identifiers);
26
+
27
+ callbacks.forEach((callback) => callback(result));
28
+ }).enqueue();
29
+ }
30
+
31
+ export default class PrivateSchedulerService extends Service {
32
+ @service calumaOptions;
33
+
34
+ @typeResolver("group") resolveGroup;
35
+ @typeResolver("user") resolveUser;
36
+
37
+ /**
38
+ * Resolve a certain object of a type only once in the runloop.
39
+ *
40
+ * This method adds the given identifier to a set of already requested
41
+ * identifiers of a type and calls the resolve method of that type once in a
42
+ * single render loop and then passes the resolved object to a passed
43
+ * callback.
44
+ *
45
+ * @method resolveOnce
46
+ * @param {String} identifier The identifier used to find the resolved object
47
+ * @param {"group"|"user"} type The type of the object to resolve
48
+ * @param {Function} callback The callback function to call after the object is resolved
49
+ */
50
+ resolveOnce(identifier, type, callback) {
51
+ const _callback = (result) => {
52
+ callback(
53
+ result.find(
54
+ (obj) =>
55
+ String(obj[this.calumaOptions[`${type}IdentifierProperty`]]) ===
56
+ String(identifier)
57
+ )
58
+ );
59
+ };
60
+
61
+ if (!this[type]) {
62
+ this[type] = { identifiers: new Set(), callbacks: new Set() };
63
+ }
64
+
65
+ this[type].identifiers.add(identifier);
66
+ this[type].callbacks.add(_callback);
67
+
68
+ const typeResolverName = camelize(`resolve-${type}`);
69
+
70
+ assert(
71
+ `${typeResolverName} needs to be defined on the scheduler service`,
72
+ typeResolverName in this
73
+ );
74
+
75
+ once(this[typeResolverName], "perform");
76
+ }
77
+ }
@@ -88,4 +88,22 @@ export default class CalumaOptionsService extends Service {
88
88
  getComponentOverrides() {
89
89
  return Object.values(this._overrides);
90
90
  }
91
+
92
+ groupIdentifierProperty = "id";
93
+ groupNameProperty = "name";
94
+ resolveGroups(identifiers) {
95
+ return identifiers.map((identifier) => ({
96
+ [this.groupIdentifierProperty]: identifier,
97
+ [this.groupNameProperty]: identifier,
98
+ }));
99
+ }
100
+
101
+ userIdentifierProperty = "username";
102
+ userNameProperty = "fullName";
103
+ resolveUsers(identifiers) {
104
+ return identifiers.map((identifier) => ({
105
+ [this.userIdentifierProperty]: identifier,
106
+ [this.userNameProperty]: identifier,
107
+ }));
108
+ }
91
109
  }
@@ -0,0 +1 @@
1
+ export { default } from "@projectcaluma/ember-core/helpers/group-name";
@@ -0,0 +1 @@
1
+ export { default } from "@projectcaluma/ember-core/helpers/user-name";
@@ -0,0 +1 @@
1
+ export { default } from "@projectcaluma/ember-core/services/-scheduler";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectcaluma/ember-core",
3
- "version": "9.0.2",
3
+ "version": "10.0.0",
4
4
  "description": "Ember core addon for working with Caluma.",
5
5
  "keywords": [
6
6
  "ember-addon"
@@ -15,31 +15,34 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@apollo/client": "^3.4.16",
18
+ "@glimmer/tracking": "^1.0.4",
18
19
  "ember-apollo-client": "^3.2.0",
19
- "ember-auto-import": "^2.2.0",
20
+ "ember-auto-import": "^2.2.3",
20
21
  "ember-changeset-validations": "^3.16.0",
21
22
  "ember-cli-babel": "^7.26.6",
22
- "ember-cli-htmlbars": "^5.7.1",
23
- "ember-concurrency": "^2.1.2",
23
+ "ember-cli-htmlbars": "^6.0.0",
24
+ "ember-concurrency": "^2.2.0",
24
25
  "ember-concurrency-decorators": "^2.0.3",
25
26
  "ember-fetch": "^8.0.4",
26
27
  "ember-intl": "^5.7.0",
27
- "graphql": "^15.6.0",
28
- "graphql-tag": "^2.12.5",
28
+ "graphql": "^15.6.1",
29
+ "graphql-tag": "^2.12.6",
29
30
  "intersection-observer": "^0.12.0",
30
31
  "jexl": "^2.3.0",
31
32
  "lodash.clonedeep": "^4.5.0",
32
33
  "moment": "^2.29.1",
33
34
  "proxy-polyfill": "^0.3.2",
34
- "slugify": "^1.6.1"
35
+ "slugify": "^1.6.2"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@ember/optional-features": "2.0.0",
38
- "@ember/test-helpers": "2.5.0",
39
- "@embroider/test-setup": "0.45.0",
40
- "@projectcaluma/ember-testing": "9.0.0",
39
+ "@ember/render-modifiers": "2.0.0",
40
+ "@ember/test-helpers": "2.6.0",
41
+ "@embroider/test-setup": "0.47.2",
42
+ "@glimmer/component": "1.0.4",
43
+ "@projectcaluma/ember-testing": "9.1.0",
41
44
  "broccoli-asset-rev": "3.0.0",
42
- "ember-cli": "3.28.1",
45
+ "ember-cli": "3.28.4",
43
46
  "ember-cli-code-coverage": "1.0.3",
44
47
  "ember-cli-dependency-checker": "3.2.0",
45
48
  "ember-cli-inject-live-reload": "2.1.0",
@@ -50,20 +53,20 @@
50
53
  "ember-export-application-global": "2.0.1",
51
54
  "ember-load-initializers": "2.1.2",
52
55
  "ember-maybe-import-regenerator": "1.0.0",
53
- "ember-qunit": "5.1.4",
56
+ "ember-qunit": "5.1.5",
54
57
  "ember-resolver": "8.0.3",
55
- "ember-source": "3.28.1",
58
+ "ember-source": "3.28.6",
56
59
  "ember-source-channel-url": "3.0.0",
57
- "ember-try": "1.4.0",
60
+ "ember-try": "2.0.0",
58
61
  "faker": "5.5.3",
59
62
  "loader.js": "4.7.0",
60
63
  "npm-run-all": "4.1.5",
61
64
  "qunit": "2.17.2",
62
65
  "qunit-dom": "2.0.0",
63
- "webpack": "5.58.0"
66
+ "webpack": "5.64.1"
64
67
  },
65
68
  "engines": {
66
- "node": "10.* || >= 12"
69
+ "node": "12.* || 14.* || >= 16"
67
70
  },
68
71
  "ember": {
69
72
  "edition": "octane"
@@ -6,9 +6,10 @@ caluma:
6
6
  COMPLETED: "Erledigt"
7
7
  CANCELED: "Abgebrochen"
8
8
  SKIPPED: "Übersprungen"
9
-
9
+ SUSPENDED: "Pausiert"
10
10
  case:
11
11
  status:
12
12
  RUNNING: "In Bearbeitung"
13
13
  COMPLETED: "Abgeschlossen"
14
14
  CANCELED: "Abgebrochen"
15
+ SUSPENDED: "Pausiert"
@@ -6,8 +6,10 @@ caluma:
6
6
  COMPLETED: "Completed"
7
7
  CANCELED: "Canceled"
8
8
  SKIPPED: "Skipped"
9
+ SUSPENDED: "Suspended"
9
10
  case:
10
11
  status:
11
12
  RUNNING: "Pending"
12
13
  COMPLETED: "Completed"
13
14
  CANCELED: "Canceled"
15
+ SUSPENDED: "Suspended"
@@ -6,9 +6,10 @@ caluma:
6
6
  COMPLETED: "Terminé"
7
7
  CANCELED: "Annulé"
8
8
  SKIPPED: "Sauté"
9
-
9
+ SUSPENDED: "En pause"
10
10
  case:
11
11
  status:
12
12
  RUNNING: "En cours"
13
13
  COMPLETED: "Terminé"
14
14
  CANCELED: "Annulé"
15
+ SUSPENDED: "En pause"