TSK-743: Removed HATEOAS from classification- and workbasketdefinition exports
This commit is contained in:
parent
55c7453034
commit
835148c608
|
@ -355,7 +355,7 @@ include::../../../{snippets}/DeleteClassificationDocTest/http-request.adoc[]
|
|||
|
||||
include::../../../{snippets}/DeleteClassificationDocTest/http-response.adoc[]
|
||||
|
||||
== Classificationdefinition-Resource
|
||||
== Classification-definition-Resource
|
||||
|
||||
[[classification-definitions]]
|
||||
=== Get all classification definitions
|
||||
|
@ -382,15 +382,15 @@ A `POST` request is used to import new classification-definitions.
|
|||
|
||||
This minimal example shows only the required fields to import a single new classification-definition. The <<classification, classification structure>> shows all possible fields for importing (and therefore creating) new classification-definitions.
|
||||
|
||||
include::../../../{snippets}/ImportClassificationdefinitions/http-request.adoc[]
|
||||
include::../../../{snippets}/ImportClassificationDefinitions/http-request.adoc[]
|
||||
|
||||
==== Request Structure
|
||||
|
||||
include::../../../{snippets}/ImportClassificationdefinitions/request-fields.adoc[]
|
||||
include::../../../{snippets}/ImportClassificationDefinitions/request-fields.adoc[]
|
||||
|
||||
==== Example Response
|
||||
|
||||
include::../../../{snippets}/ImportClassificationdefinitions/http-response.adoc[]
|
||||
include::../../../{snippets}/ImportClassificationDefinitions/http-response.adoc[]
|
||||
|
||||
== Workbaskets-Resource
|
||||
|
||||
|
@ -540,7 +540,7 @@ include::../../../{snippets}/CreateWorkbasketDocTest/http-response.adoc[]
|
|||
The response-body is essentially the same as for getting a single classification. +
|
||||
Therefore for the response fields you can refer to the structure of the <<classification, single classification>>.
|
||||
|
||||
== Workbasketdefinition-Resource
|
||||
== Workbasket-definition-resource
|
||||
|
||||
=== Get all workbasket definitions
|
||||
|
||||
|
@ -548,16 +548,16 @@ A `GET` request is used to get all workbasket definitions.
|
|||
|
||||
==== Example Request
|
||||
|
||||
include::../../../{snippets}/ExportWorkbasktdefinitionsDocTest/http-request.adoc[]
|
||||
include::../../../{snippets}/ExportWorkbasketdefinitionsDocTest/http-request.adoc[]
|
||||
|
||||
==== Example Response
|
||||
|
||||
include::../../../{snippets}/ExportWorkbasktdefinitionsDocTest/http-response.adoc[]
|
||||
include::../../../{snippets}/ExportWorkbasketdefinitionsDocTest/http-response.adoc[]
|
||||
|
||||
[[workbasket-definitions]]
|
||||
==== Response Structure
|
||||
|
||||
include::../../../{snippets}/ExportWorkbasktdefinitionsDocTest/response-fields.adoc[]
|
||||
include::../../../{snippets}/ExportWorkbasketdefinitionsDocTest/response-fields.adoc[]
|
||||
|
||||
=== Import new workbasket-definitions
|
||||
|
||||
|
@ -567,15 +567,15 @@ A `POST` request is used to import new workbasket-definitions.
|
|||
|
||||
This minimal example shows only the required fields to import a single new workbasket-definition. The <<workbasket, workbasket structure>> shows all possible fields for importing (and therefore creating) new workbasket-definitions.
|
||||
|
||||
include::../../../{snippets}/ImportWorkbasketdefinitions/http-request.adoc[]
|
||||
include::../../../{snippets}/ImportWorkbasketDefinitions/http-request.adoc[]
|
||||
|
||||
==== Request Structure
|
||||
|
||||
include::../../../{snippets}/ImportWorkbasketdefinitions/request-fields.adoc[]
|
||||
include::../../../{snippets}/ImportWorkbasketDefinitions/request-fields.adoc[]
|
||||
|
||||
==== Response Structure
|
||||
|
||||
include::../../../{snippets}/ImportWorkbasketdefinitions/http-response.adoc[]
|
||||
include::../../../{snippets}/ImportWorkbasketDefinitions/http-response.adoc[]
|
||||
|
||||
== WorkbasketAccessItems-Resource
|
||||
|
||||
|
|
|
@ -8,7 +8,10 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.pr
|
|||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -33,59 +36,61 @@ import pro.taskana.rest.RestConfiguration;
|
|||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = RestConfiguration.class, webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class ClassificationDefinitionControllerRestDocumentation {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
|
||||
@Rule
|
||||
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
|
||||
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
private FieldDescriptor[] classificationdefinitionsFieldDescriptors;
|
||||
|
||||
|
||||
private FieldDescriptor[] classificationDefinitionsFieldDescriptors;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
document("{methodName}",
|
||||
preprocessRequest(prettyPrint()),
|
||||
preprocessResponse(prettyPrint()));
|
||||
|
||||
preprocessRequest(prettyPrint()),
|
||||
preprocessResponse(prettyPrint()));
|
||||
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(springSecurity())
|
||||
.apply(documentationConfiguration(this.restDocumentation)
|
||||
.operationPreprocessors()
|
||||
.withResponseDefaults(prettyPrint())
|
||||
.withRequestDefaults(prettyPrint()))
|
||||
.build();
|
||||
|
||||
classificationdefinitionsFieldDescriptors = new FieldDescriptor[] {
|
||||
subsectionWithPath("[]").description("An array of <<classification, classifications>>")
|
||||
.apply(springSecurity())
|
||||
.apply(documentationConfiguration(this.restDocumentation)
|
||||
.operationPreprocessors()
|
||||
.withResponseDefaults(prettyPrint())
|
||||
.withRequestDefaults(prettyPrint()))
|
||||
.build();
|
||||
|
||||
classificationDefinitionsFieldDescriptors = new FieldDescriptor[] {
|
||||
subsectionWithPath("[]").description("An array of <<ClassificationResource, classifications>>")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void exportAllClassificationdefinitions() throws Exception {
|
||||
public void exportAllClassificationDefinitions() throws Exception {
|
||||
this.mockMvc.perform(RestDocumentationRequestBuilders
|
||||
.get("http://127.0.0.1:" + port + "/v1/classificationdefinitions")
|
||||
.accept("application/json")
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcRestDocumentation.document("ExportClassificationdefinitionsDocTest",
|
||||
responseFields(classificationdefinitionsFieldDescriptors)));
|
||||
.get("http://127.0.0.1:" + port + "/v1/classification-definitions")
|
||||
.accept("application/json")
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcRestDocumentation.document("ExportClassificationDefinitionsDocTest",
|
||||
responseFields(classificationDefinitionsFieldDescriptors)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void importClassificationdefinitions() throws Exception {
|
||||
public void importClassificationDefinitions() throws Exception {
|
||||
String definitionString = "[{\"key\":\"Key0815\", \"domain\":\"DOMAIN_B\"}]";
|
||||
this.mockMvc.perform(RestDocumentationRequestBuilders
|
||||
.post("http://127.0.0.1:" + port + "/v1/classificationdefinitions/")
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")
|
||||
.contentType("application/json")
|
||||
.content(definitionString))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcRestDocumentation.document("ImportClassificationdefinitions",
|
||||
requestFields(subsectionWithPath("[]").description("An array of <<classification-definitions, classifications>>"))));
|
||||
|
||||
this.mockMvc.perform(multipart("http://127.0.0.1:" + port + "/v1/classification-definitions")
|
||||
.file("file",
|
||||
definitionString.getBytes())
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(document("ImportClassificationDefinitions", requestParts(
|
||||
partWithName("file").description("The file to upload"))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.pr
|
|||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -33,65 +36,67 @@ import pro.taskana.rest.RestConfiguration;
|
|||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = RestConfiguration.class, webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class WorkbasketDefinitionControllerRestDocumentation {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
|
||||
@Rule
|
||||
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
|
||||
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
private FieldDescriptor[] workbasketdefinitionsFieldDescriptors;
|
||||
|
||||
|
||||
private FieldDescriptor[] workbasketDefinitionsFieldDescriptors;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
document("{methodName}",
|
||||
preprocessRequest(prettyPrint()),
|
||||
preprocessResponse(prettyPrint()));
|
||||
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(springSecurity())
|
||||
.apply(documentationConfiguration(this.restDocumentation)
|
||||
.operationPreprocessors()
|
||||
.withResponseDefaults(prettyPrint())
|
||||
.withRequestDefaults(prettyPrint()))
|
||||
.build();
|
||||
preprocessRequest(prettyPrint()),
|
||||
preprocessResponse(prettyPrint()));
|
||||
|
||||
|
||||
workbasketdefinitionsFieldDescriptors = new FieldDescriptor[] {
|
||||
subsectionWithPath("[]").description("An array of <<workbasket, workbaskets>>")
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(springSecurity())
|
||||
.apply(documentationConfiguration(this.restDocumentation)
|
||||
.operationPreprocessors()
|
||||
.withResponseDefaults(prettyPrint())
|
||||
.withRequestDefaults(prettyPrint()))
|
||||
.build();
|
||||
|
||||
workbasketDefinitionsFieldDescriptors = new FieldDescriptor[] {
|
||||
subsectionWithPath("[]").description("An array of <<WorkbasketDefinitions, workbasketsDefinitions>>")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void exportAllWorkbasketDefinitions() throws Exception {
|
||||
this.mockMvc.perform(RestDocumentationRequestBuilders
|
||||
.get("http://127.0.0.1:" + port + "/v1/workbasketdefinitions")
|
||||
.accept("application/json")
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcRestDocumentation.document("ExportWorkbasktdefinitionsDocTest",
|
||||
responseFields(workbasketdefinitionsFieldDescriptors)));
|
||||
.get("http://127.0.0.1:" + port + "/v1/workbasket-definitions")
|
||||
.accept("application/json")
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcRestDocumentation.document("ExportWorkbasketdefinitionsDocTest",
|
||||
responseFields(workbasketDefinitionsFieldDescriptors)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void importWorkbasketdefinition() throws Exception {
|
||||
this.mockMvc.perform(RestDocumentationRequestBuilders
|
||||
.post("http://127.0.0.1:" + port + "/v1/workbasketdefinitions")
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")
|
||||
.contentType("application/json")
|
||||
.content("["
|
||||
+ "{"
|
||||
+ "\"distributionTargets\":[], "
|
||||
+ "\"authorizations\":[], "
|
||||
+ "\"workbasket\": {\"name\":\"wbblabla\", \"key\":\"neuerKeyXy\", \"domain\": \"DOMAIN_A\", \"type\":\"GROUP\"}"
|
||||
+ "}"
|
||||
+ "]"))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcRestDocumentation.document("ImportWorkbasketdefinitions",
|
||||
requestFields(subsectionWithPath("[]").description("An array of <<workbasket, workbaskets>>"))));
|
||||
public void importWorkbasketDefinition() throws Exception {
|
||||
String definitionString = "["
|
||||
+ "{"
|
||||
+ "\"distributionTargets\":[], "
|
||||
+ "\"authorizations\":[], "
|
||||
+ "\"workbasket\": {\"name\":\"wbblabla\", \"key\":\"neuerKeyXy\", \"domain\": \"DOMAIN_A\", \"type\":\"GROUP\" , \"workbasketId\":\"gibtsNed\"}"
|
||||
+ "}"
|
||||
+ "]";
|
||||
|
||||
this.mockMvc.perform(multipart("http://127.0.0.1:" + port + "/v1/workbasket-definitions")
|
||||
.file("file",
|
||||
definitionString.getBytes())
|
||||
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(document("ImportWorkbasketDefinitions", requestParts(
|
||||
partWithName("file").description("The file to upload"))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package pro.taskana.rest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -9,13 +10,12 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import pro.taskana.Classification;
|
||||
import pro.taskana.ClassificationQuery;
|
||||
|
@ -27,54 +27,72 @@ import pro.taskana.exceptions.ConcurrencyException;
|
|||
import pro.taskana.exceptions.DomainNotFoundException;
|
||||
import pro.taskana.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.exceptions.NotAuthorizedException;
|
||||
import pro.taskana.rest.resource.ClassificationResource;
|
||||
import pro.taskana.rest.resource.ClassificationResourceAssembler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Controller for Importing / Exporting classifications.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(path = "/v1/classificationdefinitions", produces = {MediaType.APPLICATION_JSON_VALUE})
|
||||
@RequestMapping(path = "/v1/classification-definitions", produces = {MediaType.APPLICATION_JSON_VALUE})
|
||||
public class ClassificationDefinitionController {
|
||||
|
||||
@Autowired
|
||||
private ClassificationService classificationService;
|
||||
|
||||
@Autowired
|
||||
private ClassificationResourceAssembler classificationResourceAssembler;
|
||||
|
||||
@GetMapping
|
||||
@Transactional(readOnly = true, rollbackFor = Exception.class)
|
||||
public ResponseEntity<List<ClassificationSummary>> exportClassifications(
|
||||
@RequestParam(required = false) String domain) {
|
||||
public ResponseEntity<List<ClassificationResource>> exportClassifications(
|
||||
@RequestParam(required = false) String domain)
|
||||
throws ClassificationNotFoundException, DomainNotFoundException, ConcurrencyException, InvalidArgumentException,
|
||||
NotAuthorizedException, ClassificationAlreadyExistException {
|
||||
ClassificationQuery query = classificationService.createClassificationQuery();
|
||||
List<ClassificationSummary> summaries = domain != null ? query.domainIn(domain).list() : query.list();
|
||||
|
||||
return new ResponseEntity<>(summaries, HttpStatus.OK);
|
||||
List<ClassificationSummary> summaries = domain != null ? query.domainIn(domain).list() : query.list();
|
||||
List<ClassificationResource> export = new ArrayList<>();
|
||||
|
||||
for (ClassificationSummary summary : summaries) {
|
||||
Classification classification = classificationService.getClassification(summary.getKey(),
|
||||
summary.getDomain());
|
||||
|
||||
export.add(classificationResourceAssembler.toDefinition(classification));
|
||||
}
|
||||
return new ResponseEntity<>(export, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResponseEntity<String> importClassifications(
|
||||
@RequestBody List<Classification> classifications) throws InvalidArgumentException {
|
||||
@RequestParam("file") MultipartFile file)
|
||||
throws InvalidArgumentException, NotAuthorizedException, ConcurrencyException, ClassificationNotFoundException,
|
||||
ClassificationAlreadyExistException, DomainNotFoundException, IOException {
|
||||
Map<String, String> systemIds = classificationService.createClassificationQuery()
|
||||
.list()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(i -> i.getKey() + "|" + i.getDomain(), ClassificationSummary::getId));
|
||||
try {
|
||||
for (Classification classification : classifications) {
|
||||
if (systemIds.containsKey(classification.getKey() + "|" + classification.getDomain())) {
|
||||
classificationService.updateClassification(classification);
|
||||
} else {
|
||||
classificationService.createClassification(classification);
|
||||
}
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
List<ClassificationResource> classificationsDefinitions = mapper.readValue(file.getInputStream(),
|
||||
new TypeReference<List<ClassificationResource>>() {
|
||||
|
||||
});
|
||||
|
||||
for (ClassificationResource classification : classificationsDefinitions) {
|
||||
if (systemIds.containsKey(classification.getKey() + "|" + classification.getDomain())) {
|
||||
classificationService.updateClassification(classificationResourceAssembler.toModel(classification));
|
||||
} else {
|
||||
classificationService.createClassification(classificationResourceAssembler.toModel(classification));
|
||||
}
|
||||
} catch (NotAuthorizedException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
} catch (ClassificationNotFoundException | DomainNotFoundException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
} catch (ClassificationAlreadyExistException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.CONFLICT);
|
||||
// TODO why is this occuring???
|
||||
} catch (ConcurrencyException e) {
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
|
|
|
@ -11,13 +11,12 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import pro.taskana.Workbasket;
|
||||
import pro.taskana.WorkbasketAccessItem;
|
||||
|
@ -31,25 +30,43 @@ import pro.taskana.exceptions.NotAuthorizedException;
|
|||
import pro.taskana.exceptions.WorkbasketAlreadyExistException;
|
||||
import pro.taskana.exceptions.WorkbasketNotFoundException;
|
||||
import pro.taskana.rest.resource.WorkbasketDefinition;
|
||||
import pro.taskana.rest.resource.WorkbasketDefinitionAssembler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Controller for all {@link WorkbasketDefinition} related endpoints.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(path = "/v1/workbasketdefinitions", produces = {MediaType.APPLICATION_JSON_VALUE})
|
||||
@RequestMapping(path = "/v1/workbasket-definitions", produces = {MediaType.APPLICATION_JSON_VALUE})
|
||||
public class WorkbasketDefinitionController {
|
||||
|
||||
@Autowired
|
||||
private WorkbasketService workbasketService;
|
||||
|
||||
@Autowired
|
||||
private WorkbasketDefinitionAssembler workbasketDefinitionAssembler;
|
||||
|
||||
@GetMapping
|
||||
@Transactional(readOnly = true, rollbackFor = Exception.class)
|
||||
public ResponseEntity<List<WorkbasketSummary>> exportWorkbaskets(@RequestParam(required = false) String domain) {
|
||||
public ResponseEntity<List<WorkbasketDefinition>> exportWorkbaskets(@RequestParam(required = false) String domain)
|
||||
throws NotAuthorizedException, WorkbasketNotFoundException {
|
||||
|
||||
WorkbasketQuery workbasketQuery = workbasketService.createWorkbasketQuery();
|
||||
List<WorkbasketSummary> workbasketSummaryList = domain != null
|
||||
? workbasketQuery.domainIn(domain).list()
|
||||
: workbasketQuery.list();
|
||||
return new ResponseEntity<>(workbasketSummaryList, HttpStatus.OK);
|
||||
List<WorkbasketDefinition> basketExports = new ArrayList<>();
|
||||
for (WorkbasketSummary summary : workbasketSummaryList) {
|
||||
Workbasket workbasket = workbasketService.getWorkbasket(summary.getId());
|
||||
basketExports.add(workbasketDefinitionAssembler.toDefinition(workbasket));
|
||||
}
|
||||
return new ResponseEntity<>(basketExports, HttpStatus.OK);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,85 +74,76 @@ public class WorkbasketDefinitionController {
|
|||
* we want to have an option to import all settings at once. When a logical equal (key and domain are equal)
|
||||
* workbasket already exists an update will be executed. Otherwise a new workbasket will be created.
|
||||
*
|
||||
* @param definitions the list of workbasket definitions which will be imported to the current system.
|
||||
* @param file the list of workbasket definitions which will be imported to the current system.
|
||||
* @return Return answer is determined by the status code: 200 - all good 400 - list state error (referring to non
|
||||
* existing id's) 401 - not authorized
|
||||
*/
|
||||
@PostMapping
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResponseEntity<String> importWorkbaskets(@RequestBody List<WorkbasketDefinition> definitions) {
|
||||
try {
|
||||
// key: logical ID
|
||||
// value: system ID (in database)
|
||||
Map<String, String> systemIds = workbasketService.createWorkbasketQuery()
|
||||
.list()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(this::logicalId, WorkbasketSummary::getId));
|
||||
public ResponseEntity<String> importWorkbaskets(@RequestParam("file") MultipartFile file)
|
||||
throws IOException, NotAuthorizedException, DomainNotFoundException, InvalidWorkbasketException,
|
||||
WorkbasketAlreadyExistException, WorkbasketNotFoundException, InvalidArgumentException {
|
||||
|
||||
// key: old system ID
|
||||
// value: system ID
|
||||
Map<String, String> idConversion = new HashMap<>();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
List<WorkbasketDefinition> definitions = mapper.readValue(file.getInputStream(),
|
||||
new TypeReference<List<WorkbasketDefinition>>() {
|
||||
|
||||
// STEP 1: update or create workbaskets from the import
|
||||
for (WorkbasketDefinition definition : definitions) {
|
||||
Workbasket importedWb = definition.workbasket;
|
||||
Workbasket workbasket;
|
||||
if (systemIds.containsKey(logicalId(importedWb))) {
|
||||
workbasket = workbasketService.updateWorkbasket(importedWb);
|
||||
} else {
|
||||
workbasket = workbasketService.createWorkbasket(importedWb);
|
||||
}
|
||||
});
|
||||
|
||||
// Since we would have a n² runtime when doing a lookup and updating the access items we decided to
|
||||
// simply delete all existing accessItems and create new ones.
|
||||
for (WorkbasketAccessItem accessItem : workbasketService.getWorkbasketAccessItems(workbasket.getId())) {
|
||||
workbasketService.deleteWorkbasketAccessItem(accessItem.getId());
|
||||
}
|
||||
for (WorkbasketAccessItem authorization : definition.authorizations) {
|
||||
workbasketService.createWorkbasketAccessItem(authorization);
|
||||
}
|
||||
idConversion.put(definition.workbasket.getId(), workbasket.getId());
|
||||
// key: logical ID
|
||||
// value: system ID (in database)
|
||||
Map<String, String> systemIds = workbasketService.createWorkbasketQuery()
|
||||
.list()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(this::logicalId, WorkbasketSummary::getId));
|
||||
|
||||
// key: old system ID
|
||||
// value: system ID
|
||||
Map<String, String> idConversion = new HashMap<>();
|
||||
|
||||
// STEP 1: update or create workbaskets from the import
|
||||
for (WorkbasketDefinition definition : definitions) {
|
||||
Workbasket importedWb = workbasketDefinitionAssembler.toModel(definition.workbasket);
|
||||
Workbasket workbasket;
|
||||
if (systemIds.containsKey(logicalId(importedWb))) {
|
||||
workbasket = workbasketService.updateWorkbasket(importedWb);
|
||||
} else {
|
||||
workbasket = workbasketService.createWorkbasket(importedWb);
|
||||
}
|
||||
|
||||
// STEP 2: update distribution targets
|
||||
// This can not be done in step 1 because the system IDs are only known after step 1
|
||||
for (WorkbasketDefinition definition : definitions) {
|
||||
List<String> distributionTargets = new ArrayList<>();
|
||||
for (String oldId : definition.distributionTargets) {
|
||||
if (idConversion.containsKey(oldId)) {
|
||||
distributionTargets.add(idConversion.get(oldId));
|
||||
} else {
|
||||
throw new InvalidWorkbasketException(
|
||||
String.format(
|
||||
"invalid import state: Workbasket '%s' does not exist in the given import list",
|
||||
oldId));
|
||||
}
|
||||
}
|
||||
|
||||
workbasketService.setDistributionTargets(
|
||||
// no verification necessary since the workbasket was already imported in step 1.
|
||||
idConversion.get(definition.workbasket.getId()), distributionTargets);
|
||||
// Since we would have a n² runtime when doing a lookup and updating the access items we decided to
|
||||
// simply delete all existing accessItems and create new ones.
|
||||
for (WorkbasketAccessItem accessItem : workbasketService.getWorkbasketAccessItems(workbasket.getId())) {
|
||||
workbasketService.deleteWorkbasketAccessItem(accessItem.getId());
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
} catch (WorkbasketNotFoundException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
} catch (InvalidWorkbasketException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
} catch (NotAuthorizedException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
} catch (InvalidArgumentException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
|
||||
} catch (WorkbasketAlreadyExistException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.CONFLICT);
|
||||
} catch (DomainNotFoundException e) {
|
||||
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
for (WorkbasketAccessItem authorization : definition.authorizations) {
|
||||
workbasketService.createWorkbasketAccessItem(authorization);
|
||||
}
|
||||
idConversion.put(importedWb.getId(), workbasket.getId());
|
||||
}
|
||||
|
||||
// STEP 2: update distribution targets
|
||||
// This can not be done in step 1 because the system IDs are only known after step 1
|
||||
for (WorkbasketDefinition definition : definitions) {
|
||||
List<String> distributionTargets = new ArrayList<>();
|
||||
for (String oldId : definition.distributionTargets) {
|
||||
if (idConversion.containsKey(oldId)) {
|
||||
distributionTargets.add(idConversion.get(oldId));
|
||||
} else {
|
||||
throw new InvalidWorkbasketException(
|
||||
String.format(
|
||||
"invalid import state: Workbasket '%s' does not exist in the given import list",
|
||||
oldId));
|
||||
}
|
||||
}
|
||||
|
||||
workbasketService.setDistributionTargets(
|
||||
// no verification necessary since the workbasket was already imported in step 1.
|
||||
idConversion.get(definition.workbasket.getWorkbasketId()), distributionTargets);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
|
||||
}
|
||||
|
||||
private String logicalId(WorkbasketSummary workbasket) {
|
||||
|
|
|
@ -29,16 +29,18 @@ public class ClassificationResourceAssembler {
|
|||
@Autowired
|
||||
ClassificationService classificationService;
|
||||
|
||||
public ClassificationResource toResource(Classification classification) throws ClassificationNotFoundException,
|
||||
NotAuthorizedException, ClassificationAlreadyExistException, ConcurrencyException, DomainNotFoundException,
|
||||
InvalidArgumentException {
|
||||
ClassificationResource resource = new ClassificationResource();
|
||||
BeanUtils.copyProperties(classification, resource);
|
||||
// need to be set by hand, because they are named different, or have different types
|
||||
resource.setClassificationId(classification.getId());
|
||||
resource.setCreated(classification.getCreated().toString());
|
||||
resource.setModified(classification.getModified().toString());
|
||||
return addLinks(resource, classification);
|
||||
public ClassificationResource toResource(Classification classification)
|
||||
throws InvalidArgumentException, ConcurrencyException, ClassificationNotFoundException, DomainNotFoundException,
|
||||
ClassificationAlreadyExistException, NotAuthorizedException {
|
||||
return this.createResource(classification);
|
||||
}
|
||||
|
||||
public ClassificationResource toDefinition(Classification classification)
|
||||
throws InvalidArgumentException, ConcurrencyException, ClassificationNotFoundException, DomainNotFoundException,
|
||||
ClassificationAlreadyExistException, NotAuthorizedException {
|
||||
ClassificationResource resource = this.createResource(classification);
|
||||
resource.removeLinks();
|
||||
return resource;
|
||||
}
|
||||
|
||||
public Classification toModel(ClassificationResource classificationResource) {
|
||||
|
@ -56,6 +58,22 @@ public class ClassificationResourceAssembler {
|
|||
return classification;
|
||||
}
|
||||
|
||||
private ClassificationResource createResource(Classification classification)
|
||||
throws NotAuthorizedException, ConcurrencyException, InvalidArgumentException, DomainNotFoundException,
|
||||
ClassificationAlreadyExistException, ClassificationNotFoundException {
|
||||
ClassificationResource resource = new ClassificationResource();
|
||||
BeanUtils.copyProperties(classification, resource);
|
||||
// need to be set by hand, because they are named different, or have different types
|
||||
resource.setClassificationId(classification.getId());
|
||||
if(classification.getCreated() != null){
|
||||
resource.setCreated(classification.getCreated().toString());
|
||||
}
|
||||
if(classification.getModified() != null){
|
||||
resource.setModified(classification.getModified().toString());
|
||||
}
|
||||
return addLinks(resource, classification);
|
||||
}
|
||||
|
||||
private ClassificationResource addLinks(ClassificationResource resource, Classification classification)
|
||||
throws ClassificationNotFoundException, NotAuthorizedException, ClassificationAlreadyExistException,
|
||||
ConcurrencyException, DomainNotFoundException, InvalidArgumentException {
|
||||
|
|
|
@ -13,13 +13,13 @@ public class WorkbasketDefinition {
|
|||
|
||||
public Set<String> distributionTargets;
|
||||
public List<WorkbasketAccessItem> authorizations;
|
||||
public Workbasket workbasket;
|
||||
public WorkbasketResource workbasket;
|
||||
|
||||
public WorkbasketDefinition() {
|
||||
// necessary for de-serializing
|
||||
}
|
||||
|
||||
public WorkbasketDefinition(Workbasket workbasket,
|
||||
public WorkbasketDefinition(WorkbasketResource workbasket,
|
||||
Set<String> distributionTargets,
|
||||
List<WorkbasketAccessItem> authorizations) {
|
||||
super();
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
package pro.taskana.rest.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import pro.taskana.Workbasket;
|
||||
import pro.taskana.WorkbasketAccessItem;
|
||||
import pro.taskana.WorkbasketService;
|
||||
import pro.taskana.WorkbasketSummary;
|
||||
import pro.taskana.exceptions.DomainNotFoundException;
|
||||
import pro.taskana.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.exceptions.InvalidWorkbasketException;
|
||||
import pro.taskana.exceptions.NotAuthorizedException;
|
||||
import pro.taskana.exceptions.WorkbasketAlreadyExistException;
|
||||
import pro.taskana.exceptions.WorkbasketNotFoundException;
|
||||
import pro.taskana.impl.WorkbasketImpl;
|
||||
|
||||
/**
|
||||
* Transforms {@link Workbasket} into a {@link WorkbasketDefinition}
|
||||
|
@ -28,7 +37,7 @@ public class WorkbasketDefinitionAssembler {
|
|||
/**
|
||||
* maps the distro targets to their id to remove overhead.
|
||||
*
|
||||
* @param basket
|
||||
* @param workbasket
|
||||
* {@link Workbasket} which will be converted
|
||||
* @return a {@link WorkbasketDefinition}, containing the {@code basket}, its distribution targets and its
|
||||
* authorizations
|
||||
|
@ -37,16 +46,37 @@ public class WorkbasketDefinitionAssembler {
|
|||
* @throws WorkbasketNotFoundException
|
||||
* if {@code basket} is an unknown workbasket
|
||||
*/
|
||||
public WorkbasketDefinition toDefinition(Workbasket basket)
|
||||
public WorkbasketDefinition toDefinition(Workbasket workbasket)
|
||||
throws NotAuthorizedException, WorkbasketNotFoundException {
|
||||
|
||||
WorkbasketResource basket = new WorkbasketResource();
|
||||
BeanUtils.copyProperties(workbasket, basket);
|
||||
basket.setWorkbasketId(workbasket.getId());
|
||||
basket.setModified(workbasket.getModified().toString());
|
||||
basket.setCreated(workbasket.getCreated().toString());
|
||||
|
||||
List<WorkbasketAccessItem> authorizations = new ArrayList<>();
|
||||
for (WorkbasketAccessItem accessItem : workbasketService.getWorkbasketAccessItems(basket.getKey())) {
|
||||
authorizations.add(accessItem);
|
||||
}
|
||||
Set<String> distroTargets = workbasketService.getDistributionTargets(basket.getId())
|
||||
Set<String> distroTargets = workbasketService.getDistributionTargets(workbasket.getId())
|
||||
.stream()
|
||||
.map(WorkbasketSummary::getId)
|
||||
.collect(Collectors.toSet());
|
||||
return new WorkbasketDefinition(basket, distroTargets, authorizations);
|
||||
}
|
||||
|
||||
public Workbasket toModel(WorkbasketResource wbResource) {
|
||||
WorkbasketImpl workbasket = (WorkbasketImpl) workbasketService.newWorkbasket(wbResource.key, wbResource.domain);
|
||||
BeanUtils.copyProperties(wbResource, workbasket);
|
||||
|
||||
workbasket.setId(wbResource.workbasketId);
|
||||
if (wbResource.getModified() != null) {
|
||||
workbasket.setModified(Instant.parse(wbResource.modified));
|
||||
}
|
||||
if (wbResource.getCreated() != null) {
|
||||
workbasket.setCreated(Instant.parse(wbResource.created));
|
||||
}
|
||||
return workbasket;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import {WorkbasketDefinitionService} from './services/workbasket-definition/work
|
|||
import {ClassificationsService} from '../services/classifications/classifications.service';
|
||||
import {ClassificationCategoriesService} from 'app/services/classifications/classification-categories.service';
|
||||
import { AccessItemsManagementComponent } from 'app/administration/access-items-management/access-items-management.component';
|
||||
import { ImportExportService } from './services/import-export/import-export.service';
|
||||
|
||||
const MODULES = [
|
||||
CommonModule,
|
||||
|
@ -69,6 +70,7 @@ const DECLARATIONS = [
|
|||
SavingWorkbasketService,
|
||||
ClassificationsService,
|
||||
ClassificationCategoriesService,
|
||||
ImportExportService,
|
||||
]
|
||||
})
|
||||
export class AdministrationModule {
|
||||
|
|
|
@ -26,6 +26,7 @@ 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';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-dummy-detail',
|
||||
|
@ -52,8 +53,7 @@ describe('ClassificationDetailsComponent', () => {
|
|||
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes), AngularSvgIconModule],
|
||||
declarations: [ClassificationDetailsComponent, DummyDetailComponent],
|
||||
providers: [MasterAndDetailService, RequestInProgressService, ClassificationsService, HttpClient, ErrorModalService, AlertService,
|
||||
TreeService, ClassificationCategoriesService,
|
||||
CustomFieldsService]
|
||||
TreeService, ClassificationCategoriesService, CustomFieldsService, ImportExportService]
|
||||
})
|
||||
};
|
||||
configureTests(configure).then(testBed => {
|
||||
|
|
|
@ -26,6 +26,7 @@ import { CustomFieldsService } from '../../../services/custom-fields/custom-fiel
|
|||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-classification-details',
|
||||
|
@ -37,7 +38,6 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
classification: ClassificationDefinition;
|
||||
classificationClone: ClassificationDefinition;
|
||||
selectedClassification: ClassificationDefinition = undefined;
|
||||
showDetail = false;
|
||||
classificationTypes: Array<string> = [];
|
||||
badgeMessage = '';
|
||||
|
@ -65,6 +65,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
private selectedClassificationTypeSubscription: Subscription;
|
||||
private categoriesSubscription: Subscription;
|
||||
private domainSubscription: Subscription;
|
||||
private importingExportingSubscription: Subscription;
|
||||
|
||||
@ViewChild('ClassificationForm') classificationForm: NgForm;
|
||||
toogleValidationMap = new Map<string, boolean>();
|
||||
|
@ -81,7 +82,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
private domainService: DomainService,
|
||||
private customFieldsService: CustomFieldsService,
|
||||
private removeConfirmationService: RemoveConfirmationService,
|
||||
private formsValidatorService: FormsValidatorService) {
|
||||
private formsValidatorService: FormsValidatorService,
|
||||
private importExportService: ImportExportService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -107,7 +109,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
if (id === 'undefined') {
|
||||
id = undefined;
|
||||
}
|
||||
this.fillClassificationInformation(this.selectedClassification ? this.selectedClassification : new ClassificationDefinition())
|
||||
this.fillClassificationInformation(this.classification ? this.classification : new ClassificationDefinition())
|
||||
}
|
||||
|
||||
if (!this.classification || this.classification.classificationId !== id && id && id !== '') {
|
||||
|
@ -125,6 +127,10 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
this.classification.category = categories[0];
|
||||
}
|
||||
});
|
||||
|
||||
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => {
|
||||
if (this.classification.classificationId) { this.selectClassification(this.classification.classificationId); }
|
||||
})
|
||||
}
|
||||
|
||||
backClicked(): void {
|
||||
|
@ -216,14 +222,14 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
this.requestInProgress = true;
|
||||
this.selectedClassificationSubscription = this.classificationsService.getClassification(id).subscribe(classification => {
|
||||
this.selectedClassification = classification;
|
||||
this.fillClassificationInformation(classification)
|
||||
this.classificationsService.selectClassification(classification);
|
||||
this.requestInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
private classificationIsAlreadySelected(): boolean {
|
||||
if (this.action === ACTION.CREATE && this.selectedClassification) { return true }
|
||||
if (this.action === ACTION.CREATE && this.classification) { return true }
|
||||
}
|
||||
|
||||
private fillClassificationInformation(classificationSelected: ClassificationDefinition) {
|
||||
|
@ -287,7 +293,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
})
|
||||
}
|
||||
|
||||
private cloneClassification (classification: ClassificationDefinition) {
|
||||
private cloneClassification(classification: ClassificationDefinition) {
|
||||
this.classificationClone = { ...classification };
|
||||
}
|
||||
|
||||
|
@ -311,5 +317,6 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
if (this.selectedClassificationTypeSubscription) { this.selectedClassificationTypeSubscription.unsubscribe(); }
|
||||
if (this.categoriesSubscription) { this.categoriesSubscription.unsubscribe(); }
|
||||
if (this.domainSubscription) { this.domainSubscription.unsubscribe(); }
|
||||
if (this.importingExportingSubscription) { this.importingExportingSubscription.unsubscribe(); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<button type="button" (click)="addClassification()" data-toggle="tooltip" title="Add" class="btn btn-default">
|
||||
<span class="material-icons md-20 green-blue">add_circle_outline</span>
|
||||
</button>
|
||||
<taskana-import-export-component class ="btn-group" [currentSelection]="selectionToImport" (importSucessful)="refreshClassificationList()">
|
||||
<taskana-import-export-component class ="btn-group" [currentSelection]="selectionToImport">
|
||||
</taskana-import-export-component>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from 'app/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';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-dummy-detail',
|
||||
|
@ -54,7 +55,7 @@ describe('ClassificationListComponent', () => {
|
|||
imports: [HttpClientModule, RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule],
|
||||
providers: [
|
||||
HttpClient, WorkbasketDefinitionService, AlertService, ClassificationsService, DomainService, ClassificationDefinitionService,
|
||||
ErrorModalService, RequestInProgressService, ClassificationCategoriesService, TreeService
|
||||
ErrorModalService, RequestInProgressService, ClassificationCategoriesService, TreeService, ImportExportService
|
||||
]
|
||||
})
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from 'app/services/classifications/classification-categories.service';
|
||||
import { Pair } from 'app/models/pair';
|
||||
import { ClassificationDefinition } from '../../../../models/classification-definition';
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-classification-list',
|
||||
|
@ -37,12 +38,14 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
|
|||
classificationSavedSubscription: Subscription;
|
||||
selectedClassificationSubscription: Subscription;
|
||||
categoriesSubscription: Subscription;
|
||||
importingExportingSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private classificationService: ClassificationsService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private categoryService: ClassificationCategoriesService) {
|
||||
private categoryService: ClassificationCategoriesService,
|
||||
private importExportService: ImportExportService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -52,14 +55,17 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
|
|||
this.performRequest(true);
|
||||
});
|
||||
this.selectedClassificationSubscription = this.categoryService.getSelectedClassificationType().subscribe(value => {
|
||||
this.classificationTypeSelected = value;
|
||||
this.classificationTypeSelected = value;
|
||||
this.performRequest();
|
||||
});
|
||||
|
||||
this.categoriesSubscription =
|
||||
this.categoryService.getCategories(this.classificationTypeSelected).subscribe((categories: Array<string>) => {
|
||||
this.categories = categories;
|
||||
});
|
||||
this.categoriesSubscription =
|
||||
this.categoryService.getCategories(this.classificationTypeSelected).subscribe((categories: Array<string>) => {
|
||||
this.categories = categories;
|
||||
});
|
||||
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => {
|
||||
this.performRequest(true);
|
||||
})
|
||||
}
|
||||
|
||||
selectClassificationType(classificationTypeSelected: string) {
|
||||
|
@ -70,8 +76,8 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
|
|||
.subscribe((classifications: Array<TreeNodeModel>) => {
|
||||
this.classifications = classifications;
|
||||
this.requestInProgress = false;
|
||||
});
|
||||
this.selectClassification(undefined);
|
||||
});
|
||||
this.selectClassification(undefined);
|
||||
}
|
||||
|
||||
selectClassification(id: string) {
|
||||
|
@ -123,16 +129,11 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
|
|||
this.initialized = true;
|
||||
|
||||
}
|
||||
|
||||
refreshClassificationList() {
|
||||
this.performRequest(true);
|
||||
}
|
||||
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
<input #selectedFile type="file" accept=".json" (change)="onSelectFile($event)" class="hide" />
|
||||
<button type="button" (click)="selectedFile.click()" data-toggle="tooltip" title="Import" class="btn btn-default">
|
||||
|
||||
<button type="button" [ngClass]="{disabled: uploadservice?.isInUse}" (click)="selectedFile.click()" data-toggle="tooltip" title="Import" class="btn btn-default">
|
||||
<span class="material-icons md-20 green-blue">cloud_upload</span>
|
||||
</button>
|
||||
<form class="hidden" id="upload_form" enctype="multipart/form-data" method="post">
|
||||
<input #selectedFile type="file" accept=".json" (change)="uploadFile()" class="hide" />
|
||||
</form>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="export()">
|
||||
<label>All Domains</label>
|
||||
</a>
|
||||
</li>
|
||||
<div role="separator" class="divider"></div>
|
||||
<li *ngFor="let domain of domains">
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="export(domain)">
|
||||
<label>{{domain === '' ? 'Master' : domain}}</label>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" data-toggle="dropdown" title="Export" class="btn btn-default">
|
||||
<li>
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="export()">
|
||||
<label>All Domains</label>
|
||||
</a>
|
||||
</li>
|
||||
<div role="separator" class="divider"></div>
|
||||
<li *ngFor="let domain of domains">
|
||||
<a href="javascript:void(0)" class="dropdown-item" (click)="export(domain)">
|
||||
<label>{{domain === '' ? 'Master' : domain}}</label>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button [ngClass]="{disabled: uploadservice?.isInUse}" type="button" data-toggle="dropdown" title="Export" class="btn btn-default">
|
||||
<span class="material-icons md-20 red">cloud_download</span>
|
||||
</button>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { HttpClientModule } from '@angular/common/http';
|
|||
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
|
||||
import { AngularSvgIconModule } from 'angular-svg-icon';
|
||||
import { configureTests } from 'app/app.test.configuration';
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
describe('ImportExportComponent', () => {
|
||||
let component: ImportExportComponent;
|
||||
|
@ -21,7 +22,7 @@ describe('ImportExportComponent', () => {
|
|||
declarations: [ImportExportComponent],
|
||||
imports: [HttpClientModule, AngularSvgIconModule],
|
||||
providers: [WorkbasketService, ClassificationDefinitionService, WorkbasketDefinitionService, AlertService,
|
||||
ErrorModalService]
|
||||
ErrorModalService, ImportExportService]
|
||||
})
|
||||
};
|
||||
configureTests(configure).then(testBed => {
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service';
|
||||
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service';
|
||||
import { DomainService } from 'app/services/domain/domain.service';
|
||||
import { TaskanaType } from 'app/models/taskana-type';
|
||||
import { ErrorModel } from 'app/models/modal-error';
|
||||
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
|
||||
import { environment } from 'environments/environment';
|
||||
import { AlertService } from 'app/services/alert/alert.service';
|
||||
import { AlertModel, AlertType } from 'app/models/alert';
|
||||
import { UploadService } from 'app/shared/services/upload/upload.service';
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-import-export-component',
|
||||
|
@ -15,13 +20,20 @@ export class ImportExportComponent implements OnInit {
|
|||
|
||||
@Input() currentSelection: TaskanaType;
|
||||
|
||||
@Output() importSucessful = new EventEmitter();
|
||||
|
||||
@ViewChild('selectedFile')
|
||||
selectedFileInput;
|
||||
|
||||
domains: string[] = [];
|
||||
errorWhileUploadingText: string;
|
||||
|
||||
constructor(private domainService: DomainService, private workbasketDefinitionService: WorkbasketDefinitionService,
|
||||
private classificationDefinitionService: ClassificationDefinitionService, private errorModalService: ErrorModalService) {
|
||||
constructor(
|
||||
private domainService: DomainService,
|
||||
private workbasketDefinitionService: WorkbasketDefinitionService,
|
||||
private classificationDefinitionService: ClassificationDefinitionService,
|
||||
private errorModalService: ErrorModalService,
|
||||
private alertService: AlertService,
|
||||
public uploadservice: UploadService,
|
||||
private importExportService: ImportExportService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -30,30 +42,6 @@ export class ImportExportComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
onSelectFile(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
const ending = file.name.match(/\.([^\.]+)$/)[1];
|
||||
switch (ending) {
|
||||
case 'json':
|
||||
break;
|
||||
default:
|
||||
file.value = '';
|
||||
this.errorModalService.triggerError(new ErrorModel(undefined,
|
||||
`This file format is not allowed! Please use a .json file.`));
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
if (this.currentSelection === TaskanaType.WORKBASKETS) {
|
||||
reader.onload = <Event>(e) => this.workbasketDefinitionService.importWorkbasketDefinitions(e.target.result);
|
||||
this.importSucessful.emit();
|
||||
} else {
|
||||
reader.onload = <Event>(e) => this.classificationDefinitionService.importClassifications(e.target.result);
|
||||
this.importSucessful.emit();
|
||||
}
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
export(domain = '') {
|
||||
if (this.currentSelection === TaskanaType.WORKBASKETS) {
|
||||
this.workbasketDefinitionService.exportWorkbaskets(domain);
|
||||
|
@ -61,4 +49,81 @@ export class ImportExportComponent implements OnInit {
|
|||
this.classificationDefinitionService.exportClassifications(domain);
|
||||
}
|
||||
}
|
||||
|
||||
uploadFile() {
|
||||
const file = this.selectedFileInput.nativeElement.files[0],
|
||||
formdata = new FormData(),
|
||||
ajax = new XMLHttpRequest();
|
||||
if (!this.checkFormatFile(file)) { return false }
|
||||
formdata.append('file', file);
|
||||
ajax.upload.addEventListener('progress', this.progressHandler.bind(this), false);
|
||||
ajax.addEventListener('load', this.resetProgress.bind(this), false);
|
||||
ajax.addEventListener('error', this.errorHandler.bind(this), false);
|
||||
ajax.onreadystatechange = this.onReadyStateChangeHandler.bind(this, ajax);
|
||||
if (this.currentSelection === TaskanaType.WORKBASKETS) {
|
||||
ajax.open('POST', environment.taskanaRestUrl + '/v1/workbasket-definitions');
|
||||
} else {
|
||||
ajax.open('POST', environment.taskanaRestUrl + '/v1/classification-definitions');
|
||||
}
|
||||
ajax.send(formdata);
|
||||
this.uploadservice.isInUse = true;
|
||||
this.uploadservice.setCurrentProgressValue(1)
|
||||
}
|
||||
|
||||
progressHandler(event) {
|
||||
const percent = (event.loaded / event.total) * 100;
|
||||
this.uploadservice.setCurrentProgressValue(Math.round(percent))
|
||||
}
|
||||
|
||||
private checkFormatFile(file): boolean {
|
||||
const ending = file.name.match(/\.([^\.]+)$/)[1];
|
||||
let check = false;
|
||||
switch (ending) {
|
||||
case 'json':
|
||||
check = true;
|
||||
break;
|
||||
default:
|
||||
file.value = '';
|
||||
this.errorModalService.triggerError(new ErrorModel(undefined,
|
||||
`This file format is not allowed! Please use a .json file.`));
|
||||
}
|
||||
return check;
|
||||
}
|
||||
|
||||
private resetProgress() {
|
||||
this.uploadservice.setCurrentProgressValue(0)
|
||||
this.uploadservice.isInUse = false;
|
||||
this.selectedFileInput.nativeElement.value = '';
|
||||
}
|
||||
|
||||
private onReadyStateChangeHandler(event) {
|
||||
|
||||
if (event.readyState === 4 && event.status >= 400) {
|
||||
let title;
|
||||
if (event.status === 401) {
|
||||
title = 'Import was not successful, you have no access to apply this operation.';
|
||||
} else if (event.status === 404) {
|
||||
title = 'Import was not successful, operation was not found.';
|
||||
} else if (event.status === 409) {
|
||||
title = 'Import was not successful, operation has some conflicts.';
|
||||
}
|
||||
this.errorHandler(title, JSON.parse(event.responseText).message);
|
||||
|
||||
} else if (event.readyState === 4 && event.status === 200) {
|
||||
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful'))
|
||||
this.importExportService.setImportingFinished(true);
|
||||
this.resetProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private errorHandler(title = 'Import was not successful', message) {
|
||||
this.errorModalService.triggerError(
|
||||
new ErrorModel(
|
||||
title,
|
||||
message
|
||||
)
|
||||
);
|
||||
this.selectedFileInput.files = undefined;
|
||||
this.resetProgress();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from 'app/../environments/environment';
|
||||
import { AlertService } from 'app/services/alert/alert.service';
|
||||
import { ClassificationDefinition } from 'app/models/classification-definition';
|
||||
import { AlertModel, AlertType } from 'app/models/alert';
|
||||
import { saveAs } from 'file-saver/FileSaver';
|
||||
import { TaskanaDate } from 'app/shared/util/taskana.date';
|
||||
|
||||
@Injectable()
|
||||
export class ClassificationDefinitionService {
|
||||
|
||||
url = environment.taskanaRestUrl + '/v1/classificationdefinitions';
|
||||
url = environment.taskanaRestUrl + '/v1/classification-definitions';
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private alertService: AlertService
|
||||
private httpClient: HttpClient
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -26,14 +23,4 @@ export class ClassificationDefinitionService {
|
|||
'Classifications_' + TaskanaDate.getDate() + '.json')
|
||||
);
|
||||
}
|
||||
|
||||
// POST
|
||||
// TODO handle error
|
||||
importClassifications(classifications: any) {
|
||||
this.httpClient.post(this.url,
|
||||
JSON.parse(classifications)).subscribe(
|
||||
classificationsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')),
|
||||
error => this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'Import was not successful'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class ImportExportService {
|
||||
|
||||
public importingFinished = new Subject<boolean>();
|
||||
|
||||
constructor() { }
|
||||
|
||||
setImportingFinished(value: boolean) {
|
||||
this.importingFinished.next(value);
|
||||
}
|
||||
|
||||
getImportingFinished(): Observable<boolean> {
|
||||
return this.importingFinished.asObservable();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +1,15 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from 'app/../environments/environment';
|
||||
import { saveAs } from 'file-saver/FileSaver';
|
||||
import { AlertService } from 'app/services/alert/alert.service';
|
||||
import { WorkbasketDefinition } from 'app/models/workbasket-definition';
|
||||
import { AlertModel, AlertType } from 'app/models/alert';
|
||||
import { TaskanaDate } from 'app/shared/util/taskana.date';
|
||||
import { ErrorModel } from 'app/models/modal-error';
|
||||
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class WorkbasketDefinitionService {
|
||||
url: string = environment.taskanaRestUrl + '/v1/workbasketdefinitions';
|
||||
url: string = environment.taskanaRestUrl + '/v1/workbasket-definitions';
|
||||
|
||||
constructor(private httpClient: HttpClient, private alertService: AlertService,
|
||||
private errorModalService: ErrorModalService) {
|
||||
constructor(private httpClient: HttpClient) {
|
||||
}
|
||||
|
||||
// GET
|
||||
|
@ -28,14 +22,4 @@ export class WorkbasketDefinitionService {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
// POST
|
||||
importWorkbasketDefinitions(workbasketDefinitions: any) {
|
||||
this.httpClient.post(environment.taskanaRestUrl + '/v1/workbasketdefinitions',
|
||||
JSON.parse(workbasketDefinitions)).subscribe(
|
||||
workbasketsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')),
|
||||
error => this.errorModalService.triggerError(new ErrorModel(
|
||||
`There was an error importing workbaskets`, error.message))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import { AccessItemsComponent } from './access-items/access-items.component';
|
|||
import { DistributionTargetsComponent } from './distribution-targets/distribution-targets.component';
|
||||
import { DualListComponent } from './distribution-targets//dual-list/dual-list.component';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-dummy-detail',
|
||||
|
@ -56,14 +57,13 @@ describe('WorkbasketDetailsComponent', () => {
|
|||
beforeEach(done => {
|
||||
const configure = (testBed: TestBed) => {
|
||||
testBed.configureTestingModule({
|
||||
imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, ReactiveFormsModule,
|
||||
InfiniteScrollModule],
|
||||
imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, ReactiveFormsModule,
|
||||
InfiniteScrollModule],
|
||||
declarations: [WorkbasketDetailsComponent, WorkbasketInformationComponent,
|
||||
AccessItemsComponent,
|
||||
DistributionTargetsComponent, DualListComponent, DummyDetailComponent],
|
||||
providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService,
|
||||
AlertService, SavingWorkbasketService,
|
||||
CustomFieldsService]
|
||||
AlertService, SavingWorkbasketService, CustomFieldsService, ImportExportService]
|
||||
})
|
||||
};
|
||||
configureTests(configure).then(testBed => {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service'
|
|||
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'
|
||||
import { DomainService } from 'app/services/domain/domain.service';
|
||||
import { ErrorModalService } from '../../../services/errorModal/error-modal.service';
|
||||
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-workbasket-details',
|
||||
|
@ -18,7 +18,6 @@ import { ErrorModalService } from '../../../services/errorModal/error-modal.serv
|
|||
})
|
||||
export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
|
||||
workbasket: Workbasket;
|
||||
workbasketCopy: Workbasket;
|
||||
selectedId: string = undefined;
|
||||
|
@ -33,13 +32,15 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
|
|||
private masterAndDetailSubscription: Subscription;
|
||||
private permissionSubscription: Subscription;
|
||||
private domainSubscription: Subscription;
|
||||
private importingExportingSubscription: Subscription;
|
||||
|
||||
constructor(private service: WorkbasketService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private masterAndDetailService: MasterAndDetailService,
|
||||
private domainService: DomainService,
|
||||
private errorModalService: ErrorModalService) { }
|
||||
private errorModalService: ErrorModalService,
|
||||
private importExportService: ImportExportService) { }
|
||||
|
||||
|
||||
|
||||
|
@ -77,6 +78,10 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
|
|||
this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => {
|
||||
this.showDetail = showDetail;
|
||||
});
|
||||
|
||||
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => {
|
||||
if (this.workbasket) { this.getWorkbasketInformation(this.workbasket.workbasketId); }
|
||||
})
|
||||
}
|
||||
|
||||
backClicked(): void {
|
||||
|
@ -113,9 +118,9 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
|
|||
this.requestInProgress = false;
|
||||
this.checkDomainAndRedirect();
|
||||
}, err => {
|
||||
this.errorModalService.triggerError(
|
||||
new ErrorModel('An error occurred while fetching the workbasket', err));
|
||||
});
|
||||
this.errorModalService.triggerError(
|
||||
new ErrorModel('An error occurred while fetching the workbasket', err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,5 +139,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
|
|||
if (this.masterAndDetailSubscription) { this.masterAndDetailSubscription.unsubscribe(); }
|
||||
if (this.permissionSubscription) { this.permissionSubscription.unsubscribe(); }
|
||||
if (this.domainSubscription) { this.domainSubscription.unsubscribe(); }
|
||||
if (this.importingExportingSubscription) { this.importingExportingSubscription.unsubscribe(); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<li id="wb-action-toolbar" class="list-group-item tab-align">
|
||||
<div class="row">
|
||||
<div class="col-xs-9 btn-group">
|
||||
<div class="col-xs-7 btn-group">
|
||||
<button type="button" (click)="addWorkbasket()" data-toggle="tooltip" title="Add" class="btn btn-default">
|
||||
<span class="material-icons md-20 green-blue">add_circle_outline</span>
|
||||
</button>
|
||||
<taskana-import-export-component class="btn-group" (importSucessful)="importEvent()" [currentSelection]="selectionToImport"></taskana-import-export-component>
|
||||
<taskana-import-export-component class="btn-group" [currentSelection]="selectionToImport"></taskana-import-export-component>
|
||||
</div>
|
||||
<div class="margin-right pull-right btn-group">
|
||||
<taskana-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)" class="btn-group"></taskana-sort>
|
||||
|
|
|
@ -22,6 +22,7 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
|
|||
import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service';
|
||||
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service';
|
||||
import { configureTests } from 'app/app.test.configuration';
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-dummy-detail',
|
||||
|
@ -50,6 +51,7 @@ describe('WorkbasketListToolbarComponent', () => {
|
|||
WorkbasketService,
|
||||
ClassificationDefinitionService,
|
||||
WorkbasketDefinitionService,
|
||||
ImportExportService
|
||||
]
|
||||
})
|
||||
};
|
||||
|
|
|
@ -22,7 +22,6 @@ export class WorkbasketListToolbarComponent implements OnInit {
|
|||
@Input() workbaskets: Array<WorkbasketSummary>;
|
||||
@Output() performSorting = new EventEmitter<SortingModel>();
|
||||
@Output() performFilter = new EventEmitter<FilterModel>();
|
||||
@Output() importSucessful = new EventEmitter();
|
||||
workbasketServiceSubscription: Subscription;
|
||||
selectionToImport = TaskanaType.WORKBASKETS;
|
||||
sortingFields = new Map([['name', 'Name'], ['key', 'Key'], ['description', 'Description'], ['owner', 'Owner'], ['type', 'Type']]);
|
||||
|
@ -54,7 +53,4 @@ export class WorkbasketListToolbarComponent implements OnInit {
|
|||
this.router.navigate([{ outlets: { detail: ['new-workbasket'] } }], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
importEvent() {
|
||||
this.importSucessful.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
|
|||
import { OrientationService } from 'app/services/orientation/orientation.service';
|
||||
import { configureTests } from 'app/app.test.configuration';
|
||||
import { Page } from 'app/models/page';
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-dummy-detail',
|
||||
|
@ -80,7 +81,8 @@ describe('WorkbasketListComponent', () => {
|
|||
WorkbasketService,
|
||||
WorkbasketDefinitionService,
|
||||
ClassificationDefinitionService,
|
||||
OrientationService
|
||||
OrientationService,
|
||||
ImportExportService
|
||||
]
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import {Orientation} from 'app/models/orientation';
|
|||
import {WorkbasketService} from 'app/services/workbasket/workbasket.service'
|
||||
import {OrientationService} from 'app/services/orientation/orientation.service';
|
||||
import {TaskanaQueryParameters} from 'app/shared/util/query-parameters';
|
||||
import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-workbasket-list',
|
||||
|
@ -38,12 +39,14 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
|
|||
private workbasketServiceSubscription: Subscription;
|
||||
private workbasketServiceSavedSubscription: Subscription;
|
||||
private orientationSubscription: Subscription;
|
||||
private importingExportingSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private workbasketService: WorkbasketService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private orientationService: OrientationService) {
|
||||
private orientationService: OrientationService,
|
||||
private importExportService: ImportExportService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -64,6 +67,10 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
|
|||
this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => {
|
||||
this.refreshWorkbasketList();
|
||||
});
|
||||
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => {
|
||||
this.refreshWorkbasketList();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
selectWorkbasket(id: string) {
|
||||
|
@ -121,5 +128,8 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
|
|||
this.orientationSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.importingExportingSubscription) {
|
||||
this.importingExportingSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
<taskana-spinner [isRunning]="requestInProgress" isModal=true></taskana-spinner>
|
||||
<taskana-alert></taskana-alert>
|
||||
<taskana-remove-confirmation></taskana-remove-confirmation>
|
||||
<taskana-progress-bar [ngClass]="{hidden: currentProgressValue === 0}" currentValue={{currentProgressValue}}></taskana-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -9,6 +9,7 @@ import { RequestInProgressService } from './services/requestInProgress/request-i
|
|||
import { OrientationService } from './services/orientation/orientation.service';
|
||||
import { SelectedRouteService } from './services/selected-route/selected-route';
|
||||
import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service';
|
||||
import { UploadService } from './shared/services/upload/upload.service';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-root',
|
||||
|
@ -24,11 +25,13 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
selectedRoute = '';
|
||||
|
||||
requestInProgress = false;
|
||||
currentProgressValue = 0;
|
||||
|
||||
errorModalSubscription: Subscription;
|
||||
requestInProgressSubscription: Subscription;
|
||||
selectedRouteSubscription: Subscription;
|
||||
routerSubscription: Subscription;
|
||||
uploadingFileSubscription: Subscription;
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
|
@ -41,7 +44,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
private requestInProgressService: RequestInProgressService,
|
||||
private orientationService: OrientationService,
|
||||
private selectedRouteService: SelectedRouteService,
|
||||
private formsValidatorService: FormsValidatorService) {
|
||||
private formsValidatorService: FormsValidatorService,
|
||||
public uploadService: UploadService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -73,6 +77,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
this.selectedRoute = value;
|
||||
})
|
||||
this.uploadingFileSubscription = this.uploadService.getCurrentProgressValue().subscribe(value => {
|
||||
this.currentProgressValue = value;
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -80,5 +87,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
if (this.errorModalSubscription) { this.errorModalSubscription.unsubscribe(); }
|
||||
if (this.requestInProgressSubscription) { this.requestInProgressSubscription.unsubscribe(); }
|
||||
if (this.selectedRouteSubscription) { this.selectedRouteSubscription.unsubscribe(); }
|
||||
if (this.uploadingFileSubscription) { this.uploadingFileSubscription.unsubscribe(); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||
import { TreeModule } from 'angular-tree-component';
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
|
||||
|
||||
/**
|
||||
* Services
|
||||
*/
|
||||
|
@ -34,6 +33,7 @@ import { WindowRefService } from 'app/services/window/window.service';
|
|||
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
|
||||
import { RemoveConfirmationService } from './services/remove-confirmation/remove-confirmation.service';
|
||||
import { FormsValidatorService } from './shared/services/forms/forms-validator.service';
|
||||
import { UploadService } from './shared/services/upload/upload.service';
|
||||
|
||||
/**
|
||||
* Components
|
||||
|
@ -110,7 +110,8 @@ export function startupServiceFactory(startupService: StartupService): () => Pro
|
|||
CustomFieldsService,
|
||||
TaskanaEngineService,
|
||||
RemoveConfirmationService,
|
||||
FormsValidatorService
|
||||
FormsValidatorService,
|
||||
UploadService
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
|
|
@ -71,5 +71,5 @@
|
|||
<p id="taskana-version"> Taskana version: {{version}} </p>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showNavbar" class="navbar-backdrop" (click)="toogleNavBar()"></div>
|
||||
</nav>
|
||||
<div *ngIf="showNavbar" class="backdrop" (click)="toogleNavBar()"></div>
|
||||
</nav>
|
|
@ -48,17 +48,6 @@ h2.navbar-brand{
|
|||
font-size: 20px;
|
||||
}
|
||||
|
||||
.navbar-backdrop{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 990;
|
||||
background-color: grey;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.domain-form {
|
||||
margin: 13px;
|
||||
color: white;
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Subject , Observable } from 'rxjs';
|
||||
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class RequestInProgressService {
|
||||
|
||||
|
|
|
@ -31,6 +31,6 @@ ul.pagination{
|
|||
}
|
||||
|
||||
.footer{
|
||||
margin: 0 5px 0 0;
|
||||
margin: 0px 5px 0 0;
|
||||
color: $blue;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<div class="upload-file-container col-xs-12 col-md-4">
|
||||
<div class="item progress-{{currentValue}}">
|
||||
<div class="radial-inner-bg"><h1 class="blue"> {{currentValue}}%</h1></div>
|
||||
</div>
|
||||
<h4 class="blue">Uploading file</h4>
|
||||
</div>
|
||||
<div *ngIf="currentValue" class="mask"></div>
|
|
@ -0,0 +1,11 @@
|
|||
.upload-file-container{
|
||||
z-index: 3000;
|
||||
width: 170px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 33%;
|
||||
margin-left: -85px;
|
||||
> .item{
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProgressBarComponent } from './progress-bar.component';
|
||||
|
||||
describe('ProgressBarComponent', () => {
|
||||
let component: ProgressBarComponent;
|
||||
let fixture: ComponentFixture<ProgressBarComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ProgressBarComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProgressBarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import { Component, OnInit, Input, SimpleChanges, OnChanges } from '@angular/core';
|
||||
@Component({
|
||||
selector: 'taskana-progress-bar',
|
||||
templateUrl: './progress-bar.component.html',
|
||||
styleUrls: ['./progress-bar.component.scss']
|
||||
})
|
||||
export class ProgressBarComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input()
|
||||
currentValue = 0;
|
||||
@Input()
|
||||
min = 0;
|
||||
@Input()
|
||||
max = 100;
|
||||
|
||||
inProgress = false;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this.inProgress && changes.currentValue.currentValue > this.min) {
|
||||
this.inProgress = true;
|
||||
}
|
||||
if (this.inProgress && changes.currentValue.currentValue >= this.max) {
|
||||
this.inProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UploadService {
|
||||
|
||||
private currentProgressValue = new Subject<number>();
|
||||
public isInUse = false;
|
||||
|
||||
constructor() { }
|
||||
|
||||
setCurrentProgressValue(value: number) {
|
||||
this.currentProgressValue.next(value);
|
||||
}
|
||||
|
||||
getCurrentProgressValue() {
|
||||
return this.currentProgressValue.asObservable();
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,7 @@ import { FilterComponent } from 'app/shared/filter/filter.component';
|
|||
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
|
||||
import { FieldErrorDisplayComponent } from 'app/shared/field-error-display/field-error-display.component';
|
||||
import { PaginationComponent } from './pagination/pagination.component';
|
||||
import { ProgressBarComponent } from './progress-bar/progress-bar.component';
|
||||
|
||||
/**
|
||||
* Pipes
|
||||
|
@ -73,6 +74,7 @@ const DECLARATIONS = [
|
|||
RemoveConfirmationComponent,
|
||||
FieldErrorDisplayComponent,
|
||||
PaginationComponent,
|
||||
ProgressBarComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="footer-space-pagination-list">
|
||||
<div #wbToolbar>
|
||||
<taskana-tasklist-toolbar (performSorting)="performSorting($event)" (performFilter)="performFilter($event)"
|
||||
(selectSearchType)="selectSearchType($event)" (importSucessful)="refreshWorkbasketList()">
|
||||
(selectSearchType)="selectSearchType($event)">
|
||||
</taskana-tasklist-toolbar>
|
||||
</div>
|
||||
<div *ngIf="!requestInProgress">
|
||||
|
|
|
@ -4,4 +4,8 @@
|
|||
}
|
||||
.btn-group ul + .btn, {
|
||||
margin-left: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group > div.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
|
|
@ -11,3 +11,4 @@
|
|||
@import 'tabs';
|
||||
@import 'bootstrap-3-backward-compatibility';
|
||||
@import 'mixin/colors';
|
||||
@import 'progress-bar';
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
@import '_colors';
|
||||
.item {
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
width: 113px;
|
||||
height: 113px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #fff;
|
||||
background-color: #FFF;
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.radial-inner-bg {
|
||||
border-radius: 50%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
background: #FFF;
|
||||
position: absolute;
|
||||
>h1 {
|
||||
margin: 30px 0 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
$step: 1; // step of % for created classes
|
||||
$loops: 100;
|
||||
$increment: (360 / $loops);
|
||||
$half: round($loops / 2);
|
||||
@for $i from 0 through $loops {
|
||||
.progress-#{$i*$step} {
|
||||
@if $i < 50 {
|
||||
$nextdeg: 90deg + ( $increment * $i );
|
||||
background-image: linear-gradient(90deg, #fff 50%, transparent 50%, transparent), linear-gradient($nextdeg, $pallete-green 51%, #fff 50%, #fff);
|
||||
}
|
||||
@else {
|
||||
$nextdeg: -90deg + ( $increment * ( $i - $half ) );
|
||||
background-image: linear-gradient($nextdeg, $pallete-green 51%, transparent 50%, transparent), linear-gradient(270deg, $pallete-green 50%, #fff 50%, #fff);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -395,3 +395,25 @@ li.list-group-item:hover {
|
|||
.padding-right.pull-right {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.backdrop{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 990;
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 2000;
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue