diff --git a/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java b/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java index ca1d84be6..2a9b0c7cc 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java +++ b/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; @@ -82,14 +83,13 @@ public class TaskanaEngineConfiguration { // global switch to enable JAAS based authentication and Taskana // authorizations - protected boolean securityEnabled = true; + protected boolean securityEnabled; protected boolean useManagedTransactions; // List of configured domain names - protected List domains = new ArrayList(); + protected List domains = new ArrayList<>(); // List of configured classification types - protected List classificationTypes = new ArrayList(); - protected Map> classificationCategoriesByTypeMap = - new HashMap>(); + protected List classificationTypes = new ArrayList<>(); + protected Map> classificationCategoriesByTypeMap = new HashMap<>(); // Properties for the monitor private boolean germanPublicHolidaysEnabled; private List customHolidays; @@ -294,8 +294,13 @@ public class TaskanaEngineConfiguration { return classificationCategories; } + public Map> getClassificationCategoriesByTypeMap() { + return this.classificationCategoriesByTypeMap.entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, e -> new ArrayList<>(e.getValue()))); + } + public List getClassificationCategoriesByType(String type) { - return classificationCategoriesByTypeMap.get(type); + return classificationCategoriesByTypeMap.getOrDefault(type, Collections.emptyList()); } public void setClassificationCategoriesByType( diff --git a/lib/taskana-core/src/test/java/acceptance/config/TaskanaConfigAccTest.java b/lib/taskana-core/src/test/java/acceptance/config/TaskanaConfigAccTest.java index 8503a0123..997f84fee 100644 --- a/lib/taskana-core/src/test/java/acceptance/config/TaskanaConfigAccTest.java +++ b/lib/taskana-core/src/test/java/acceptance/config/TaskanaConfigAccTest.java @@ -1,8 +1,8 @@ package acceptance.config; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; @@ -83,9 +83,10 @@ class TaskanaConfigAccTest extends TaskanaEngineImpl { } finally { deleteFile(propertiesFileName); } - assertNull( - taskanaEngineConfiguration.getClassificationCategoriesByType( - taskanaEngineConfiguration.getClassificationTypes().get(0))); + assertThat( + taskanaEngineConfiguration.getClassificationCategoriesByType( + taskanaEngineConfiguration.getClassificationTypes().get(0))) + .isEmpty(); } @Test diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java index bba614cff..bba188296 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java @@ -18,10 +18,12 @@ public final class Mapping { URL_MONITOR + "/tasks-classification-report"; public static final String URL_MONITOR_TIMESTAMP = URL_MONITOR + "/timestamp-report"; public static final String URL_DOMAIN = PRE + "domains"; - public static final String URL_CLASSIFICATIONCATEGORIES = PRE + "classification-categories"; - public static final String URL_CLASSIFICATIONTYPES = PRE + "classification-types"; - public static final String URL_CURRENTUSER = PRE + "current-user-info"; - public static final String URL_HISTORYENABLED = PRE + "history-provider-enabled"; + public static final String URL_CLASSIFICATION_CATEGORIES = PRE + "classification-categories"; + public static final String URL_CLASSIFICATION_TYPES = PRE + "classification-types"; + public static final String URL_CLASSIFICATION_CATEGORIES_BY_TYPES = + PRE + "classifications-by-type"; + public static final String URL_CURRENT_USER = PRE + "current-user-info"; + public static final String URL_HISTORY_ENABLED = PRE + "history-provider-enabled"; public static final String URL_VERSION = PRE + "version"; public static final String URL_TASKS = PRE + "tasks"; public static final String URL_TASKS_ID = URL_TASKS + "/{taskId}"; diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskanaEngineController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskanaEngineController.java index d5021d5e2..e3c2cec69 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskanaEngineController.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskanaEngineController.java @@ -1,6 +1,7 @@ package pro.taskana.rest; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -46,7 +47,7 @@ public class TaskanaEngineController { return response; } - @GetMapping(path = Mapping.URL_CLASSIFICATIONCATEGORIES) + @GetMapping(path = Mapping.URL_CLASSIFICATION_CATEGORIES) public ResponseEntity> getClassificationCategories(String type) { LOGGER.debug("Entry to getClassificationCategories(type = {})", type); ResponseEntity> response; @@ -65,7 +66,7 @@ public class TaskanaEngineController { return response; } - @GetMapping(path = Mapping.URL_CLASSIFICATIONTYPES) + @GetMapping(path = Mapping.URL_CLASSIFICATION_TYPES) public ResponseEntity> getClassificationTypes() { ResponseEntity> response = ResponseEntity.ok(taskanaEngineConfiguration.getClassificationTypes()); @@ -75,7 +76,17 @@ public class TaskanaEngineController { return response; } - @GetMapping(path = Mapping.URL_CURRENTUSER) + @GetMapping(path = Mapping.URL_CLASSIFICATION_CATEGORIES_BY_TYPES) + public ResponseEntity>> getClassificationCategoriesByTypeMap() { + ResponseEntity>> response = + ResponseEntity.ok(taskanaEngineConfiguration.getClassificationCategoriesByTypeMap()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exit from getClassificationCategoriesByTypeMap(), returning {}", response); + } + return response; + } + + @GetMapping(path = Mapping.URL_CURRENT_USER) public ResponseEntity getCurrentUserInfo() { LOGGER.debug("Entry to getCurrentUserInfo()"); TaskanaUserInfoResource resource = new TaskanaUserInfoResource(); @@ -94,7 +105,7 @@ public class TaskanaEngineController { return response; } - @GetMapping(path = Mapping.URL_HISTORYENABLED) + @GetMapping(path = Mapping.URL_HISTORY_ENABLED) public ResponseEntity getIsHistoryProviderEnabled() { ResponseEntity response = ResponseEntity.ok(taskanaEngine.isHistoryEnabled()); LOGGER.debug("Exit from getIsHistoryProviderEnabled(), returning {}", response); diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskanaEngineControllerRestDocumentation.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskanaEngineControllerRestDocumentation.java index 8fe9cd191..81a41a81a 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskanaEngineControllerRestDocumentation.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskanaEngineControllerRestDocumentation.java @@ -65,7 +65,7 @@ class TaskanaEngineControllerRestDocumentation extends BaseRestDocumentation { this.mockMvc .perform( RestDocumentationRequestBuilders.get( - restHelper.toUrl(Mapping.URL_CLASSIFICATIONCATEGORIES)) + restHelper.toUrl(Mapping.URL_CLASSIFICATION_CATEGORIES)) .accept("application/json") .header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -79,7 +79,7 @@ class TaskanaEngineControllerRestDocumentation extends BaseRestDocumentation { void getAllClassificationTypesDocTest() throws Exception { this.mockMvc .perform( - RestDocumentationRequestBuilders.get(restHelper.toUrl(Mapping.URL_CLASSIFICATIONTYPES)) + RestDocumentationRequestBuilders.get(restHelper.toUrl(Mapping.URL_CLASSIFICATION_TYPES)) .accept("application/json") .header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -93,7 +93,7 @@ class TaskanaEngineControllerRestDocumentation extends BaseRestDocumentation { void getCurrentUserInfo() throws Exception { this.mockMvc .perform( - RestDocumentationRequestBuilders.get(restHelper.toUrl(Mapping.URL_CURRENTUSER)) + RestDocumentationRequestBuilders.get(restHelper.toUrl(Mapping.URL_CURRENT_USER)) .accept("application/json") .header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -106,7 +106,7 @@ class TaskanaEngineControllerRestDocumentation extends BaseRestDocumentation { void getHistoryProviderIsEnabled() throws Exception { this.mockMvc .perform( - RestDocumentationRequestBuilders.get(restHelper.toUrl(Mapping.URL_HISTORYENABLED)) + RestDocumentationRequestBuilders.get(restHelper.toUrl(Mapping.URL_HISTORY_ENABLED)) .accept("application/json") .header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")) .andExpect(MockMvcResultMatchers.status().isOk()) diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskanaEngineControllerIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskanaEngineControllerIntTest.java index af321a34b..e123d9bba 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskanaEngineControllerIntTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskanaEngineControllerIntTest.java @@ -43,7 +43,7 @@ class TaskanaEngineControllerIntTest { void testClassificationTypes() { ResponseEntity> response = template.exchange( - restHelper.toUrl(Mapping.URL_CLASSIFICATIONTYPES), + restHelper.toUrl(Mapping.URL_CLASSIFICATION_TYPES), HttpMethod.GET, restHelper.defaultRequest(), ParameterizedTypeReference.forType(List.class)); @@ -54,7 +54,7 @@ class TaskanaEngineControllerIntTest { void testClassificationCategories() { ResponseEntity> response = template.exchange( - restHelper.toUrl(Mapping.URL_CLASSIFICATIONCATEGORIES), + restHelper.toUrl(Mapping.URL_CLASSIFICATION_CATEGORIES), HttpMethod.GET, restHelper.defaultRequest(), ParameterizedTypeReference.forType(List.class)); @@ -65,7 +65,7 @@ class TaskanaEngineControllerIntTest { void testGetCurrentUserInfo() { ResponseEntity response = template.exchange( - restHelper.toUrl(Mapping.URL_CURRENTUSER), + restHelper.toUrl(Mapping.URL_CURRENT_USER), HttpMethod.GET, restHelper.defaultRequest(), ParameterizedTypeReference.forType(TaskanaUserInfoResource.class)); diff --git a/web/package-lock.json b/web/package-lock.json index 2e28d54d4..b154d3ee8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -2358,6 +2358,22 @@ } } }, + "@ngxs/devtools-plugin": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@ngxs/devtools-plugin/-/devtools-plugin-3.6.2.tgz", + "integrity": "sha512-0UUZlpXgEtrHoWNeVQXEvUyC6pW8nTACpqJgecuBjYJMa5imCCUSXdrpear8ztJuWwpLqMUSGk5cICNhKqK59g==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngxs/store": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.6.2.tgz", + "integrity": "sha512-al7GU618SAuz2Ul4rFYVDgS1DM0gHReGOGvbnE7LBt4Etz3gsJERNYXPp2bSA7lZGaRPRaFfIHJN+Lm/GikFJw==", + "requires": { + "tslib": "^1.9.0" + } + }, "@schematics/angular": { "version": "8.3.22", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.22.tgz", @@ -3876,6 +3892,29 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + } + } } }, "caller-callsite": { @@ -5072,9 +5111,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.382", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.382.tgz", - "integrity": "sha512-gJfxOcgnBlXhfnUUObsq3n3ReU8CT6S8je97HndYRkKsNZMJJ38zO/pI5aqO7L3Myfq+E3pqPyKK/ynyLEQfBA==", + "version": "1.3.390", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.390.tgz", + "integrity": "sha512-4RvbM5x+002gKI8sltkqWEk5pptn0UnzekUx8RTThAMPDSb8jjpm6SwGiSnEve7f85biyZl8DMXaipaCxDjXag==", "dev": true }, "elliptic": { @@ -5773,9 +5812,9 @@ } }, "eslint-module-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", - "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "requires": { "debug": "^2.6.9", @@ -6416,9 +6455,9 @@ } }, "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, "figures": { @@ -6631,9 +6670,9 @@ } }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, "flush-write-stream": { @@ -6647,9 +6686,9 @@ } }, "follow-redirects": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz", - "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", + "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", "dev": true, "requires": { "debug": "^3.0.0" @@ -6954,9 +6993,9 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, "handle-thing": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", - "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, "har-schema": { @@ -7165,9 +7204,9 @@ "dev": true }, "html-escaper": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.1.tgz", - "integrity": "sha512-hNX23TjWwD3q56HpWjUHOKj1+4KKlnjv9PcmBUYKVpga+2cnb9nDx/B1o0yO4n+RZXZdiNxzx6B24C9aNMTkkQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, "http-cache-semantics": { @@ -7684,7 +7723,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -7767,8 +7805,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isstream": { "version": "0.1.2", @@ -9740,9 +9777,9 @@ } }, "node-fetch-npm": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.3.tgz", - "integrity": "sha512-DgwoKEsqLnFZtk3ap7GWBHcHwnUhsNmQqEDcdjfQ8GofLEFJ081NAd4Uin3R7RFZBWVJCwHISw1oaEqPgSLloA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz", + "integrity": "sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==", "dev": true, "requires": { "encoding": "^0.1.11", @@ -9822,21 +9859,10 @@ } }, "node-releases": { - "version": "1.1.52", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.52.tgz", - "integrity": "sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } + "version": "1.1.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz", + "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==", + "dev": true }, "node-sass": { "version": "4.13.1", @@ -11018,9 +11044,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "public-encrypt": { "version": "4.0.3", @@ -11854,26 +11880,11 @@ "dev": true }, "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-3.0.2.tgz", + "integrity": "sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA==", "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "is-plain-object": "^2.0.4" } }, "setimmediate": { @@ -11923,9 +11934,9 @@ "dev": true }, "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "slash": { "version": "1.0.0", @@ -12638,24 +12649,46 @@ } } }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "string.prototype.trimend": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", + "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" } }, "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", + "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, "string_decoder": { @@ -13322,6 +13355,29 @@ "get-value": "^2.0.6", "is-extendable": "^0.1.1", "set-value": "^2.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + } + } } }, "unique-filename": { @@ -13589,12 +13645,12 @@ "dev": true }, "watchpack": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", - "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", + "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", "dev": true, "requires": { - "chokidar": "^2.0.2", + "chokidar": "^2.1.8", "graceful-fs": "^4.1.2", "neo-async": "^2.5.0" }, diff --git a/web/package.json b/web/package.json index 507d5ba76..de1111f0f 100644 --- a/web/package.json +++ b/web/package.json @@ -21,6 +21,8 @@ "@angular/platform-browser": "8.2.14", "@angular/platform-browser-dynamic": "8.2.14", "@angular/router": "8.2.14", + "@ngxs/devtools-plugin": "3.6.2", + "@ngxs/store": "3.6.2", "angular-svg-icon": "7.2.1", "angular-tree-component": "8.5.2", "bootstrap": "4.3.1", @@ -37,6 +39,7 @@ "node-sass": "4.13.1", "popper.js": "1.16.0", "rxjs": "6.5.3", + "set-value": "3.0.2", "zone.js": "0.9.1" }, "devDependencies": { diff --git a/web/src/app/administration/access-items-management/access-items-management.component.html b/web/src/app/administration/access-items-management/access-items-management.component.html index 3063cd638..5ff4c2cf7 100644 --- a/web/src/app/administration/access-items-management/access-items-management.component.html +++ b/web/src/app/administration/access-items-management/access-items-management.component.html @@ -26,18 +26,11 @@ Append Transfer Distribute - {{custom1Field.field}} - {{custom2Field.field}} - {{custom3Field.field}} - {{custom4Field.field}} - {{custom5Field.field}} - {{custom6Field.field}} - {{custom7Field.field}} - {{custom8Field.field}} - {{custom9Field.field}} - {{custom10Field.field}} - {{custom11Field.field}} - {{custom12Field.field}} + + + {{customField.field}} + + - +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - -
+
-
+
@@ -67,26 +67,26 @@
- +
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
- - + +
+ +
+ +
+ +
+
diff --git a/web/src/app/administration/classification/details/classification-details.component.scss b/web/src/app/administration/classification/details/classification-details.component.scss index e69de29bb..cc6d96c2b 100644 --- a/web/src/app/administration/classification/details/classification-details.component.scss +++ b/web/src/app/administration/classification/details/classification-details.component.scss @@ -0,0 +1,13 @@ +.custom-field-row { + display:flex; + flex-wrap: wrap; + flex-direction: column; + height: 40vh; + width: 100%; + margin: 0; +} + +.custom-field-wrapper { + height: 70px; + padding: 0 15px +} diff --git a/web/src/app/administration/classification/details/classification-details.component.spec.ts b/web/src/app/administration/classification/details/classification-details.component.spec.ts index fb3ff7980..a6e3a0fb7 100644 --- a/web/src/app/administration/classification/details/classification-details.component.spec.ts +++ b/web/src/app/administration/classification/details/classification-details.component.spec.ts @@ -7,13 +7,12 @@ import { Component } from '@angular/core'; import { of } from 'rxjs'; import { AngularSvgIconModule } from 'angular-svg-icon'; import { configureTests } from 'app/app.test.configuration'; +import { NgxsModule, Store } from '@ngxs/store'; import { ClassificationDefinition } from 'app/models/classification-definition'; import { LinksClassification } from 'app/models/links-classfication'; -import { Pair } from 'app/models/pair'; -import { ClassificationCategoriesService } from 'app/shared/services/classifications/classification-categories.service'; import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { ClassificationsService } from 'app/shared/services/classifications/classifications.service'; @@ -21,11 +20,13 @@ import { TreeNodeModel } from 'app/models/tree-node'; import { GeneralModalService } from 'app/services/general-modal/general-modal.service'; import { AlertService } from 'app/services/alert/alert.service'; import { TreeService } from 'app/services/tree/tree.service'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service'; import { ImportExportService } from 'app/administration/services/import-export/import-export.service'; +import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors'; +import { ClassificationSelectors } from 'app/store/classification-store/classification.selectors'; import { ClassificationDetailsComponent } from './classification-details.component'; + @Component({ selector: 'taskana-dummy-detail', template: 'dummydetail' @@ -43,33 +44,42 @@ describe('ClassificationDetailsComponent', () => { const treeNodes: Array = new Array(new TreeNodeModel()); let classificationsService; - let classificationCategoriesService; let treeService; let removeConfirmationService; + const storeSpy: jasmine.SpyObj = jasmine.createSpyObj('Store', ['select']); + const configure = (testBed: TestBed) => { + testBed.configureTestingModule({ + imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes), AngularSvgIconModule, NgxsModule.forRoot()], + declarations: [ClassificationDetailsComponent, DummyDetailComponent], + providers: [MasterAndDetailService, RequestInProgressService, ClassificationsService, HttpClient, GeneralModalService, AlertService, + TreeService, ImportExportService, { provide: Store, useValue: storeSpy }] + }); + }; + beforeEach(done => { - const configure = (testBed: TestBed) => { - testBed.configureTestingModule({ - imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes), AngularSvgIconModule], - declarations: [ClassificationDetailsComponent, DummyDetailComponent], - providers: [MasterAndDetailService, RequestInProgressService, ClassificationsService, HttpClient, GeneralModalService, AlertService, - TreeService, ClassificationCategoriesService, CustomFieldsService, ImportExportService] - }); - }; configureTests(configure).then(testBed => { - fixture = TestBed.createComponent(ClassificationDetailsComponent); + storeSpy.select.and.callFake(selector => { + switch (selector) { + case EngineConfigurationSelectors.classificationsCustomisation: + return of({ information: {} }); + case ClassificationSelectors.selectCategories: + return of(['EXTERNAL', 'MANUAL']); + default: + return of(); + } + }); + + fixture = testBed.createComponent(ClassificationDetailsComponent); + component = fixture.componentInstance; - classificationsService = TestBed.get(ClassificationsService); - classificationCategoriesService = TestBed.get(ClassificationCategoriesService); - removeConfirmationService = TestBed.get(RemoveConfirmationService); + classificationsService = testBed.get(ClassificationsService); + removeConfirmationService = testBed.get(RemoveConfirmationService); spyOn(classificationsService, 'getClassifications').and.returnValue(of(treeNodes)); - spyOn(classificationCategoriesService, 'getClassificationTypes').and.returnValue(of([])); - spyOn(classificationCategoriesService, 'getCategories').and.returnValue(of(['firstCategory', 'secondCategory'])); spyOn(classificationsService, 'deleteClassification').and.returnValue(of(true)); - spyOn(classificationCategoriesService, 'getCategoryIcon').and.returnValue(new Pair('assets/icons/categories/external.svg')); component.classification = new ClassificationDefinition('id1'); component.classification._links = new LinksClassification({ self: '' }); - treeService = TestBed.get(TreeService); + treeService = testBed.get(TreeService); fixture.detectChanges(); done(); }); @@ -86,8 +96,4 @@ describe('ClassificationDetailsComponent', () => { removeConfirmationService.runCallbackFunction(); expect(treeServiceSpy).toHaveBeenCalledWith('id1'); }); - - it('should selected first classificationCategory if is defined', () => { - expect(component.classification.category).toBe('firstCategory'); - }); }); diff --git a/web/src/app/administration/classification/details/classification-details.component.ts b/web/src/app/administration/classification/details/classification-details.component.ts index d74afab7c..b4d1697d3 100644 --- a/web/src/app/administration/classification/details/classification-details.component.ts +++ b/web/src/app/administration/classification/details/classification-details.component.ts @@ -1,10 +1,10 @@ -import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Select, Store } from '@ngxs/store'; +import { Observable, Subscription, zip } from 'rxjs'; -import { ClassificationDefinition } from 'app/models/classification-definition'; +import { ClassificationDefinition, customFieldCount } from 'app/models/classification-definition'; import { ACTION } from 'app/models/action'; -import { MessageModal } from 'app/models/message-modal'; import { AlertModel, AlertType } from 'app/models/alert'; import { highlight } from 'app/shared/animations/validation.animation'; @@ -18,15 +18,17 @@ import { AlertService } from 'app/services/alert/alert.service'; import { TreeService } from 'app/services/tree/tree.service'; import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service'; -import { ClassificationCategoriesService } from 'app/shared/services/classifications/classification-categories.service'; import { DomainService } from 'app/services/domain/domain.service'; import { Pair } from 'app/models/pair'; import { NgForm } from '@angular/forms'; import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service'; import { ImportExportService } from 'app/administration/services/import-export/import-export.service'; -import { CustomFieldsService } from '../../../services/custom-fields/custom-fields.service'; +import { map, take } from 'rxjs/operators'; +import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors'; +import { ClassificationSelectors } from 'app/store/classification-store/classification.selectors'; import { ERROR_TYPES } from '../../../models/errors'; import { ErrorsService } from '../../../services/errors/errors.service'; +import { ClassificationCategoryImages, CustomField, getCustomFields } from '../../../models/customisation'; @Component({ selector: 'taskana-classification-details', @@ -37,22 +39,20 @@ import { ErrorsService } from '../../../services/errors/errors.service'; export class ClassificationDetailsComponent implements OnInit, OnDestroy { classification: ClassificationDefinition; classificationClone: ClassificationDefinition; + classificationCategories: string[]; showDetail = false; - classificationTypes: Array = []; badgeMessage = ''; requestInProgress = false; - categories: Array = []; - categorySelected: string; - spinnerIsRunning = false; - custom1Field = this.customFieldsService.getCustomField('Custom 1', 'classifications.information.custom1'); - custom2Field = this.customFieldsService.getCustomField('Custom 2', 'classifications.information.custom2'); - custom3Field = this.customFieldsService.getCustomField('Custom 3', 'classifications.information.custom3'); - custom4Field = this.customFieldsService.getCustomField('Custom 4', 'classifications.information.custom4'); - custom5Field = this.customFieldsService.getCustomField('Custom 5', 'classifications.information.custom5'); - custom6Field = this.customFieldsService.getCustomField('Custom 6', 'classifications.information.custom6'); - custom7Field = this.customFieldsService.getCustomField('Custom 7', 'classifications.information.custom7'); - custom8Field = this.customFieldsService.getCustomField('Custom 8', 'classifications.information.custom8'); + @Select(ClassificationSelectors.selectCategories) categories$: Observable; + @Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable; + @Select(ClassificationSelectors.selectedClassificationType) selectedClassificationType$: Observable; + @Select(ClassificationSelectors.selectClassificationTypesObject) classificationTypes$: Observable; + spinnerIsRunning = false; + customFields$: Observable; + + @ViewChild('ClassificationForm', { static: false }) classificationForm: NgForm; + toogleValidationMap = new Map(); private action: any; private classificationServiceSubscription: Subscription; private classificationSelectedSubscription: Subscription; @@ -60,15 +60,9 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { private masterAndDetailSubscription: Subscription; private classificationSavingSubscription: Subscription; private classificationRemoveSubscription: Subscription; - private selectedClassificationSubscription: Subscription; - private selectedClassificationTypeSubscription: Subscription; - private categoriesSubscription: Subscription; private domainSubscription: Subscription; private importingExportingSubscription: Subscription; - @ViewChild('ClassificationForm', { static: false }) classificationForm: NgForm; - toogleValidationMap = new Map(); - constructor(private classificationsService: ClassificationsService, private route: ActivatedRoute, private router: Router, @@ -77,19 +71,19 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { private requestInProgressService: RequestInProgressService, private alertService: AlertService, private treeService: TreeService, - private categoryService: ClassificationCategoriesService, private domainService: DomainService, - private customFieldsService: CustomFieldsService, private removeConfirmationService: RemoveConfirmationService, private formsValidatorService: FormsValidatorService, private errorsService: ErrorsService, - private importExportService: ImportExportService) { + private importExportService: ImportExportService, + private store: Store) { } ngOnInit() { - this.categoryService.getClassificationTypes().subscribe((classificationTypes: Array) => { - this.classificationTypes = classificationTypes; - }); + this.customFields$ = this.store.select(EngineConfigurationSelectors.classificationsCustomisation).pipe( + map(customisation => customisation.information), + getCustomFields(customFieldCount) + ); this.classificationSelectedSubscription = this.classificationsService.getSelectedClassification() .subscribe(classificationSelected => { if (classificationSelected && this.classification @@ -122,20 +116,10 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { this.showDetail = showDetail; }); - this.categoriesSubscription = this.categoryService.getCategories().subscribe((categories: Array) => { - this.categories = categories; - // ToDo: Remove this line during refactoring. Atm checking why it was written takes too long - if (categories.length > 0 && this.classification) { - // TSK-891 fix: The property is already set and is crucial value - // Wrapped with an if to set a default if not already set. - if (!this.classification.category) { - [this.classification.category] = categories; - } - } - }); - this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => { - if (this.classification.classificationId) { this.selectClassification(this.classification.classificationId); } + if (this.classification.classificationId) { + this.selectClassification(this.classification.classificationId); + } }); } @@ -144,10 +128,6 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { this.router.navigate(['./'], { relativeTo: this.route.parent }); } - selectType(type: string) { - this.classification.type = type; - } - removeClassification() { this.removeConfirmationService.setRemoveConfirmation(this.removeClassificationConfirmation.bind(this), `You are going to delete classification: ${this.classification.key}. Can you confirm this action?`); @@ -166,6 +146,37 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { }); } + onClear() { + this.formsValidatorService.formSubmitAttempt = false; + // new Key: ALERT_TYPES.INFO_ALERT + this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields')); + this.classification = { ...this.classificationClone }; + } + + selectCategory(category: string) { + this.classification.category = category; + } + + getCategoryIcon(category: string): Observable { + return this.categoryIcons$.pipe(map( + iconMap => (iconMap[category] + ? new Pair(iconMap[category], category) + : new Pair(iconMap.missing, 'Category does not match with the configuration')) + )); + } + + spinnerRunning(value) { + this.spinnerIsRunning = value; + } + + validChanged(): void { + this.classification.isValidInDomain = !this.classification.isValidInDomain; + } + + masterDomainSelected(): boolean { + return this.domainService.getSelectedDomainValue() === ''; + } + private initProperties() { delete this.classification; delete this.action; @@ -204,24 +215,6 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { } } - onClear() { - this.formsValidatorService.formSubmitAttempt = false; - // new Key: ALERT_TYPES.INFO_ALERT - this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields')); - this.classification = { ...this.classificationClone }; - } - - - selectCategory(category: string) { - this.classification.category = category; - } - - getCategoryIcon(category: string): Pair { - return this.categoryService.getCategoryIcon(category); - } - - spinnerRunning(value) { this.spinnerIsRunning = value; } - private afterRequest() { this.requestInProgressService.setRequestInProgress(false); this.classificationsService.triggerClassificationSaved(); @@ -251,23 +244,24 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { } } - private addDateToClassification() { + private addDateToClassification(classification: ClassificationDefinition) { const date = TaskanaDate.getDate(); - this.classification.created = date; - this.classification.modified = date; + classification.created = date; + classification.modified = date; } private initClassificationOnCreation(classificationSelected: ClassificationDefinition) { - this.classification = new ClassificationDefinition(); - this.classification.parentId = classificationSelected.classificationId; - this.classification.parentKey = classificationSelected.key; - this.classification.category = classificationSelected.category; - this.classification.domain = this.domainService.getSelectedDomainValue(); - this.selectedClassificationSubscription = this.categoryService.getSelectedClassificationType().subscribe(type => { - if (this.classification) { this.classification.type = type; } + zip(this.categories$, this.selectedClassificationType$).pipe(take(1)).subscribe(([categories, selectedType]: [string[], string]) => { + const tempClassification: ClassificationDefinition = new ClassificationDefinition(); + // tempClassification.parentId = classificationSelected.classificationId; + // tempClassification.parentKey = classificationSelected.key; + [tempClassification.category] = categories; + tempClassification.domain = this.domainService.getSelectedDomainValue(); + tempClassification.type = selectedType; + this.addDateToClassification(tempClassification); + this.classification = tempClassification; + this.cloneClassification(this.classification); }); - this.addDateToClassification(); - this.cloneClassification(this.classification); } private checkDomainAndRedirect() { @@ -306,25 +300,44 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { this.classificationClone = { ...classification }; } - validChanged(): void { - this.classification.isValidInDomain = !this.classification.isValidInDomain; + getClassificationCustom(customNumber: number): string { + return `custom${customNumber}`; } - masterDomainSelected(): boolean { - return this.domainService.getSelectedDomainValue() === ''; + // TODO: Remove when classification is in store + getAvailableCategories(type: string) { + let returnCategories: string[] = []; + this.classificationTypes$.subscribe(classTypes => { + returnCategories = classTypes[type]; + }); + + return returnCategories; } ngOnDestroy(): void { - if (this.masterAndDetailSubscription) { this.masterAndDetailSubscription.unsubscribe(); } - if (this.routeSubscription) { this.routeSubscription.unsubscribe(); } - if (this.classificationSelectedSubscription) { this.classificationSelectedSubscription.unsubscribe(); } - if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); } - if (this.classificationSavingSubscription) { this.classificationSavingSubscription.unsubscribe(); } - if (this.classificationRemoveSubscription) { this.classificationRemoveSubscription.unsubscribe(); } - if (this.selectedClassificationSubscription) { this.selectedClassificationSubscription.unsubscribe(); } - if (this.selectedClassificationTypeSubscription) { this.selectedClassificationTypeSubscription.unsubscribe(); } - if (this.categoriesSubscription) { this.categoriesSubscription.unsubscribe(); } - if (this.domainSubscription) { this.domainSubscription.unsubscribe(); } - if (this.importingExportingSubscription) { this.importingExportingSubscription.unsubscribe(); } + if (this.masterAndDetailSubscription) { + this.masterAndDetailSubscription.unsubscribe(); + } + if (this.routeSubscription) { + this.routeSubscription.unsubscribe(); + } + if (this.classificationSelectedSubscription) { + this.classificationSelectedSubscription.unsubscribe(); + } + if (this.classificationServiceSubscription) { + this.classificationServiceSubscription.unsubscribe(); + } + if (this.classificationSavingSubscription) { + this.classificationSavingSubscription.unsubscribe(); + } + if (this.classificationRemoveSubscription) { + this.classificationRemoveSubscription.unsubscribe(); + } + if (this.domainSubscription) { + this.domainSubscription.unsubscribe(); + } + if (this.importingExportingSubscription) { + this.importingExportingSubscription.unsubscribe(); + } } } diff --git a/web/src/app/administration/classification/master/list/classification-list.component.html b/web/src/app/administration/classification/master/list/classification-list.component.html index f27e03642..a39d0275f 100644 --- a/web/src/app/administration/classification/master/list/classification-list.component.html +++ b/web/src/app/administration/classification/master/list/classification-list.component.html @@ -9,16 +9,15 @@
- +
- @@ -87,54 +78,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts b/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts index 2782e4143..b25fc0e17 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts @@ -19,9 +19,10 @@ import { SavingWorkbasketService } from 'app/administration/services/saving-work import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { AlertService } from 'app/services/alert/alert.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service'; import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service'; +import { NgxsModule, Store } from '@ngxs/store'; +import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors'; import { AccessItemsComponent } from './access-items.component'; describe('AccessItemsComponent', () => { @@ -33,26 +34,43 @@ describe('AccessItemsComponent', () => { let accessIdsService; let formsValidatorService; + const storeSpy: jasmine.SpyObj = jasmine.createSpyObj('Store', ['select']); + + const configure = (testBed: TestBed) => { + testBed.configureTestingModule({ + declarations: [AccessItemsComponent], + imports: [FormsModule, AngularSvgIconModule, HttpClientModule, ReactiveFormsModule, NgxsModule.forRoot()], + providers: [WorkbasketService, AlertService, GeneralModalService, SavingWorkbasketService, RequestInProgressService, + AccessIdsService, FormsValidatorService, { provide: Store, useValue: storeSpy }] + }); + }; beforeEach(done => { - const configure = (testBed: TestBed) => { - testBed.configureTestingModule({ - declarations: [AccessItemsComponent], - imports: [FormsModule, AngularSvgIconModule, HttpClientModule, ReactiveFormsModule], - providers: [WorkbasketService, AlertService, GeneralModalService, SavingWorkbasketService, RequestInProgressService, - CustomFieldsService, AccessIdsService, FormsValidatorService] - }); - }; configureTests(configure).then(testBed => { - fixture = TestBed.createComponent(AccessItemsComponent); + storeSpy.select.and.callFake(selector => { + switch (selector) { + case EngineConfigurationSelectors.accessItemsCustomisation: + return of({ + accessId: { + lookupField: false + }, + custom1: {} + }); + default: + return of(); + } + }); + + fixture = testBed.createComponent(AccessItemsComponent); + component = fixture.componentInstance; component.workbasket = new Workbasket('1'); component.workbasket.type = ICONTYPES.TOPIC; component.workbasket._links = new Links(); component.workbasket._links.accessItems = { href: 'someurl' }; - workbasketService = TestBed.get(WorkbasketService); - alertService = TestBed.get(AlertService); + workbasketService = testBed.get(WorkbasketService); + alertService = testBed.get(AlertService); spyOn(workbasketService, 'getWorkBasketAccessItems').and.returnValue(of(new WorkbasketAccessItemsResource( new Array( new WorkbasketAccessItems('id1', '1', 'accessID1', '', false, false, false, false, false, false, false, false, @@ -64,11 +82,11 @@ describe('AccessItemsComponent', () => { spyOn(workbasketService, 'updateWorkBasketAccessItem').and.returnValue(of(true)); spyOn(alertService, 'triggerAlert').and.returnValue(of(true)); debugElement = fixture.debugElement.nativeElement; - accessIdsService = TestBed.get(AccessIdsService); + accessIdsService = testBed.get(AccessIdsService); spyOn(accessIdsService, 'getAccessItemsInformation').and.returnValue(of(new Array( 'accessID1', 'accessID2' ))); - formsValidatorService = TestBed.get(FormsValidatorService); + formsValidatorService = testBed.get(FormsValidatorService); component.ngOnChanges({ active: new SimpleChange(undefined, 'accessItems', true) }); diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.ts b/web/src/app/administration/workbasket/details/access-items/access-items.component.ts index f80a71159..1913a7cb6 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.ts +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.ts @@ -1,25 +1,26 @@ import { Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; +import { Select } from '@ngxs/store'; import { FormArray, FormBuilder, Validators } from '@angular/forms'; import { Workbasket } from 'app/models/workbasket'; -import { WorkbasketAccessItems } from 'app/models/workbasket-access-items'; +import { WorkbasketAccessItems, customFieldCount } from 'app/models/workbasket-access-items'; import { WorkbasketAccessItemsResource } from 'app/models/workbasket-access-items-resource'; import { ACTION } from 'app/models/action'; -import { AlertModel, AlertType } from 'app/models/alert'; -import { SavingInformation, - SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; +import { AlertModel, AlertType } from 'app/models/alert'; +import { SavingInformation, SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { GeneralModalService } from 'app/services/general-modal/general-modal.service'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { AlertService } from 'app/services/alert/alert.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { highlight } from 'app/shared/animations/validation.animation'; import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service'; import { AccessIdDefinition } from 'app/models/access-id'; +import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors'; import { ERROR_TYPES } from '../../../../models/errors'; import { ErrorsService } from '../../../../services/errors/errors.service'; +import { AccessItemsCustomisation, CustomField, getCustomFields } from '../../../../models/customisation'; @Component({ selector: 'taskana-workbasket-access-items', @@ -39,26 +40,13 @@ export class AccessItemsComponent implements OnChanges, OnDestroy { badgeMessage = ''; - accessIdField = this.customFieldsService.getCustomField('Owner', 'workbaskets.access-items.accessId'); - custom1Field = this.customFieldsService.getCustomField('Custom 1', 'workbaskets.access-items.custom1'); - custom2Field = this.customFieldsService.getCustomField('Custom 2', 'workbaskets.access-items.custom2'); - custom3Field = this.customFieldsService.getCustomField('Custom 3', 'workbaskets.access-items.custom3'); - custom4Field = this.customFieldsService.getCustomField('Custom 4', 'workbaskets.access-items.custom4'); - custom5Field = this.customFieldsService.getCustomField('Custom 5', 'workbaskets.access-items.custom5'); - custom6Field = this.customFieldsService.getCustomField('Custom 6', 'workbaskets.access-items.custom6'); - custom7Field = this.customFieldsService.getCustomField('Custom 7', 'workbaskets.access-items.custom7'); - custom8Field = this.customFieldsService.getCustomField('Custom 8', 'workbaskets.access-items.custom8'); - custom9Field = this.customFieldsService.getCustomField('Custom 9', 'workbaskets.access-items.custom9'); - custom10Field = this.customFieldsService.getCustomField('Custom 10', 'workbaskets.access-items.custom10'); - custom11Field = this.customFieldsService.getCustomField('Custom 11', 'workbaskets.access-items.custom11'); - custom12Field = this.customFieldsService.getCustomField('Custom 12', 'workbaskets.access-items.custom12'); + @Select(EngineConfigurationSelectors.accessItemsCustomisation) accessItemsCustomization$: Observable; + customFields$: Observable; accessItemsResource: WorkbasketAccessItemsResource; accessItemsClone: Array; accessItemsResetClone: Array; requestInProgress = false; - modalTitle: string; - modalErrorMessage: string; accessItemsubscription: Subscription; savingAccessItemsSubscription: Subscription; AccessItemsForm = this.formBuilder.group({ @@ -74,7 +62,6 @@ export class AccessItemsComponent implements OnChanges, OnDestroy { private generalModalService: GeneralModalService, private savingWorkbaskets: SavingWorkbasketService, private requestInProgressService: RequestInProgressService, - private customFieldsService: CustomFieldsService, private formBuilder: FormBuilder, private formsValidatorService: FormsValidatorService, private errorsService: ErrorsService @@ -85,6 +72,10 @@ export class AccessItemsComponent implements OnChanges, OnDestroy { return this.AccessItemsForm.get('accessItemsGroups') as FormArray; } + ngOnInit() { + this.customFields$ = this.accessItemsCustomization$.pipe(getCustomFields(customFieldCount)); + } + setAccessItemsGroups(accessItems: Array) { const AccessItemsFormGroups = accessItems.map(accessItem => this.formBuilder.group(accessItem)); AccessItemsFormGroups.forEach(accessItemGroup => { @@ -225,4 +216,8 @@ export class AccessItemsComponent implements OnChanges, OnDestroy { element.workbasketId = workbasketId; }); } + + getAccessItemCustomProperty(customNumber: number): string { + return `permCustom${customNumber}`; + } } diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html index 9163303fb..360edf42b 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html @@ -44,7 +44,7 @@
- @@ -107,26 +107,13 @@
-
- - -
-
- - -
-
- - -
-
- - -
+ +
+ + +
+
diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts b/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts index a31fc43f0..e07119f34 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts @@ -17,9 +17,10 @@ import { GeneralModalService } from 'app/services/general-modal/general-modal.se import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { AlertService } from 'app/services/alert/alert.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { configureTests } from 'app/app.test.configuration'; import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service'; +import { NgxsModule, Store } from '@ngxs/store'; +import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors'; import { WorkbasketInformationComponent } from './workbasket-information.component'; @Component({ @@ -44,29 +45,38 @@ describe('WorkbasketInformationComponent', () => { let requestInProgressService; let formsValidatorService; - beforeEach(done => { - const configure = (testBed: TestBed) => { - testBed.configureTestingModule({ - declarations: [WorkbasketInformationComponent, DummyDetailComponent], - imports: [FormsModule, - AngularSvgIconModule, - HttpClientModule, - RouterTestingModule.withRoutes(routes)], - providers: [WorkbasketService, AlertService, SavingWorkbasketService, GeneralModalService, RequestInProgressService, - CustomFieldsService, FormsValidatorService] + const storeSpy: jasmine.SpyObj = jasmine.createSpyObj('Store', ['select']); - }); - }; + const configure = (testBed: TestBed) => { + testBed.configureTestingModule({ + declarations: [WorkbasketInformationComponent, DummyDetailComponent], + imports: [FormsModule, AngularSvgIconModule, HttpClientModule, RouterTestingModule.withRoutes(routes), NgxsModule.forRoot()], + providers: [WorkbasketService, AlertService, SavingWorkbasketService, GeneralModalService, + RequestInProgressService, FormsValidatorService, { provide: Store, useValue: storeSpy }] + + }); + }; + + beforeEach(done => { configureTests(configure).then(testBed => { - fixture = TestBed.createComponent(WorkbasketInformationComponent); + storeSpy.select.and.callFake(selector => { + switch (selector) { + case EngineConfigurationSelectors.workbasketsCustomisation: + return of({ information: {} }); + default: + return of(); + } + }); + + fixture = testBed.createComponent(WorkbasketInformationComponent); component = fixture.componentInstance; debugElement = fixture.debugElement.nativeElement; - workbasketService = TestBed.get(WorkbasketService); - alertService = TestBed.get(AlertService); - savingWorkbasketService = TestBed.get(SavingWorkbasketService); - requestInProgressService = TestBed.get(RequestInProgressService); + workbasketService = testBed.get(WorkbasketService); + alertService = testBed.get(AlertService); + savingWorkbasketService = testBed.get(SavingWorkbasketService); + requestInProgressService = testBed.get(RequestInProgressService); - formsValidatorService = TestBed.get(FormsValidatorService); + formsValidatorService = testBed.get(FormsValidatorService); spyOn(alertService, 'triggerAlert'); fixture.detectChanges(); @@ -113,7 +123,7 @@ describe('WorkbasketInformationComponent', () => { expect(component.workbasket.workbasketId).toEqual(component.workbasketClone.workbasketId); }); - it('should reset requestInProgress after saving request is done', () => { + it('should reset requestInProgress after saving request is done', async(() => { component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description', 'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4', new Links({ href: 'someUrl' })); @@ -122,7 +132,7 @@ describe('WorkbasketInformationComponent', () => { spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(of(component.workbasket)); component.onSubmit(); expect(component.requestInProgress).toBeFalsy(); - }); + })); it('should trigger triggerWorkBasketSaved method after saving request is done', async(() => { component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description', diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts index 894371987..a2988dcbb 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts @@ -1,12 +1,12 @@ import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { NgForm } from '@angular/forms'; +import { Select } from '@ngxs/store'; import { ICONTYPES } from 'app/models/type'; -import { MessageModal } from 'app/models/message-modal'; import { ACTION } from 'app/models/action'; -import { Workbasket } from 'app/models/workbasket'; +import { customFieldCount, Workbasket } from 'app/models/workbasket'; import { AlertModel, AlertType } from 'app/models/alert'; import { TaskanaDate } from 'app/shared/util/taskana.date'; @@ -15,11 +15,13 @@ import { GeneralModalService } from 'app/services/general-modal/general-modal.se import { SavingWorkbasketService, SavingInformation } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service'; import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service'; +import { map } from 'rxjs/operators'; +import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors'; import { ERROR_TYPES } from '../../../../models/errors'; import { ErrorsService } from '../../../../services/errors/errors.service'; +import { CustomField, getCustomFields, WorkbasketsCustomisation } from '../../../../models/customisation'; @Component({ selector: 'taskana-workbasket-information', @@ -40,30 +42,8 @@ implements OnInit, OnChanges, OnDestroy { requestInProgress = false; badgeMessage = ''; - ownerField = this.customFieldsService.getCustomField( - 'Owner', - 'workbaskets.information.owner' - ); - - custom1Field = this.customFieldsService.getCustomField( - 'Custom 1', - 'workbaskets.information.custom1' - ); - - custom2Field = this.customFieldsService.getCustomField( - 'Custom 2', - 'workbaskets.information.custom2' - ); - - custom3Field = this.customFieldsService.getCustomField( - 'Custom 3', - 'workbaskets.information.custom3' - ); - - custom4Field = this.customFieldsService.getCustomField( - 'Custom 4', - 'workbaskets.information.custom4' - ); + @Select(EngineConfigurationSelectors.workbasketsCustomisation) workbasketsCustomisation$: Observable; + customFields$: Observable; toogleValidationMap = new Map(); @@ -80,21 +60,24 @@ implements OnInit, OnChanges, OnDestroy { private generalModalService: GeneralModalService, private savingWorkbasket: SavingWorkbasketService, private requestInProgressService: RequestInProgressService, - private customFieldsService: CustomFieldsService, private removeConfirmationService: RemoveConfirmationService, private formsValidatorService: FormsValidatorService, private errorsService: ErrorsService - ) { + ) {} + + ngOnInit(): void { this.allTypes = new Map([ ['PERSONAL', 'Personal'], ['GROUP', 'Group'], ['CLEARANCE', 'Clearance'], ['TOPIC', 'Topic'] ]); + this.customFields$ = this.workbasketsCustomisation$.pipe( + map(customisation => customisation.information), + getCustomFields(customFieldCount) + ); } - ngOnInit(): void { } - ngOnChanges(changes: SimpleChanges): void { this.workbasketClone = { ...this.workbasket }; if (this.action === ACTION.CREATE) { @@ -290,4 +273,8 @@ implements OnInit, OnChanges, OnDestroy { this.routeSubscription.unsubscribe(); } } + + getWorkbasketCustomProperty(custom: number) { + return `custom${custom}`; + } } diff --git a/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts b/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts index f5cc732c8..e5c84e3e1 100644 --- a/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts +++ b/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts @@ -22,7 +22,6 @@ import { AlertService } from 'app/services/alert/alert.service'; import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { GeneralModalService } from 'app/services/general-modal/general-modal.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { configureTests } from 'app/app.test.configuration'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; @@ -63,7 +62,7 @@ describe('WorkbasketDetailsComponent', () => { AccessItemsComponent, DistributionTargetsComponent, DualListComponent, DummyDetailComponent], providers: [WorkbasketService, MasterAndDetailService, GeneralModalService, RequestInProgressService, - AlertService, SavingWorkbasketService, CustomFieldsService, ImportExportService] + AlertService, SavingWorkbasketService, ImportExportService] }); }; configureTests(configure).then(testBed => { diff --git a/web/src/app/app.module.ts b/web/src/app/app.module.ts index ed128ae84..1a7c9d547 100644 --- a/web/src/app/app.module.ts +++ b/web/src/app/app.module.ts @@ -2,9 +2,11 @@ * Modules */ import { BrowserModule } from '@angular/platform-browser'; -import { NgModule, APP_INITIALIZER } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; +import { NgxsModule } from '@ngxs/store'; +import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; import { AlertModule } from 'ngx-bootstrap'; import { AngularSvgIconModule } from 'angular-svg-icon'; import { TabsModule } from 'ngx-bootstrap/tabs'; @@ -15,7 +17,6 @@ import { SharedModule } from 'app/shared/shared.module'; /** * Services */ - import { GeneralModalService } from 'app/services/general-modal/general-modal.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { OrientationService } from 'app/services/orientation/orientation.service'; @@ -26,7 +27,6 @@ import { AlertService } from 'app/services/alert/alert.service'; import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'; import { TreeService } from 'app/services/tree/tree.service'; import { TitlesService } from 'app/services/titles/titles.service'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { WindowRefService } from 'app/services/window/window.service'; import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service'; import { NavBarComponent } from 'app/components/nav-bar/nav-bar.component'; @@ -36,14 +36,11 @@ import { RemoveConfirmationService } from './services/remove-confirmation/remove import { FormsValidatorService } from './shared/services/forms/forms-validator.service'; import { UploadService } from './shared/services/upload/upload.service'; import { ErrorsService } from './services/errors/errors.service'; - - /** * Components */ import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; - /** * Guards */ @@ -51,7 +48,12 @@ import { DomainGuard } from './guards/domain.guard'; import { BusinessAdminGuard } from './guards/business-admin.guard'; import { MonitorGuard } from './guards/monitor.guard'; import { UserGuard } from './guards/user.guard'; - +/** + * Store + */ +import { ClassificationCategoriesService } from './shared/services/classifications/classification-categories.service'; +import { environment } from '../environments/environment'; +import { STATES } from './store'; const MODULES = [ TabsModule.forRoot(), @@ -64,7 +66,9 @@ const MODULES = [ BrowserAnimationsModule, ReactiveFormsModule, TreeModule, - SharedModule + SharedModule, + NgxsModule.forRoot(STATES, { developmentMode: !environment.production }), + NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production, maxAge: 25 }) ]; const DECLARATIONS = [ @@ -104,12 +108,12 @@ export function startupServiceFactory(startupService: StartupService): () => Pro MasterAndDetailService, TreeService, TitlesService, - CustomFieldsService, TaskanaEngineService, RemoveConfirmationService, FormsValidatorService, UploadService, ErrorsService, + ClassificationCategoriesService, ], bootstrap: [AppComponent] }) diff --git a/web/src/app/app.test.configuration.ts b/web/src/app/app.test.configuration.ts index 44d655a81..28268b706 100644 --- a/web/src/app/app.test.configuration.ts +++ b/web/src/app/app.test.configuration.ts @@ -3,7 +3,6 @@ import { getTestBed, TestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -35,7 +34,7 @@ export const configureTests = (configure: (testBed: TestBed) => void) => { testBed.configureTestingModule({ imports: [BrowserAnimationsModule, SharedModule, FormsModule, ReactiveFormsModule, HttpClientModule, AngularSvgIconModule], providers: [{ provide: TaskanaEngineService, useClass: TaskanaEngineServiceMock }, - { provide: DomainService, useClass: DomainServiceMock }, CustomFieldsService, RemoveConfirmationService, + { provide: DomainService, useClass: DomainServiceMock }, RemoveConfirmationService, AlertService, GeneralModalService, RequestInProgressService, OrientationService, SelectedRouteService, FormsValidatorService] }); diff --git a/web/src/app/models/classification-definition.ts b/web/src/app/models/classification-definition.ts index 731cf419c..7b0c109ec 100644 --- a/web/src/app/models/classification-definition.ts +++ b/web/src/app/models/classification-definition.ts @@ -27,3 +27,5 @@ export class ClassificationDefinition { public _links?: LinksClassification) { } } + +export const customFieldCount: number = 8; diff --git a/web/src/app/models/customField.ts b/web/src/app/models/customField.ts deleted file mode 100644 index 38cf0c860..000000000 --- a/web/src/app/models/customField.ts +++ /dev/null @@ -1,8 +0,0 @@ - -export class CustomField { - constructor( - public visible: boolean, - public field: string - ) { - } -} diff --git a/web/src/app/models/customisation.ts b/web/src/app/models/customisation.ts new file mode 100644 index 000000000..7ccb59c0c --- /dev/null +++ b/web/src/app/models/customisation.ts @@ -0,0 +1,56 @@ +import { map } from 'rxjs/operators'; +import { OperatorFunction } from 'rxjs'; + +export interface Customisation { + [language: string]: CustomisationContent +} + +export interface CustomisationContent { + workbaskets?: WorkbasketsCustomisation; + classifications?: ClassificationsCustomisation; + tasks?: TasksCustomisation; +} + +export interface TasksCustomisation { + information?: { + owner: LookupField + }; +} + +export interface ClassificationsCustomisation { + information?: CustomFields; + categories?: ClassificationCategoryImages +} + +export interface ClassificationCategoryImages { + [key: string]: string +} + +export interface WorkbasketsCustomisation { + information?: { owner: LookupField } & CustomFields; + 'access-items'?: AccessItemsCustomisation; +} + +export type AccessItemsCustomisation = { accessId?: LookupField } & CustomFields; + +export interface CustomFields { + [key: string]: CustomField +} + +export interface CustomField { + visible: boolean + field: string +} + +export interface LookupField { + lookupField: boolean +} + +export function getCustomFields(amount: number): OperatorFunction { + return map(customisation => [...Array(amount).keys()] + .map(x => x + 1) + .map(x => customisation[`custom${x}`] || { + field: `Custom ${x}`, + visible: true + })); +} diff --git a/web/src/app/models/workbasket-access-items.ts b/web/src/app/models/workbasket-access-items.ts index 7c444f843..3d2a67816 100644 --- a/web/src/app/models/workbasket-access-items.ts +++ b/web/src/app/models/workbasket-access-items.ts @@ -26,3 +26,5 @@ export class WorkbasketAccessItems { public _links: Links = new Links() ) { } } + +export const customFieldCount: number = 12; diff --git a/web/src/app/models/workbasket.ts b/web/src/app/models/workbasket.ts index 6e05600cc..192b757e2 100644 --- a/web/src/app/models/workbasket.ts +++ b/web/src/app/models/workbasket.ts @@ -46,3 +46,5 @@ export class Workbasket { ) { } } + +export const customFieldCount: number = 4; diff --git a/web/src/app/services/custom-fields/custom-fields.service.spec.ts b/web/src/app/services/custom-fields/custom-fields.service.spec.ts deleted file mode 100644 index c7d09583b..000000000 --- a/web/src/app/services/custom-fields/custom-fields.service.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { TestBed, inject } from '@angular/core/testing'; - -import { CustomFieldsService } from './custom-fields.service'; - -const json = require('./taskana-customization-test.json'); - -describe('CustomFieldsService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [CustomFieldsService] - }); - }); - - it('should be created', inject([CustomFieldsService], (service: CustomFieldsService) => { - expect(service).toBeTruthy(); - })); - - it('should take default icon path', inject([CustomFieldsService], (service: CustomFieldsService) => { - const categoriesData = { DEFAULT: 'assets/icons/categories/default.svg' }; - const returnedValue = service.getCustomObject(categoriesData); - expect(returnedValue).toBe(categoriesData); - expect(service).toBeTruthy(); - })); - - it('should take default icon path in merge', inject([CustomFieldsService], (service: CustomFieldsService) => { - service.initCustomFields('EN', json); - const categoriesDefault = json.EN.classifications.categories; - const categoriesData = { - EXTERNAL: 'assets/icons/categories/external.svg', - MANUAL: 'assets/icons/categories/manual.svg', - AUTOMATIC: 'assets/icons/categories/automatic.svg', - PROCESS: 'assets/icons/categories/external.svg' - }; - const returnedValue = service.getCustomObject(categoriesData, 'classifications.categories'); - expect(returnedValue).toEqual(categoriesDefault); - expect(service).toBeTruthy(); - })); - - it('should take merge icon path', inject([CustomFieldsService], (service: CustomFieldsService) => { - service.initCustomFields('EN', json); - const categoriesData = { DEFAULT: 'assets/icons/categories/default.svg' }; - const result = { - AUTOMATIC: 'assets/icons/categories/automatic.svg', - DEFAULT: 'assets/icons/categories/default.svg', - EXTERNAL: 'assets/icons/categories/external.svg', - MANUAL: 'assets/icons/categories/manual.svg', - PROCESS: 'assets/icons/categories/process.svg' - }; - const returnedValue = service.getCustomObject(categoriesData, 'classifications.categories'); - expect(returnedValue).toEqual(result); - expect(service).toBeTruthy(); - })); -}); diff --git a/web/src/app/services/custom-fields/custom-fields.service.ts b/web/src/app/services/custom-fields/custom-fields.service.ts deleted file mode 100644 index f36f98b30..000000000 --- a/web/src/app/services/custom-fields/custom-fields.service.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Injectable } from '@angular/core'; -import { CustomField } from '../../models/customField'; - -@Injectable() -export class CustomFieldsService { - private customizedFields: any = {}; - - initCustomFields(language: string = 'EN', jsonFile: any) { - this.customizedFields = jsonFile[language]; - } - - getCustomField(fallbacktext: string, customPath?: string): CustomField { - if (!customPath) { - return new CustomField(true, fallbacktext); - } - return this.jsonPath(customPath, fallbacktext); - } - - getCustomObject(fallbackObject: Object, customPath?: string): Object { - if (!customPath) { - return fallbackObject; - } - return this.jsonPathObject(customPath, fallbackObject); - } - - private jsonPath(path: string, fallbacktext: string): CustomField { - if (!this.customizedFields) { - return new CustomField(true, fallbacktext); - } - const paths = path.split('.'); - let value = this.customizedFields; - paths.every(element => { - value = value[element]; - if (!value) { - value = new CustomField(true, fallbacktext); - return false; - } - return true; - }); - - return value; - } - - - private jsonPathObject(path: string, fallbackObject: Object): Object { - if (!this.customizedFields) { - return fallbackObject; - } - const paths = path.split('.'); - let value = this.customizedFields; - paths.every(element => { - value = value[element]; - if (!value) { - value = fallbackObject; - return false; - } - return true; - }); - - value = this.mergeKeys(value, fallbackObject); - - return value; - } - - private mergeKeys(defaultObject: Object, newObject: Object) { - const value = {}; - - Object.keys(defaultObject).forEach(item => { - value[item] = value[item] ? value[item] : defaultObject[item]; - }); - - Object.keys(newObject).forEach(item => { - value[item] = value[item] ? value[item] : newObject[item]; - }); - - return value; - } -} diff --git a/web/src/app/services/custom-fields/taskana-customization-test.json b/web/src/app/services/custom-fields/taskana-customization-test.json deleted file mode 100644 index 166d53016..000000000 --- a/web/src/app/services/custom-fields/taskana-customization-test.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "EN": { - "classifications": { - "information": { - "custom1": { - "field": "Classification custom 1", - "visible": true - }, - "custom3": { - "field": "", - "visible": false - } - }, - "categories": { - "EXTERNAL": "assets/icons/categories/external.svg", - "MANUAL": "assets/icons/categories/manual.svg", - "AUTOMATIC": "assets/icons/categories/automatic.svg", - "PROCESS": "assets/icons/categories/process.svg" - } - } - } -} diff --git a/web/src/app/services/startup-service/startup.service.spec.ts b/web/src/app/services/startup-service/startup.service.spec.ts index 410d441a9..9e3c26031 100644 --- a/web/src/app/services/startup-service/startup.service.spec.ts +++ b/web/src/app/services/startup-service/startup.service.spec.ts @@ -3,7 +3,6 @@ import { TestBed, inject, getTestBed } from '@angular/core/testing'; import { HttpClient, HttpClientModule } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { StartupService } from './startup.service'; -import { CustomFieldsService } from '../custom-fields/custom-fields.service'; import { TaskanaEngineService } from '../taskana-engine/taskana-engine.service'; import { WindowRefService } from '../window/window.service'; import { environment } from '../../../environments/environment'; @@ -29,7 +28,6 @@ describe('StartupService', () => { providers: [ StartupService, HttpClient, - CustomFieldsService, TaskanaEngineService, WindowRefService ] diff --git a/web/src/app/services/startup-service/startup.service.ts b/web/src/app/services/startup-service/startup.service.ts index c0e237c8e..396465193 100644 --- a/web/src/app/services/startup-service/startup.service.ts +++ b/web/src/app/services/startup-service/startup.service.ts @@ -3,7 +3,6 @@ import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import { environment } from 'app/../environments/environment'; import { Injectable, Injector } from '@angular/core'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service'; import { map } from 'rxjs/operators'; import { WindowRefService } from 'app/services/window/window.service'; @@ -12,7 +11,6 @@ import { WindowRefService } from 'app/services/window/window.service'; export class StartupService { constructor( private httpClient: HttpClient, - private customFieldsService: CustomFieldsService, private taskanaEngineService: TaskanaEngineService, private injector: Injector, private window: WindowRefService @@ -27,6 +25,7 @@ export class StartupService { return this.loadEnvironment(); } + // TODO: refactor this getEnvironmentFilePromise() { return this.httpClient.get('environments/data-sources/environment-information.json').pipe(map(jsonFile => { if (jsonFile && jsonFile.taskanaRestUrl) { @@ -36,24 +35,12 @@ export class StartupService { if (jsonFile && jsonFile.taskanaLogoutUrl) { environment.taskanaLogoutUrl = jsonFile.taskanaLogoutUrl; } - this.customFieldsService.initCustomFields('EN', jsonFile); - })).toPromise() - .catch(() => of(true)); - } - - geCustomizedFieldsFilePromise() { - return this.httpClient.get('environments/data-sources/taskana-customization.json').pipe(map(jsonFile => { - if (jsonFile) { - this.customFieldsService.initCustomFields('EN', jsonFile); - } })).toPromise() .catch(() => of(true)); } private loadEnvironment() { return this.getEnvironmentFilePromise().then( - () => this.geCustomizedFieldsFilePromise() - ).then( () => this.taskanaEngineService.getUserInformation() ).catch(error => { // this.window.nativeWindow.location.href = environment.taskanaRestUrl + '/login'; diff --git a/web/src/app/shared/classification-types-selector/classification-types-selector.component.html b/web/src/app/shared/classification-types-selector/classification-types-selector.component.html index 4656add3b..a152807a1 100644 --- a/web/src/app/shared/classification-types-selector/classification-types-selector.component.html +++ b/web/src/app/shared/classification-types-selector/classification-types-selector.component.html @@ -1,15 +1,15 @@ @@ -83,7 +83,7 @@
- { TestBed.configureTestingModule({ imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes)], declarations: [TaskdetailsGeneralFieldsComponent, DummyDetailComponent], - providers: [HttpClient, ClassificationCategoriesService, CustomFieldsService, + providers: [HttpClient, ClassificationCategoriesService, DomainService, RequestInProgressService, SelectedRouteService, ClassificationsService] }); }; diff --git a/web/src/app/workplace/taskdetails/general/general-fields.component.ts b/web/src/app/workplace/taskdetails/general/general-fields.component.ts index 2bfe776f9..e25fb99e7 100644 --- a/web/src/app/workplace/taskdetails/general/general-fields.component.ts +++ b/web/src/app/workplace/taskdetails/general/general-fields.component.ts @@ -1,11 +1,14 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewChild, SimpleChanges, OnChanges, HostListener } from '@angular/core'; import { Task } from 'app/workplace/models/task'; -import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service'; import { NgForm } from '@angular/forms'; import { DomainService } from 'app/services/domain/domain.service'; +import { Select } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors'; import { ClassificationsService } from '../../../shared/services/classifications/classifications.service'; import { Classification } from '../../../models/classification'; +import { TasksCustomisation } from '../../../models/customisation'; @Component({ selector: 'taskana-task-details-general-fields', @@ -30,14 +33,10 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges { requestInProgress = false; classifications: Classification[]; - ownerField = this.customFieldsService.getCustomField( - 'Owner', - 'tasks.information.owner' - ); + @Select(EngineConfigurationSelectors.tasksCustomisation) tasksCustomisation$: Observable; constructor( private classificationService: ClassificationsService, - private customFieldsService: CustomFieldsService, private formsValidatorService: FormsValidatorService, private domainService: DomainService ) { diff --git a/web/src/environments/data-sources/taskana-customization.json b/web/src/environments/data-sources/taskana-customization.json index a7b5a2572..2235f1f5b 100644 --- a/web/src/environments/data-sources/taskana-customization.json +++ b/web/src/environments/data-sources/taskana-customization.json @@ -66,4 +66,4 @@ } } } -} \ No newline at end of file +} diff --git a/web/src/test.ts b/web/src/test.ts index f83c25dbd..555d36b52 100644 --- a/web/src/test.ts +++ b/web/src/test.ts @@ -15,7 +15,7 @@ declare let __karma__: any; declare let require: any; // Prevent Karma from running prematurely. -__karma__.loaded = function () {}; +__karma__.loaded = function noop() {}; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment(