diff --git a/waltz-data/src/main/java/org/finos/waltz/data/CommonTableFieldsRegistry.java b/waltz-data/src/main/java/org/finos/waltz/data/CommonTableFieldsRegistry.java index 194191410..38a0d075d 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/CommonTableFieldsRegistry.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/CommonTableFieldsRegistry.java @@ -34,6 +34,7 @@ import org.finos.waltz.schema.tables.Person; import org.finos.waltz.schema.tables.PhysicalFlow; import org.finos.waltz.schema.tables.PhysicalSpecification; +import org.finos.waltz.schema.tables.Role; import org.finos.waltz.schema.tables.ServerInformation; import org.finos.waltz.schema.tables.SoftwarePackage; import org.finos.waltz.schema.tables.SurveyQuestion; @@ -395,6 +396,18 @@ public static CommonTableFields determineCommonTableFields(EntityKind kind, S .externalIdField(ps.EXTERNAL_ID) .lifecycleField(DSL.when(ps.IS_REMOVED.isTrue(), EntityLifecycleStatus.REMOVED.name()).otherwise(EntityLifecycleStatus.ACTIVE.name())) .build(); + case ROLE: + Role role = alias == null ? Tables.ROLE : Tables.ROLE.as(alias); + return ImmutableCommonTableFields + .builder() + .entityKind(EntityKind.ROLE) + .table(role) + .idField(role.ID) + .parentIdField(null) + .nameField(role.NAME) + .descriptionField(role.DESCRIPTION) + .externalIdField(role.KEY) + .build(); case SERVER: ServerInformation srv = alias == null ? Tables.SERVER_INFORMATION : Tables.SERVER_INFORMATION.as(alias); return ImmutableCommonTableFields diff --git a/waltz-data/src/main/java/org/finos/waltz/data/InlineSelectFieldFactory.java b/waltz-data/src/main/java/org/finos/waltz/data/InlineSelectFieldFactory.java index 5028fef3c..e1def6ec8 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/InlineSelectFieldFactory.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/InlineSelectFieldFactory.java @@ -67,6 +67,7 @@ public class InlineSelectFieldFactory { EntityKind.PERSON, EntityKind.PHYSICAL_FLOW, EntityKind.PHYSICAL_SPECIFICATION, + EntityKind.ROLE, EntityKind.SERVER, EntityKind.SOFTWARE); @@ -95,6 +96,7 @@ public class InlineSelectFieldFactory { EntityKind.PERSON, EntityKind.PHYSICAL_FLOW, EntityKind.PHYSICAL_SPECIFICATION, + EntityKind.ROLE, EntityKind.SERVER, EntityKind.SOFTWARE); @@ -123,6 +125,7 @@ public class InlineSelectFieldFactory { EntityKind.PERSON, EntityKind.PHYSICAL_FLOW, EntityKind.PHYSICAL_SPECIFICATION, + EntityKind.ROLE, EntityKind.SERVER, EntityKind.SOFTWARE); diff --git a/waltz-data/src/main/java/org/finos/waltz/data/role/RoleDao.java b/waltz-data/src/main/java/org/finos/waltz/data/role/RoleDao.java index 1887b3be4..cdff426b9 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/role/RoleDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/role/RoleDao.java @@ -45,10 +45,13 @@ public RoleDao(DSLContext dsl) { this.dsl = dsl; } - public boolean create(RoleRecord role) { - int execute = dsl.insertInto(ROLE) - .set(role).execute(); - return execute > 0; + public Long create(RoleRecord role) { + return dsl + .insertInto(ROLE) + .set(role) + .returning(ROLE.ID) + .fetchOne() + .getId(); } public Set findAllRoles() { @@ -58,10 +61,21 @@ public Set findAllRoles() { .fetchSet(TO_ROLE_RECORD); } + + public Role getRoleById(Long id) { + return dsl + .select(ROLE.fields()) + .from(ROLE) + .where(ROLE.ID.eq(id)) + .fetchOne(TO_ROLE_RECORD); + } + + private final static RecordMapper TO_ROLE_RECORD = r -> { RoleRecord record = r.into(ROLE); return ImmutableRole.builder() .key(record.getKey()) + .id(record.getId()) .name(record.getName()) .description(record.getDescription()) .isCustom(record.getIsCustom()) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/user/UserRoleDao.java b/waltz-data/src/main/java/org/finos/waltz/data/user/UserRoleDao.java index fe3afe338..e8cb4c729 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/user/UserRoleDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/user/UserRoleDao.java @@ -21,29 +21,30 @@ import org.finos.waltz.model.DiffResult; import org.finos.waltz.model.user.ImmutableUser; import org.finos.waltz.model.user.User; +import org.jooq.Condition; import org.jooq.DSLContext; import org.jooq.Record2; import org.jooq.Result; +import org.jooq.impl.DSL; import org.jooq.lambda.tuple.Tuple2; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; -import java.util.HashSet; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; import static org.finos.waltz.common.Checks.checkNotNull; +import static org.finos.waltz.common.MapUtilities.groupBy; import static org.finos.waltz.common.SetUtilities.map; import static org.finos.waltz.data.JooqUtilities.summarizeResults; import static org.finos.waltz.model.DiffResult.mkDiff; +import static org.finos.waltz.schema.tables.Role.ROLE; import static org.finos.waltz.schema.tables.User.USER; import static org.finos.waltz.schema.tables.UserRole.USER_ROLE; import static org.jooq.lambda.tuple.Tuple.tuple; @@ -52,9 +53,6 @@ @Repository public class UserRoleDao { - - private static final Logger LOG = LoggerFactory.getLogger(UserRoleDao.class); - private final DSLContext dsl; @Autowired @@ -66,35 +64,21 @@ public UserRoleDao(DSLContext dsl) { public Set getUserRoles(String userName) { - List roles = dsl.select(USER_ROLE.ROLE) + return dsl + .select(USER_ROLE.ROLE) .from(USER_ROLE) .where(USER_ROLE.USER_NAME.equalIgnoreCase(userName)) - .fetch(USER_ROLE.ROLE); - - return new HashSet(roles); + .fetchSet(USER_ROLE.ROLE); } - public List findAllUsers() { - Result> records = dsl.select(USER.USER_NAME, USER_ROLE.ROLE) - .from(USER) - .leftOuterJoin(USER_ROLE) - .on(USER.USER_NAME.eq(USER_ROLE.USER_NAME)) - .fetch(); + public Set findAllUsers() { + return findUsersByCondition(DSL.trueCondition()); + } - Map>> byUserName = records.stream() - .collect(groupingBy(r -> r.getValue(USER.USER_NAME))); - return byUserName.entrySet().stream() - .map( entry -> ImmutableUser.builder() - .userName(entry.getKey()) - .roles(entry.getValue() - .stream() - .map(record -> record.getValue(USER_ROLE.ROLE)) - .filter(Objects::nonNull) - .collect(Collectors.toList())) - .build()) - .collect(toList()); + public Set findUsersForRole(Long roleId) { + return findUsersByCondition(ROLE.ID.eq(roleId)); } @@ -171,4 +155,34 @@ private int removeRoles(DSLContext tx, return summarizeResults(rc); } + + + private Set findUsersByCondition(Condition condition) { + Result> records = dsl + .select(USER.USER_NAME, USER_ROLE.ROLE) + .from(USER) + .innerJoin(USER_ROLE) + .on(USER.USER_NAME.eq(USER_ROLE.USER_NAME)) + .innerJoin(ROLE).on(ROLE.KEY.eq(USER_ROLE.ROLE)) + .where(condition) + .fetch(); + + Map>> byUserName = groupBy( + records, + r -> r.getValue(USER.USER_NAME)); + + return byUserName + .entrySet() + .stream() + .map( entry -> ImmutableUser.builder() + .userName(entry.getKey()) + .roles(entry.getValue() + .stream() + .map(record -> record.getValue(USER_ROLE.ROLE)) + .filter(Objects::nonNull) + .collect(Collectors.toList())) + .build()) + .collect(toSet()); + } + } diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java index 92e2dd6dc..acc4d144c 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java @@ -11,7 +11,6 @@ import org.finos.waltz.service.entity_hierarchy.EntityHierarchyService; import org.h2.tools.Server; import org.jooq.DSLContext; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.context.annotation.AnnotationConfigApplicationContext; diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/UserRoleServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/UserRoleServiceTest.java index c2d4e6732..d75b58f84 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/UserRoleServiceTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/UserRoleServiceTest.java @@ -1,5 +1,6 @@ package org.finos.waltz.integration_test.inmem.service; +import org.finos.waltz.common.SetUtilities; import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; import org.finos.waltz.model.user.ImmutableUpdateRolesCommand; import org.finos.waltz.model.user.User; @@ -8,11 +9,14 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.util.Set; + import static org.finos.waltz.common.SetUtilities.asSet; import static org.finos.waltz.test_common.helpers.NameHelper.mkName; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.util.AssertionErrors.assertEquals; public class UserRoleServiceTest extends BaseInMemoryIntegrationTest { @@ -94,9 +98,33 @@ public void rolesCanBeAddedAndRemoved() { svc.updateRoles("admin", userName, addRolesCmd3); assertTrue(svc.hasRole(userName, asSet(role2Name))); assertFalse(svc.hasRole(userName, asSet(role1Name))); - } + @Test + public void canFindAllUsersForAGivenRole() { + String user1Name = mkName("canFindAllUsersForAGivenRole_User1"); + String user2Name = mkName("canFindAllUsersForAGivenRole_User2"); + String roleName = mkName("canFindAllUsersForAGivenRole_Role1"); + + helper.createUser(user1Name); + helper.createUser(user2Name); + Long roleId = helper.createRole(roleName); + ImmutableUpdateRolesCommand addRolesCmd = ImmutableUpdateRolesCommand + .builder() + .roles(asSet(roleName)) + .comment("test comment") + .build(); + svc.updateRoles("admin", user1Name, addRolesCmd); + svc.updateRoles("admin", user2Name, addRolesCmd); + + Set users = svc.findUsersForRole(roleId); + + assertEquals( + "Expect (only) user 1 and 2 to be associated to role ("+roleId+")", + SetUtilities.asSet(user1Name, user2Name), + SetUtilities.map(users, User::userName)); + + } } diff --git a/waltz-model/src/main/java/org/finos/waltz/model/role/Role.java b/waltz-model/src/main/java/org/finos/waltz/model/role/Role.java index f31297642..a06bd3f07 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/role/Role.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/role/Role.java @@ -20,12 +20,13 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.finos.waltz.model.IdProvider; import org.immutables.value.Value; @Value.Immutable @JsonSerialize(as = ImmutableRole.class) @JsonDeserialize(as = ImmutableRole.class) -public abstract class Role { +public abstract class Role implements IdProvider { public abstract String key(); public abstract String name(); public abstract String description(); diff --git a/waltz-model/src/main/java/org/finos/waltz/model/role/RoleView/RoleView.java b/waltz-model/src/main/java/org/finos/waltz/model/role/RoleView/RoleView.java new file mode 100644 index 000000000..0c1825faf --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/role/RoleView/RoleView.java @@ -0,0 +1,16 @@ +package org.finos.waltz.model.role.RoleView; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.finos.waltz.model.role.Role; +import org.immutables.value.Value; + +import java.util.Set; + +@Value.Immutable +@JsonSerialize(as = ImmutableRoleView.class) +public abstract class RoleView { + + public abstract Role role(); + public abstract Set users(); + +} diff --git a/waltz-ng/client/common/index.js b/waltz-ng/client/common/index.js index 7db3b921e..f463e87d4 100644 --- a/waltz-ng/client/common/index.js +++ b/waltz-ng/client/common/index.js @@ -57,6 +57,17 @@ export function randomPick(xs) { } +export function simpleTermSearch(data = [], qry) { + if (_.isEmpty(qry)) return data; + if (_.isEmpty(data)) return []; + const terms = _.map(_.split(qry, " "), t => t.toLowerCase()); + + return _.filter( + data, + d => !_.isNil(d) && _.every(terms, t => d.toLowerCase().indexOf(t) > -1)); +} + + /** * * @param items - items to be searched diff --git a/waltz-ng/client/common/link-utils.js b/waltz-ng/client/common/link-utils.js index 5e2541b1f..c2edbb908 100644 --- a/waltz-ng/client/common/link-utils.js +++ b/waltz-ng/client/common/link-utils.js @@ -61,7 +61,8 @@ const stateKindTuples = [ {kind: "SOFTWARE", state: "main.software-package.view"}, {kind: "SOFTWARE_VERSION", state: "main.software-version.view"}, //todo: no separate view for this (for now), just a workaround for the entity-link tooltip {kind: "SURVEY_INSTANCE", state: "main.survey.instance.view"}, - {kind: "TAG", state: "main.tag.id.view"} + {kind: "TAG", state: "main.tag.id.view"}, + {kind: "ROLE", state: "main.role.view"} ]; diff --git a/waltz-ng/client/common/svelte/ViewLink.svelte b/waltz-ng/client/common/svelte/ViewLink.svelte index 6c0c40447..dd282e3b7 100644 --- a/waltz-ng/client/common/svelte/ViewLink.svelte +++ b/waltz-ng/client/common/svelte/ViewLink.svelte @@ -128,6 +128,14 @@ "main.survey.template.list": { path: () => `survey/template/list`, title: "Survey Templates" + }, + "main.role.list": { + path: () => `role/list`, + title: "Role List" + }, + "main.role.view": { + path: (ctx) => `role/view/${ctx.id}`, + title: "Role View" } }; diff --git a/waltz-ng/client/dynamic-section/dynamic-section-definitions.js b/waltz-ng/client/dynamic-section/dynamic-section-definitions.js index 28b49647e..fe3775846 100644 --- a/waltz-ng/client/dynamic-section/dynamic-section-definitions.js +++ b/waltz-ng/client/dynamic-section/dynamic-section-definitions.js @@ -910,6 +910,14 @@ const endUserApplicationSections = [ changeLogSection ]; +const roleSections = [ + assessmentRatingSection, + bookmarksSection, + entityNamedNotesSection, + involvedPeopleSection, + changeLogSection +] + export const dynamicSectionsByKind = { "main.actor.view": actorSections, @@ -946,6 +954,7 @@ export const dynamicSectionsByKind = { "main.physical-flow.view": physicalFlowSections, "main.physical-specification.view": physicalSpecificationSections, "main.process-diagram.view": processDiagramSections, + "main.role.view": roleSections, "main.scenario.view": scenarioSections, "main.software-package.view": softwarePackageSections }; diff --git a/waltz-ng/client/involvement-kind/components/svelte/InvolvementKindTable.svelte b/waltz-ng/client/involvement-kind/components/svelte/InvolvementKindTable.svelte index 7a3e782b4..34cc978a7 100644 --- a/waltz-ng/client/involvement-kind/components/svelte/InvolvementKindTable.svelte +++ b/waltz-ng/client/involvement-kind/components/svelte/InvolvementKindTable.svelte @@ -32,16 +32,17 @@ let searchStr = ""; let entityList = [ - entity.APPLICATION, - entity.CHANGE_INITIATIVE, - entity.ORG_UNIT, entity.ACTOR, - entity.MEASURABLE, - entity.MEASURABLE_CATEGORY, entity.APP_GROUP, + entity.APPLICATION, + entity.CHANGE_INITIATIVE, entity.DATA_TYPE, entity.END_USER_APPLICATION, - entity.PHYSICAL_FLOW + entity.MEASURABLE, + entity.MEASURABLE_CATEGORY, + entity.ORG_UNIT, + entity.PHYSICAL_FLOW, + entity.ROLE ]; $: involvementStatCall = involvementKindStore.findUsageStats(); diff --git a/waltz-ng/client/role/index.js b/waltz-ng/client/role/index.js index eef88b7c5..45f7ac846 100644 --- a/waltz-ng/client/role/index.js +++ b/waltz-ng/client/role/index.js @@ -17,19 +17,22 @@ * */ -import angular from 'angular'; -import {registerStore} from '../common/module-utils'; - -import roleStore from './services/role-store'; -import Routes from './routes'; +import angular from "angular"; +import {registerComponents, registerStore} from "../common/module-utils"; +import RoleView from "./pages/role-view"; +import RoleList from "./pages/role-list"; +import roleStore from "./services/role-store"; +import Routes from "./routes"; export default () => { - const module = angular.module('waltz.role', []); + const module = angular.module("waltz.role", []); module.config(Routes); registerStore(module, roleStore); + registerComponents(module, [RoleList, RoleView]); + return module.name; }; diff --git a/waltz-ng/client/role/custom-role.html b/waltz-ng/client/role/pages/role-list.html similarity index 59% rename from waltz-ng/client/role/custom-role.html rename to waltz-ng/client/role/pages/role-list.html index f78abc7ba..c13ceb898 100644 --- a/waltz-ng/client/role/custom-role.html +++ b/waltz-ng/client/role/pages/role-list.html @@ -18,61 +18,22 @@
+ name="Roles">
  1. Home
  2. System Admin
  3. -
  4. Custom Role
  5. +
  6. Roles
-
-
-
-
- - -
-
- -
- -
- - - - - - - - - - - - - - - - - -
NameDescriptionKeyCustom role
-

-

-
- -
-
-
+ +

@@ -80,7 +41,7 @@

Custom roles can be used to restrict certain features in Waltz that allow dynamic role selection (i.e. allocations, assessment definitions)

+ ng-submit="$ctrl.createRole($ctrl.roleName, $ctrl.roleDescription)">
@@ -100,9 +61,9 @@
@@ -114,7 +75,7 @@ diff --git a/waltz-ng/client/role/custom-role.js b/waltz-ng/client/role/pages/role-list.js similarity index 71% rename from waltz-ng/client/role/custom-role.js rename to waltz-ng/client/role/pages/role-list.js index 072e3487a..171f231ff 100644 --- a/waltz-ng/client/role/custom-role.js +++ b/waltz-ng/client/role/pages/role-list.js @@ -16,17 +16,26 @@ * */ -import {CORE_API} from "../common/services/core-api-utils"; -import template from "./custom-role.html"; -import {displayError} from "../common/error-utils"; -import toasts from "../svelte-stores/toast-store"; +import {CORE_API} from "../../common/services/core-api-utils"; +import template from "./role-list.html"; +import {displayError} from "../../common/error-utils"; +import toasts from "../../svelte-stores/toast-store"; +import RoleListPanel from "../svelte/RoleListPanel.svelte"; +import {initialiseData} from "../../common"; + +const initialState = { + RoleListPanel +}; + +const bindings = {}; function controller(serviceBroker) { - const vm = this; + const vm = initialiseData(this, initialState); const reload = () => - serviceBroker.loadViewData(CORE_API.RoleStore.findAllRoles, [], {force: true}) + serviceBroker + .loadViewData(CORE_API.RoleStore.findAllRoles, [], {force: true}) .then(result => vm.roles = result.data); reload(); @@ -42,15 +51,14 @@ function controller(serviceBroker) { }; vm.createRole = (roleName, description) => { - vm.errorMessage = null; - vm.successMessage = null; - let payload = { name: roleName, description: description, key: vm.roleKey }; - serviceBroker.execute(CORE_API.RoleStore.createCustomRole, [payload]) + + serviceBroker + .execute(CORE_API.RoleStore.createCustomRole, [payload]) .then( () => { toasts.info("Role created successfully"); @@ -66,11 +74,14 @@ function controller(serviceBroker) { controller.$inject = ["ServiceBroker"]; -export default { - template, +const component = { + bindings, controller, - controllerAs: "ctrl", - bindToController: true, - scope: {} + template, }; + +export default { + id: "waltzRoleList", + component +}; diff --git a/waltz-ng/client/role/pages/role-view.js b/waltz-ng/client/role/pages/role-view.js new file mode 100644 index 000000000..f4212bf52 --- /dev/null +++ b/waltz-ng/client/role/pages/role-view.js @@ -0,0 +1,46 @@ + +import {initialiseData} from "../../common"; +import RoleView from "../svelte/RoleView.svelte"; +import {entity} from "../../common/services/enums/entity"; + +const bindings = { +}; + + +const initialState = { + RoleView +}; + + +function controller($stateParams) { + const vm = initialiseData(this, initialState); + + vm.$onInit = () => { + vm.roleId = $stateParams.id; + vm.primaryEntityRef = {id: $stateParams.id, kind: entity.ROLE.key} + }; + +} + + +controller.$inject = [ + "$stateParams" +]; + + +const component = { + bindings, + controller, + template: ` +
+ ` +}; + + +export default { + id: "waltzRoleView", + component +}; + + + diff --git a/waltz-ng/client/role/routes.js b/waltz-ng/client/role/routes.js index c58382afd..175d57eaa 100644 --- a/waltz-ng/client/role/routes.js +++ b/waltz-ng/client/role/routes.js @@ -16,25 +16,33 @@ * */ -import customRoleView from "./custom-role"; +import roleListView from "./pages/role-list"; +import roleView from "./pages/role-view"; const base = { - url: 'role' + url: "role" }; -const customRoleState = { - url: '/custom-role', - views: {'content@': customRoleView } +const roleListState = { + url: "/list", + views: {"content@": roleListView.id } +}; + + +const roleViewState = { + url: "/view/{id:int}", + views: {"content@": roleView.id } }; function configureStates(stateProvider) { stateProvider - .state('main.role', base) - .state('main.role.list', customRoleState); + .state("main.role", base) + .state("main.role.list", roleListState) + .state("main.role.view", roleViewState); } -configureStates.$inject = ['$stateProvider']; +configureStates.$inject = ["$stateProvider"]; export default configureStates; diff --git a/waltz-ng/client/role/svelte/RoleList.svelte b/waltz-ng/client/role/svelte/RoleList.svelte new file mode 100644 index 000000000..9d7cc6205 --- /dev/null +++ b/waltz-ng/client/role/svelte/RoleList.svelte @@ -0,0 +1,64 @@ + + + +{#if _.isNil(roles)} +

Loading....

+{:else} + + + +
+ + + + + + + + + + {#each _.orderBy(displayRoles, d => d.name) as row} + onSelectRole(row)}> + + + + + + {/each} + +
NameDescriptionKey
+ + {row.name} + + + {row.description}{row.key}
+
+{/if} \ No newline at end of file diff --git a/waltz-ng/client/role/svelte/RoleListPanel.svelte b/waltz-ng/client/role/svelte/RoleListPanel.svelte new file mode 100644 index 000000000..f3f613e50 --- /dev/null +++ b/waltz-ng/client/role/svelte/RoleListPanel.svelte @@ -0,0 +1,42 @@ + + + +
+
+ +
+
+
+ Roles are used to grant coarse grained permissions to users. + +
+
+ + Coarse grained permissions are those that are not data driven - + you can't use them to restrict permissions on apps to only users with a certain involvement + (if you want to be able to do this look at the fine grained permission support). + +
+
+ + Waltz ships with a set of pre-defined roles, these are indicated with the lock icon () + and should not be removed. + +
+
+ + Select a role on the table to see more information about it. + + +
+
+
\ No newline at end of file diff --git a/waltz-ng/client/role/svelte/RoleView.svelte b/waltz-ng/client/role/svelte/RoleView.svelte new file mode 100644 index 000000000..222ca69a1 --- /dev/null +++ b/waltz-ng/client/role/svelte/RoleView.svelte @@ -0,0 +1,99 @@ + + + + + + +
+
    +
  1. + Home +
  2. +
  3. + Roles +
  4. +
  5. + {role?.name} +
  6. +
+
+
+ +
+
+
+ + {#if role} +
+

{role.name}

+
+
Name:
+
{role.name}
+
Description:
+
{role.description}
+
Key:
+
{role.key}
+
Is Custom:
+
+ + {role.isCustom ? "Custom role" : "System defined role"} +
+ Waltz ships with a set of custom roles which are needed for it's basic operation. + Installations of Waltz can register additional roles, these are flagged with the is_custom attribute. +
+
+
+
+ {/if} + {#if users} +
+

Users (#{users.length})

+ +
+
    + {#each users as user} +
  • {user}
  • + {/each} +
+
+
+ {/if} +
+
+
+ + + \ No newline at end of file diff --git a/waltz-ng/client/role/svelte/SelectedRolePanel.svelte b/waltz-ng/client/role/svelte/SelectedRolePanel.svelte new file mode 100644 index 000000000..3ec2e8f33 --- /dev/null +++ b/waltz-ng/client/role/svelte/SelectedRolePanel.svelte @@ -0,0 +1,75 @@ + + +
+
Name:
+
{selectedRole.name}
+
Description:
+
{selectedRole.description}
+
Key:
+
{selectedRole.key}
+
Is Custom:
+
+ + {selectedRole.isCustom ? "Custom role" : "System defined role"} +
+ Waltz ships with a set of custom roles which are needed for it's basic operation. + Installations of Waltz can register additional roles, these are flagged with the is_custom attribute. +
+
+
+ +{#if mode === Modes.SHOW_ACTIONS} +
+ Actions + +
  • + + Show details + +
    Retrieve all users assigned to this role
    +
  • +
    +
    +{/if} + + + + + + + \ No newline at end of file diff --git a/waltz-ng/client/svelte-stores/role-store.js b/waltz-ng/client/svelte-stores/role-store.js index 48a2e3952..a17a6550a 100644 --- a/waltz-ng/client/svelte-stores/role-store.js +++ b/waltz-ng/client/svelte-stores/role-store.js @@ -9,9 +9,17 @@ export function mkRoleStore() { [], {force}); + const getViewById = (id, force = false) => remote + .fetchViewDatum( + "GET", + `api/role/view/${id}`, + [], + {force}); + return { - findAll - } + findAll, + getViewById + }; } export const roleStore = mkRoleStore(); diff --git a/waltz-ng/client/system/svelte/assessment-definitions/assessment-definition-utils.js b/waltz-ng/client/system/svelte/assessment-definitions/assessment-definition-utils.js index 53d8e04b7..42d7da9e8 100644 --- a/waltz-ng/client/system/svelte/assessment-definitions/assessment-definition-utils.js +++ b/waltz-ng/client/system/svelte/assessment-definitions/assessment-definition-utils.js @@ -18,31 +18,35 @@ */ import {writable} from "svelte/store"; +import _ from "lodash"; export function getRequiredFields(d) { return [d.name, d.entityKind, d.description]; } -export const possibleAssessmentKinds = [ - {value: "ACTOR", name: "Actor", qualifierKind: null}, - {value: "APPLICATION", name: "Application", qualifierKind: null}, - {value: "CHANGE_INITIATIVE", name: "Change Initiative", qualifierKind: null}, - {value: "CHANGE_SET", name: "Change Set", qualifierKind: null}, - {value: "ENTITY_RELATIONSHIP", name: "Entity Relationship", qualifierKind: null}, - {value: "FLOW_CLASSIFICATION_RULE", name: "Flow Classification Rule", qualifierKind: null}, - {value: "INVOLVEMENT_KIND", name: "Involvement Kind", qualifierKind: null}, - {value: "LEGAL_ENTITY", name: "Legal Entity", qualifierKind: null}, - {value: "LEGAL_ENTITY_RELATIONSHIP", name: "Legal Entity Relationship", qualifierKind: "LEGAL_ENTITY_RELATIONSHIP_KIND"}, - {value: "LICENCE", name: "Software Licence", qualifierKind: null}, - {value: "LOGICAL_DATA_FLOW", name: "Logical Data Flow", needsQualifier: false}, - {value: "LOGICAL_DATA_FLOW_DATA_TYPE_DECORATOR", name: "Logical Flow Data Type Decorator", qualifierKind: null}, - {value: "MEASURABLE", name: "Measurable", qualifierKind: "MEASURABLE_CATEGORY"}, - {value: "MEASURABLE_RATING", name: "Measurable Rating", qualifierKind: "MEASURABLE_CATEGORY"}, - {value: "PHYSICAL_FLOW", name: "Physical Flow", qualifierKind: null}, - {value: "PHYSICAL_SPEC_DATA_TYPE_DECORATOR", name: "Physical Spec Data Type Decorator", qualifierKind: null}, - {value: "PHYSICAL_SPECIFICATION", name: "Physical Specification", qualifierKind: null}, - {value: "SOFTWARE_PACKAGE", name: "Software Package", qualifierKind: null} -]; +export const possibleAssessmentKinds = _.orderBy( + [ + {value: "ACTOR", name: "Actor", qualifierKind: null}, + {value: "APPLICATION", name: "Application", qualifierKind: null}, + {value: "CHANGE_INITIATIVE", name: "Change Initiative", qualifierKind: null}, + {value: "CHANGE_SET", name: "Change Set", qualifierKind: null}, + {value: "ENTITY_RELATIONSHIP", name: "Entity Relationship", qualifierKind: null}, + {value: "FLOW_CLASSIFICATION_RULE", name: "Flow Classification Rule", qualifierKind: null}, + {value: "INVOLVEMENT_KIND", name: "Involvement Kind", qualifierKind: null}, + {value: "LEGAL_ENTITY", name: "Legal Entity", qualifierKind: null}, + {value: "LEGAL_ENTITY_RELATIONSHIP", name: "Legal Entity Relationship", qualifierKind: "LEGAL_ENTITY_RELATIONSHIP_KIND"}, + {value: "LICENCE", name: "Software Licence", qualifierKind: null}, + {value: "LOGICAL_DATA_FLOW", name: "Logical Data Flow", needsQualifier: false}, + {value: "LOGICAL_DATA_FLOW_DATA_TYPE_DECORATOR", name: "Logical Flow Data Type Decorator", qualifierKind: null}, + {value: "MEASURABLE", name: "Measurable", qualifierKind: "MEASURABLE_CATEGORY"}, + {value: "MEASURABLE_RATING", name: "Measurable Rating", qualifierKind: "MEASURABLE_CATEGORY"}, + {value: "PHYSICAL_FLOW", name: "Physical Flow", qualifierKind: null}, + {value: "PHYSICAL_SPEC_DATA_TYPE_DECORATOR", name: "Physical Spec Data Type Decorator", qualifierKind: null}, + {value: "PHYSICAL_SPECIFICATION", name: "Physical Specification", qualifierKind: null}, + {value: "ROLE", name: "Role", qualifierKind: null}, + {value: "SOFTWARE_PACKAGE", name: "Software Package", qualifierKind: null} + ], + d => d.name); export const possibleVisibility = [ {value: "PRIMARY", name: "Primary"}, diff --git a/waltz-ng/client/user/svelte/UserRolesList.svelte b/waltz-ng/client/user/svelte/UserRolesList.svelte index 90c32e4a0..c1bdf93d9 100644 --- a/waltz-ng/client/user/svelte/UserRolesList.svelte +++ b/waltz-ng/client/user/svelte/UserRolesList.svelte @@ -8,6 +8,7 @@ import {userStore} from "../../svelte-stores/user-store"; import toasts from "../../svelte-stores/toast-store"; import Icon from "../../common/svelte/Icon.svelte"; + import ViewLink from "../../common/svelte/ViewLink.svelte"; let qry = ""; let comment = null; @@ -126,7 +127,12 @@ on:change={() => selectRole(role)} checked={hasRole($userRoles, role.key)}> - {role.name} + + + {role.name} + + {role.key} {role.description || ""} diff --git a/waltz-schema/src/main/resources/liquibase/db.changelog-1.62.xml b/waltz-schema/src/main/resources/liquibase/db.changelog-1.62.xml index 370cc9a81..3879d4781 100644 --- a/waltz-schema/src/main/resources/liquibase/db.changelog-1.62.xml +++ b/waltz-schema/src/main/resources/liquibase/db.changelog-1.62.xml @@ -57,4 +57,61 @@ remarks="If set, then the editing user must have this role to assign this involvement"/> + + + 7118: dropping the 'key' foreign key constraint to user_role + + + + + 7118: dropping the 'key' column as the primary key + + + + + + + + + 7118: making the 'key' column unique + + + + + + + 7118: recreate the 'key' foreign key constraint to user_role + + + + + 7118: adding a new 'id' column and making it the pk + + + + + + + \ No newline at end of file diff --git a/waltz-service/src/main/java/org/finos/waltz/service/role/RoleService.java b/waltz-service/src/main/java/org/finos/waltz/service/role/RoleService.java index ddab97ce6..6e61bc65f 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/role/RoleService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/role/RoleService.java @@ -18,6 +18,11 @@ package org.finos.waltz.service.role; +import org.finos.waltz.common.SetUtilities; +import org.finos.waltz.data.user.UserRoleDao; +import org.finos.waltz.model.role.RoleView.ImmutableRoleView; +import org.finos.waltz.model.role.RoleView.RoleView; +import org.finos.waltz.model.user.User; import org.finos.waltz.schema.tables.records.RoleRecord; import org.finos.waltz.data.role.RoleDao; import org.finos.waltz.model.role.Role; @@ -36,13 +41,16 @@ public class RoleService { private static final Logger LOG = LoggerFactory.getLogger(RoleService.class); private final RoleDao roleDao; + private final UserRoleDao userRoleDao; @Autowired - public RoleService(RoleDao roleDao) { + public RoleService(RoleDao roleDao, + UserRoleDao userRoleDao) { this.roleDao = roleDao; + this.userRoleDao = userRoleDao; } - public boolean create(String key, String roleName, String description) { + public Long create(String key, String roleName, String description) { checkNotEmpty(roleName, "role name cannot be empty"); checkNotEmpty(key, "key cannot be empty"); LOG.info("creating new role: {}", roleName); @@ -59,4 +67,15 @@ public boolean create(String key, String roleName, String description) { public Set findAllRoles() { return roleDao.findAllRoles(); } + + public RoleView getRoleView(Long id) { + Set users = userRoleDao.findUsersForRole(id); + Role role = roleDao.getRoleById(id); + + return ImmutableRoleView + .builder() + .role(role) + .users(SetUtilities.map(users, User::userName)) + .build(); + } } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/user/UserRoleService.java b/waltz-service/src/main/java/org/finos/waltz/service/user/UserRoleService.java index 5056d29be..b1caf4bc0 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/user/UserRoleService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/user/UserRoleService.java @@ -127,11 +127,15 @@ public boolean hasAnyRole(String userName, Set requiredRoles) { } - public List findAllUsers() { + public Set findAllUsers() { return userRoleDao.findAllUsers(); } + public Set findUsersForRole(Long roleId) { + return userRoleDao.findUsersForRole(roleId); + } + public User getByUserId(String userId) { return ImmutableUser.builder() .userName(userId) diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/UserHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/UserHelper.java index 324c58fed..0d5d3d78c 100644 --- a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/UserHelper.java +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/UserHelper.java @@ -1,6 +1,5 @@ package org.finos.waltz.test_common.helpers; -import org.finos.waltz.model.role.Role; import org.finos.waltz.model.user.ImmutableUpdateRolesCommand; import org.finos.waltz.model.user.ImmutableUserRegistrationRequest; import org.finos.waltz.model.user.SystemRole; @@ -28,14 +27,18 @@ public class UserHelper { @Autowired private RoleService roleService; - public void createRole(String role) { - Set allRoles = roleService.findAllRoles(); - Set roles = map( - allRoles, - Role::name); - if (!roles.contains(role)) { - roleService.create(role, role + " name", role + " desc"); - } + public Long createRole(String role) { + return roleService + .findAllRoles() + .stream() + .filter(r -> r.name().equals(role)) + .findFirst() + .map(r -> r.id().get()) + .orElseGet(() -> roleService.create( + role, + role + " name", + role + " desc")); + } public void createUser(String user) { diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/RoleEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/RoleEndpoint.java index 8abc48a4d..6f7945f49 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/RoleEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/RoleEndpoint.java @@ -18,20 +18,25 @@ package org.finos.waltz.web.endpoints.api; +import org.finos.waltz.model.role.Role; +import org.finos.waltz.model.role.RoleView.RoleView; +import org.finos.waltz.model.user.SystemRole; import org.finos.waltz.service.role.RoleService; import org.finos.waltz.service.user.UserRoleService; import org.finos.waltz.web.DatumRoute; +import org.finos.waltz.web.ListRoute; import org.finos.waltz.web.endpoints.Endpoint; -import org.finos.waltz.model.role.Role; -import org.finos.waltz.model.user.SystemRole; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Map; -import java.util.Set; -import static org.finos.waltz.web.WebUtilities.*; +import static org.finos.waltz.web.WebUtilities.getId; +import static org.finos.waltz.web.WebUtilities.mkPath; +import static org.finos.waltz.web.WebUtilities.readBody; +import static org.finos.waltz.web.WebUtilities.requireAnyRole; import static org.finos.waltz.web.endpoints.EndpointUtilities.getForDatum; +import static org.finos.waltz.web.endpoints.EndpointUtilities.getForList; import static org.finos.waltz.web.endpoints.EndpointUtilities.postForDatum; @@ -55,11 +60,9 @@ public RoleEndpoint(RoleService roleService, @Override public void register() { - DatumRoute> findAllRolesRoute = (request, response) -> { - return roleService.findAllRoles(); - }; + ListRoute findAllRolesRoute = (request, response) -> roleService.findAllRoles(); - DatumRoute createCustomRoleRoute = (request, response) -> { + DatumRoute createCustomRoleRoute = (request, response) -> { requireAnyRole(userRoleService, request, SystemRole.USER_ADMIN, SystemRole.ADMIN); Map body = (Map) readBody(request, Map.class); String key = body.get("key"); @@ -68,10 +71,13 @@ public void register() { return roleService.create(key, roleName, description); }; + DatumRoute getRoleViewRoute = (request, response) -> roleService.getRoleView(getId(request)); + // --- register postForDatum(BASE_URL, createCustomRoleRoute); - getForDatum(BASE_URL, findAllRolesRoute); + getForList(BASE_URL, findAllRolesRoute); + getForDatum(mkPath(BASE_URL, "view", ":id"), getRoleViewRoute); } }