TSK-652, TSK-1170, TSK-1023 Fixed various bugs by introducing ngxs state management

TSK-1170 Remove @inputs from classification-types-selector

TSK-1170 Import Store in Classification-Detail-Component



TSK-1170 Removed getSelectedClassificationType() from category service

TSK-1170 Removed getSelectedClassificationType() from category service

TSK-1170 Put icons in store

TSK-1170 Removed selectCategoryType from service

TSK-1170 Refactored classification-category service

TSK-1170 Fixed multiple calls of actions

TSK-1170

TSK-1170: build ngxs store in parallel to the ngrx store


TSK-1170: replaced ngrx store with ngxs store

tests are still failing.
TSK-1170 Split store into two stores

TSK-1170 Fixed tests

TSK-1170 added categories service test

TSK-1170 Fixed checkstyle and test

TSK-1170: minimized diff for classification-details component


TSK-1170: replaced state with store observable in classificaiton details


TSK-1170 Incorporated requested changes in spec files


TSK-1170 Removed @output from classification-types-selector

Removed getClassifications() call when selecting a classification type because the code is duplicated in performRequest() 
TSK-1170 Extracted selectors and actions


TSK-1170 Dispatch action in classification-service




TSK-1170 Incorporated 'set-value' function


dynamically rendering classification types

TSK-1170: disabled parent reference while creating classification


TSK-1170 fixed two ways binding for classification types

TSK-1170 removed console log

TSK-1170: Category dropdown shows correct values

TSK-1170 Renamed selectors




TSK-1170
This commit is contained in:
Sofie Hofmann 2020-04-09 17:20:34 +02:00 committed by Mustapha Zorgati
parent 69fac1bdf4
commit 673f1896da
58 changed files with 946 additions and 969 deletions

View File

@ -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<String> domains = new ArrayList<String>();
protected List<String> domains = new ArrayList<>();
// List of configured classification types
protected List<String> classificationTypes = new ArrayList<String>();
protected Map<String, List<String>> classificationCategoriesByTypeMap =
new HashMap<String, List<String>>();
protected List<String> classificationTypes = new ArrayList<>();
protected Map<String, List<String>> classificationCategoriesByTypeMap = new HashMap<>();
// Properties for the monitor
private boolean germanPublicHolidaysEnabled;
private List<LocalDate> customHolidays;
@ -294,8 +294,13 @@ public class TaskanaEngineConfiguration {
return classificationCategories;
}
public Map<String, List<String>> getClassificationCategoriesByTypeMap() {
return this.classificationCategoriesByTypeMap.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, e -> new ArrayList<>(e.getValue())));
}
public List<String> getClassificationCategoriesByType(String type) {
return classificationCategoriesByTypeMap.get(type);
return classificationCategoriesByTypeMap.getOrDefault(type, Collections.emptyList());
}
public void setClassificationCategoriesByType(

View File

@ -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

View File

@ -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}";

View File

@ -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<List<String>> getClassificationCategories(String type) {
LOGGER.debug("Entry to getClassificationCategories(type = {})", type);
ResponseEntity<List<String>> response;
@ -65,7 +66,7 @@ public class TaskanaEngineController {
return response;
}
@GetMapping(path = Mapping.URL_CLASSIFICATIONTYPES)
@GetMapping(path = Mapping.URL_CLASSIFICATION_TYPES)
public ResponseEntity<List<String>> getClassificationTypes() {
ResponseEntity<List<String>> 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<Map<String, List<String>>> getClassificationCategoriesByTypeMap() {
ResponseEntity<Map<String, List<String>>> 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<TaskanaUserInfoResource> 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<Boolean> getIsHistoryProviderEnabled() {
ResponseEntity<Boolean> response = ResponseEntity.ok(taskanaEngine.isHistoryEnabled());
LOGGER.debug("Exit from getIsHistoryProviderEnabled(), returning {}", response);

View File

@ -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())

View File

@ -43,7 +43,7 @@ class TaskanaEngineControllerIntTest {
void testClassificationTypes() {
ResponseEntity<List<String>> 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<List<String>> 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<TaskanaUserInfoResource> response =
template.exchange(
restHelper.toUrl(Mapping.URL_CURRENTUSER),
restHelper.toUrl(Mapping.URL_CURRENT_USER),
HttpMethod.GET,
restHelper.defaultRequest(),
ParameterizedTypeReference.forType(TaskanaUserInfoResource.class));

216
web/package-lock.json generated
View File

@ -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"
},

View File

@ -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": {

View File

@ -26,18 +26,11 @@
<th>Append</th>
<th>Transfer</th>
<th>Distribute</th>
<th *ngIf="custom1Field.visible">{{custom1Field.field}}</th>
<th *ngIf="custom2Field.visible">{{custom2Field.field}}</th>
<th *ngIf="custom3Field.visible">{{custom3Field.field}}</th>
<th *ngIf="custom4Field.visible">{{custom4Field.field}}</th>
<th *ngIf="custom5Field.visible">{{custom5Field.field}}</th>
<th *ngIf="custom6Field.visible">{{custom6Field.field}}</th>
<th *ngIf="custom7Field.visible">{{custom7Field.field}}</th>
<th *ngIf="custom8Field.visible">{{custom8Field.field}}</th>
<th *ngIf="custom9Field.visible">{{custom9Field.field}}</th>
<th *ngIf="custom10Field.visible">{{custom10Field.field}}</th>
<th *ngIf="custom11Field.visible">{{custom11Field.field}}</th>
<th *ngIf="custom12Field.visible">{{custom12Field.field}}</th>
<ng-container *ngFor="let customField of customFields$ | async">
<th *ngIf="customField.visible">
{{customField.field}}
</th>
</ng-container>
</tr>
<tr>
<th colspan="2" class="text-align"><input type="text" formControlName="workbasketKeyFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
@ -74,7 +67,7 @@
<td colspan="2">
<label class="wrap">{{accessItem.value.workbasketKey}}</label>
</td>
<td *ngIf="accessIdField.lookupField else accessIdInput" colspan="2" class="text-align text-width taskana-type-ahead">
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" colspan="2" class="text-align text-width taskana-type-ahead">
<div>
<taskana-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required"
[validationValue]="toogleValidationAccessIdMap.get(index)" [displayError]="!isFieldValid('accessItem.value.accessId', index)"
@ -109,60 +102,19 @@
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label>
</td>
<td *ngIf="custom1Field.visible">
<input id="checkbox-{{index}}-5" type="checkbox" formControlName="permCustom1">
<label for="checkbox-{{index}}-5"></label>
</td>
<td *ngIf="custom2Field.visible">
<input id="checkbox-{{index}}-6" type="checkbox" formControlName="permCustom2">
<label for="checkbox-{{index}}-6"></label>
</td>
<td *ngIf="custom3Field.visible">
<input id="checkbox-{{index}}-7" type="checkbox" formControlName="permCustom3">
<label for="checkbox-{{index}}-7"></label>
</td>
<td *ngIf="custom4Field.visible">
<input id="checkbox-{{index}}-8" type="checkbox" formControlName="permCustom4">
<label for="checkbox-{{index}}-8"></label>
</td>
<td *ngIf="custom5Field.visible">
<input id="checkbox-{{index}}-9" type="checkbox" formControlName="permCustom5">
<label for="checkbox-{{index}}-9"></label>
</td>
<td *ngIf="custom6Field.visible">
<input id="checkbox-{{index}}-10" type="checkbox" formControlName="permCustom6">
<label for="checkbox-{{index}}-10"></label>
</td>
<td *ngIf="custom7Field.visible">
<input id="checkbox-{{index}}-11" type="checkbox" formControlName="permCustom7">
<label for="checkbox-{{index}}-11"></label>
</td>
<td *ngIf="custom8Field.visible">
<input id="checkbox-{{index}}-12" type="checkbox" formControlName="permCustom8">
<label for="checkbox-{{index}}-12"></label>
</td>
<td *ngIf="custom9Field.visible">
<input id="checkbox-{{index}}-13" type="checkbox" formControlName="permCustom9">
<label for="checkbox-{{index}}-13"></label>
</td>
<td *ngIf="custom10Field.visible">
<input id="checkbox-{{index}}-14" type="checkbox" formControlName="permCustom10">
<label for="checkbox-{{index}}-14"></label>
</td>
<td *ngIf="custom11Field.visible">
<input id="checkbox-{{index}}-15" type="checkbox" formControlName="permCustom11">
<label for="checkbox-{{index}}-15"></label>
</td>
<td *ngIf="custom12Field.visible">
<input id="checkbox-{{index}}-16" type="checkbox" formControlName="permCustom12">
<label for="checkbox-{{index}}-16"></label>
</td>
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible">
<input id="checkbox-{{index}}-{{customIndex + 5}}" type="checkbox" formControlName="permCustom{{customIndex + 1}}">
<label for="checkbox-{{index}}-{{customIndex + 5}}"></label>
</td>
</ng-container>
</tr>
</tbody>
</table>
<button *ngIf="!isGroup" class="pull-left btn-group" type="button" class="btn btn-primary" data-toggle="modal"
data-target="#myModal">
<button *ngIf="!isGroup" class="btn btn-primary pull-left btn-group" type="button"
data-toggle="modal"
data-target="#myModal">
Belonging groups
</button>
<div class="modal" id="myModal">

View File

@ -6,6 +6,7 @@ import { FormsValidatorService } from 'app/shared/services/forms/forms-validator
import { AccessIdDefinition } from 'app/models/access-id';
import { AccessItemsWorkbasketResource } from 'app/models/access-item-workbasket-resource';
import { of } from 'rxjs';
import { NgxsModule } from '@ngxs/store';
import { AccessItemsManagementComponent } from './access-items-management.component';
@ -14,20 +15,20 @@ describe('AccessItemsManagementComponent', () => {
let fixture: ComponentFixture<AccessItemsManagementComponent>;
let accessIdsService;
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [NgxsModule.forRoot()],
declarations: [AccessItemsManagementComponent],
providers: [AccessIdsService, FormsValidatorService]
});
};
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [],
declarations: [AccessItemsManagementComponent],
providers: [AccessIdsService, FormsValidatorService]
});
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(AccessItemsManagementComponent);
fixture = testBed.createComponent(AccessItemsManagementComponent);
component = fixture.componentInstance;
accessIdsService = TestBed.get(AccessIdsService);
accessIdsService = testBed.get(AccessIdsService);
spyOn(accessIdsService, 'getAccessItemsPermissions').and.returnValue(of(new Array<AccessIdDefinition>()));
spyOn(accessIdsService, 'getAccessItemsInformation').and.returnValue(of(new AccessItemsWorkbasketResource()));
fixture.detectChanges();

View File

@ -1,23 +1,25 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Select } from '@ngxs/store';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service';
import { AccessItemsWorkbasketResource } from 'app/models/access-item-workbasket-resource';
import { AccessItemWorkbasket } from 'app/models/access-item-workbasket';
import { SortingModel } from 'app/models/sorting';
import { GeneralModalService } from 'app/services/general-modal/general-modal.service';
import { MessageModal } from 'app/models/message-modal';
import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service';
import { AlertModel, AlertType } from 'app/models/alert';
import { AlertService } from 'app/services/alert/alert.service';
import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors';
import { RequestInProgressService } from '../../services/requestInProgress/request-in-progress.service';
import { AccessIdsService } from '../../shared/services/access-ids/access-ids.service';
import { AccessIdDefinition } from '../../models/access-id';
import { ErrorsService } from '../../services/errors/errors.service';
import { ERROR_TYPES } from '../../models/errors';
import { AccessItemsCustomisation, CustomField, getCustomFields } from '../../models/customisation';
import { customFieldCount } from '../../models/workbasket-access-items';
@Component({
selector: 'taskana-access-items-management',
@ -40,22 +42,10 @@ export class AccessItemsManagementComponent implements OnInit, OnDestroy {
isGroup: boolean;
groupsKey = 'ou=groups';
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<AccessItemsCustomisation>;
customFields$: Observable<CustomField[]>;
constructor(private formBuilder: FormBuilder,
private customFieldsService: CustomFieldsService,
private accessIdsService: AccessIdsService,
private formsValidatorService: FormsValidatorService,
private requestInProgressService: RequestInProgressService,
@ -75,6 +65,10 @@ export class AccessItemsManagementComponent implements OnInit, OnDestroy {
}
}
ngOnInit() {
this.customFields$ = this.accessItemsCustomization$.pipe(getCustomFields(customFieldCount));
}
setAccessItemsGroups(accessItems: Array<AccessItemWorkbasket>) {
const AccessItemsFormGroups = accessItems.map(accessItem => this.formBuilder.group(accessItem));
AccessItemsFormGroups.forEach(accessItemGroup => {
@ -96,9 +90,6 @@ export class AccessItemsManagementComponent implements OnInit, OnDestroy {
}
}
ngOnInit() {
}
onSelectAccessId(selected: AccessIdDefinition) {
if (!selected) {
this.AccessItemsForm = null;

View File

@ -39,7 +39,7 @@ const MODULES = [
SharedModule,
AdministrationRoutingModule,
TypeaheadModule,
InfiniteScrollModule
InfiniteScrollModule,
];
const DECLARATIONS = [

View File

@ -30,9 +30,9 @@
<span *ngIf="action=== 'CREATE'" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div>
<div class="panel-body">
<div class="panel-body" style="padding: 0">
<form #ClassificationForm="ngForm">
<div class="row">
<div class="row" style="padding: 15px">
<div class="col-md-6">
<div class="form-group required">
@ -67,26 +67,26 @@
<div class="row">
<div class="form-group col-xs-6">
<label for="classification-priority" class="control-label">Priority</label>
<taskana-number-picker [(ngModel)]="classification.priority" name="classification.priority"></taskana-number-picker>
<taskana-number-picker [(ngModel)]="classification.priority" name="classification.priority" id="classification-priority"></taskana-number-picker>
</div>
<div class="form-group required btn-group col-xs-6">
<label for="classification-category" class="control-label">Category</label>
<div class="dropdown clearfix ">
<button class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
aria-expanded="true" id="classification-category">
<span class="text-top">
<svg-icon class="blue fa-fw" src="{{getCategoryIcon(classification.category).name}}"
data-toggle="tooltip" [title]="getCategoryIcon(classification.category).text"></svg-icon>
<svg-icon class="blue fa-fw" src="{{(getCategoryIcon(classification.category) | async)?.name}}"
data-toggle="tooltip" [title]="(getCategoryIcon(classification.category) | async)?.text"></svg-icon>
</span>
{{classification.category}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu">
<li>
<a *ngFor="let category of categories" (click)="selectCategory(category)">
<a *ngFor="let category of getAvailableCategories(classification.type)" (click)="selectCategory(category)">
<span class="text-top">
<svg-icon class="blue fa-fw" src="{{getCategoryIcon(category).name}}" data-toggle="tooltip"
[title]="getCategoryIcon(category).text"></svg-icon>
<svg-icon class="blue fa-fw" src="{{(getCategoryIcon(category) | async)?.name}}" data-toggle="tooltip"
[title]="(getCategoryIcon(category) | async)?.text"></svg-icon>
{{category}}
</span>
</a>
@ -114,50 +114,18 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div *ngIf="custom1Field.visible" class="form-group">
<label for="classification-custom-1" class="control-label">{{custom1Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-1" placeholder="{{custom1Field.field}}"
[(ngModel)]="classification.custom1" name="classification.custom1">
</div>
<div *ngIf="custom2Field.visible" class="form-group">
<label for="classification-custom-2" class="control-label">{{custom2Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-2" placeholder="{{custom2Field.field}}"
[(ngModel)]="classification.custom2" name="classification.custom2">
</div>
<div *ngIf="custom3Field.visible" class="form-group">
<label for="classification-custom-3" class="control-label">{{custom3Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-3" placeholder="{{custom3Field.field}}"
[(ngModel)]="classification.custom3" name="classification.custom3">
</div>
<div *ngIf="custom4Field.visible" class="form-group">
<label for="classification-custom-4" class="control-label">{{custom4Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-4" placeholder="{{custom4Field.field}}"
[(ngModel)]="classification.custom4" name="classification.custom4">
</div>
</div>
<div class="col-md-6">
<div *ngIf="custom5Field.visible" class="form-group">
<label for="classification-custom-5" class="control-label">{{custom5Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-5" placeholder="{{custom5Field.field}}"
[(ngModel)]="classification.custom5" name="classification.custom5">
</div>
<div *ngIf="custom6Field.visible" class="form-group">
<label for="classification-custom-6" class="control-label">{{custom6Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-6" placeholder="{{custom6Field.field}}"
[(ngModel)]="classification.custom6" name="classification.custom6">
</div>
<div *ngIf="custom7Field.visible" class="form-group">
<label for="classification-custom-7" class="control-label">{{custom7Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-7" placeholder="{{custom7Field.field}}"
[(ngModel)]="classification.custom7" name="classification.custom7">
</div>
<div *ngIf="custom8Field.visible" class="form-group">
<label for="classification-custom-8" class="control-label">{{custom8Field.field}}</label>
<input type="text" class="form-control" id="classification-custom-8" placeholder="{{custom8Field.field}}"
[(ngModel)]="classification.custom8" name="classification.custom8">
<div class="row custom-field-row">
<div class="custom-classification-form" *ngFor="let customField of (customFields$ | async), let i = index"
style="width: 50%;">
<div *ngIf="customField.visible" class="form-group custom-field-wrapper">
<label for="classification-custom-{{i + 1}}" class="control-label">{{customField.field}}</label>
<input type="text" class="form-control" id="classification-custom-{{i + 1}}" placeholder="{{customField.field}}"
[(ngModel)]="classification[getClassificationCustom(i + 1)]" name="classification.custom{{i + 1}}">
</div>
</div>
</div>
</form>

View File

@ -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
}

View File

@ -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<TreeNodeModel> = new Array(new TreeNodeModel());
let classificationsService;
let classificationCategoriesService;
let treeService;
let removeConfirmationService;
const storeSpy: jasmine.SpyObj<Store> = 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');
});
});

View File

@ -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<string> = [];
badgeMessage = '';
requestInProgress = false;
categories: Array<string> = [];
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<string[]>;
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
@Select(ClassificationSelectors.selectedClassificationType) selectedClassificationType$: Observable<string>;
@Select(ClassificationSelectors.selectClassificationTypesObject) classificationTypes$: Observable<Object>;
spinnerIsRunning = false;
customFields$: Observable<CustomField[]>;
@ViewChild('ClassificationForm', { static: false }) classificationForm: NgForm;
toogleValidationMap = new Map<string, boolean>();
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<string, boolean>();
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<string>) => {
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<string>) => {
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<Pair> {
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();
}
}
}

View File

@ -9,16 +9,15 @@
</taskana-import-export-component>
</div>
<div class="col-xs-6">
<taskana-classification-types-selector class="pull-right" [classificationTypes]="classificationsTypes"
[(classificationTypeSelected)]="classificationTypeSelected" (classificationTypeChanged)=selectClassificationType($event)></taskana-classification-types-selector>
<taskana-classification-types-selector class="pull-right"></taskana-classification-types-selector>
</div>
</div>
</li>
<div class="col-xs-2 category-filter">
<button class="btn btn-default" data-toggle="dropdown" type="button" id="dropdown-classification-filter" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<svg-icon *ngIf="selectedCategory else category_unselected" class="blue" [src]="getCategoryIcon(selectedCategory).name"
data-toggle="tooltip" [title]="getCategoryIcon(category).text"></svg-icon>
<svg-icon *ngIf="selectedCategory else category_unselected" class="blue" [src]="(getCategoryIcon(selectedCategory) | async)?.name"
data-toggle="tooltip" [title]="(getCategoryIcon(category) | async)?.text"></svg-icon>
<ng-template #category_unselected>
<svg-icon data-toggle="tooltip" title="All" class="blue " src="./assets/icons/asterisk.svg"></svg-icon>
</ng-template>
@ -29,9 +28,9 @@
<svg-icon class="blue" src="./assets/icons/asterisk.svg"></svg-icon>
All
</a>
<a *ngFor="let category of categories" type="button" (click)="selectCategory(category);" data-toggle="tooltip"
<a *ngFor="let category of categories$ | async" type="button" (click)="selectCategory(category);" data-toggle="tooltip"
[title]="category">
<svg-icon class="blue" [src]="getCategoryIcon(category).name" data-toggle="tooltip" [title]="getCategoryIcon(category).text"></svg-icon>
<svg-icon class="blue" [src]="(getCategoryIcon(category) | async)?.name" data-toggle="tooltip" [title]="(getCategoryIcon(category) | async)?.text"></svg-icon>
{{category}}
</a>
</li>

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { of } from 'rxjs';
import { Routes } from '@angular/router';
@ -20,12 +20,12 @@ import { DomainService } from 'app/services/domain/domain.service';
import { GeneralModalService } from 'app/services/general-modal/general-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { configureTests } from 'app/app.test.configuration';
import { ClassificationCategoriesService } from 'app/shared/services/classifications/classification-categories.service';
import { Pair } from 'app/models/pair';
import { TreeService } from 'app/services/tree/tree.service';
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
import { NgxsModule } from '@ngxs/store';
import { ClassificationListComponent } from './classification-list.component';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
@ -37,38 +37,32 @@ const routes: Routes = [
{ path: ':id', component: DummyDetailComponent }
];
describe('ClassificationListComponent', () => {
let component: ClassificationListComponent;
let fixture: ComponentFixture<ClassificationListComponent>;
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
const classificationTypes: Array<string> = new Array<string>('type1', 'type2');
let classificationsService;
let classificationCategoriesService;
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [ClassificationListComponent, ImportExportComponent, ClassificationTypesSelectorComponent,
DummyDetailComponent],
imports: [HttpClientModule, RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, NgxsModule.forRoot()],
providers: [
HttpClient, WorkbasketDefinitionService, AlertService, ClassificationsService, DomainService, ClassificationDefinitionService,
GeneralModalService, RequestInProgressService, TreeService, ImportExportService
]
});
};
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [ClassificationListComponent, ImportExportComponent, ClassificationTypesSelectorComponent,
DummyDetailComponent],
imports: [HttpClientModule, RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule],
providers: [
HttpClient, WorkbasketDefinitionService, AlertService, ClassificationsService, DomainService, ClassificationDefinitionService,
GeneralModalService, RequestInProgressService, ClassificationCategoriesService, TreeService, ImportExportService
]
});
};
configureTests(configure).then(testBed => {
fixture = testBed.createComponent(ClassificationListComponent);
component = fixture.componentInstance;
classificationsService = testBed.get(ClassificationsService);
classificationCategoriesService = testBed.get(ClassificationCategoriesService);
spyOn(classificationsService, 'getClassifications').and.returnValue(of(treeNodes));
spyOn(classificationCategoriesService, 'getClassificationTypes')
.and.returnValue(of(classificationTypes));
spyOn(classificationCategoriesService, 'getCategories').and.returnValue(of(new Array<string>('cat1', 'cat2')));
spyOn(classificationCategoriesService, 'getCategoryIcon').and.returnValue(new Pair('assets/icons/categories/external.svg'));
fixture.detectChanges();
done();
});

View File

@ -1,20 +1,25 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { Subscription, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Router, ActivatedRoute } from '@angular/router';
import { Select } from '@ngxs/store';
import { TaskanaType } from 'app/models/taskana-type';
import { Classification } from 'app/models/classification';
import { TreeNodeModel } from 'app/models/tree-node';
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
import { ClassificationCategoriesService } from 'app/shared/services/classifications/classification-categories.service';
import { Pair } from 'app/models/pair';
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 { ClassificationDefinition } from '../../../../models/classification-definition';
import { AlertModel, AlertType } from '../../../../models/alert';
import { AlertService } from '../../../../services/alert/alert.service';
import { ERROR_TYPES } from '../../../../models/errors';
import { ClassificationCategoryImages } from '../../../../models/customisation';
@Component({
selector: 'taskana-classification-list',
templateUrl: './classification-list.component.html',
@ -27,23 +32,21 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
requestInProgress = false;
initialized = false;
inputValue: string;
categories: Array<string> = [];
classifications: Array<Classification> = [];
classificationsTypes: Array<string> = [];
classificationTypeSelected: string;
classifications: Classification[] = [];
@Select(ClassificationSelectors.classificationTypes) classificationTypes$: Observable<string[]>;
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
@Select(ClassificationSelectors.selectCategories) categories$: Observable<string[]>;
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
classificationServiceSubscription: Subscription;
classificationTypeServiceSubscription: Subscription;
classificationTypeSubscription: Subscription;
classificationSelectedSubscription: Subscription;
classificationSavedSubscription: Subscription;
selectedClassificationSubscription: Subscription;
categoriesSubscription: Subscription;
importingExportingSubscription: Subscription;
constructor(
private classificationService: ClassificationsService,
private router: Router,
private route: ActivatedRoute,
private categoryService: ClassificationCategoriesService,
private importExportService: ImportExportService,
private alertService: AlertService
) {
@ -52,35 +55,27 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
ngOnInit() {
this.classificationSavedSubscription = this.classificationService
.classificationSavedTriggered()
.subscribe(value => {
.subscribe(() => {
this.performRequest(true);
});
this.selectedClassificationSubscription = this.categoryService.getSelectedClassificationType().subscribe(value => {
this.classificationTypeSelected = value;
this.classificationTypeSubscription = this.classificationTypeSelected$.subscribe(() => {
this.performRequest();
this.selectClassification();
this.selectedCategory = '';
});
this.categoriesSubscription = this.categoryService.getCategories(this.classificationTypeSelected)
.subscribe((categories: Array<string>) => { this.categories = categories; });
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => {
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe(() => {
this.performRequest(true);
});
}
selectClassificationType(classificationTypeSelected: string) {
this.classifications = [];
this.categoryService.selectClassificationType(classificationTypeSelected);
this.getClassifications();
this.selectClassification();
}
selectClassification(id?: string) {
this.selectedId = id;
if (!id) {
this.router.navigate(['taskana/administration/classifications']);
return;
if (id) {
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
}
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
}
addClassification() {
@ -91,8 +86,14 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
this.selectedCategory = category;
}
getCategoryIcon(category: string): Pair {
return this.categoryService.getCategoryIcon(category);
getCategoryIcon(category: string): Observable<Pair> {
return this.categoryIcons$.pipe(
map(
iconMap => (iconMap[category]
? new Pair(iconMap[category], category)
: new Pair(iconMap.missing, 'Category does not match with the configuration'))
)
);
}
private performRequest(forceRequest = false) {
@ -107,13 +108,9 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
if (this.classificationSelectedSubscription) { this.classificationSelectedSubscription.unsubscribe(); }
this.classificationServiceSubscription = this.classificationService.getClassifications()
.subscribe((classifications: Array<TreeNodeModel>) => {
.subscribe((classifications: TreeNodeModel[]) => {
this.requestInProgress = false;
this.classifications = classifications;
this.classificationTypeServiceSubscription = this.categoryService.getClassificationTypes()
.subscribe((classificationsTypes: Array<string>) => {
this.classificationsTypes = classificationsTypes;
});
});
this.classificationSelectedSubscription = this.classificationService.getSelectedClassification()
.subscribe((classificationSelected: ClassificationDefinition) => {
@ -126,7 +123,7 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
private getClassifications(key?: string) {
this.requestInProgress = true;
this.classificationService.getClassifications()
.subscribe((classifications: Array<TreeNodeModel>) => {
.subscribe((classifications: TreeNodeModel[]) => {
this.classifications = classifications;
this.requestInProgress = false;
});
@ -143,9 +140,9 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); }
if (this.classificationTypeServiceSubscription) { this.classificationTypeServiceSubscription.unsubscribe(); }
if (this.classificationSelectedSubscription) { this.classificationSelectedSubscription.unsubscribe(); }
if (this.classificationSavedSubscription) { this.classificationSavedSubscription.unsubscribe(); }
if (this.importingExportingSubscription) { this.importingExportingSubscription.unsubscribe(); }
if (this.classificationTypeSubscription) { this.classificationTypeSubscription.unsubscribe(); }
}
}

View File

@ -25,18 +25,9 @@
<th>Append</th>
<th>Transfer</th>
<th>Distribute</th>
<th *ngIf="custom1Field.visible">{{custom1Field.field}}</th>
<th *ngIf="custom2Field.visible">{{custom2Field.field}}</th>
<th *ngIf="custom3Field.visible">{{custom3Field.field}}</th>
<th *ngIf="custom4Field.visible">{{custom4Field.field}}</th>
<th *ngIf="custom5Field.visible">{{custom5Field.field}}</th>
<th *ngIf="custom6Field.visible">{{custom6Field.field}}</th>
<th *ngIf="custom7Field.visible">{{custom7Field.field}}</th>
<th *ngIf="custom8Field.visible">{{custom8Field.field}}</th>
<th *ngIf="custom9Field.visible">{{custom9Field.field}}</th>
<th *ngIf="custom10Field.visible">{{custom10Field.field}}</th>
<th *ngIf="custom11Field.visible">{{custom11Field.field}}</th>
<th *ngIf="custom12Field.visible">{{custom12Field.field}}</th>
<ng-container *ngFor="let customField of customFields$ | async">
<th *ngIf="customField.visible">{{customField.field}}</th>
</ng-container>
</tr>
</thead>
<tbody>
@ -46,7 +37,7 @@
<span class="material-icons md-20 red">clear</span>
</button>
</td>
<td *ngIf="accessIdField.lookupField else accessIdInput" class="input-group text-align text-width taskana-type-ahead"
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" class="input-group text-align text-width taskana-type-ahead"
[ngClass]="{
'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
'has-error': !accessItem.value.accessId }">
@ -87,54 +78,12 @@
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label>
</td>
<td *ngIf="custom1Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom1 !== accessItem.value.permCustom1)}">
<input id="checkbox-{{index}}-5" type="checkbox" formControlName="permCustom1">
<label for="checkbox-{{index}}-5"></label>
</td>
<td *ngIf="custom2Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom2 !== accessItem.value.permCustom2)}">
<input id="checkbox-{{index}}-6" type="checkbox" formControlName="permCustom2">
<label for="checkbox-{{index}}-6"></label>
</td>
<td *ngIf="custom3Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom3 !== accessItem.value.permCustom3)}">
<input id="checkbox-{{index}}-7" type="checkbox" formControlName="permCustom3">
<label for="checkbox-{{index}}-7"></label>
</td>
<td *ngIf="custom4Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom4 !== accessItem.value.permCustom4)}">
<input id="checkbox-{{index}}-8" type="checkbox" formControlName="permCustom4">
<label for="checkbox-{{index}}-8"></label>
</td>
<td *ngIf="custom5Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom5 !== accessItem.value.permCustom5)}">
<input id="checkbox-{{index}}-9" type="checkbox" formControlName="permCustom5">
<label for="checkbox-{{index}}-9"></label>
</td>
<td *ngIf="custom6Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom6 !== accessItem.value.permCustom6)}">
<input id="checkbox-{{index}}-10" type="checkbox" formControlName="permCustom6">
<label for="checkbox-{{index}}-10"></label>
</td>
<td *ngIf="custom7Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom7 !== accessItem.value.permCustom7)}">
<input id="checkbox-{{index}}-11" type="checkbox" formControlName="permCustom7">
<label for="checkbox-{{index}}-11"></label>
</td>
<td *ngIf="custom8Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom8 !== accessItem.value.permCustom8)}">
<input id="checkbox-{{index}}-12" type="checkbox" formControlName="permCustom8">
<label for="checkbox-{{index}}-12"></label>
</td>
<td *ngIf="custom9Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom9 !== accessItem.value.permCustom9)}">
<input id="checkbox-{{index}}-13" type="checkbox" formControlName="permCustom9">
<label for="checkbox-{{index}}-13"></label>
</td>
<td *ngIf="custom10Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom10 !== accessItem.value.permCustom10)}">
<input id="checkbox-{{index}}-14" type="checkbox" formControlName="permCustom10">
<label for="checkbox-{{index}}-14"></label>
</td>
<td *ngIf="custom11Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom11 !== accessItem.value.permCustom11)}">
<input id="checkbox-{{index}}-15" type="checkbox" formControlName="permCustom11">
<label for="checkbox-{{index}}-15"></label>
</td>
<td *ngIf="custom12Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom12 !== accessItem.value.permCustom12)}">
<input id="checkbox-{{index}}-16" type="checkbox" formControlName="permCustom12">
<label for="checkbox-{{index}}-16"></label>
</td>
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible" [ngClass]="{ 'has-changes': accessItemsClone[index][getAccessItemCustomProperty(customIndex + 1)] !== accessItem.value[getAccessItemCustomProperty(customIndex+1)] }">
<input id="checkbox-{{index}}-{{customIndex + 5}}" type="checkbox" formControlName="permCustom{{customIndex+1}}">
<label for="checkbox-{{index}}-{{customIndex + 5}}"></label>
</td>
</ng-container>
</tr>
</tbody>
</table>

View File

@ -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<Store> = 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<WorkbasketAccessItems>(
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<string>(
'accessID1', 'accessID2'
)));
formsValidatorService = TestBed.get(FormsValidatorService);
formsValidatorService = testBed.get(FormsValidatorService);
component.ngOnChanges({
active: new SimpleChange(undefined, 'accessItems', true)
});

View File

@ -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<AccessItemsCustomisation>;
customFields$: Observable<CustomField[]>;
accessItemsResource: WorkbasketAccessItemsResource;
accessItemsClone: Array<WorkbasketAccessItems>;
accessItemsResetClone: Array<WorkbasketAccessItems>;
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<WorkbasketAccessItems>) {
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}`;
}
}

View File

@ -44,7 +44,7 @@
</div>
<div class="input-group form-group col-xs-12 required">
<label for="wb-owner" class="control-label ">Owner</label>
<taskana-type-ahead *ngIf="ownerField.lookupField else ownerInput" required #owner="ngModel" name="workbasket.owner"
<taskana-type-ahead *ngIf="(workbasketsCustomisation$ | async)?.information?.lookupField else ownerInput" required #owner="ngModel" name="workbasket.owner"
[(ngModel)]="workbasket.owner" placeHolderMessage="* Owner is required" [validationValue]="this.toogleValidationMap.get('workbasket.owner')"
[displayError]="!isFieldValid('workbasket.owner')" width="100%"></taskana-type-ahead>
<ng-template #ownerInput>
@ -107,26 +107,13 @@
<input type="text" class="form-control" id="wb-org-level-4" placeholder="OrgLevel 4" [(ngModel)]="workbasket.orgLevel4"
name="workbasket.orgLevel4">
</div>
<div *ngIf="custom1Field.visible" class="form-group">
<label for="wb-custom-1" class="control-label">{{custom1Field.field}}</label>
<input type="text" class="form-control" id="wb-custom-1" [placeholder]="custom1Field.field"
[(ngModel)]="workbasket.custom1" name="workbasket.custom1">
</div>
<div *ngIf="custom2Field.visible" class="form-group">
<label for="wb-custom-2" class="control-label">{{custom2Field.field}}</label>
<input type="text" class="form-control" id="wb-custom-2" [placeholder]="custom2Field.field"
[(ngModel)]="workbasket.custom2" name="workbasket.custom2">
</div>
<div *ngIf="custom3Field.visible" class="form-group">
<label for="wb-custom-3" class="control-label">{{custom3Field.field}}</label>
<input type="text" class="form-control" id="wb-custom-3" [placeholder]="custom3Field.field"
[(ngModel)]="workbasket.custom3" name="workbasket.custom3">
</div>
<div *ngIf="custom4Field.visible" class="form-group">
<label for="wb-custom-4" class="control-label">{{custom4Field.field}}</label>
<input type="text" class="form-control" id="wb-custom-4" [placeholder]="custom4Field.field"
[(ngModel)]="workbasket.custom4" name="workbasket.custom4">
</div>
<ng-container *ngFor="let customField of customFields$ | async; let index = index">
<div *ngIf="customField.visible" class="form-group">
<label for='wb-custom-{{index+1}}' class="control-label">{{customField.field}}</label>
<input type="text" class="form-control" id="wb-custom-{{index+1}}" [placeholder]="customField.field"
[(ngModel)]="workbasket[getWorkbasketCustomProperty(index + 1)]" name="workbasket[getWorkbasketCustomValue(index + 1)]">
</div>
</ng-container>
</div>
</form>
</div>

View File

@ -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<Store> = 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',

View File

@ -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<WorkbasketsCustomisation>;
customFields$: Observable<CustomField[]>;
toogleValidationMap = new Map<string, boolean>();
@ -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}`;
}
}

View File

@ -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 => {

View File

@ -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]
})

View File

@ -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]
});

View File

@ -27,3 +27,5 @@ export class ClassificationDefinition {
public _links?: LinksClassification) {
}
}
export const customFieldCount: number = 8;

View File

@ -1,8 +0,0 @@
export class CustomField {
constructor(
public visible: boolean,
public field: string
) {
}
}

View File

@ -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<CustomFields, CustomField[]> {
return map<CustomFields, CustomField[]>(customisation => [...Array(amount).keys()]
.map(x => x + 1)
.map(x => customisation[`custom${x}`] || {
field: `Custom ${x}`,
visible: true
}));
}

View File

@ -26,3 +26,5 @@ export class WorkbasketAccessItems {
public _links: Links = new Links()
) { }
}
export const customFieldCount: number = 12;

View File

@ -46,3 +46,5 @@ export class Workbasket {
) {
}
}
export const customFieldCount: number = 4;

View File

@ -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();
}));
});

View File

@ -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;
}
}

View File

@ -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"
}
}
}
}

View File

@ -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
]

View File

@ -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<any>('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<any>('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';

View File

@ -1,15 +1,15 @@
<div class="dropdown clearfix btn-group">
<button type="button" class="btn btn-default"> {{classificationTypeSelected}}</button>
<button type="button" class="btn btn-default"> {{classificationTypeSelected$ | async}}</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu dropdown-menu-right sortby-dropdown popup" aria-labelledby="sortingDropdown">
<li *ngFor="let classificationType of classificationTypes">
<li *ngFor="let classificationType of classificationTypes$ | async">
<a (click)="select(classificationType)">
<label>
<span data-toggle="tooltip" class="material-icons md-20 blue">{{classificationTypeSelected === classificationType?
<span data-toggle="tooltip" class="material-icons md-20 blue">{{(classificationTypeSelected$ | async) === classificationType?
'check_box': 'check_box_outline_blank'}}</span>
<span>{{classificationType}}</span>
</label>

View File

@ -1,5 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NgxsModule } from '@ngxs/store';
import { ClassificationTypesSelectorComponent } from './classification-types-selector.component';
describe('ClassificationTypesSelectorComponent', () => {
@ -8,9 +9,10 @@ describe('ClassificationTypesSelectorComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ClassificationTypesSelectorComponent]
})
.compileComponents();
imports: [NgxsModule.forRoot()],
declarations: [ClassificationTypesSelectorComponent],
providers: []
}).compileComponents();
}));
beforeEach(() => {

View File

@ -1,28 +1,22 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Store, Select } from '@ngxs/store';
import { ClassificationSelectors } from 'app/store/classification-store/classification.selectors';
import { SetSelectedClassificationType } from 'app/store/classification-store/classification.actions';
@Component({
selector: 'taskana-classification-types-selector',
templateUrl: './classification-types-selector.component.html',
styleUrls: ['./classification-types-selector.component.scss']
})
export class ClassificationTypesSelectorComponent implements OnInit {
@Input()
classificationTypes: Array<string> = [];
export class ClassificationTypesSelectorComponent {
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
@Select(ClassificationSelectors.classificationTypes) classificationTypes$: Observable<string[]>;
@Input()
classificationTypeSelected: string;
constructor(private store: Store) {}
@Output()
classificationTypeSelectedChange = new EventEmitter<string>();
@Output()
classificationTypeChanged = new EventEmitter<string>();
ngOnInit() {
}
select(value: string) {
this.classificationTypeSelected = value;
this.classificationTypeChanged.emit(value);
select(value: string): void {
this.store.dispatch(new SetSelectedClassificationType(value));
}
}

View File

@ -0,0 +1,42 @@
import { TestBed, async } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { Customisation, CustomisationContent } from 'app/models/customisation';
import { ClassificationCategoriesService, missingIcon } from './classification-categories.service';
describe('ClassificationCategoriesService', () => {
let categoryService: ClassificationCategoriesService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ClassificationCategoriesService]
});
categoryService = TestBed.get(ClassificationCategoriesService);
httpMock = TestBed.get(HttpTestingController);
});
it('should insert missing icon into customisation', async(() => {
const expectedCustomisationContent: CustomisationContent = { classifications:
{ categories:
{ missing: missingIcon } } };
const expectedCustomisation: Customisation = { EN: expectedCustomisationContent, DE: expectedCustomisationContent };
const initialCustomisations: Customisation[] = [
{ EN: { classifications: { categories: {} } }, DE: { classifications: { categories: {} } } },
{ EN: { classifications: {} }, DE: { classifications: {} } },
{ EN: {}, DE: {} }
];
initialCustomisations.forEach(initialCustomisation => {
categoryService.getCustomisation()
.subscribe(customisation => { expect(customisation).toEqual(expectedCustomisation); });
httpMock.expectOne('environments/data-sources/taskana-customization.json').flush(initialCustomisation);
httpMock.verify();
});
}));
});

View File

@ -2,87 +2,37 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';
import { Observable, ReplaySubject, BehaviorSubject } from 'rxjs';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { Pair } from 'app/models/pair';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import set from 'set-value';
import { Customisation } from '../../../models/customisation';
const customisationUrl = 'environments/data-sources/taskana-customization.json';
export const missingIcon = 'assets/icons/categories/missing-icon.svg';
export interface CategoriesResponse { [key: string]: string[] }
@Injectable()
export class ClassificationCategoriesService {
private mainUrl = environment.taskanaRestUrl;
private urlCategoriesByType = `${this.mainUrl}/v1/classifications-by-type`;
// categories
private urlCategories = `${this.mainUrl}/v1/classification-categories`;
private param = '/?type=';
private dataObsCategories$ = new ReplaySubject<Array<string>>(1);
private categoriesObject = {};
private missingIcon = 'assets/icons/categories/missing-icon.svg';
private type = 'UNKNOW';
constructor(private httpClient: HttpClient) {}
// type
private classificationTypeSelectedValue = 'TASK';
private urlType = `${this.mainUrl}/v1/classification-types`;
private classificationTypeSelected = new BehaviorSubject<string>(this.classificationTypeSelectedValue);
private dataObsType$ = new ReplaySubject<Array<string>>(1);
constructor(
private httpClient: HttpClient,
private customFieldsService: CustomFieldsService
) { }
getCategories(type?: string): Observable<Array<string>> {
if (!this.dataObsCategories$.observers.length || type !== this.type) {
this.httpClient.get<Array<string>>(type ? this.urlCategories + this.param + type : this.urlCategories).subscribe(
data => { this.dataObsCategories$.next(data); this.categoriesObject = this.getCustomCategoriesObject(data); this.type = type; },
error => { this.dataObsCategories$.error(error); this.dataObsCategories$ = new ReplaySubject(1); }
);
}
return this.dataObsCategories$;
// TODO: convert to Map (maybe via ES6)
getClassificationCategoriesByType(): Observable<CategoriesResponse> {
return this.httpClient.get<CategoriesResponse>(this.urlCategoriesByType);
}
getCategoryIcon(category: string): Pair {
let categoryIcon = this.categoriesObject[category];
let text = category;
if (!categoryIcon) {
categoryIcon = this.missingIcon;
text = 'Category does not match with the configuration';
}
return new Pair(categoryIcon, text);
}
private getCustomCategoriesObject(categories: Array<string>): Object {
return this.customFieldsService.getCustomObject(
this.getDefaultCategoryMap(categories), 'classifications.categories'
getCustomisation(): Observable<Customisation> {
return this.httpClient.get<Customisation>(customisationUrl).pipe(
map(customisation => {
Object.keys(customisation).forEach(lang => {
set(customisation[lang], 'classifications.categories.missing', missingIcon);
});
return customisation;
})
);
}
private getDefaultCategoryMap(categoryList: Array<string>): Object {
const defaultCategoryMap = {};
categoryList.forEach(element => {
defaultCategoryMap[element] = `assets/icons/categories/${element.toLowerCase()}.svg`;
});
return defaultCategoryMap;
}
getClassificationTypes(forceRefresh = false): Observable<Array<string>> {
if (!this.dataObsType$.observers.length || forceRefresh) {
this.httpClient.get<Array<string>>(this.urlType).subscribe(
data => this.dataObsType$.next(data),
error => {
this.dataObsType$.error(error);
this.dataObsType$ = new ReplaySubject(1);
}
);
}
return this.dataObsType$;
}
selectClassificationType(id: string) {
this.getCategories(id);
this.classificationTypeSelectedValue = id;
this.classificationTypeSelected.next(id);
}
getSelectedClassificationType(): Observable<string> {
return this.classificationTypeSelected.asObservable();
}
}

View File

@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment';
import { combineLatest, Observable, Subject } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import { Select, Store } from '@ngxs/store';
import { Classification } from 'app/models/classification';
import { ClassificationDefinition } from 'app/models/classification-definition';
@ -12,7 +13,8 @@ import { DomainService } from 'app/services/domain/domain.service';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { Direction } from 'app/models/sorting';
import { QueryParametersModel } from 'app/models/query-parameters';
import { ClassificationCategoriesService } from './classification-categories.service';
import { ClassificationSelectors } from 'app/store/classification-store/classification.selectors';
import { SetSelectedClassificationType } from 'app/store/classification-store/classification.actions';
@Injectable()
export class ClassificationsService {
@ -21,13 +23,14 @@ export class ClassificationsService {
private classificationSaved = new Subject<number>();
private classificationResourcePromise: Promise<ClassificationResource>;
private lastDomain: string;
// TODO: this should not be here in the service
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
constructor(
private httpClient: HttpClient,
private classificationCategoriesService: ClassificationCategoriesService,
private domainService: DomainService
) {
}
private domainService: DomainService,
private store: Store
) {}
private static classificationParameters(domain: string): QueryParametersModel {
const parameters = new QueryParametersModel();
@ -68,7 +71,7 @@ export class ClassificationsService {
return this.httpClient.get<ClassificationDefinition>(`${this.url}${id}`)
.pipe(tap((classification: ClassificationDefinition) => {
if (classification) {
this.classificationCategoriesService.selectClassificationType(classification.type);
this.store.dispatch(new SetSelectedClassificationType(classification.type));
}
})).toPromise();
}
@ -107,15 +110,14 @@ export class ClassificationsService {
// #endregion
private getClassificationObservable(classificationRef: Observable<any>): Observable<Array<Classification>> {
const classificationTypes: Observable<string> = this.classificationCategoriesService.getSelectedClassificationType();
private getClassificationObservable(classificationRef: Observable<ClassificationResource>): Observable<Array<Classification>> {
return combineLatest(
[classificationRef,
classificationTypes]
this.classificationTypeSelected$]
).pipe(
map(
(classification: any[]) => (
classification[0].classifications ? this.buildHierarchy(classification[0].classifications, classification[1]) : []
([resource, type]: [ClassificationResource, string]) => (
resource.classifications ? this.buildHierarchy(resource.classifications, type) : []
)
)
);

View File

@ -2,8 +2,8 @@
(moveNode)="onMoveNode($event)" (treeDrop)="onDrop($event)">
<ng-template #treeNodeTemplate let-node let-index="index">
<span class="text-top">
<svg-icon *ngIf="node.data.category" class="blue fa-fw" [src]="getCategoryIcon(node.data.category).name" data-toggle="tooltip"
[title]="getCategoryIcon(node.data.category).text"></svg-icon>
<svg-icon *ngIf="node.data.category" class="blue fa-fw" [src]="(getCategoryIcon(node.data.category) | async)?.name" data-toggle="tooltip"
[title]="(getCategoryIcon(node.data.category) | async)?.text"></svg-icon>
</span>
<span>
<strong>{{ node.data.key }}</strong>

View File

@ -3,14 +3,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { TreeService } from 'app/services/tree/tree.service';
import { configureTests } from 'app/app.test.configuration';
import { Pair } from 'app/models/pair';
import { NgxsModule } from '@ngxs/store';
import { TaskanaTreeComponent } from './tree.component';
import { ClassificationDefinition } from '../../models/classification-definition';
import { LinksClassification } from '../../models/links-classfication';
import { ClassificationCategoriesService } from '../services/classifications/classification-categories.service';
import { ClassificationsService } from '../services/classifications/classifications.service';
@Component({
@ -21,34 +19,27 @@ class TreeVendorComponent {
@Input() options;
@Input() state;
@Input() nodes;
treeModel = {
getActiveNode() {
}
};
}
describe('TaskanaTreeComponent', () => {
let component: TaskanaTreeComponent;
let fixture: ComponentFixture<TaskanaTreeComponent>;
let classificationCategoriesService;
let classificationsService;
let moveNodeEvent;
let dropEvent;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule],
declarations: [TreeVendorComponent],
providers: [TreeService, ClassificationCategoriesService, ClassificationsService]
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, NgxsModule.forRoot()],
declarations: [TreeVendorComponent],
providers: [TreeService, ClassificationsService]
});
};
});
};
beforeEach(done => {
configureTests(configure).then(testBed => {
fixture = testBed.createComponent(TaskanaTreeComponent);
classificationCategoriesService = testBed.get(ClassificationCategoriesService);
spyOn(classificationCategoriesService, 'getCategoryIcon').and.returnValue(new Pair('assets/icons/categories/external.svg'));
classificationsService = TestBed.get(ClassificationsService);
classificationsService = testBed.get(ClassificationsService);
spyOn(classificationsService, 'putClassification').and.callFake((url, classification) => classification);
moveNodeEvent = {
eventName: 'moveNode',

View File

@ -11,13 +11,16 @@ import { AfterViewChecked,
import { TreeNodeModel } from 'app/models/tree-node';
import { ITreeOptions, KEYS, TreeComponent, TreeNode } from 'angular-tree-component';
import { ClassificationCategoriesService } from 'app/shared/services/classifications/classification-categories.service';
import { Pair } from 'app/models/pair';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { Select } from '@ngxs/store';
import { EngineConfigurationSelectors } from 'app/store/engine-configuration-store/engine-configuration.selectors';
import { TreeService } from '../../services/tree/tree.service';
import { Classification } from '../../models/classification';
import { ClassificationDefinition } from '../../models/classification-definition';
import { ClassificationsService } from '../services/classifications/classifications.service';
import { ClassificationCategoryImages } from '../../models/customisation';
@Component({
selector: 'taskana-tree',
@ -25,9 +28,6 @@ import { ClassificationsService } from '../services/classifications/classificati
styleUrls: ['./tree.component.scss'],
})
export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy {
@ViewChild('tree', { static: true })
private tree: TreeComponent;
@Input() treeNodes: Array<TreeNodeModel>;
@Output() treeNodesChange = new EventEmitter<Array<TreeNodeModel>>();
@Input() selectNodeId: string;
@ -36,11 +36,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
@Input() filterIcon = '';
@Output() refreshClassification = new EventEmitter<string>();
@Output() switchTaskanaSpinnerEmit = new EventEmitter<boolean>();
private filterTextOld: string;
private filterIconOld = '';
private removedNodeIdSubscription: Subscription;
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
options: ITreeOptions = {
displayField: 'name',
idField: 'classificationId',
@ -59,6 +55,20 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
allowDrop: true
};
@ViewChild('tree', { static: true })
private tree: TreeComponent;
private filterTextOld: string;
private filterIconOld = '';
private removedNodeIdSubscription: Subscription;
constructor(
private treeService: TreeService,
private elementRef: ElementRef,
private classificationsService: ClassificationsService,
) {
}
@HostListener('document:click', ['$event'])
onDocumentClick(event) {
if (this.checkValidElements(event) && this.tree.treeModel.getActiveNode()) {
@ -66,14 +76,6 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
}
}
constructor(
private treeService: TreeService,
private categoryService: ClassificationCategoriesService,
private elementRef: ElementRef,
private classificationsService: ClassificationsService
) {
}
ngOnInit() {
this.removedNodeIdSubscription = this.treeService.getRemovedNodeId().subscribe(value => {
const removedNode = this.getNode(value);
@ -91,7 +93,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
}
if (this.filterTextOld !== this.filterText
|| this.filterIconOld !== this.filterIcon) {
|| this.filterIconOld !== this.filterIcon) {
this.filterIconOld = this.filterIcon;
this.filterTextOld = this.filterText;
this.filterNodes(this.filterText ? this.filterText : '', this.filterIcon);
@ -127,8 +129,22 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
}
}
getCategoryIcon(category: string): Pair {
return this.categoryService.getCategoryIcon(category);
getCategoryIcon(category: string): Observable<Pair> {
return this.categoryIcons$.pipe(map(
iconMap => (iconMap[category]
? new Pair(iconMap[category], category)
: new Pair(iconMap.missing, 'Category does not match with the configuration'))
));
}
switchTaskanaSpinner(active: boolean) {
this.switchTaskanaSpinnerEmit.emit(active);
}
ngOnDestroy(): void {
if (this.removedNodeIdSubscription) {
this.removedNodeIdSubscription.unsubscribe();
}
}
private selectNode(nodeId: string) {
@ -167,12 +183,12 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
private checkNameAndKey(node: any, text: string): boolean {
return (node.data.name.toUpperCase().includes(text.toUpperCase())
|| node.data.key.toUpperCase().includes(text.toUpperCase()));
|| node.data.key.toUpperCase().includes(text.toUpperCase()));
}
private checkIcon(node: any, iconText: string): boolean {
return (node.data.category.toUpperCase() === iconText.toUpperCase()
|| iconText === '');
|| iconText === '');
}
private manageTreeState() {
@ -183,9 +199,9 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
private checkValidElements(event): boolean {
return (this.elementRef.nativeElement.contains(event.target)
|| this.elementRef.nativeElement === event.target)
&& (event.target.localName === 'tree-viewport'
|| event.target.localName === 'taskana-tree');
|| this.elementRef.nativeElement === event.target)
&& (event.target.localName === 'tree-viewport'
|| event.target.localName === 'taskana-tree');
}
private getClassification(classificationId: string): Promise<ClassificationDefinition> {
@ -204,14 +220,4 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
this.getNode(node.parentId).collapse();
}
}
switchTaskanaSpinner(active: boolean) {
this.switchTaskanaSpinnerEmit.emit(active);
}
ngOnDestroy(): void {
if (this.removedNodeIdSubscription) {
this.removedNodeIdSubscription.unsubscribe();
}
}
}

View File

@ -0,0 +1,5 @@
export class SetSelectedClassificationType {
static readonly type = '[Classification-Types-Selector] Set selected classification type';
constructor(public selectedType: string) {
}
}

View File

@ -0,0 +1,24 @@
import { Selector } from '@ngxs/store';
import { ClassificationStateModel, ClassificationState } from './classification.state';
export class ClassificationSelectors {
@Selector([ClassificationState])
static classificationTypes(state: ClassificationStateModel): string[] {
return Object.keys(state.classificationTypes);
}
@Selector([ClassificationState])
static selectedClassificationType(state: ClassificationStateModel): string {
return state.selectedClassificationType;
}
@Selector([ClassificationState])
static selectCategories(state: ClassificationStateModel): string[] {
return state.classificationTypes[state.selectedClassificationType];
}
@Selector([ClassificationState])
static selectClassificationTypesObject(state: ClassificationStateModel): Object {
return state.classificationTypes;
}
}

View File

@ -0,0 +1,45 @@
import { Action, State, StateContext } from '@ngxs/store';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { CategoriesResponse, ClassificationCategoriesService } from '../../shared/services/classifications/classification-categories.service';
import { SetSelectedClassificationType } from './classification.actions';
class InitializeStore {
static readonly type = '[ClassificationState] Initializing state';
}
@State<ClassificationStateModel>({ name: 'classification' })
export class ClassificationState {
constructor(private categoryService: ClassificationCategoriesService) {
}
@Action(SetSelectedClassificationType)
setSelectedClassificationType(ctx: StateContext<ClassificationStateModel>, action: SetSelectedClassificationType): void {
const state: ClassificationStateModel = ctx.getState();
if (state.classificationTypes[action.selectedType]) {
ctx.patchState({ selectedClassificationType: action.selectedType });
}
}
@Action(InitializeStore)
initializeStore(ctx: StateContext<ClassificationStateModel>): Observable<any> {
return this.categoryService.getClassificationCategoriesByType().pipe(
tap(classificationTypes => {
ctx.setState({
...ctx.getState(),
classificationTypes,
selectedClassificationType: Object.keys(classificationTypes)[0],
});
}),
);
}
ngxsOnInit(ctx: StateContext<ClassificationStateModel>): void {
ctx.dispatch(new InitializeStore());
}
}
export interface ClassificationStateModel {
selectedClassificationType: string;
classificationTypes: CategoriesResponse,
}

View File

@ -0,0 +1,32 @@
import { WorkbasketsCustomisation, ClassificationsCustomisation, AccessItemsCustomisation, TasksCustomisation, ClassificationCategoryImages } from 'app/models/customisation';
import { Selector } from '@ngxs/store';
import { EngineConfigurationStateModel, EngineConfigurationState } from './engine-configuration.state';
export class EngineConfigurationSelectors {
@Selector([EngineConfigurationState])
static workbasketsCustomisation(state: EngineConfigurationStateModel): WorkbasketsCustomisation {
return state.customisation[state.language].workbaskets;
}
@Selector([EngineConfigurationState])
static classificationsCustomisation(state: EngineConfigurationStateModel): ClassificationsCustomisation {
return state.customisation[state.language].classifications;
}
@Selector([EngineConfigurationState])
static accessItemsCustomisation(state: EngineConfigurationStateModel): AccessItemsCustomisation {
return state.customisation[state.language].workbaskets['access-items'];
}
@Selector([EngineConfigurationState])
static tasksCustomisation(state: EngineConfigurationStateModel): TasksCustomisation {
return state.customisation[state.language].tasks;
}
@Selector([EngineConfigurationState])
static selectCategoryIcons(state: EngineConfigurationStateModel): ClassificationCategoryImages {
return {
...state.customisation[state.language].classifications.categories,
};
}
}

View File

@ -0,0 +1,36 @@
import { Customisation } from 'app/models/customisation';
import { State, NgxsOnInit, StateContext, Action } from '@ngxs/store';
import { ClassificationCategoriesService } from 'app/shared/services/classifications/classification-categories.service';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
class InitializeStore {
static readonly type = '[EngineConfigurationState] Initializing state';
}
@State<EngineConfigurationStateModel>({ name: 'engineConfiguration' })
export class EngineConfigurationState implements NgxsOnInit {
constructor(private categoryService: ClassificationCategoriesService) {
}
@Action(InitializeStore)
initializeStore(ctx: StateContext<EngineConfigurationStateModel>): Observable<any> {
return this.categoryService.getCustomisation().pipe(
tap(customisation => ctx.setState({
...ctx.getState(),
customisation,
language: 'EN'
})),
);
}
ngxsOnInit(ctx: StateContext<EngineConfigurationStateModel>): void {
ctx.dispatch(new InitializeStore());
}
}
export interface EngineConfigurationStateModel {
customisation: Customisation,
language: string
}

View File

@ -0,0 +1,4 @@
import { EngineConfigurationState } from './engine-configuration-store/engine-configuration.state';
import { ClassificationState } from './classification-store/classification.state';
export const STATES = [EngineConfigurationState, ClassificationState];

View File

@ -65,7 +65,7 @@
<taskana-date-picker placeholder="Due date" [value]="task.due" [name]="'task.due'" [id]="'task-due'" (dateOutput)="updateDate($event)"></taskana-date-picker>
</div>
<div class="form-group col-xs-2">
<label for="task-priority" disabled class="control-label">Priority</label>
<label for="task-priority" class="control-label">Priority</label>
<taskana-number-picker [(ngModel)]="task.priority" title="priority" id="task-priority" name="task.priority"></taskana-number-picker>
</div>
</div>
@ -83,7 +83,7 @@
</div>
<div class="input-group form-group col-xs-12">
<label for="wb-owner" class="control-label ">Owner</label>
<taskana-type-ahead *ngIf="ownerField?.lookupField else ownerInput" #owner="ngModel" name="task.owner"
<taskana-type-ahead *ngIf="(tasksCustomisation$ |async)?.information.owner.lookupField else ownerInput" #owner="ngModel" name="task.owner"
[(ngModel)]="task.owner" width="100%" [isRequired]="false"></taskana-type-ahead>
<ng-template #ownerInput>
<input type="text" #task.owner="ngModel" class="form-control" id="ts-owner" placeholder="Owner" [(ngModel)]="task.owner"

View File

@ -4,7 +4,6 @@ import { FormsModule } from '@angular/forms';
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { ClassificationCategoriesService } from 'app/shared/services/classifications/classification-categories.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { DomainService } from 'app/services/domain/domain.service';
import { RouterTestingModule } from '@angular/router/testing';
import { Routes } from '@angular/router';
@ -39,7 +38,7 @@ xdescribe('GeneralComponent', () => {
TestBed.configureTestingModule({
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes)],
declarations: [TaskdetailsGeneralFieldsComponent, DummyDetailComponent],
providers: [HttpClient, ClassificationCategoriesService, CustomFieldsService,
providers: [HttpClient, ClassificationCategoriesService,
DomainService, RequestInProgressService, SelectedRouteService, ClassificationsService]
});
};

View File

@ -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<TasksCustomisation>;
constructor(
private classificationService: ClassificationsService,
private customFieldsService: CustomFieldsService,
private formsValidatorService: FormsValidatorService,
private domainService: DomainService
) {

View File

@ -66,4 +66,4 @@
}
}
}
}
}

View File

@ -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(