diff --git a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/modelCatalogueDataArchitect.coffee b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/modelCatalogueDataArchitect.coffee index 202e9a0791..b00c1422db 100644 --- a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/modelCatalogueDataArchitect.coffee +++ b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/modelCatalogueDataArchitect.coffee @@ -9,5 +9,9 @@ angular.module("mc.core.modelCatalogueDataArchitect", ['mc.util.rest', 'mc.util. params = angular.extend({key: query}, additionalParams) enhance rest method: 'GET', url: "#{modelCatalogueApiRoot}/dataArchitect/metadataKeyCheck", params: params + modelCatalogueDataArchitect.findRelationsByMetadataKeys = (query, query2, additionalParams = {}) -> + params = angular.extend({keyOne: query}, {keyTwo: query2}, additionalParams) + enhance rest method: 'GET', url: "#{modelCatalogueApiRoot}/dataArchitect/findRelationsByMetadataKeys", params: params + modelCatalogueDataArchitect ] \ No newline at end of file diff --git a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeview.coffee b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeview.coffee index 144950a3cf..4def047b73 100644 --- a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeview.coffee +++ b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeview.coffee @@ -6,7 +6,7 @@ angular.module('mc.core.ui.bs.catalogueElementTreeview', ['mc.core.ui.catalogueE
  • - Show more + Show more
  • diff --git a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeviewItem.coffee b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeviewItem.coffee index aaab1a3ce4..85a03c5c60 100644 --- a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeviewItem.coffee +++ b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/bs/catalogueElementTreeviewItem.coffee @@ -6,8 +6,9 @@ angular.module('mc.core.ui.bs.catalogueElementTreeviewItem', ['mc.core.ui.catalo No Data - - + + + @@ -20,7 +21,7 @@ angular.module('mc.core.ui.bs.catalogueElementTreeviewItem', ['mc.core.ui.catalo
  • - Show more + Show more
  • diff --git a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementTreeviewItem.coffee b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementTreeviewItem.coffee index f85dff43e9..28df921a06 100644 --- a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementTreeviewItem.coffee +++ b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementTreeviewItem.coffee @@ -11,8 +11,8 @@ angular.module('mc.core.ui.catalogueElementTreeviewItem', ['mc.util.names', 'mc. compile: recursiveCompile.compile - controller: ['$scope', '$rootScope', ($scope, $rootScope) -> - loadingChildren = false + controller: ['$scope', '$rootScope', '$log', ($scope, $rootScope, $log) -> + $scope.loadingChildren = false isEqual = (a, b) -> return false if not a? or not b? @@ -23,9 +23,12 @@ angular.module('mc.core.ui.catalogueElementTreeviewItem', ['mc.util.names', 'mc. createShowMore = (list) -> -> + $log.info "this is ", this list.next().then (nextList) -> + $log.info "adding items to children ", $scope.children, " from ", nextList for item in nextList.list $scope.children.push(item.relation) + $log.info "new children are", $scope.children $scope.hasMore = $scope.numberOfChildren > $scope.children.length $scope.showMore = createShowMore(nextList) @@ -80,10 +83,10 @@ angular.module('mc.core.ui.catalogueElementTreeviewItem', ['mc.util.names', 'mc. $scope.numberOfChildren-- $scope.collapseOrExpand = -> - return if loadingChildren + return if $scope.loadingChildren if $scope.collapsed if $scope.children.length == 0 and $scope.numberOfChildren > 0 - loadingChildren = true + $scope.loadingChildren = true fun = $scope.element[$scope.currentDescend] if angular.isFunction(fun) fun().then (list) -> @@ -99,9 +102,9 @@ angular.module('mc.core.ui.catalogueElementTreeviewItem', ['mc.util.names', 'mc. $scope.showMore = createShowMore(list) else $scope.showMore = -> - loadingChildren = false + $scope.loadingChildren = false else - loadingChildren = false + $scope.loadingChildren = false else $scope.collapsed = false else diff --git a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementView.coffee b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementView.coffee index 17e1bd4fb3..f1c8a43ae4 100644 --- a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementView.coffee +++ b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/catalogueElementView.coffee @@ -50,7 +50,7 @@ angular.module('mc.core.ui.catalogueElementView', ['mc.core.catalogueElementEnha $state.go 'mc.resource.show.property', {resource: names.getPropertyNameFromType($scope.element.elementType), id: $scope.element.id, property: newProperty, page: page}, options if $scope.element onElementUpdate = (element) -> - resource = catalogueElementResource(element.elementType) if element + resource = catalogueElementResource(element.elementType) if element and element.elementType activeTabSet = false @@ -125,6 +125,10 @@ angular.module('mc.core.ui.catalogueElementView', ['mc.core.catalogueElementEnha isDirty: () -> angular.equals(@original, @value) reset: () -> @value = angular.copy @original update: () -> + if not resource + messages.error("Cannot update property #{names.getNaturalName(self.name)} of #{element.name}. See application logs for details.") + return + payload = { id: element.id } @@ -177,7 +181,7 @@ angular.module('mc.core.ui.catalogueElementView', ['mc.core.catalogueElementEnha tabDefinition.active = true activeTabSet = true if element.elementTypeName == 'Model' - $scope.reports = [{name: "exportAll COSD", url: modelCatalogueApiRoot + "/dataArchitect/getSubModelElements/" + element.id + "?format=xlsx&report=COSD"}, {name: "exportAll NHIC", url: modelCatalogueApiRoot + "/dataArchitect/getSubModelElements/" + element.id + "?format=xlsx&report=NHIC"}] + $scope.reports = [{name: "exportAll COSD", url: modelCatalogueApiRoot + "/dataArchitect/getSubModelElements/" + element.id + "?format=xlsx&report=COSD"}, {name: "exportAll NHIC", url: modelCatalogueApiRoot + "/dataArchitect/getSubModelElements/" + element.id + "?format=xlsx&report=NHIC"},{name: "exportAll XML", url: modelCatalogueApiRoot + "/dataArchitect/getSubModelElements/" + element.id + "?format=xml"}] tabs.unshift tabDefinition @@ -211,9 +215,11 @@ angular.module('mc.core.ui.catalogueElementView', ['mc.core.catalogueElementEnha messages.prompt('Create Relationship', '', {type: 'new-relationship', element: $scope.element}) $scope.canEdit = -> + return false if not $scope.element messages.hasPromptFactory('edit-' + names.getPropertyNameFromType($scope.element.elementType)) $scope.edit = -> + return if not $scope.element messages.prompt('Edit ' + $scope.element.elementTypeName, '', {type: 'edit-' + names.getPropertyNameFromType($scope.element.elementType), element: $scope.element}).then (updated)-> $scope.element = updated diff --git a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/states/defaultStates.coffee b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/states/defaultStates.coffee index 17e8c0bd27..9ee95fdd9b 100644 --- a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/states/defaultStates.coffee +++ b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/core/ui/states/defaultStates.coffee @@ -9,9 +9,10 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router']) $scope.title = names.getNaturalName($stateParams.resource) $scope.natural = (name) -> if name then names.getNaturalName(name) else "General" $scope.resource = $stateParams.resource - $scope.containedElements = listEnhancer.createEmptyList('org.modelcatalogue.core.DataElement') + $scope.contained = {} + $scope.contained.elements = listEnhancer.createEmptyList('org.modelcatalogue.core.DataElement') $scope.selectedElement = if list.size > 0 then list.list[0] else {name: 'No Selection'} - $scope.containedElementsColumns = [ + $scope.contained.columns = [ {header: 'Name', value: "relation.name", classes: 'col-md-6', show: "relation.show()"} {header: 'Description', value: "relation.description", classes: 'col-md-6'} ] @@ -24,10 +25,17 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router']) unless element._containedElements_?.empty element.contains().then (contained)-> element._containedElements_ = contained - $scope.containedElements = contained + $scope.contained.elements = contained $scope.selectedElement = element - $scope.containedElements = element._containedElements_ ? listEnhancer.createEmptyList('org.modelcatalogue.core.DataElement') -]) + $scope.contained.elements = element._containedElements_ ? listEnhancer.createEmptyList('org.modelcatalogue.core.DataElement') + + else if $scope.resource == 'newRelationships' + $scope.columns = [ + {header: "source", value: 'source.name', class: 'col-md-6' } + {header: "destination", value: 'destination.name', class: 'col-md-6' } + ] + + ]) .config(['$stateProvider', ($stateProvider) -> DEFAULT_ITEMS_PER_PAGE = 10 @@ -80,7 +88,6 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router']) templateUrl: 'modelcatalogue/core/ui/state/list.html' resolve: { list: ['$stateParams','modelCatalogueSearch', ($stateParams, modelCatalogueSearch) -> - page = parseInt($stateParams.page ? 1, 10) $stateParams.resource = "dataElement" return modelCatalogueSearch($stateParams.searchString) ] @@ -100,7 +107,6 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router']) templateUrl: 'modelcatalogue/core/ui/state/list.html' resolve: list: ['$stateParams', 'modelCatalogueDataArchitect', ($stateParams, modelCatalogueDataArchitect) -> - page = parseInt($stateParams.page ? 1, 10) $stateParams.resource = "dataElement" # it's safe to call top level for each controller, only model controller will respond on it modelCatalogueDataArchitect.uninstantiatedDataElements() @@ -109,6 +115,7 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router']) controller: 'mc.core.ui.states.ListCtrl' } + $stateProvider.state 'mc.dataArchitect.metadataKey', { url: "/metadataKeyCheck", templateUrl: 'modelcatalogue/core/ui/state/parent.html' @@ -143,7 +150,6 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router']) templateUrl: 'modelcatalogue/core/ui/state/list.html' resolve: list: ['$stateParams', 'modelCatalogueDataArchitect', ($stateParams, modelCatalogueDataArchitect) -> - page = parseInt($stateParams.page ? 1, 10) $stateParams.resource = "dataElement" # it's safe to call top level for each controller, only model controller will respond on it return modelCatalogueDataArchitect.metadataKeyCheck($stateParams.metadata) @@ -152,6 +158,50 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router']) controller: 'mc.core.ui.states.ListCtrl' } + $stateProvider.state 'mc.dataArchitect.findRelationsByMetadataKeys', { + url: "/findRelationsByMetadataKeys", + templateUrl: 'modelcatalogue/core/ui/state/parent.html', + controller: ['$scope','$state','$modal',($scope, $state, $modal)-> + dialog = $modal.open { + windowClass: 'messages-modal-prompt' + template: ''' + +

    {{title}} List

    - +
    @@ -192,7 +242,7 @@ angular.module('mc.core.ui.states.defaultStates', ['ui.router'])
    - +

    diff --git a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/util/names.coffee b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/util/names.coffee index 8241625535..ecc63e67d2 100644 --- a/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/util/names.coffee +++ b/ModelCatalogueCorePlugin/grails-app/assets/javascripts/modelcatalogue/util/names.coffee @@ -1,10 +1,12 @@ angular.module( 'mc.util.names', []).service 'names', -> names = getNaturalName: (propertyName) -> + return null if not propertyName (word[0].toUpperCase() + word[1..-1].toLowerCase() for word in propertyName.split /(?=[A-Z])(?=[A-Z])|\s+/g).join(' ').replace /([A-Z]) (?=([A-Z] ))/g, '$1' getPropertyNameFromType: (type) -> + return null if not type simpleName = if type.indexOf('.') > -1 then type.substring(type.lastIndexOf('.') + 1) else type simpleName[0].toLowerCase() + simpleName[1..-1] diff --git a/ModelCatalogueCorePlugin/grails-app/conf/BuildConfig.groovy b/ModelCatalogueCorePlugin/grails-app/conf/BuildConfig.groovy index ec36c5a34f..9b4b9cd491 100644 --- a/ModelCatalogueCorePlugin/grails-app/conf/BuildConfig.groovy +++ b/ModelCatalogueCorePlugin/grails-app/conf/BuildConfig.groovy @@ -50,8 +50,9 @@ grails.project.dependency.resolution = { export = false } - compile ":coffee-asset-pipeline:1.5.0" - compile ":less-asset-pipeline:1.5.0" + compile ":asset-pipeline:1.8.8" + compile ":coffee-asset-pipeline:1.8.0" + compile ":less-asset-pipeline:1.7.0" compile ":hibernate:3.6.10.8" compile ":excel-export:0.2.1" diff --git a/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueConfig.groovy b/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueConfig.groovy index cfbfd7bf76..518c1d9511 100644 --- a/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueConfig.groovy +++ b/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueConfig.groovy @@ -38,6 +38,7 @@ grails.doc.copyright = ''// The copyright message to display grails.doc.footer = ''// The footer to use +grails.assets.minifyJs = false //configured data types, measurement units, relationship types @@ -135,7 +136,7 @@ modelcatalogue.defaults.relationshiptypes = [ [name: "inclusion", sourceToDestination: "includes", destinationToSource: "included in", sourceClass: ConceptualDomain, destinationClass: ValueDomain], [name: "instantiation", sourceToDestination: "instantiated by", destinationToSource: "instantiates", sourceClass: DataElement, destinationClass: ValueDomain], [name: "supersession", sourceToDestination: "superseded by", destinationToSource: "supersedes", sourceClass: PublishedElement, destinationClass: PublishedElement, rule: "source.class == destination.class", system: true], - [name: "relatedTo", sourceToDestination: "related to", destinationToSource: "related to", sourceClass: CatalogueElement, destinationClass: CatalogueElement] + [name: "relatedTo", sourceToDestination: "related to", destinationToSource: "related to", sourceClass: DataElement, destinationClass: DataElement] ] // Uncomment and edit the following lines to start using Grails encoding & escaping improvements diff --git a/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueCorePluginUrlMappings.groovy b/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueCorePluginUrlMappings.groovy index e1739e7e27..416720b4da 100644 --- a/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueCorePluginUrlMappings.groovy +++ b/ModelCatalogueCorePlugin/grails-app/conf/ModelCatalogueCorePluginUrlMappings.groovy @@ -72,6 +72,10 @@ class ModelCatalogueCorePluginUrlMappings { "/getSubModelElements/$modelId?" (controller:"dataArchitect"){ action = [GET: "getSubModelElements"] } + "/findRelationsByMetadataKeys/$key?" (controller:"dataArchitect"){ + action = [GET: "findRelationsByMetadataKeys"] + } + } diff --git a/ModelCatalogueCorePlugin/grails-app/controllers/org/modelcatalogue/core/DataArchitectController.groovy b/ModelCatalogueCorePlugin/grails-app/controllers/org/modelcatalogue/core/DataArchitectController.groovy index 1951e28097..7249a8f5bf 100644 --- a/ModelCatalogueCorePlugin/grails-app/controllers/org/modelcatalogue/core/DataArchitectController.groovy +++ b/ModelCatalogueCorePlugin/grails-app/controllers/org/modelcatalogue/core/DataArchitectController.groovy @@ -102,7 +102,66 @@ class DataArchitectController { respond elements } + def findRelationsByMetadataKeys(Integer max){ + setSafeMax(max) + def results + def keyOne = params.keyOne + def keyTwo = params.keyTwo + if(keyOne && keyTwo) { + try { + results = dataArchitectService.findRelationsByMetadataKeys(keyOne, keyTwo, params) + } catch (Exception e) { + println(e) + return + } + + //FIXME we need new method to do this and integrate it with the ui + try { + dataArchitectService.actionRelationshipList(results.list) + } catch (Exception e) { + println(e) + return + } + + def baseLink = "/dataArchitect/findRelationsByMetadataKeys" + def links = ListWrapper.nextAndPreviousLinks(params, baseLink, results.count) + + Elements elements = new Elements( + base: baseLink, + total: results.count, + items: results.list, + itemType: Relationship, + previous: links.previous, + next: links.next, + offset: params.int('offset') ?: 0, + page: params.int('max') ?: 10, + sort: params.sort, + order: params.order + ) + + respond elements + + }else{ + respond "please enter keys" + } + + } + +// def actionRelationships(){ +// +// def relations = params.relatedElements +// +// try { +// def errors = dataArchitectService.createRelationshipByType(relations, "relatedTo") +// } +// catch (Exception ex) { +// //log.error("Exception in handling excel file: "+ ex.message) +// log.error("Exception in handling excel file") +// flash.message = "Error while creating relationships`."; +// } +// +// } protected setSafeMax(Integer max) { withFormat { diff --git a/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/CatalogueElement.groovy b/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/CatalogueElement.groovy index c6bd260ae2..f1603a0d3a 100644 --- a/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/CatalogueElement.groovy +++ b/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/CatalogueElement.groovy @@ -18,7 +18,7 @@ abstract class CatalogueElement { String name String description - static transients = ['relations', 'info', 'archived'] + static transients = ['relations', 'info', 'archived', 'incomingRelations', 'outgoingRelations'] static hasMany = [incomingRelationships: Relationship, outgoingRelationships: Relationship, outgoingMappings: Mapping, incomingMappings: Mapping] @@ -50,21 +50,29 @@ abstract class CatalogueElement { List getRelations() { return [ - (outgoingRelationships ?: []).collect { it.destination }, - (incomingRelationships ?: []).collect { it.source } + outgoingRelations, + incomingRelations ].flatten() } List getIncomingRelations() { - return [ - (incomingRelationships ?: []).collect { it.source } - ].flatten() + relationshipService.getRelationships([:], RelationshipDirection.INCOMING, this).list.collect { it.source } } List getOutgoingRelations() { - return [ - (outgoingRelationships ?: []).collect { it.destination } - ].flatten() + relationshipService.getRelationships([:], RelationshipDirection.OUTGOING, this).list.collect { it.destination } + } + + Long countIncomingRelations() { + relationshipService.getRelationships([:], RelationshipDirection.INCOMING, getClass().get(this.id)).count + } + + Long countOutgoingRelations() { + relationshipService.getRelationships([:], RelationshipDirection.OUTGOING, getClass().get(this.id)).count + } + + Long countRelations() { + relationshipService.getRelationships([:], RelationshipDirection.BOTH, getClass().get(this.id)).count } diff --git a/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/EnumeratedType.groovy b/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/EnumeratedType.groovy index 7747e157cd..129f6a6697 100644 --- a/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/EnumeratedType.groovy +++ b/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/EnumeratedType.groovy @@ -27,7 +27,7 @@ class EnumeratedType extends DataType { static transients = ['enumerations'] static constraints = { - enumAsString nullable: false, unique:true, maxSize: 10000, validator: { encodedVal, obj -> + enumAsString nullable: false, /*unique:true,*/ maxSize: 10000, validator: { encodedVal, obj -> Map val = stringToMap(encodedVal) if (!val) return true if (val.size() < 1) return false diff --git a/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/dataarchitect/Importer.groovy b/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/dataarchitect/Importer.groovy index 49db72d953..f01c4f2541 100644 --- a/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/dataarchitect/Importer.groovy +++ b/ModelCatalogueCorePlugin/grails-app/domain/org/modelcatalogue/core/dataarchitect/Importer.groovy @@ -401,7 +401,7 @@ class Importer { } catch (Exception e) { return null } dataTypeReturn = EnumeratedType.findWhere(enumAsString: data) if (!dataTypeReturn) { dataTypeReturn = new EnumeratedType(name: name.replaceAll("\\s", "_"), enumAsString: data).save() } - } else if (data.contains("\\n") || data.contains("\\r")) { + } else if (data.contains("\n") || data.contains("\r")) { String[] lines = data.split("\\r?\\n") if (lines.size() > 0 && lines[] != null) { Map enumerations = new HashMap() diff --git a/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/ModelService.groovy b/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/ModelService.groovy index 894362ab00..9ed5e1fd82 100644 --- a/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/ModelService.groovy +++ b/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/ModelService.groovy @@ -15,6 +15,8 @@ class ModelService { select distinct m from Model m where m.status = :status and m.id not in (select distinct r.destination.id from Relationship r where r.relationshipType = :type) + group by m.name + order by m.name """, [type: hierarchy, status: status], params) Long count = Model.executeQuery(""" diff --git a/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataArchitectService.groovy b/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataArchitectService.groovy index 6286967ed4..29b79a04c9 100644 --- a/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataArchitectService.groovy +++ b/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataArchitectService.groovy @@ -21,7 +21,7 @@ import org.modelcatalogue.core.util.ListAndCount @Transactional class DataArchitectService { - def modelCatalogueSearchService, publishedElementService + def modelCatalogueSearchService, publishedElementService, relationshipService def uninstantiatedDataElements(Map params){ ListAndCount results = new ListAndCount() @@ -58,29 +58,69 @@ class DataArchitectService { ListAndCount results = new ListAndCount() def searchParams = getParams(params) - totalCount = DataElement.executeQuery("SELECT DISTINCT COUNT(a) FROM DataElement a " + - "WHERE a.extensions IS EMPTY " + - "OR a NOT IN " + - "(SELECT a2 from DataElement a2 " + - "JOIN a2.extensions e2 " + - "WHERE e2.name = ?)", [searchParams.key], [cache:true] - ) + totalCount = DataElement.executeQuery("SELECT DISTINCT COUNT(a) FROM DataElement a " + + "WHERE a.extensions IS EMPTY " + + "OR a NOT IN " + + "(SELECT a2 from DataElement a2 " + + "JOIN a2.extensions e2 " + + "WHERE e2.name = ?)", [searchParams.key], [cache:true] + ) + + missingMetadataKey = DataElement.executeQuery("SELECT DISTINCT a FROM DataElement a " + + "WHERE a.extensions IS EMPTY " + + "OR a NOT IN " + + "(SELECT a2 from DataElement a2 " + + "JOIN a2.extensions e2 " + + "WHERE e2.name = ?)", [searchParams.key], [max: searchParams.max, offset: searchParams.offset] + ) - missingMetadataKey = DataElement.executeQuery("SELECT DISTINCT a FROM DataElement a " + - "WHERE a.extensions IS EMPTY " + - "OR a NOT IN " + - "(SELECT a2 from DataElement a2 " + - "JOIN a2.extensions e2 " + - "WHERE e2.name = ?)", [searchParams.key], [max: searchParams.max, offset: searchParams.offset] - ) + results.count = (totalCount.get(0))?totalCount.get(0):0 + results.list = missingMetadataKey - results.count = (totalCount.get(0))?totalCount.get(0):0 - results.list = missingMetadataKey + return results + } + def findRelationsByMetadataKeys(String keyOne, String keyTwo, Map params){ + + ListAndCount results = new ListAndCount() + def searchParams = getParams(params) + def synonymDataElements = [] + //FIXME the relationship type should be configurable + def relType = RelationshipType.findByName("relatedTo") + + def key1Elements = DataElement.executeQuery("SELECT DISTINCT a FROM DataElement a " + + "inner join a.extensions e " + + "WHERE e.name = ?)", [keyOne])//, [max: searchParams.max, offset: searchParams.offset]) + + key1Elements.eachWithIndex { DataElement dataElement, int dataElementIndex -> + def extensionName = dataElement.extensions[dataElement.extensions.findIndexOf { + it.name == keyOne + }].extensionValue + def key2Elements = DataElement.executeQuery("SELECT DISTINCT a FROM DataElement a " + + "inner join a.extensions e " + + "WHERE e.name = ? and e.extensionValue = ?) ", [keyTwo, extensionName], [max: searchParams.max, offset: searchParams.offset]) + + // Create a Map + key2Elements.each { + //FIXME the relationship type needs to be configurable + def relationship = new Relationship(source: dataElement, destination: it, relationshipType: relType) + synonymDataElements << relationship + } + } + + results.list = synonymDataElements + results.count = synonymDataElements.size() return results } + def actionRelationshipList(ArrayList list){ + def errorMessages = [] + list.each { relationship -> + relationship.save() + } + } + def indexAll(){ modelCatalogueSearchService.index(ConceptualDomain.list()) modelCatalogueSearchService.index(DataType.list()) @@ -106,6 +146,8 @@ class DataArchitectService { private static Map getParams(Map params){ def searchParams = [:] if(params.key){searchParams.put("key" , params.key)} + if(params.keyOne){searchParams.put("keyOne" , params.keyOne)} + if(params.keyTwo){searchParams.put("keyTwo" , params.keyTwo)} if(params.max){searchParams.put("max" , params.max.toInteger())}else{searchParams.put("max" , 10)} // if(params.sort){searchParams.put("sort" , "$params.sort")}else{searchParams.put("sort" , "name")} // if(params.order){searchParams.put("order" , params.order.toLowerCase())}else{searchParams.put("order" , "asc")} diff --git a/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataImportService.groovy b/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataImportService.groovy index efe27b2a99..0b80f5f42b 100644 --- a/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataImportService.groovy +++ b/ModelCatalogueCorePlugin/grails-app/services/org/modelcatalogue/core/dataarchitect/DataImportService.groovy @@ -20,7 +20,7 @@ import org.modelcatalogue.core.ValueDomain class DataImportService { - static transactional = false + static transactional = true private static final QUOTED_CHARS = ["\\": "\", ":" : ":", "|" : "|", "%" : "%"] //the import script accepts and array of headers these should include the following: diff --git a/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/ListWrapper.groovy b/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/ListWrapper.groovy index 4db3bd1d47..2ac810ff69 100644 --- a/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/ListWrapper.groovy +++ b/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/ListWrapper.groovy @@ -30,6 +30,15 @@ abstract class ListWrapper { if (params.key) { link += "&key=${params.key}" } + if (params.keyOne) { + link += "&keyOne=${params.keyOne}" + } + if (params.keyTwo) { + link += "&keyTwo=${params.keyTwo}" + } + if (params.toplevel) { + link += "&toplevel=${params.toplevel}" + } def nextLink = "" def previousLink = "" if (params?.max && params.max < total) { diff --git a/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/marshalling/CatalogueElementMarshallers.groovy b/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/marshalling/CatalogueElementMarshallers.groovy index 1b0ee25914..3a3810ed4a 100644 --- a/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/marshalling/CatalogueElementMarshallers.groovy +++ b/ModelCatalogueCorePlugin/src/groovy/org/modelcatalogue/core/util/marshalling/CatalogueElementMarshallers.groovy @@ -25,9 +25,9 @@ abstract class CatalogueElementMarshallers extends AbstractMarshallers { elementType: el.class.name, elementTypeName: GrailsNameUtils.getNaturalName(el.class.simpleName), link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id", - relationships: [count: (el.outgoingRelationships ? el.outgoingRelationships.size() : 0) + (el.incomingRelationships ? el.incomingRelationships.size() : 0), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/relationships"], - outgoingRelationships: [count: el.outgoingRelationships ? el.outgoingRelationships.size() : 0, itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/outgoing"], - incomingRelationships: [count: el.incomingRelationships ? el.incomingRelationships.size() : 0, itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/incoming"] + relationships: [count: el.countRelations(), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/relationships"], + outgoingRelationships: [count: el.countOutgoingRelations(), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/outgoing"], + incomingRelationships: [count: el.countIncomingRelations(), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/incoming"] ] Map> relationships = getRelationshipConfiguration(type) @@ -56,9 +56,9 @@ abstract class CatalogueElementMarshallers extends AbstractMarshallers { xml.build { name el.name description el.description - relationships count: (el.outgoingRelationships ? el.outgoingRelationships.size() : 0) + (el.incomingRelationships ? el.incomingRelationships.size() : 0), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/relationships" - outgoingRelationships count: el.outgoingRelationships ? el.outgoingRelationships.size() : 0, itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/outgoing" - incomingRelationships count: el.incomingRelationships ? el.incomingRelationships.size() : 0, itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/incoming" + relationships count: (el.countRelations()), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/relationships" + outgoingRelations count: el.countOutgoingRelations(), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/outgoing" + incomingRelations count: el.countIncomingRelations(), itemType: Relationship.name, link: "/${GrailsNameUtils.getPropertyName(el.getClass())}/$el.id/incoming" } def relationships = getRelationshipConfiguration(type) diff --git a/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectControllerIntegrationSpec.groovy b/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectControllerIntegrationSpec.groovy index 2abb4d57c7..0bdb5f3de8 100644 --- a/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectControllerIntegrationSpec.groovy +++ b/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectControllerIntegrationSpec.groovy @@ -36,6 +36,9 @@ class DataArchitectControllerIntegrationSpec extends AbstractIntegrationSpec{ de2.addToInstantiatedBy(vd) relationshipService.link(de3, de2, RelationshipType.findByName("supersession")) md.addToParentOf(md2) + + de1.ext.put("Data item No.", "C1031") + de2.ext.put("Optional_Local_Identifier", "C1031") } def cleanupSpec(){ @@ -219,4 +222,65 @@ class DataArchitectControllerIntegrationSpec extends AbstractIntegrationSpec{ xml.previous.text() == "" } + @Unroll + def "json - create dataElement relationships"(){ + + def controller = new DataArchitectController() + ResultRecorder recorder = DefaultResultRecorder.create( + "../ModelCatalogueCorePlugin/target/xml-samples/modelcatalogue/core", + "../ModelCatalogueCorePlugin/test/js/modelcatalogue/core", + "dataArchitect" + ) + when: + controller.response.format = "json" + controller.params.put("keyOne", "Data item No.") + controller.params.put("keyTwo", "Optional_Local_Identifier") + controller.findRelationsByMetadataKeys() + JSONElement json = controller.response.json + String list = "dataElement_Relationships" + recorder.recordResult list, json + + then: + + json.success + json.total == 1 + json.offset == 0 + json.page == 10 + json.list + json.list.size() == 1 + //json.next == "/dataArchitect/metadataKeyCheck?max=10&key=metadata&offset=10" + json.previous == "" + + } + @Unroll + def "xml - create dataElement relationships"(){ + def controller = new DataArchitectController() + ResultRecorder recorder = DefaultResultRecorder.create( + "../ModelCatalogueCorePlugin/target/xml-samples/modelcatalogue/core", + "../ModelCatalogueCorePlugin/test/js/modelcatalogue/core", + "dataArchitect" + ) + + when: + controller.response.format = "xml" + controller.params.put("keyOne", "Data item No.") + controller.params.put("keyTwo", "Optional_Local_Identifier") + controller.findRelationsByMetadataKeys() + GPathResult xml = controller.response.xml + String list = "dataElement_Relationships" + recorder.recordResult list, xml + + then: + + xml.@success.text() == "true" + xml.@total.text() == "1" + xml.@offset.text() == "0" + xml.@page.text() =="10" + xml.element + xml.element.size() == 1 + //xml.next.text() == "/dataArchitect/metadataKeyCheck?max=10&key=metadata&offset=10" + xml.previous.text() == "" + } + + } diff --git a/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectSpec.groovy b/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectSpec.groovy index bfd1890db0..be6e259fb6 100644 --- a/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectSpec.groovy +++ b/ModelCatalogueCorePlugin/test/integration/org/modelcatalogue/core/DataArchitectSpec.groovy @@ -31,9 +31,15 @@ class DataArchitectSpec extends AbstractIntegrationSpec{ de4.ext.put("metadata", "test2") de4.ext.put("test3", "test2") de4.ext.put("test4", "test2") + de1.ext.put("Data item No.", "C1031") // used in def "find relationships" + de2.ext.put("Optional_Local_Identifier", "C1031") // used in def "find relationships" } def cleanupSpec(){ + de1 = DataElement.findByName("DE_author") + de2 = DataElement.findByName("DE_author1") + md.refresh() + vd.refresh() de1.removeFromContainedIn(md) de2.removeFromInstantiatedBy(vd) } @@ -66,7 +72,24 @@ class DataArchitectSpec extends AbstractIntegrationSpec{ } + def "find relationships and action them"() { + when: + Map params = [:] + params.put("max", 12) + def relatedDataElements = dataArchitectService.findRelationsByMetadataKeys("Data item No.","Optional_Local_Identifier", params) + then: + relatedDataElements.each {row -> + relatedDataElements.list.collect{it.source}contains(de1) + relatedDataElements.list.collect{it.destination}contains(de2) + } + when: + dataArchitectService.actionRelationshipList(relatedDataElements.list) + + then: + de1.relations.contains(de2) + + } } diff --git a/ModelCatalogueCorePlugin/test/unit/org/modelcatalogue/core/RelationshipSpec.groovy b/ModelCatalogueCorePlugin/test/unit/org/modelcatalogue/core/RelationshipSpec.groovy deleted file mode 100644 index f5a1fcc96b..0000000000 --- a/ModelCatalogueCorePlugin/test/unit/org/modelcatalogue/core/RelationshipSpec.groovy +++ /dev/null @@ -1,80 +0,0 @@ -package org.modelcatalogue.core - -import grails.test.mixin.Mock -import org.modelcatalogue.fixtures.FixturesLoader -import spock.lang.Specification - -/** - * Created by adammilward on 03/02/2014. - * - * Relationship exist between catalogue elements of particular types (see relationship type) - * - * - */ - -@Mock([Relationship, DataElement, DataType, RelationshipType]) -class RelationshipSpec extends Specification{ - - FixturesLoader fixturesLoader = new FixturesLoader("../ModelCatalogueCorePlugin/fixtures") - - def "Won't create org.modelcatalogue.core.Relationship if the catalogue elements have not been persisted"() { - - expect: - Relationship.list().isEmpty() - - when: - - Relationship rel = new RelationshipService().link(new DataElement(name:"test2DE") , new DataElement(name:"test1DE"), createRelationshipType()) - - then: - - rel.hasErrors() - - - } - - def "Create relationship then delete an element on one side of the relationship"(){ - - def loadItem1, loadItem2, type, rel - fixturesLoader.load('dataElements/DE_author', 'dataElements/DE_author1', 'dataElements/DE_author2', 'relationshipTypes/RT_relationship') - - assert (loadItem1 = fixturesLoader.DE_author.save(flush: true)) - assert (loadItem2 = fixturesLoader.DE_author1.save(flush: true)) - assert (type = fixturesLoader.RT_relationship.save(flush: true)) - - def item1Id = loadItem1.id - - - when: - - rel = new RelationshipService().link(loadItem1, loadItem2, type) - !rel.hasErrors() - def relId = rel.id - - then: - - loadItem1.outgoingRelations == [loadItem2] - loadItem2.incomingRelations == [loadItem1] - - - when: - - loadItem1.delete(flush:true, failOnError:true) - - then: - - !DataElement.get(item1Id) - !Relationship.get(relId) - - loadItem1.outgoingRelations == [] - loadItem2.incomingRelations == [] - - - } - - - RelationshipType createRelationshipType(){ - new RelationshipType(name:'relationship1', sourceToDestination:'parent', destinationToSource: 'child', sourceClass: DataElement,destinationClass: DataElement) - } - -} diff --git a/ModelCatalogueCorePlugin/test/unit/org/modelcatalogue/core/util/CatalogueElementDynamicHelperSpec.groovy b/ModelCatalogueCorePlugin/test/unit/org/modelcatalogue/core/util/CatalogueElementDynamicHelperSpec.groovy index 30b0fc75b0..b2d0f39156 100644 --- a/ModelCatalogueCorePlugin/test/unit/org/modelcatalogue/core/util/CatalogueElementDynamicHelperSpec.groovy +++ b/ModelCatalogueCorePlugin/test/unit/org/modelcatalogue/core/util/CatalogueElementDynamicHelperSpec.groovy @@ -21,8 +21,8 @@ class CatalogueElementDynamicHelperSpec extends Specification { where: clazz | transients - TestCatalogueElement1 | ['relations', 'info', 'archived', 'hasContextOf', 'parentOf', 'childOf'] - TestCatalogueElement2 | ['relations', 'info', 'archived', 'hasContextOf', 'parentOf', 'childOf', 'b', 'd'] + TestCatalogueElement1 | ['relations', 'info', 'archived', 'incomingRelations', 'outgoingRelations', 'hasContextOf', 'parentOf', 'childOf'] + TestCatalogueElement2 | ['relations', 'info', 'archived', 'incomingRelations', 'outgoingRelations', 'hasContextOf', 'parentOf', 'childOf', 'b', 'd'] } diff --git a/ModelCatalogueCorePluginTestApp/grails-app/assets/javascripts/demo.coffee b/ModelCatalogueCorePluginTestApp/grails-app/assets/javascripts/demo.coffee index 1b1fd87908..d750d8b9c0 100644 --- a/ModelCatalogueCorePluginTestApp/grails-app/assets/javascripts/demo.coffee +++ b/ModelCatalogueCorePluginTestApp/grails-app/assets/javascripts/demo.coffee @@ -5,21 +5,18 @@ #= require modelcatalogue/core/index #= require modelcatalogue/core/ui/index #= require modelcatalogue/core/ui/bs/index +#= require modelcatalogue/core/ui/states/index #= require modelcatalogue/core/ui/bs/elementViews/index angular.module('demo', [ 'demo.config' 'mc.core.ui.bs' + 'mc.core.ui.states' 'ui.bootstrap' -]).controller('demo.DemoCtrl', ['catalogueElementResource', 'modelCatalogueSearch', '$scope', '$log', '$q', 'columns', '$rootScope', 'messages', (catalogueElementResource, modelCatalogueSearch, $scope, $log, $q, columns, $rootScope, messages)-> - emptyList = - list: [] - next: {size: 0} - previous: {size: 0} - total: 0 - empty: true - source: 'demo' +]).controller('demo.DemoCtrl', ['catalogueElementResource', 'modelCatalogueSearch', '$scope', '$log', '$q', 'columns', '$rootScope', 'messages', 'enhance', (catalogueElementResource, modelCatalogueSearch, $scope, $log, $q, columns, $rootScope, messages, enhance)-> + listEnhancer = enhance.getEnhancer('list') + $scope.listResource = """resource("dataElement").list()""" @@ -27,7 +24,7 @@ angular.module('demo', [ $scope.searchSomething = """search("patient")""" $scope.searchModel = """resource("model").search("patient")""" $scope.outgoing = """resource("dataElement").list() >>> $r.list[0].outgoingRelationships()""" - $scope.indicator = """resource("dataElement").search("NHS NUMBER STATUS INDICATOR CODE") >>> $r.list[0]""" + $scope.indicator = """resource("conceptualDomain").search("NHIC") >>> $r.list[0]""" $scope.resource = catalogueElementResource $scope.search = modelCatalogueSearch @@ -42,7 +39,7 @@ angular.module('demo', [ $scope.list = result $scope.element = null if result?.elementType? - $scope.list = emptyList + $scope.list = listEnhancer.createSingletonList(result) $scope.element = result else $log.info "Instead of list or element got: ", result @@ -62,7 +59,7 @@ angular.module('demo', [ $scope.columns = columns(result.itemType) $scope.element = null if result?.elementType? - $scope.list = emptyList + $scope.list = listEnhancer.createSingletonList(result) $scope.element = result else $log.info "Instead of list got: ", result @@ -71,7 +68,7 @@ angular.module('demo', [ $scope.selection = [] $scope.columns = columns() - $scope.list = emptyList + $scope.list = listEnhancer.createEmptyList() $scope.actions = [ {type: 'primary', title: 'Test', icon: 'info-sign', action: (element) -> alert(element.name)} diff --git a/ModelCatalogueCorePluginTestApp/grails-app/conf/BootStrap.groovy b/ModelCatalogueCorePluginTestApp/grails-app/conf/BootStrap.groovy index dc8dc6d9ce..8ea724e5f4 100644 --- a/ModelCatalogueCorePluginTestApp/grails-app/conf/BootStrap.groovy +++ b/ModelCatalogueCorePluginTestApp/grails-app/conf/BootStrap.groovy @@ -42,8 +42,21 @@ class BootStrap { def de = new DataElement(name: "testera", description:"test data architect").save() de.ext.metadata = "test metadata" - Model anotherRoot = new Model(name: "Another root") - anotherRoot.save() + 15.times { + new Model(name: "Another root #${String.format('%03d', it)}").save() + } + + def parentModel1 = Model.findByName("Another root #001") + + 15.times{ + def child = new Model(name: "Another root #${String.format('%03d', it)}").save() + parentModel1.addToParentOf(child) + } + + + for (DataElement element in DataElement.list()) { + parentModel1.addToContains element + } PublishedElement.list().each { diff --git a/ModelCatalogueCorePluginTestApp/grails-app/views/index.gsp b/ModelCatalogueCorePluginTestApp/grails-app/views/index.gsp index da6de35281..339273c409 100644 --- a/ModelCatalogueCorePluginTestApp/grails-app/views/index.gsp +++ b/ModelCatalogueCorePluginTestApp/grails-app/views/index.gsp @@ -42,15 +42,15 @@ -
  • Relationship Types
  • +
  • Relationship Types